Skip to content

Migrate SelectPanel tests from Jest to Vitest #6416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions packages/react/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ module.exports = {
'<rootDir>/src/ScrollableRegion/',
'<rootDir>/src/SegmentedControl/',
'<rootDir>/src/Select/',
'<rootDir>/src/SelectPanel/',
'<rootDir>/src/Skeleton/',
'<rootDir>/src/SkeletonAvatar/',
'<rootDir>/src/SkeletonText/',
Expand Down
104 changes: 41 additions & 63 deletions packages/react/src/SelectPanel/SelectPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'] = [
{
Expand Down Expand Up @@ -59,7 +58,7 @@ function BasicSelectPanel(passthroughProps: Record<string, unknown>) {
)
}

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`', () => {
Expand Down Expand Up @@ -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()
})
Expand Down Expand Up @@ -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<SelectPanelProps['items']>([])
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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(<FilterableSelectPanel />)

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<SelectPanelProps['items']>([])
Expand Down Expand Up @@ -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(<FilterableSelectPanel />)

await user.click(screen.getByText('Select items'))
expect(screen.getByLabelText('Filter items')).toHaveFocus()

jest.runAllTimers()
await waitFor(
async () => {
expect(getLiveRegion().getMessage('polite')?.trim()).toEqual(
Expand All @@ -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(
Expand All @@ -738,39 +728,29 @@ 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(<FilterableSelectPanel />)

await user.click(screen.getByText('Select items'))

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('')
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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(<SelectPanelWithCustomMessages items={[]} withAction={true} onAction={handleAction} />)

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')
})

Expand Down Expand Up @@ -957,7 +935,7 @@ describe('SelectPanel', () => {
render(<SelectPanelWithFooter />)

await user.click(screen.getByText('Select items'))
expect(screen.getByText('test footer')).toBeVisible()
expect(screen.getByText('test footer')).toBeInTheDocument()
})
})

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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()
})
})

Expand Down
1 change: 1 addition & 0 deletions packages/react/vitest.config.browser.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand Down
Loading