Skip to content

Commit 779314b

Browse files
severoCopilot
andauthored
Rely on userEvent instead of fireEvent (#341)
* fix the flaky test * clear mocks, fix test, replace fireEvent with userEvent * replace fireEvent with userEvent * remove import * Update src/components/Dropdown/Dropdown.test.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 623a63c commit 779314b

File tree

11 files changed

+161
-122
lines changed

11 files changed

+161
-122
lines changed

src/components/Dropdown/Dropdown.test.tsx

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { fireEvent, render } from '@testing-library/react'
1+
import { act, render } from '@testing-library/react'
2+
import { userEvent } from '@testing-library/user-event'
23
import { describe, expect, it, vi } from 'vitest'
34
import Dropdown from './Dropdown'
45
import styles from './Dropdown.module.css'
@@ -14,7 +15,7 @@ describe('Dropdown Component', () => {
1415
expect(div?.classList).toContain(styles.dropdownLeft)
1516
})
1617

17-
it('toggles dropdown content on button click', () => {
18+
it('toggles dropdown content on button click', async () => {
1819
const { container: { children: [ div ] }, getByRole } = render(
1920
<Dropdown label='go'>
2021
<div>Child 1</div>
@@ -24,15 +25,16 @@ describe('Dropdown Component', () => {
2425
const dropdownButton = getByRole('button')
2526

2627
// open menu with click
27-
fireEvent.click(dropdownButton)
28+
const user = userEvent.setup()
29+
await user.click(dropdownButton)
2830
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')
2931

3032
// click again to close
31-
fireEvent.click(dropdownButton)
33+
await user.click(dropdownButton)
3234
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
3335
})
3436

35-
it('closes dropdown when clicking outside', () => {
37+
it('closes dropdown when clicking outside', async () => {
3638
const { container: { children: [ div ] }, getByRole } = render(
3739
<Dropdown>
3840
<div>Child 1</div>
@@ -41,15 +43,16 @@ describe('Dropdown Component', () => {
4143
)
4244

4345
const dropdownButton = getByRole('button')
44-
fireEvent.click(dropdownButton) // open dropdown
46+
const user = userEvent.setup()
47+
await user.click(dropdownButton) // open dropdown
4548
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')
4649

4750
// Simulate a click outside
48-
fireEvent.mouseDown(document)
51+
await user.click(document.body)
4952
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
5053
})
5154

52-
it('does not close dropdown when clicking inside', () => {
55+
it('closes dropdown when clicking inside', async () => {
5356
const { container: { children: [ div ] }, getByRole, getByText } = render(
5457
<Dropdown>
5558
<div>Child 1</div>
@@ -58,16 +61,17 @@ describe('Dropdown Component', () => {
5861
)
5962

6063
const dropdownButton = getByRole('button')
61-
fireEvent.click(dropdownButton) // open dropdown
64+
const user = userEvent.setup()
65+
await user.click(dropdownButton) // open dropdown
6266
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')
6367

6468
const dropdownContent = getByText('Child 1').parentElement
6569
if (!dropdownContent) throw new Error('Dropdown content not found')
66-
fireEvent.mouseDown(dropdownContent)
67-
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')
70+
await user.click(dropdownContent)
71+
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
6872
})
6973

70-
it('closes dropdown on escape key press', () => {
74+
it('closes dropdown on escape key press', async () => {
7175
const { container: { children: [ div ] }, getByRole } = render(
7276
<Dropdown>
7377
<div>Child 1</div>
@@ -76,11 +80,12 @@ describe('Dropdown Component', () => {
7680
)
7781

7882
const dropdownButton = getByRole('button')
79-
fireEvent.click(dropdownButton) // open dropdown
83+
const user = userEvent.setup()
84+
await user.click(dropdownButton) // open dropdown
8085
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')
8186

8287
// Press escape key
83-
fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' })
88+
await user.keyboard('{Escape}')
8489
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
8590
})
8691

@@ -107,7 +112,7 @@ describe('Dropdown Component', () => {
107112
})
108113

109114
// Keyboard navigation tests
110-
it('opens dropdown and focuses first item on ArrowDown when closed', () => {
115+
it('opens dropdown and focuses first item on ArrowDown when closed', async () => {
111116
const { getByRole, getAllByRole } = render(
112117
<Dropdown label="Menu">
113118
<button role="menuitem">Item 1</button>
@@ -120,15 +125,20 @@ describe('Dropdown Component', () => {
120125
// initially closed
121126
expect(dropdownButton.getAttribute('aria-expanded')).toBe('false')
122127

128+
// focus the button
129+
act(() => {
130+
dropdownButton.focus()
131+
})
123132
// down arrow to open menu
124-
fireEvent.keyDown(dropdownButton, { key: 'ArrowDown', code: 'ArrowDown' })
133+
const user = userEvent.setup()
134+
await user.keyboard('{ArrowDown}')
125135
expect(dropdownButton.getAttribute('aria-expanded')).toBe('true')
126136

127137
// first menu item should be focused
128138
expect(document.activeElement).toBe(menuItems[0])
129139
})
130140

131-
it('focuses the next item on ArrowDown and wraps to first item if at the end', () => {
141+
it('focuses the next item on ArrowDown and wraps to first item if at the end', async () => {
132142
const { getByRole, getAllByRole } = render(
133143
<Dropdown label="Menu">
134144
<button role="menuitem">Item 1</button>
@@ -139,19 +149,20 @@ describe('Dropdown Component', () => {
139149
const dropdownButton = getByRole('button')
140150

141151
// open menu, first item has focus
142-
fireEvent.click(dropdownButton)
152+
const user = userEvent.setup()
153+
await user.click(dropdownButton)
143154
expect(document.activeElement).toBe(menuItems[0])
144155

145156
// second item should be focused
146-
fireEvent.keyDown(menuItems[0], { key: 'ArrowDown', code: 'ArrowDown' })
157+
await user.keyboard('{ArrowDown}')
147158
expect(document.activeElement).toBe(menuItems[1])
148159

149160
// wrap back to first item
150-
fireEvent.keyDown(menuItems[1], { key: 'ArrowDown', code: 'ArrowDown' })
161+
await user.keyboard('{ArrowDown}')
151162
expect(document.activeElement).toBe(menuItems[0])
152163
})
153164

154-
it('focuses the previous item on ArrowUp and wraps to the last item if at the top', () => {
165+
it('focuses the previous item on ArrowUp and wraps to the last item if at the top', async () => {
155166
const { getByRole, getAllByRole } = render(
156167
<Dropdown label="Menu">
157168
<button role="menuitem">Item 1</button>
@@ -162,15 +173,16 @@ describe('Dropdown Component', () => {
162173
const dropdownButton = getByRole('button')
163174

164175
// open menu, first item has focus
165-
fireEvent.click(dropdownButton)
176+
const user = userEvent.setup()
177+
await user.click(dropdownButton)
166178
expect(document.activeElement).toBe(menuItems[0])
167179

168180
// ArrowUp -> should wrap to last item
169-
fireEvent.keyDown(menuItems[0], { key: 'ArrowUp', code: 'ArrowUp' })
181+
await user.keyboard('{ArrowUp}')
170182
expect(document.activeElement).toBe(menuItems[1])
171183
})
172184

173-
it('focuses first item on Home key press', () => {
185+
it('focuses first item on Home key press', async () => {
174186
const { getByRole, getAllByRole } = render(
175187
<Dropdown label="Menu">
176188
<button role="menuitem">Item 1</button>
@@ -182,19 +194,20 @@ describe('Dropdown Component', () => {
182194
const dropdownButton = getByRole('button')
183195

184196
// open menu, first item has focus
185-
fireEvent.click(dropdownButton)
197+
const user = userEvent.setup()
198+
await user.click(dropdownButton)
186199
expect(document.activeElement).toBe(menuItems[0])
187200

188201
// move to the second item
189-
fireEvent.keyDown(menuItems[0], { key: 'ArrowDown', code: 'ArrowDown' })
202+
await user.keyboard('{ArrowDown}')
190203
expect(document.activeElement).toBe(menuItems[1])
191204

192205
// Home key should focus first item
193-
fireEvent.keyDown(menuItems[1], { key: 'Home', code: 'Home' })
206+
await user.keyboard('{Home}')
194207
expect(document.activeElement).toBe(menuItems[0])
195208
})
196209

197-
it('focuses last item on End key press', () => {
210+
it('focuses last item on End key press', async () => {
198211
const { getByRole, getAllByRole } = render(
199212
<Dropdown label="Menu">
200213
<button role="menuitem">Item 1</button>
@@ -206,15 +219,16 @@ describe('Dropdown Component', () => {
206219
const dropdownButton = getByRole('button')
207220

208221
// open menu, first item has focus
209-
fireEvent.click(dropdownButton)
222+
const user = userEvent.setup()
223+
await user.click(dropdownButton)
210224
expect(document.activeElement).toBe(menuItems[0])
211225

212226
// End key should focus the last item
213-
fireEvent.keyDown(menuItems[0], { key: 'End', code: 'End' })
227+
await user.keyboard('{End}')
214228
expect(document.activeElement).toBe(menuItems[2])
215229
})
216230

217-
it('closes the menu and puts focus back on the button on Escape', () => {
231+
it('closes the menu and puts focus back on the button on Escape', async () => {
218232
const { getByRole, getAllByRole } = render(
219233
<Dropdown label="Menu">
220234
<button role="menuitem">Item 1</button>
@@ -225,12 +239,13 @@ describe('Dropdown Component', () => {
225239
const dropdownButton = getByRole('button')
226240

227241
// open menu, first item has focus
228-
fireEvent.click(dropdownButton)
242+
const user = userEvent.setup()
243+
await user.click(dropdownButton)
229244
expect(document.activeElement).toBe(menuItems[0])
230245
expect(dropdownButton.getAttribute('aria-expanded')).toBe('true')
231246

232247
// escape closes menu
233-
fireEvent.keyDown(menuItems[0], { key: 'Escape', code: 'Escape' })
248+
await user.keyboard('{Escape}')
234249
expect(dropdownButton.getAttribute('aria-expanded')).toBe('false')
235250

236251
// focus returns to the button

src/components/File/File.test.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { render } from '@testing-library/react'
22
import { strict as assert } from 'assert'
33
import { act } from 'react'
4-
import { describe, expect, it, vi } from 'vitest'
4+
import { beforeEach, describe, expect, it, vi } from 'vitest'
55
import { Config, ConfigProvider } from '../../hooks/useConfig.js'
66
import { getHttpSource, getHyperparamSource } from '../../lib/sources/index.js'
77
import File from './File.js'
@@ -21,6 +21,10 @@ const headers = { get: vi.fn() }
2121
globalThis.fetch = vi.fn(() => Promise.resolve({ text, headers } as unknown as Response))
2222

2323
describe('File Component', () => {
24+
beforeEach(() => {
25+
vi.clearAllMocks()
26+
})
27+
2428
it('renders a local file path', async () => {
2529
text.mockResolvedValueOnce('test content')
2630
const source = getHyperparamSource('folder/subfolder/test.txt', { endpoint })

src/components/Folder/Folder.test.tsx

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { fireEvent, render, waitFor } from '@testing-library/react'
1+
import { render, waitFor } from '@testing-library/react'
2+
import { userEvent } from '@testing-library/user-event'
23
import { strict as assert } from 'assert'
34
import { act } from 'react'
4-
import { describe, expect, it, test, vi } from 'vitest'
5+
import { beforeEach, describe, expect, it, test, vi } from 'vitest'
56
import { Config, ConfigProvider } from '../../hooks/useConfig.js'
67
import { DirSource, FileMetadata, HyperparamFileMetadata, getHyperparamSource } from '../../lib/sources/index.js'
78
import Folder from './Folder.js'
@@ -21,6 +22,10 @@ const config: Config = {
2122
globalThis.fetch = vi.fn()
2223

2324
describe('Folder Component', () => {
25+
beforeEach(() => {
26+
vi.clearAllMocks()
27+
})
28+
2429
test.for([
2530
'',
2631
'subfolder/',
@@ -99,19 +104,16 @@ describe('Folder Component', () => {
99104

100105
// Type a search query
101106
const searchInput = getByPlaceholderText('Search...') as HTMLInputElement
102-
act(() => {
103-
fireEvent.keyUp(searchInput, { target: { value: 'file1' } })
104-
})
107+
const user = userEvent.setup()
108+
await user.type(searchInput, 'file1')
105109

106110
// Only matching files are displayed
107111
await findByText('file1.txt')
108112
expect(queryByText('folder1/')).toBeNull()
109113
expect(queryByText('report.pdf')).toBeNull()
110114

111115
// Clear search with escape key
112-
act(() => {
113-
fireEvent.keyUp(searchInput, { key: 'Escape' })
114-
})
116+
await user.type(searchInput, '{Escape}')
115117

116118
await findByText('report.pdf')
117119
getByText('folder1/')
@@ -140,26 +142,24 @@ describe('Folder Component', () => {
140142

141143
// Type a search query and hit enter
142144
const searchInput = getByPlaceholderText('Search...') as HTMLInputElement
143-
act(() => {
144-
fireEvent.keyUp(searchInput, { target: { value: 'file1' } })
145-
})
146-
145+
const user = userEvent.setup()
146+
await user.type(searchInput, 'file1')
147147
await findByText('file1.txt')
148148

149-
act(() => {
150-
fireEvent.keyUp(searchInput, { key: 'Enter' })
151-
})
152-
149+
await user.type(searchInput, '{Enter}')
153150
expect(location.href).toBe('/files?key=file1.txt')
154151
})
155152

156-
it('jumps to search box when user types /', async () => {
153+
it('jumps to search box when user types / while the body is focused', async () => {
157154
const dirSource: DirSource = {
158155
sourceId: 'test-source',
159156
sourceParts: [{ text: 'test-source', sourceId: 'test-source' }],
160157
kind: 'directory',
161158
prefix: '',
162-
listFiles: () => Promise.resolve([]),
159+
listFiles: async () => {
160+
await fetch('something') // to ensure we wait for loading
161+
return []
162+
},
163163
}
164164
const { getByPlaceholderText } = render(<Folder source={dirSource} />)
165165

@@ -169,30 +169,33 @@ describe('Folder Component', () => {
169169
})
170170

171171
const searchInput = getByPlaceholderText('Search...') as HTMLInputElement
172+
const user = userEvent.setup()
172173

173-
// Typing / should focus the search box
174-
act(() => {
175-
fireEvent.keyDown(document.body, { key: '/' })
176-
})
174+
// By default, the search box is already focused in this test
177175
expect(document.activeElement).toBe(searchInput)
178176

179177
// Typing inside the search box should work including /
180-
act(() => {
181-
fireEvent.keyUp(searchInput, { target: { value: 'file1/' } })
182-
})
178+
await user.type(searchInput, 'file1/')
183179
expect(searchInput.value).toBe('file1/')
184180

185181
// Unfocus and re-focus should select all text in search box
186182
act(() => {
187183
searchInput.blur()
188184
})
189185
expect(document.activeElement).not.toBe(searchInput)
186+
expect(document.activeElement).toBe(document.body)
190187

191-
act(() => {
192-
fireEvent.keyDown(document.body, { key: '/' })
193-
})
188+
await user.keyboard('/')
194189
expect(document.activeElement).toBe(searchInput)
195190
expect(searchInput.selectionStart).toBe(0)
196191
expect(searchInput.selectionEnd).toBe(searchInput.value.length)
192+
193+
// Focus another element and try again: it does not focus the search box
194+
await user.tab()
195+
expect(document.activeElement).not.toBe(searchInput)
196+
expect(document.activeElement).not.toBe(document.body)
197+
198+
await user.keyboard('/')
199+
expect(document.activeElement).not.toBe(searchInput)
197200
})
198201
})

src/components/ImageView/ImageView.test.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { render } from '@testing-library/react'
22
import { strict as assert } from 'assert'
3-
import { describe, expect, it, vi } from 'vitest'
3+
import { beforeEach, describe, expect, it, vi } from 'vitest'
44
import { getHyperparamSource } from '../../lib/sources/index.js'
55
import ImageView from './ImageView.js'
66

77
globalThis.fetch = vi.fn()
88

99
describe('ImageView Component', () => {
10+
beforeEach(() => {
11+
vi.clearAllMocks()
12+
// unnecessary for now because it has only one test, but safer for future tests
13+
})
14+
1015
it('renders the image correctly', async () => {
1116
const body = new ArrayBuffer(8)
1217
vi.mocked(fetch).mockResolvedValueOnce({

0 commit comments

Comments
 (0)