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(
1
2
); }); it('and anchors to right', function () { render(
1
2
); }); }); describe('Uncontrolled', function () { describe('While Closed', function () { it('displays dropdown menu when dropdown actor button clicked', async function () { render(
  • hi
  • ); 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(
  • hi
  • ); // 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(
  • hi
  • ); // 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(
  • hi
  • ); // 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(
  • hi
  • ); // 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(
  • hi
  • ); // 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(
  • hi
  • ); // 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(
  • hi
  • ); // 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(
  • hi
  • ); // Click menu await userEvent.click(screen.getByText('test'), {delay: null}); expect(screen.queryByText('hi')).not.toBeInTheDocument(); }); }); }); describe('Nested Dropdown', function () { function NestedDropdown() { return (
  • Hello
  • Item 2
  • ); } it('closes when top-level actor is clicked', async function () { render(); // 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(); // 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(); // 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(); // 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(); }); }); });