Skip to content

Commit

Permalink
test(dropdown.tsx): updated and added more tests
Browse files Browse the repository at this point in the history
Resolution for dependency nwsapi was added in order to fix an issue in the tababble package
  • Loading branch information
nigellima committed Jul 6, 2023
1 parent af57dee commit 3210072
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 146 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@
"react-dom": "^18",
"tailwindcss": "^3"
},
"resolutions": {
"nwsapi": "2.2.2"
},
"private": false,
"eslintConfig": {
"extends": [
Expand Down
173 changes: 141 additions & 32 deletions src/components/Dropdown/Dropdown.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,156 @@
import { act, render, screen } from '@testing-library/react';
import { act, fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type { FC, ReactNode } from 'react';
import type { FC, PropsWithChildren } from 'react';
import { describe, expect, it } from 'vitest';
import type { FlowbiteDropdownTheme } from './Dropdown';
import type { DropdownProps } from './Dropdown';
import { Dropdown } from './Dropdown';

describe('Components / Dropdown', () => {
describe('A11y', async () => {
it('should use `role="menu"` in menu container', async () => {
const user = userEvent.setup();
render(<TestDropdown />);

await act(() => user.click(button()));

expect(screen.getByRole('menu')).toBe(dropdown());
});

it('should use `role="menuitem"` in dropdown items', async () => {
const user = userEvent.setup();
render(<TestDropdown />);

await act(() => user.click(button()));

expect(screen.getAllByRole('menuitem')).toHaveLength(4);
});

it('should not open when `disabled={true}`', async () => {
const user = userEvent.setup();
const { rerender } = render(<TestDropdown disabled />);

expect(button()).toBeDisabled();

await act(() => user.click(button()));
expect(dropdown()).not.toBeInTheDocument();

rerender(<TestDropdown disabled trigger="hover" />);

await act(() => user.hover(button()));
expect(dropdown()).not.toBeInTheDocument();
});
});

describe('Keyboard interactions', () => {
it('should collapse if expanded when `Space` is pressed', async () => {
const user = userEvent.setup();
render(<TestDropdown />);
expect(dropdown()).not.toBeInTheDocument();

expect(dropdown()).toHaveClass('invisible');

await user.click(button());
await act(() => user.click(button()));

expect(dropdown()).not.toHaveClass('invisible');
expect(dropdown()).toBeInTheDocument();
});

it('should expand if collapsed when `Space` is pressed', async () => {
const user = userEvent.setup();
render(<TestDropdown />);

await user.click(button());
await user.click(button());
await act(() => user.click(button()));
await act(() => user.click(button()));

expect(dropdown()).toHaveClass('invisible');
expect(dropdown()).not.toBeInTheDocument();
});

it('should expand when focus button and press arrow down key', async () => {
const user = userEvent.setup();
render(<TestDropdown />);

await act(() => user.tab());
expect(button()).toHaveFocus();
expect(dropdown()).not.toBeInTheDocument();

await act(() => fireEvent.keyDown(button(), { key: 'ArrowDown', code: 'ArrowDown' }));
expect(dropdown()).toBeInTheDocument();
});

it('should focus matching item when user types the first option char and dropdown is open', async () => {
const user = userEvent.setup();
render(<TestDropdown />);

await act(() => user.click(button()));
expect(dropdown()).toBeInTheDocument();

await act(() => fireEvent.keyDown(button(), { key: 'S', code: 'KeyS' }));

const item = screen.getByText('Settings');
expect(item).toHaveFocus();
});
});

describe('Mouse interactions', () => {
it('should collapse if item is clicked', async () => {
const user = userEvent.setup();
render(<TestDropdown />);

act(() => {
user.click(button());
userEvent.click(dropdownItem());
});
await act(() => user.click(button()));
await act(() => userEvent.click(dropdownItem()));

expect(dropdown()).toHaveClass('invisible');
expect(dropdown()).not.toBeInTheDocument();
});

it('should collapse if CustomTriggerItem is clicked', async () => {
const user = userEvent.setup();
render(<TestDropdown renderTrigger={() => <button type="button"></button>} />);

act(() => {
user.click(button());
userEvent.click(dropdownItem());
});
await act(() => user.click(screen.getByRole('button')));
await act(() => userEvent.click(dropdownItem()));

expect(dropdown()).toHaveClass('invisible');
expect(dropdown()).not.toBeInTheDocument();
});

it('should always collapse when item is clicked', async () => {
const user = userEvent.setup();
render(<TestDropdown />);

await act(() => user.click(button()));
await act(() => userEvent.click(dropdownItem()));

expect(dropdown()).not.toBeInTheDocument();

await act(() => user.click(button()));
await act(() => userEvent.click(dropdownItem()));

expect(dropdown()).not.toBeInTheDocument();
});

it('should not collapse in case item is clicked if dismissOnClick = false', async () => {
const user = userEvent.setup();
render(<TestDropdown dismissOnClick={false} />);

expect(dropdown()).toHaveClass('invisible');
expect(dropdown()).not.toBeInTheDocument();

await user.click(button());
await act(() => user.click(button()));

expect(dropdown()).not.toHaveClass('invisible');
expect(dropdown()).toBeInTheDocument();

await user.click(dropdownItem());
await act(() => userEvent.click(dropdownItem()));

expect(dropdown()).not.toHaveClass('invisible');
expect(dropdown()).toBeInTheDocument();
});

it('should open on hover when `trigger="hover"`', async () => {
const user = userEvent.setup();
render(<TestDropdown trigger="hover" />);

expect(dropdown()).not.toBeInTheDocument();

await act(() => user.hover(button()));

expect(dropdown()).toBeInTheDocument();
});
});

describe('Type of button', async () => {
it('should be of type `button`', async () => {
render(<TestDropdown />);
Expand All @@ -78,18 +162,43 @@ describe('Components / Dropdown', () => {
expect(button()).toHaveAttribute('type', 'button');
});
});

describe('Dropdown item render', async () => {
it('should override Dropdownn.Item base component when using `as` prop', async () => {
const user = userEvent.setup();

const CustomBaseItem = ({ children }: PropsWithChildren) => {
return <a href="#">{children}</a>;
};

render(
<Dropdown label="Dropdown button" placement="right">
<Dropdown.Item as={CustomBaseItem}>Settings</Dropdown.Item>
</Dropdown>,
);

await act(() => user.click(button()));

const item = screen.getByText('Settings');
expect(screen.getByRole('link')).toBe(item);
});
});
});

const TestDropdown: FC<{
dismissOnClick?: boolean;
inline?: boolean;
renderTrigger?: (theme: FlowbiteDropdownTheme) => ReactNode;
}> = ({ dismissOnClick = true, inline = false, renderTrigger }) => (
const TestDropdown: FC<Partial<DropdownProps>> = ({
dismissOnClick = true,
inline = false,
disabled,
trigger,
renderTrigger,
}) => (
<Dropdown
label="Dropdown button"
placement="right"
dismissOnClick={dismissOnClick}
inline={inline}
trigger={trigger}
disabled={disabled}
renderTrigger={renderTrigger}
>
<Dropdown.Header>
Expand All @@ -104,8 +213,8 @@ const TestDropdown: FC<{
</Dropdown>
);

const button = () => screen.getByRole('button');
const button = () => screen.getByRole('button', { name: /Dropdown button/i });

const dropdown = () => screen.getByTestId('flowbite-tooltip');
const dropdown = () => screen.queryByTestId('flowbite-dropdown');

const dropdownItem = () => screen.getByText('Dashboard');
1 change: 1 addition & 0 deletions src/components/Dropdown/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default {
title: 'Dropdown example',
label: 'Dropdown button',
placement: 'auto',
disabled: false,
},
} as Meta;

Expand Down
7 changes: 4 additions & 3 deletions src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import type { FlowbiteDropdownItemTheme } from './DropdownItem';
import { DropdownItem } from './DropdownItem';

import { twMerge } from 'tailwind-merge';
import { mergeWrapperClassName } from '~/src/helpers/floating';
import { useBaseFLoating, useFloatingInteractions } from '~/src/helpers/use-floating';
import { mergeWrapperClassName } from '../../helpers/floating';
import { useBaseFLoating, useFloatingInteractions } from '../../helpers/use-floating';

export interface FlowbiteDropdownFloatingTheme
extends FlowbiteFloatingTheme,
Expand Down Expand Up @@ -93,7 +93,8 @@ const Trigger = ({
}, [ref, setButtonWidth]);

if (renderTrigger) {
return cloneElement(renderTrigger(theme), { ref: refs.setReference, disabled, ...a11yProps });
const triggerElement = renderTrigger(theme);
return cloneElement(triggerElement, { ref: refs.setReference, disabled, ...a11yProps, ...triggerElement.props });
}

return inline ? (
Expand Down
Loading

0 comments on commit 3210072

Please sign in to comment.