diff --git a/packages/react/jest.config.js b/packages/react/jest.config.js index 912ba5cb640..1afaec1ca74 100644 --- a/packages/react/jest.config.js +++ b/packages/react/jest.config.js @@ -61,6 +61,7 @@ module.exports = { '/src/ScrollableRegion/', '/src/SegmentedControl/', '/src/Select/', + '/src/SelectPanel/', '/src/Skeleton/', '/src/SkeletonAvatar/', '/src/SkeletonText/', diff --git a/packages/react/src/SelectPanel/SelectPanel.test.tsx b/packages/react/src/SelectPanel/SelectPanel.test.tsx index 01c3cd9e63a..4f837f8ff02 100644 --- a/packages/react/src/SelectPanel/SelectPanel.test.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.test.tsx @@ -1,3 +1,4 @@ +import {describe, expect, it, vi, beforeEach, afterEach} from 'vitest' import {render, screen, waitFor} from '@testing-library/react' import React from 'react' import {SelectPanel, type SelectPanelProps} from '../SelectPanel' @@ -6,13 +7,11 @@ import {userEvent} from '@testing-library/user-event' import ThemeProvider from '../ThemeProvider' import {FeatureFlags} from '../FeatureFlags' import type {InitialLoadingType} from './SelectPanel' -import {getLiveRegion} from '../utils/testing' import {IconButton} from '../Button' import {ArrowLeftIcon} from '@primer/octicons-react' import Box from '../Box' -import {setupMatchMedia} from '../utils/test-helpers' +import {getLiveRegion} from '../live-region/__tests__/test-helpers' -setupMatchMedia() const items: SelectPanelProps['items'] = [ { @@ -59,7 +58,7 @@ function BasicSelectPanel(passthroughProps: Record) { ) } -global.Element.prototype.scrollTo = jest.fn() +globalThis.Element.prototype.scrollTo = vi.fn() describe('SelectPanel', () => { it('should render an anchor to open the select panel using `placeholder`', () => { @@ -88,8 +87,8 @@ describe('SelectPanel', () => { expect(trigger).toHaveAttribute('aria-expanded', 'true') // Verify that the input and listbox are visible - expect(screen.getByLabelText('Filter items')).toBeVisible() - expect(screen.getByRole('listbox')).toBeVisible() + expect(screen.getByLabelText('Filter items')).toBeInTheDocument() + expect(screen.getByRole('listbox')).toBeInTheDocument() expect(screen.getByLabelText('Filter items')).toHaveFocus() }) @@ -138,7 +137,7 @@ describe('SelectPanel', () => { }) it('should call `onOpenChange` when opening and closing the dialog', async () => { - const onOpenChange = jest.fn() + const onOpenChange = vi.fn() function SelectPanelOpenChange() { const [selected, setSelected] = React.useState([]) @@ -560,10 +559,6 @@ describe('SelectPanel', () => { }) describe('screen reader announcements', () => { - beforeEach(() => { - const liveRegion = document.createElement('live-region') - document.body.appendChild(liveRegion) - }) function LoadingSelectPanel({ initialLoadingType = 'spinner', @@ -633,30 +628,25 @@ describe('SelectPanel', () => { }) it('should announce initially focused item', async () => { - jest.useFakeTimers() - const user = userEvent.setup({ - advanceTimers: jest.advanceTimersByTime, - }) + const user = userEvent.setup() render() await user.click(screen.getByText('Select items')) expect(screen.getByLabelText('Filter items')).toHaveFocus() - jest.runAllTimers() // we wait because announcement is intentionally updated after a timeout to not interrupt user input - await waitFor(async () => { - expect(getLiveRegion().getMessage('polite')?.trim()).toEqual( - 'List updated, Focused item: item one, not selected, 1 of 3', - ) - }) - jest.useRealTimers() + await waitFor( + () => { + expect(getLiveRegion().getMessage('polite')?.trim()).toEqual( + 'List updated, Focused item: item one, not selected, 1 of 3', + ) + }, + {timeout: 3000}, + ) }) it('should announce notice text', async () => { - jest.useFakeTimers() - const user = userEvent.setup({ - advanceTimers: jest.advanceTimersByTime, - }) + const user = userEvent.setup() function SelectPanelWithNotice() { const [selected, setSelected] = React.useState([]) @@ -699,20 +689,21 @@ describe('SelectPanel', () => { await user.click(screen.getByText('Select items')) expect(screen.getByLabelText('Filter items')).toHaveFocus() - expect(getLiveRegion().getMessage('polite')?.trim()).toContain('This is a notice') + await waitFor( + () => { + expect(getLiveRegion().getMessage('polite')?.trim()).toContain('This is a notice') + }, + {timeout: 3000}, + ) }) it('should announce filtered results', async () => { - jest.useFakeTimers() - const user = userEvent.setup({ - advanceTimers: jest.advanceTimersByTime, - }) + const user = userEvent.setup() render() await user.click(screen.getByText('Select items')) expect(screen.getByLabelText('Filter items')).toHaveFocus() - jest.runAllTimers() await waitFor( async () => { expect(getLiveRegion().getMessage('polite')?.trim()).toEqual( @@ -725,7 +716,6 @@ describe('SelectPanel', () => { await user.type(document.activeElement!, 'o') expect(screen.getAllByRole('option')).toHaveLength(2) - jest.runAllTimers() await waitFor( async () => { expect(getLiveRegion().getMessage('polite')).toBe( @@ -738,20 +728,15 @@ describe('SelectPanel', () => { await user.type(document.activeElement!, 'ne') // now: one expect(screen.getAllByRole('option')).toHaveLength(1) - jest.runAllTimers() await waitFor(async () => { expect(getLiveRegion().getMessage('polite')?.trim()).toBe( 'List updated, Focused item: item one, not selected, 1 of 1', ) }) - jest.useRealTimers() }) it('should announce default empty message when no results are available (no custom message is provided)', async () => { - jest.useFakeTimers() - const user = userEvent.setup({ - advanceTimers: jest.advanceTimersByTime, - }) + const user = userEvent.setup() render() await user.click(screen.getByText('Select items')) @@ -759,18 +744,13 @@ describe('SelectPanel', () => { await user.type(document.activeElement!, 'zero') expect(screen.queryByRole('option')).toBeNull() - jest.runAllTimers() await waitFor(async () => { expect(getLiveRegion().getMessage('polite')).toBe('No items available. ') }) - jest.useRealTimers() }) it('should announce custom empty message when no results are available', async () => { - jest.useFakeTimers() - const user = userEvent.setup({ - advanceTimers: jest.advanceTimersByTime, - }) + const user = userEvent.setup() function SelectPanelWithCustomEmptyMessage() { const [filter, setFilter] = React.useState('') @@ -811,11 +791,9 @@ describe('SelectPanel', () => { await user.type(document.activeElement!, 'zero') expect(screen.queryByRole('option')).toBeNull() - jest.runAllTimers() await waitFor(async () => { expect(getLiveRegion().getMessage('polite')).toBe(`Nothing found. There's nothing here.`) }) - jest.useRealTimers() }) it('should accept a className to style the component', async () => { @@ -840,7 +818,7 @@ describe('SelectPanel', () => { expect(screen.getAllByRole('option')).toHaveLength(3) await user.type(document.activeElement!, 'something') - expect(screen.getByText('No items available')).toBeVisible() + expect(screen.getByText('No items available')).toBeInTheDocument() }) it('should display the default empty state message when there is no item after the initial load (No custom message is provided)', async () => { @@ -850,7 +828,7 @@ describe('SelectPanel', () => { await waitFor(async () => { await user.click(screen.getByText('Select items')) - expect(screen.getByText('No items available')).toBeVisible() + expect(screen.getByText('No items available')).toBeInTheDocument() }) }) it('should display the custom empty state message when there is no matching item after filtering', async () => { @@ -877,8 +855,8 @@ describe('SelectPanel', () => { expect(screen.getAllByRole('option')).toHaveLength(3) await user.type(document.activeElement!, 'something') - expect(screen.getByText('No language found for something')).toBeVisible() - expect(screen.getByText('Adjust your search term to find other languages')).toBeVisible() + expect(screen.getByText('No language found for something')).toBeInTheDocument() + expect(screen.getByText('Adjust your search term to find other languages')).toBeInTheDocument() }) it('should display the custom empty state message when there is no item after the initial load', async () => { @@ -888,25 +866,25 @@ describe('SelectPanel', () => { await waitFor(async () => { await user.click(screen.getByText('Select items')) - expect(screen.getByText("You haven't created any projects yet")).toBeVisible() - expect(screen.getByText('Start your first project to organise your issues')).toBeVisible() + expect(screen.getByText("You haven't created any projects yet")).toBeInTheDocument() + expect(screen.getByText('Start your first project to organise your issues')).toBeInTheDocument() }) }) it('should display action button in custom empty state message', async () => { - const handleAction = jest.fn() + const handleAction = vi.fn() const user = userEvent.setup() render() await waitFor(async () => { await user.click(screen.getByText('Select items')) - expect(screen.getByText("You haven't created any projects yet")).toBeVisible() - expect(screen.getByText('Start your first project to organise your issues')).toBeVisible() + expect(screen.getByText("You haven't created any projects yet")).toBeInTheDocument() + expect(screen.getByText('Start your first project to organise your issues')).toBeInTheDocument() // Check that action button is visible const actionButton = screen.getByTestId('create-project-action') - expect(actionButton).toBeVisible() + expect(actionButton).toBeInTheDocument() expect(actionButton).toHaveTextContent('Create new project') }) @@ -957,7 +935,7 @@ describe('SelectPanel', () => { render() await user.click(screen.getByText('Select items')) - expect(screen.getByText('test footer')).toBeVisible() + expect(screen.getByText('test footer')).toBeInTheDocument() }) }) @@ -1035,7 +1013,7 @@ describe('SelectPanel', () => { await user.click(screen.getByText('Select items')) const listbox = screen.getByRole('listbox') - expect(listbox).toBeVisible() + expect(listbox).toBeInTheDocument() expect(listbox).toHaveAttribute('aria-multiselectable', 'true') // listbox should has 3 groups and each have heading @@ -1088,8 +1066,8 @@ describe('SelectPanel', () => { expect(screen.getAllByRole('radio').length).toBe(items.length) - expect(screen.getByRole('button', {name: 'Save'})).toBeVisible() - expect(screen.getByRole('button', {name: 'Cancel'})).toBeVisible() + expect(screen.getByRole('button', {name: 'Save'})).toBeInTheDocument() + expect(screen.getByRole('button', {name: 'Cancel'})).toBeInTheDocument() }) it('save and oncancel buttons are present when variant modal', async () => { const user = userEvent.setup() @@ -1098,8 +1076,8 @@ describe('SelectPanel', () => { await user.click(screen.getByText('Select items')) - expect(screen.getByRole('button', {name: 'Save'})).toBeVisible() - expect(screen.getByRole('button', {name: 'Cancel'})).toBeVisible() + expect(screen.getByRole('button', {name: 'Save'})).toBeInTheDocument() + expect(screen.getByRole('button', {name: 'Cancel'})).toBeInTheDocument() }) }) diff --git a/packages/react/vitest.config.browser.mts b/packages/react/vitest.config.browser.mts index 4bd1f0f4d17..f8b3af404e0 100644 --- a/packages/react/vitest.config.browser.mts +++ b/packages/react/vitest.config.browser.mts @@ -74,6 +74,7 @@ export default defineConfig({ 'src/ScrollableRegion/**/*.test.?(c|m)[jt]s?(x)', 'src/SegmentedControl/**/*.test.?(c|m)[jt]s?(x)', 'src/Select/**/*.test.?(c|m)[jt]s?(x)', + 'src/SelectPanel/**/*.test.?(c|m)[jt]s?(x)', 'src/Skeleton/**/*.test.?(c|m)[jt]s?(x)', 'src/SkeletonAvatar/**/*.test.?(c|m)[jt]s?(x)', 'src/SkeletonText/**/*.test.?(c|m)[jt]s?(x)',