import React, { useState } from 'react' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { FilterButton } from '~components/Filter/FilterButton' import { FilterSelect, type FilterSelectProps } from './FilterSelect' import { mixedMockItemsUnordered, singleMockItems } from './_docs/mockData' import { type SingleSelectOption } from './types' const user = userEvent.setup() const FilterSelectWrapper = ({ isOpen: propsIsOpen = false, items = mixedMockItemsUnordered, selectedKey, onSelectionChange, ...restProps }: Partial): JSX.Element => { const [isOpen, setIsOpen] = useState(propsIsOpen) const [selected, setSelected] = React.useState( selectedKey ?? null, ) return ( } items={items} selectedKey={selected} onSelectionChange={(key): void => { setSelected(key) onSelectionChange?.(key) }} {...restProps} /> ) } describe('', () => { it('does not show the options initially', () => { render() expect(screen.queryByRole('listbox')).not.toBeInTheDocument() }) it('shows the options initially when isOpen is true', async () => { render() await waitFor(() => { expect(screen.getByRole('listbox')).toBeVisible() }) }) describe('Trigger', () => { it('shows the selected option in the button', () => { render() const trigger = screen.getByRole('button', { name: 'Coffee : Magic', }) expect(trigger).toBeVisible() }) describe('Trigger - Mouse interaction', () => { it('shows the options when the filter button is clicked', async () => { render() const trigger = screen.getByRole('button', { name: 'Coffee' }) await user.click(trigger) await waitFor(() => { expect(screen.queryByRole('listbox')).toBeVisible() }) }) it('closes when user clicks outside of the menu', async () => { render() await user.click(document.body) await waitFor(() => { expect(screen.queryByRole('listbox')).not.toBeInTheDocument() }) }) it('closes when user clicks on an option', async () => { render() const option = screen.getByRole('option', { name: 'Magic' }) await user.click(option) await waitFor(() => { expect(screen.queryByRole('listbox')).not.toBeInTheDocument() }) }) }) describe('Trigger - Keyboard interaction', () => { it('opens the menu when user hits enter key', async () => { render() const trigger = screen.getByRole('button', { name: 'Coffee' }) await user.tab() await waitFor(() => { expect(trigger).toHaveFocus() }) await user.keyboard('{Enter}') await waitFor(() => { expect(screen.queryByRole('listbox')).toBeVisible() }) }) it('moves the focus to the first focusable element inside the menu initially', async () => { render() expect(screen.queryByRole('listbox')).toBeVisible() await waitFor(() => { expect(screen.getAllByRole('option')[0]).toHaveFocus() }) }) it('moves focus to the first item on ArrowDown if nothing has been selected', async () => { render() const trigger = screen.getByRole('button', { name: 'Coffee' }) await user.tab() await waitFor(() => { expect(trigger).toHaveFocus() }) await user.keyboard('{ArrowDown}') await waitFor(() => { expect(screen.getAllByRole('option')[0]).toHaveFocus() }) }) it('moves focus to the last item on ArrowUp if nothing has been selected', async () => { render() const trigger = screen.getByRole('button', { name: 'Coffee' }) await user.tab() await waitFor(() => { expect(trigger).toHaveFocus() }) await user.keyboard('{ArrowUp}') await waitFor(() => { const options = screen.getAllByRole('option') expect(options[options.length - 1]).toHaveFocus() }) }) it('moves focus to the current selected item on Enter', async () => { render() const trigger = screen.getByRole('button', { name: 'Coffee : Hazelnut', }) await user.tab() await waitFor(() => { expect(trigger).toHaveFocus() }) await user.keyboard('{Enter}') await waitFor(() => { expect(screen.getByRole('option', { name: 'Hazelnut' })).toHaveFocus() }) }) it('moves focus to the current selected item on ArrowUp', async () => { render() const trigger = screen.getByRole('button', { name: 'Coffee : Hazelnut', }) await user.tab() await waitFor(() => { expect(trigger).toHaveFocus() }) await user.keyboard('{ArrowUp}') await waitFor(() => { expect(screen.getByRole('option', { name: 'Hazelnut' })).toHaveFocus() }) }) it('moves focus to the current selected item on ArrowDown', async () => { render() const trigger = screen.getByRole('button', { name: 'Coffee : Hazelnut', }) await user.tab() await waitFor(() => { expect(trigger).toHaveFocus() }) await user.keyboard('{ArrowDown}') await waitFor(() => { expect(screen.getByRole('option', { name: 'Hazelnut' })).toHaveFocus() }) }) it('closes when user hits the escape key', async () => { render() expect(screen.queryByRole('listbox')).toBeVisible() await user.keyboard('{Escape}') await waitFor(() => { expect(screen.queryByRole('listbox')).not.toBeInTheDocument() }) }) }) }) describe('Selection', () => { it('changes the value within the button when an option is selected', async () => { render( ( )} />, ) const button = screen.getByRole('button', { name: 'Coffee' }) await user.click(button) await waitFor(() => { expect(screen.queryByRole('listbox')).toBeVisible() }) const option = screen.getByRole('option', { name: 'Magic' }) await user.click(option) await waitFor(() => { expect(screen.getByRole('button', { name: 'Coffee : Magic' })).toBeVisible() }) }) }) describe('Number values', () => { it('finds selected option when value is a number', () => { const { getByRole } = render( , ) expect(getByRole('button', { name: 'Coffee : 50' })).toBeInTheDocument() }) }) }) describe('Stringified object values', () => { it('finds selected option when value is a stringified object', () => { const { getByRole } = render( , ) expect(getByRole('button', { name: 'Coffee : Created by A-Z' })).toBeInTheDocument() }) }) const defaultProps: FilterSelectProps = { label: 'Coffee', isOpen: false, setIsOpen: () => undefined, renderTrigger: (triggerButtonProps): JSX.Element => , items: singleMockItems, } /* eslint-disable vitest/expect-expect */ describe('FilterSelect generic', () => { it('should prevent adding `options` attribute to option', () => { /* @ts-expect-error Generic test */ ; {...defaultProps} /> }) describe('Extended option', () => { it('should error when new required attribute is missing from items', () => { /* @ts-expect-error Generic test */ ; {...defaultProps} /> }) it('should allow consumer to access custom attributes in children', () => { ; {...defaultProps} items={[{ label: 'Bubblegum', value: 'bubblegum', isRubberDuck: true }]} > {({ items }): JSX.Element[] => items.map((item) => item.type === 'item' ? (
  • {item.value?.isRubberDuck}
  • ) : (
  • Section
  • ), ) }
    }) }) }) /* eslint-enable vitest/expect-expect */