աӄա 3b14c21e02
fix: popover-based focus behaviour (#2854)
* fix(autocomplete): autocomplete focus behaviour

* feat(autocomplete): add test case for catching blur cases

* refactor(autocomplete): use isOpen instead

* feat(autocomplete): add "should focus when clicking autocomplete" test case

* feat(autocomplete): add should set the input after selection

* fix(autocomplete): remove shouldUseVirtualFocus

* fix(autocomplete): uncomment blur logic

* refactor(autocomplete): remove state as it is in getPopoverProps

* refactor(autocomplete): remove unnecessary blur

* refactor(select): remove unncessary props

* fix(popover): use domRef instead

* fix(popover): revise isNonModal and isDismissable

* fix(popover): use dialogRef back

* fix(popover): rollback

* fix(autocomplete): onFocus logic

* feat(popover): set disableFocusManagement to overlay

* feat(modal): set disableFocusManagement to overlay

* fix(autocomplete): set disableFocusManagement for autocomplete

* feat(popover): include disableFocusManagement prop

* refactor(autocomplete): revise type in selectorButton

* fix(autocomplete): revise focus logic

* feat(autocomplete): add internal focus state and add shouldCloseOnInteractOutside

* feat(autocomplete): handle selectedItem change

* feat(autocomplete): add clear button test

* feat(changeset): add changeset

* refactor(components): use the original order

* refactor(autocomplete): add more comments

* fix(autocomplete): revise focus behaviours

* refactor(autocomplete): rename to listbox

* chore(popover): remove disableFocusManagement from popover

* chore(autocomplete): remove disableFocusManagement from autocomplete

* chore(changeset): add issue number

* fix(popover): don't set default value to transformOrigin

* fix(autocomplete): revise shouldCloseOnInteractOutside logic

* feat(autocomplete): should close listbox by clicking another autocomplete

* fix(popover): add disableFocusManagement to overlay

* refactor(autocomplete): revise comments and refactor shouldCloseOnInteractOutside

* feat(changeset): add issue number

* fix(autocomplete): merge with selectorButtonProps.onClick

* refactor(autocomplete): remove extra line

* refactor(autocomplete): revise comment

* feat(select): add shouldCloseOnInteractOutside

* feat(dropdown): add shouldCloseOnInteractOutside

* feat(date-picker): add shouldCloseOnInteractOutside

* feat(changeset): add dropdown and date-picker

* fix(popover): revise shouldCloseOnInteractOutside

* feat(date-picker): integrate with ariaShouldCloseOnInteractOutside

* feat(select): integrate with ariaShouldCloseOnInteractOutside

* feat(dropdown): integrate with ariaShouldCloseOnInteractOutside

* feat(popover): integrate with ariaShouldCloseOnInteractOutside

* feat(aria-utils): ariaShouldCloseOnInteractOutside

* chore(deps): update pnpm-lock.yaml

* feat(autocomplete): integrate with ariaShouldCloseOnInteractOutside

* feat(aria-utils): handle setShouldFocus logic

* feat(changeset): add @nextui-org/aria-utils

* chore(autocomplete): put the test into correct group

* feat(select): should close listbox by clicking another select

* feat(dropdown): should close listbox by clicking another dropdown

* feat(popover): should close listbox by clicking another popover

* feat(date-picker): should close listbox by clicking another datepicker

* chore(changeset): add issue numbers and revise changeset message

* refactor(autocomplete): change to useRef instead

* refactor(autocomplete): change to useRef instead

* refactor(aria-utils): revise comments and format code

* chore(changeset): add issue number

* chore: take popoverProps.shouldCloseOnInteractOutside first

* refactor(autocomplete): remove unnecessary logic

* refactor(autocomplete): focus management logic
2024-05-24 16:19:46 -03:00

217 lines
5.8 KiB
TypeScript

import * as React from "react";
import {render, fireEvent, act} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {Button} from "@nextui-org/button";
import {Popover, PopoverContent, PopoverTrigger} from "../src";
// e.g. console.error Warning: Function components cannot be given refs.
// Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
const spy = jest.spyOn(console, "error").mockImplementation(() => {});
describe("Popover", () => {
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", () => {
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
act(() => {
trigger.click();
});
expect(spy).toBeCalledTimes(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");
act(() => {
content.blur();
});
expect(onClose).toHaveBeenCalledTimes(1);
});
it("should work with NextUI button", () => {
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
act(() => {
trigger.click();
});
expect(onClose).toHaveBeenCalledTimes(0);
// close popover
act(() => {
trigger.click();
});
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 act(async () => {
await userEvent.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 act(async () => {
await userEvent.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");
});
});