123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- import {
- act,
- render,
- screen,
- userEvent,
- waitForElementToBeRemoved,
- } from 'sentry-test/reactTestingLibrary';
- import DropdownLink from 'sentry/components/dropdownLink';
- import {MENU_CLOSE_DELAY} from 'sentry/constants';
- describe('DropdownLink', function () {
- beforeEach(() => {
- jest.useFakeTimers();
- });
- afterEach(() => {
- jest.useRealTimers();
- });
- const INPUT_1 = {
- title: 'test',
- onOpen: () => {},
- onClose: () => {},
- topLevelClasses: 'top-level-class',
- alwaysRenderMenu: true,
- menuClasses: '',
- };
- describe('renders', function () {
- it('and anchors to left by default', function () {
- render(
- <DropdownLink {...INPUT_1}>
- <div>1</div>
- <div>2</div>
- </DropdownLink>
- );
- });
- it('and anchors to right', function () {
- render(
- <DropdownLink {...INPUT_1} anchorRight>
- <div>1</div>
- <div>2</div>
- </DropdownLink>
- );
- });
- });
- describe('Uncontrolled', function () {
- describe('While Closed', function () {
- it('displays dropdown menu when dropdown actor button clicked', async function () {
- render(
- <DropdownLink alwaysRenderMenu={false} title="test">
- <li>hi</li>
- </DropdownLink>
- );
- expect(screen.queryByText('hi')).not.toBeInTheDocument();
- // open
- await userEvent.click(screen.getByText('test'), {delay: null});
- expect(screen.getByText('hi')).toBeInTheDocument();
- });
- });
- describe('While Opened', function () {
- it('closes when clicked outside', async function () {
- render(
- <div data-test-id="outside-element">
- <DropdownLink title="test" alwaysRenderMenu={false}>
- <li>hi</li>
- </DropdownLink>
- </div>
- );
- // Open menu
- await userEvent.click(screen.getByText('test'), {delay: null});
- // Click outside
- await userEvent.click(screen.getByTestId('outside-element'), {delay: null});
- await waitForElementToBeRemoved(() => screen.queryByText('hi'));
- });
- it('closes when dropdown actor button is clicked', async function () {
- render(
- <DropdownLink title="test" alwaysRenderMenu={false}>
- <li>hi</li>
- </DropdownLink>
- );
- // Open menu
- await userEvent.click(screen.getByText('test'), {delay: null});
- // Click again
- await userEvent.click(screen.getByText('test'), {delay: null});
- expect(screen.queryByText('hi')).not.toBeInTheDocument();
- });
- it('closes when dropdown menu item is clicked', async function () {
- render(
- <DropdownLink title="test" alwaysRenderMenu={false}>
- <li>hi</li>
- </DropdownLink>
- );
- // Open menu
- await userEvent.click(screen.getByText('test'), {delay: null});
- await userEvent.click(screen.getByText('hi'), {delay: null});
- expect(screen.queryByText('hi')).not.toBeInTheDocument();
- });
- it('does not close when menu is clicked and `keepMenuOpen` is on', async function () {
- render(
- <DropdownLink title="test" alwaysRenderMenu={false} keepMenuOpen>
- <li>hi</li>
- </DropdownLink>
- );
- // Open menu
- await userEvent.click(screen.getByText('test'), {delay: null});
- // Click again
- await userEvent.click(screen.getByText('test'), {delay: null});
- expect(screen.getByText('test')).toBeInTheDocument();
- });
- });
- });
- describe('Controlled', function () {
- describe('Opened', function () {
- it('does not close when menu is clicked', async function () {
- render(
- <DropdownLink title="test" alwaysRenderMenu={false} isOpen>
- <li>hi</li>
- </DropdownLink>
- );
- // Click option
- await userEvent.click(screen.getByText('hi'), {delay: null});
- // Should still be open
- expect(screen.getByText('hi')).toBeInTheDocument();
- });
- it('does not close when document is clicked', async function () {
- render(
- <div data-test-id="outside-element">
- <DropdownLink title="test" alwaysRenderMenu={false} isOpen>
- <li>hi</li>
- </DropdownLink>
- </div>
- );
- // Click outside
- await userEvent.click(screen.getByTestId('outside-element'), {delay: null});
- // Should still be open
- expect(screen.getByText('hi')).toBeInTheDocument();
- });
- it('does not close when dropdown actor is clicked', async function () {
- render(
- <DropdownLink title="test" alwaysRenderMenu={false} isOpen>
- <li>hi</li>
- </DropdownLink>
- );
- // Click menu
- await userEvent.click(screen.getByText('test'), {delay: null});
- // Should still be open
- expect(screen.getByText('hi')).toBeInTheDocument();
- });
- });
- describe('Closed', function () {
- it('does not open when dropdown actor is clicked', async function () {
- render(
- <DropdownLink title="test" alwaysRenderMenu={false} isOpen={false}>
- <li>hi</li>
- </DropdownLink>
- );
- // Click menu
- await userEvent.click(screen.getByText('test'), {delay: null});
- expect(screen.queryByText('hi')).not.toBeInTheDocument();
- });
- });
- });
- describe('Nested Dropdown', function () {
- function NestedDropdown() {
- return (
- <DropdownLink title="parent" alwaysRenderMenu={false}>
- <li>
- <DropdownLink alwaysRenderMenu={false} title="nested" isNestedDropdown>
- <li>
- <DropdownLink alwaysRenderMenu={false} title="nested #2" isNestedDropdown>
- <li>Hello</li>
- </DropdownLink>
- </li>
- </DropdownLink>
- </li>
- <li id="no-nest">Item 2</li>
- </DropdownLink>
- );
- }
- it('closes when top-level actor is clicked', async function () {
- render(<NestedDropdown />);
- // Open menu
- await userEvent.click(screen.getByText('parent'), {delay: null});
- // Close menu
- await userEvent.click(screen.getByText('parent'), {delay: null});
- expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
- });
- it('Opens / closes on mouse enter and leave', async function () {
- render(<NestedDropdown />);
- // Open menu
- await userEvent.click(screen.getByText('parent'), {delay: null});
- await userEvent.hover(screen.getByText('nested'), {delay: null});
- await screen.findByText('nested #2');
- // Leaving Nested Menu
- await userEvent.unhover(screen.getByText('nested'), {delay: null});
- // Nested menus have close delay
- act(() => jest.advanceTimersByTime(MENU_CLOSE_DELAY - 1));
- // Re-entering nested menu will cancel close
- await userEvent.hover(screen.getByText('nested'), {delay: null});
- act(() => jest.advanceTimersByTime(2));
- expect(screen.getByText('nested #2')).toBeInTheDocument();
- // Re-entering an actor will also cancel close
- act(() => jest.advanceTimersByTime(MENU_CLOSE_DELAY - 1));
- act(() => jest.advanceTimersByTime(2));
- await userEvent.hover(screen.getByText('parent'), {delay: null});
- expect(screen.getByText('nested #2')).toBeInTheDocument();
- // Leave menu
- await userEvent.unhover(screen.getByText('nested'), {delay: null});
- act(jest.runAllTimers);
- expect(screen.queryByText('nested #2')).not.toBeInTheDocument();
- });
- it('does not close when nested actors are clicked', async function () {
- render(<NestedDropdown />);
- // Open menu
- await userEvent.click(screen.getByText('parent'), {delay: null});
- await userEvent.click(screen.getByText('nested'), {delay: null});
- expect(screen.getByRole('listbox')).toBeInTheDocument();
- await userEvent.hover(screen.getByText('nested'), {delay: null});
- await userEvent.click(await screen.findByText('nested #2'), {delay: null});
- expect(screen.getAllByRole('listbox')).toHaveLength(2);
- });
- it('closes when terminal nested actor is clicked', async function () {
- render(<NestedDropdown />);
- // Open menu
- await userEvent.click(screen.getByText('parent'), {delay: null});
- await userEvent.hover(screen.getByText('nested'), {delay: null});
- await userEvent.hover(await screen.findByText('nested #2'), {delay: null});
- await userEvent.click(await screen.findByText('Hello'), {delay: null});
- expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
- });
- });
- });
|