mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
* fix(popover): make PopoverContent children prop optional to fix TS error * chore(changeset): created changeset * fix(popover): do not show content when children is null / undefined * fix(popover): add test case * chore(changeset): update changeset message --------- Co-authored-by: i_nicck <i_nicck@i-niccks-MacBook-Air.local> Co-authored-by: WK Wong <wingkwong.code@gmail.com>
409 lines
11 KiB
TypeScript
409 lines
11 KiB
TypeScript
import "@testing-library/jest-dom";
|
|
import type {UserEvent} from "@testing-library/user-event";
|
|
|
|
import * as React from "react";
|
|
import {render, fireEvent, act} from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import {Button} from "@heroui/button";
|
|
import {spy, shouldIgnoreReactWarning} from "@heroui/test-utils";
|
|
|
|
import {Popover, PopoverContent, PopoverTrigger} from "../src";
|
|
import {Select, SelectItem} from "../../select/src";
|
|
|
|
describe("Popover", () => {
|
|
let user: UserEvent;
|
|
|
|
beforeEach(() => {
|
|
user = userEvent.setup();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it("should render correctly", () => {
|
|
const wrapper = render(
|
|
<Popover>
|
|
<PopoverTrigger>
|
|
<button>Open popover</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent>
|
|
<p>This is the content of the popover.</p>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
|
|
expect(() => wrapper.unmount()).not.toThrow();
|
|
});
|
|
|
|
it("should not throw error when clicking trigger button", async () => {
|
|
const wrapper = render(
|
|
<Popover>
|
|
<PopoverTrigger>
|
|
<button data-testid="trigger-test">Open popover</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent>
|
|
<p>This is the content of the popover.</p>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
const trigger = wrapper.getByTestId("trigger-test");
|
|
|
|
// open popover
|
|
await user.click(trigger);
|
|
|
|
if (!shouldIgnoreReactWarning(spy)) {
|
|
expect(spy).toHaveBeenCalledTimes(0);
|
|
}
|
|
});
|
|
|
|
it("ref should be forwarded", () => {
|
|
const ref = React.createRef<HTMLDivElement>();
|
|
|
|
render(
|
|
<Popover ref={ref} defaultOpen>
|
|
<PopoverTrigger>
|
|
<button>Open popover</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent>
|
|
<p>This is the content of the popover.</p>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
expect(ref.current).not.toBeNull();
|
|
});
|
|
|
|
it("should hide the popover when pressing the escape key", () => {
|
|
const onClose = jest.fn();
|
|
|
|
const wrapper = render(
|
|
<Popover isOpen onOpenChange={(isOpen) => (!isOpen ? onClose() : undefined)}>
|
|
<PopoverTrigger>
|
|
<button>Open popover</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent data-testid="content-test">
|
|
<p>This is the content of the popover.</p>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
|
|
const content = wrapper.getByTestId("content-test");
|
|
|
|
fireEvent.keyDown(content, {key: "Escape"});
|
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should still hide the popover when pressing the escape key ", () => {
|
|
const onClose = jest.fn();
|
|
|
|
const wrapper = render(
|
|
<Popover isOpen onClose={onClose}>
|
|
<PopoverTrigger>
|
|
<button>Open popover</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent data-testid="content-test">
|
|
<p>This is the content of the popover.</p>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
|
|
const content = wrapper.getByTestId("content-test");
|
|
|
|
fireEvent.keyDown(content, {key: "Escape"});
|
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should hide the popover on blur (shouldCloseOnBlur=true)", () => {
|
|
const onClose = jest.fn();
|
|
|
|
const wrapper = render(
|
|
<Popover isOpen shouldCloseOnBlur onClose={onClose}>
|
|
<PopoverTrigger>
|
|
<button>Open popover</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent data-testid="content-test">
|
|
<p>This is the content of the popover.</p>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
|
|
const content = wrapper.getByTestId("content-test");
|
|
|
|
expect(content).toHaveFocus();
|
|
|
|
fireEvent.keyDown(content, {key: "Escape"});
|
|
|
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should work with HeroUI button", async () => {
|
|
const onClose = jest.fn();
|
|
|
|
const wrapper = render(
|
|
<Popover onClose={onClose}>
|
|
<PopoverTrigger>
|
|
<Button disableRipple data-testid="trigger-test">
|
|
Open popover
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent>
|
|
<p>This is the content of the popover.</p>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
|
|
const trigger = wrapper.getByTestId("trigger-test");
|
|
|
|
// open popover
|
|
await user.click(trigger);
|
|
expect(onClose).toHaveBeenCalledTimes(0);
|
|
|
|
// close popover
|
|
await user.click(trigger);
|
|
|
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should close listbox by clicking another popover", async () => {
|
|
const wrapper = render(
|
|
<>
|
|
<Popover>
|
|
<PopoverTrigger>
|
|
<button data-testid="popover">Open popover</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent>
|
|
<p>This is the content of the popover.</p>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<Popover>
|
|
<PopoverTrigger>
|
|
<button data-testid="popover2">Open popover</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent>
|
|
<p>This is the content of the popover.</p>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</>,
|
|
);
|
|
|
|
const popover = wrapper.getByTestId("popover");
|
|
|
|
const popover2 = wrapper.getByTestId("popover2");
|
|
|
|
expect(popover).not.toBeNull();
|
|
|
|
expect(popover2).not.toBeNull();
|
|
|
|
// open the popover by clicking popover in the first popover
|
|
await user.click(popover);
|
|
|
|
// assert that the first popover is open
|
|
expect(popover).toHaveAttribute("aria-expanded", "true");
|
|
|
|
// assert that the second popover is close
|
|
expect(popover2).toHaveAttribute("aria-expanded", "false");
|
|
|
|
// close the popover by clicking the second popover
|
|
await user.click(popover2);
|
|
|
|
// assert that the first popover is closed
|
|
expect(popover).toHaveAttribute("aria-expanded", "false");
|
|
|
|
// assert that the second popover is open
|
|
expect(popover2).toHaveAttribute("aria-expanded", "true");
|
|
});
|
|
|
|
it("should focus on dialog when opened", async () => {
|
|
const wrapper = render(
|
|
<Popover>
|
|
<PopoverTrigger>
|
|
<Button disableRipple data-testid="trigger-test">
|
|
Open popover
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent>
|
|
<p>This is the content of the popover.</p>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
|
|
const trigger = wrapper.getByTestId("trigger-test");
|
|
|
|
// open popover
|
|
await user.click(trigger);
|
|
|
|
const {getByRole} = wrapper;
|
|
|
|
let dialog = getByRole("dialog");
|
|
|
|
// assert that the focus is on the dialog
|
|
expect(dialog).toHaveFocus();
|
|
});
|
|
|
|
it("should restore focus on trigger when closed", async () => {
|
|
const wrapper = render(
|
|
<Popover>
|
|
<PopoverTrigger>
|
|
<Button data-testid="popover-trigger">Open popover</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent>
|
|
<p>This is the content of the popover.</p>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
|
|
const trigger = wrapper.getByTestId("popover-trigger");
|
|
|
|
await act(async () => {
|
|
// open popover
|
|
await user.pointer({target: trigger, keys: "[MouseLeft>]"});
|
|
// close popover
|
|
await user.pointer({target: trigger, keys: "[/MouseLeft]"});
|
|
// assert that the focus is restored back to trigger
|
|
expect(trigger).toHaveFocus();
|
|
});
|
|
});
|
|
|
|
it("should not close popover if nested select is closed", async () => {
|
|
const wrapper = render(
|
|
<Popover>
|
|
<PopoverTrigger>
|
|
<Button data-testid="popover">Open popover</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent>
|
|
<Select data-testid="select" label="Select country">
|
|
<SelectItem key="argentina">Argentina</SelectItem>
|
|
<SelectItem key="venezuela">Venezuela</SelectItem>
|
|
<SelectItem key="brazil">Brazil</SelectItem>
|
|
</Select>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
|
|
const popover = wrapper.getByTestId("popover");
|
|
|
|
// open popover
|
|
await user.click(popover);
|
|
|
|
// assert that the popover is open
|
|
expect(popover).toHaveAttribute("aria-expanded", "true");
|
|
|
|
const select = wrapper.getByTestId("select");
|
|
|
|
// open select
|
|
await user.click(select);
|
|
|
|
// assert that the select is open
|
|
expect(select).toHaveAttribute("aria-expanded", "true");
|
|
|
|
await user.click(document.body);
|
|
|
|
// assert that the select is closed
|
|
expect(select).toHaveAttribute("aria-expanded", "false");
|
|
|
|
// assert that the popover is still open
|
|
expect(popover).toHaveAttribute("aria-expanded", "true");
|
|
});
|
|
|
|
it("should close popover on scroll", async () => {
|
|
const wrapper = render(
|
|
<Popover>
|
|
<PopoverTrigger>
|
|
<Button data-testid="popover">Open popover</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent>
|
|
<Select data-testid="select" label="Select country">
|
|
<SelectItem key="argentina">Argentina</SelectItem>
|
|
<SelectItem key="venezuela">Venezuela</SelectItem>
|
|
<SelectItem key="brazil">Brazil</SelectItem>
|
|
</Select>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
|
|
const popover = wrapper.getByTestId("popover");
|
|
|
|
// open popover
|
|
await user.click(popover);
|
|
|
|
// assert that the popover is open
|
|
expect(popover).toHaveAttribute("aria-expanded", "true");
|
|
|
|
// scroll it
|
|
fireEvent.scroll(document.body);
|
|
|
|
// assert that the popover is closed
|
|
expect(popover).toHaveAttribute("aria-expanded", "false");
|
|
});
|
|
});
|
|
|
|
it("should close popover on scroll when shouldCloseOnScroll is false", async () => {
|
|
const wrapper = render(
|
|
<Popover shouldCloseOnScroll={false}>
|
|
<PopoverTrigger>
|
|
<Button data-testid="popover">Open popover</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent>
|
|
<Select data-testid="select" label="Select country">
|
|
<SelectItem key="argentina">Argentina</SelectItem>
|
|
<SelectItem key="venezuela">Venezuela</SelectItem>
|
|
<SelectItem key="brazil">Brazil</SelectItem>
|
|
</Select>
|
|
</PopoverContent>
|
|
</Popover>,
|
|
);
|
|
|
|
const popover = wrapper.getByTestId("popover");
|
|
|
|
// open popover
|
|
await act(async () => {
|
|
await userEvent.click(popover);
|
|
});
|
|
|
|
// assert that the popover is open
|
|
expect(popover).toHaveAttribute("aria-expanded", "true");
|
|
|
|
// scroll it
|
|
fireEvent.scroll(document.body);
|
|
|
|
// assert that the popover is still open
|
|
expect(popover).toHaveAttribute("aria-expanded", "true");
|
|
});
|
|
|
|
it("should display popover content only after content is ready", async () => {
|
|
jest.useFakeTimers();
|
|
|
|
const TestComponent = () => {
|
|
const [content, setContent] = React.useState("");
|
|
|
|
React.useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
setContent("test content");
|
|
}, 1000);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, []);
|
|
|
|
return (
|
|
<Popover defaultOpen>
|
|
<PopoverTrigger>
|
|
<Button data-testid="trigger">Open popover</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent data-testid="content-test">{content}</PopoverContent>
|
|
</Popover>
|
|
);
|
|
};
|
|
|
|
const wrapper = render(<TestComponent />);
|
|
|
|
expect(wrapper.queryByTestId("content-test")).not.toBeInTheDocument();
|
|
|
|
act(() => {
|
|
jest.advanceTimersByTime(1000);
|
|
});
|
|
|
|
expect(wrapper.getByTestId("content-test")).toBeInTheDocument();
|
|
|
|
jest.useRealTimers();
|
|
});
|