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();
});
});
});