Skip to content

Commit 08d0a85

Browse files
sukvvondai-shi
andauthored
test: migrate to Vitest fake timers, remove @testing-library/user-event, and resolve act warnings (#3147)
* test(react/useSetAtom): replace 'userEvent' with 'fireEvent' * test(react/useAtomValue): replace 'userEvent' with 'fireEvent', 'findByText' with 'getByText', and add fake timers * test(react/provider): remove 'waitFor' * test(react/optimization): replace 'userEvent' with 'fireEvent', 'findByText' with 'getByText', and remove 'waitFor' * test(react/onmount): replace 'userEvent' with 'fireEvent', 'findByText' with 'getByText', add fake timer, and remove 'waitFor' * test(react/items): replace 'userEvent' with 'fireEvent', 'findByText' with 'getByText', and remove 'waitFor' * test(react/dependency): replace 'userEvent' with 'fireEvent', 'findByText' with 'getByText', add fake timer, and remove 'waitFor' * test(react/vanilla-utils/atomFamily): replace 'userEvent' with 'fireEvent', 'findByText' with 'getByText', add fake timer, and remove 'waitFor' * test(react/vanilla-utils/atomWithDefault): replace 'userEvent' with 'fireEvent', 'findByText' with 'getByText', add fake timer, and remove 'waitFor' * test(react/vanilla-utils/atomWithReducer): replace 'userEvent' with 'fireEvent', 'findByText' with 'getByText' * test(react/vanilla-utils/atomWithRefresh): replace 'userEvent' with 'fireEvent', 'findByText' with 'getByText', and add fake timer * test(react/vanilla-utils/atomWithStorage): replace 'userEvent' with 'fireEvent', 'findByText' with 'getByText', remove 'waitFor', and add fake timer * test(react/abortable): replace Promise resolver pattern with setTimeout delay, replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', remove 'waitFor', add fake timer, and wrap render and fireEvent calls in act * test(react/async): replace Promise resolver pattern with setTimeout delay, replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', remove 'waitFor', add fake timer, and wrap render and fireEvent calls in act * test(react/async2): replace Promise resolver pattern with setTimeout delay, replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', remove 'waitFor' and 'assert', add fake timer, wrap render and fireEvent calls in act, enable skipped test, add infinite loop verification with onMountCallCount, and add intermediate state checks for setSelf timing * test(react/basic): migrate to Vitest fake timers, replace Promise resolver pattern with setTimeout delay, replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', remove 'waitFor', and add appropriate timer progression and act wrapping * test(react/vanilla-utils/freezeAtom): replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', and remove unnecessary 'async' keywords * test(react/vanilla-utils/selectAtom): replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', and remove unnecessary 'async' keywords * test(react/vanilla-utils/splitAtom): replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', and remove unnecessary 'async' keywords * test(react/utils/useAtomCallback): replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', remove 'waitFor', add fake timer, and remove unnecessary 'async' keywords * test(react/utils/useHydrateAtoms): replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', and remove unnecessary 'async' keywords * test(react/utils/useReducerAtom): replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', and remove unnecessary 'async' keywords * test(react/utils/useResetAtom): replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', and remove unnecessary 'async' keywords * test(vanilla/utils/atomWithLazy): remove unnecessary 'async' keywords * test(vanilla/utils/loadable): replace Promise.resolve with setTimeout delay, add fake timer, and remove unnecessary await * test(vanilla/utils/unwrap): replace Promise resolver pattern with setTimeout delay, replace Promise.resolve with setTimeout delay, add fake timer, remove unnecessary await, and remove unnecessary async keyword * test(vanilla/dependency): replace Promise resolver pattern with setTimeout delay, replace 'Promise.resolve' with setTimeout delay, add fake timer, and replace 'expect(await promise)' with 'expect().resolves' * test(vanilla/effect): replace 'Promise.resolve' with 'vi.advanceTimersByTimeAsync' and add fake timer * test(vanilla/store): migrate to Vitest fake timers, remove unnecessary 'async' keywords, replace Promise resolver patterns with setTimeout delays, and remove 'assert' and 'waitFor' imports * test(react/transition): replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', add fake timer, and change Promise resolver pattern to setTimeout delay pattern * test(react/error): replace 'userEvent' with 'fireEvent', add loading assertions and delays to Suspense tests, and remove unnecessary 'async' keywords and timer advancements from sync error tests * test(react/vanilla-utils/loadable): replace Promise resolver pattern with setTimeout delay, replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', add fake timer, and remove unnecessary 'async' keywords * test(react/async2): remove unnecessary 'act' wrapping from 'render', 'fireEvent.click', and 'vi.advanceTimersByTimeAsync' calls * test(react/dependency): add 'async' keyword and 'await' to 'act' call, and fix 'act' callback syntax * test(react/abortable, async, dependency, error, onmount, transition): remove unnecessary await act from fireEvent and add loading assertions * test(react/vanilla-utils/atomWithObservable): replace 'userEvent' with 'fireEvent', replace 'findByText' with 'getByText', add fake timer, remove 'shouldAdvanceTime' option, and remove unnecessary 'async' keywords * test(react/vanilla-utils/atomFamily, atomWithStorage): remove unnecessary 'async' keywords and add missing loading assertions * test(react): replace string Suspense fallback with JSX element * test(react/abortable): wrap fireEvent.click with await act to resolve act warnings * test(react/async): wrap fireEvent.click with await act to resolve act warnings * test(react/vanilla-utils/atomWithStorage): wrap render and fireEvent.click with await act to resolve act warnings * test(react/vanilla-utils/loadable): wrap render with await act to resolve act warnings * test(react/async2): wrap fireEvent.click and timer advance with await act to resolve act warnings * chore(*): remove '@testing-library/user-event' * test(react): wrap render calls with StrictMode, use Fragment for timing-sensitive tests * test(react/async2): reduce to single assertion in setSelf timing test * test(react/async): fix setCountAtom to use Promise with setTimeout * test(react/async2): restore infinite pending test with Promise<never> * Update tests/react/async.test.tsx * test(react/async2): remove unnecessary act wrapper from fireEvent calls * test(react/async): fix multiple async atoms test with precise timer control * test(vanilla/utils/unwrap): replace 'setTimeout' with fake timer in error recovery test * test(react/error): revert read function to sync in async write function test * test(react/dependency): restore intentional async keyword in activateAction * test(react/dependency): remove setTimeout from asyncAtom to restore microtask delay * test(react/dependency): add initial loading assertion for async dependencies test * test(react/basic): replace FIXME with NOTE explaining no loading state * Revert "test(react/dependency): remove setTimeout from asyncAtom to restore microtask delay" This reverts commit 278abe2. * test(react/dependency): add loading assertion and remove unnecessary timer advancement in bail test * test(react/async2): restore intermediate assertions with 'expect.assertions(6)' in setSelf timing test * test(react): simplify Suspense fallback from '<div>loading</div>' to 'loading' * test(react/abortable): remove redundant 'abortedCount' assertion before unmount * test(react/async): remove 'setTimeout' from immediately-resolving async atoms in useEffect tests * Update tests/react/async.test.tsx Co-authored-by: Daishi Kato <[email protected]> * test(react/async): remove 'setTimeout' from 'asyncAtom' to restore microtask delay in 'useEffect' test * test(react/async): replace 'Promise.resolve' with 'async/await' pattern in 'anotherAsyncAtom' * test(react/async): remove 'setTimeout' delay in non-suspense async write test (#389) * test(react/async): replace 'setTimeout' Promise with 'Promise.resolve' in override promise test (#430) * test(react/async): remove 'setTimeout' from 'derivedAtom' in combine/set two promise atoms tests (#442) * test(react/async): remove 'setTimeout' from immediately-resolving async atoms in derived atom test * test(react/async2): restore intermediate assertions with 'expect.assertions(6)' in setSelf timing test * test(react/async): remove unnecessary 'setTimeout' delay in dependency chaining test (#813) * test(react/basic): remove unnecessary 'setTimeout' delay in time delayed derived atom test (#947) * test(react/dependency): remove 'setTimeout' from immediately-resolving async atom in bail test (#877) * test(react/dependency): replace 'setTimeout' with 'Promise.resolve' and add FIXME for 'loading' state (#2565) * test(react/async): add comments explaining 1000ms delay for useEffect's write operation * test(react/async): move 'setTimeout' inside condition to skip delay when disabled (#751) * restore the test order * test(react/useAtomValue): revert async atom to immediately resolve without 'setTimeout' * test(vanilla/utils/{loadable,unwrap}): revert to 'Promise.resolve' and use 'advanceTimersByTimeAsync(0)' * test(vanilla/utils/unwrap): remove unnecessary 'act' wrapper from vanilla store test * test(vanilla/store): revert 'Promise.resolve' to 'setTimeout' to preserve macrotask behavior * test(react/vanilla-utils/loadable): restore 10ms delay with fake timer for infinite loop test (#1481) * chore: empty commit --------- Co-authored-by: Daishi Kato <[email protected]> Co-authored-by: daishi <[email protected]>
1 parent 4afc31c commit 08d0a85

35 files changed

+2138
-2139
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@
140140
"@testing-library/dom": "^10.4.1",
141141
"@testing-library/jest-dom": "^6.9.1",
142142
"@testing-library/react": "^16.3.0",
143-
"@testing-library/user-event": "^14.6.1",
144143
"@types/babel__core": "^7.20.5",
145144
"@types/babel__template": "^7.4.4",
146145
"@types/node": "^24.9.2",

pnpm-lock.yaml

Lines changed: 4 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/react/abortable.test.tsx

Lines changed: 75 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
import { StrictMode, Suspense, useState } from 'react'
2-
import { act, render, screen, waitFor } from '@testing-library/react'
3-
import userEventOrig from '@testing-library/user-event'
4-
import { describe, expect, it } from 'vitest'
2+
import { act, fireEvent, render, screen } from '@testing-library/react'
3+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
54
import { useAtomValue, useSetAtom } from 'jotai/react'
65
import { atom } from 'jotai/vanilla'
76

8-
const userEvent = {
9-
click: (element: Element) => act(() => userEventOrig.click(element)),
10-
}
7+
beforeEach(() => {
8+
vi.useFakeTimers()
9+
})
10+
11+
afterEach(() => {
12+
vi.useRealTimers()
13+
})
1114

1215
describe('abortable atom test', () => {
1316
it('can abort with signal.aborted', async () => {
1417
const countAtom = atom(0)
1518
let abortedCount = 0
16-
const resolve: (() => void)[] = []
1719
const derivedAtom = atom(async (get, { signal }) => {
1820
const count = get(countAtom)
19-
await new Promise<void>((r) => resolve.push(r))
21+
await new Promise((resolve) => setTimeout(resolve, 100))
2022
if (signal.aborted) {
2123
++abortedCount
2224
}
@@ -37,47 +39,51 @@ describe('abortable atom test', () => {
3739
)
3840
}
3941

40-
await act(async () => {
42+
await act(() =>
4143
render(
4244
<StrictMode>
4345
<Suspense fallback="loading">
4446
<Component />
4547
<Controls />
4648
</Suspense>
4749
</StrictMode>,
48-
)
49-
})
50+
),
51+
)
5052

51-
expect(await screen.findByText('loading')).toBeInTheDocument()
53+
expect(screen.getByText('loading')).toBeInTheDocument()
54+
55+
await act(() => vi.advanceTimersByTimeAsync(100))
56+
expect(screen.getByText('count: 0')).toBeInTheDocument()
5257

53-
resolve.splice(0).forEach((fn) => fn())
54-
expect(await screen.findByText('count: 0')).toBeInTheDocument()
5558
expect(abortedCount).toBe(0)
5659

57-
await userEvent.click(screen.getByText('button'))
58-
await userEvent.click(screen.getByText('button'))
59-
resolve.splice(0).forEach((fn) => fn())
60-
expect(await screen.findByText('count: 2')).toBeInTheDocument()
60+
await act(() => fireEvent.click(screen.getByText('button')))
61+
expect(screen.getByText('loading')).toBeInTheDocument()
62+
await act(() => fireEvent.click(screen.getByText('button')))
63+
expect(screen.getByText('loading')).toBeInTheDocument()
64+
await act(() => vi.advanceTimersByTimeAsync(100))
65+
expect(screen.getByText('count: 2')).toBeInTheDocument()
6166

6267
expect(abortedCount).toBe(1)
6368

64-
await userEvent.click(screen.getByText('button'))
65-
resolve.splice(0).forEach((fn) => fn())
66-
expect(await screen.findByText('count: 3')).toBeInTheDocument()
69+
await act(() => fireEvent.click(screen.getByText('button')))
70+
expect(screen.getByText('loading')).toBeInTheDocument()
71+
await act(() => vi.advanceTimersByTimeAsync(100))
72+
expect(screen.getByText('count: 3')).toBeInTheDocument()
73+
6774
expect(abortedCount).toBe(1)
6875
})
6976

7077
it('can abort with event listener', async () => {
7178
const countAtom = atom(0)
7279
let abortedCount = 0
73-
const resolve: (() => void)[] = []
7480
const derivedAtom = atom(async (get, { signal }) => {
7581
const count = get(countAtom)
7682
const callback = () => {
7783
++abortedCount
7884
}
7985
signal.addEventListener('abort', callback)
80-
await new Promise<void>((r) => resolve.push(r))
86+
await new Promise((resolve) => setTimeout(resolve, 100))
8187
signal.removeEventListener('abort', callback)
8288
return count
8389
})
@@ -96,44 +102,47 @@ describe('abortable atom test', () => {
96102
)
97103
}
98104

99-
await act(async () => {
105+
await act(() =>
100106
render(
101107
<StrictMode>
102108
<Suspense fallback="loading">
103109
<Component />
104110
<Controls />
105111
</Suspense>
106112
</StrictMode>,
107-
)
108-
})
113+
),
114+
)
109115

110-
expect(await screen.findByText('loading')).toBeInTheDocument()
111-
resolve.splice(0).forEach((fn) => fn())
112-
expect(await screen.findByText('count: 0')).toBeInTheDocument()
116+
expect(screen.getByText('loading')).toBeInTheDocument()
117+
118+
await act(() => vi.advanceTimersByTimeAsync(100))
119+
expect(screen.getByText('count: 0')).toBeInTheDocument()
113120

114121
expect(abortedCount).toBe(0)
115122

116-
await userEvent.click(screen.getByText('button'))
117-
await userEvent.click(screen.getByText('button'))
118-
resolve.splice(0).forEach((fn) => fn())
119-
expect(await screen.findByText('count: 2')).toBeInTheDocument()
123+
await act(() => fireEvent.click(screen.getByText('button')))
124+
expect(screen.getByText('loading')).toBeInTheDocument()
125+
await act(() => fireEvent.click(screen.getByText('button')))
126+
expect(screen.getByText('loading')).toBeInTheDocument()
127+
await act(() => vi.advanceTimersByTimeAsync(100))
128+
expect(screen.getByText('count: 2')).toBeInTheDocument()
120129

121130
expect(abortedCount).toBe(1)
122131

123-
await userEvent.click(screen.getByText('button'))
124-
resolve.splice(0).forEach((fn) => fn())
125-
expect(await screen.findByText('count: 3')).toBeInTheDocument()
132+
await act(() => fireEvent.click(screen.getByText('button')))
133+
expect(screen.getByText('loading')).toBeInTheDocument()
134+
await act(() => vi.advanceTimersByTimeAsync(100))
135+
expect(screen.getByText('count: 3')).toBeInTheDocument()
126136

127137
expect(abortedCount).toBe(1)
128138
})
129139

130140
it('does not abort on unmount', async () => {
131141
const countAtom = atom(0)
132142
let abortedCount = 0
133-
const resolve: (() => void)[] = []
134143
const derivedAtom = atom(async (get, { signal }) => {
135144
const count = get(countAtom)
136-
await new Promise<void>((r) => resolve.push(r))
145+
await new Promise((resolve) => setTimeout(resolve, 100))
137146
if (signal.aborted) {
138147
++abortedCount
139148
}
@@ -157,37 +166,33 @@ describe('abortable atom test', () => {
157166
)
158167
}
159168

160-
await act(async () => {
169+
await act(() =>
161170
render(
162171
<StrictMode>
163172
<Suspense fallback="loading">
164173
<Parent />
165174
</Suspense>
166175
</StrictMode>,
167-
)
168-
})
176+
),
177+
)
169178

170-
expect(await screen.findByText('loading')).toBeInTheDocument()
171-
172-
resolve.splice(0).forEach((fn) => fn())
173-
expect(await screen.findByText('count: 0')).toBeInTheDocument()
174-
expect(abortedCount).toBe(0)
179+
expect(screen.getByText('loading')).toBeInTheDocument()
180+
await act(() => vi.advanceTimersByTimeAsync(100))
181+
expect(screen.getByText('count: 0')).toBeInTheDocument()
175182

176-
await userEvent.click(screen.getByText('button'))
177-
await userEvent.click(screen.getByText('toggle'))
183+
await act(() => fireEvent.click(screen.getByText('button')))
184+
expect(screen.getByText('loading')).toBeInTheDocument()
185+
await act(() => fireEvent.click(screen.getByText('toggle')))
186+
expect(screen.getByText('hidden')).toBeInTheDocument()
178187

179-
expect(await screen.findByText('hidden')).toBeInTheDocument()
180-
181-
resolve.splice(0).forEach((fn) => fn())
182-
await waitFor(() => expect(abortedCount).toBe(0))
188+
expect(abortedCount).toBe(0)
183189
})
184190

185191
it('throws aborted error (like fetch)', async () => {
186192
const countAtom = atom(0)
187-
const resolve: (() => void)[] = []
188193
const derivedAtom = atom(async (get, { signal }) => {
189194
const count = get(countAtom)
190-
await new Promise<void>((r) => resolve.push(r))
195+
await new Promise((resolve) => setTimeout(resolve, 100))
191196
if (signal.aborted) {
192197
throw new Error('aborted')
193198
}
@@ -208,29 +213,32 @@ describe('abortable atom test', () => {
208213
)
209214
}
210215

211-
await act(async () => {
216+
await act(() =>
212217
render(
213218
<StrictMode>
214219
<Suspense fallback="loading">
215220
<Component />
216221
<Controls />
217222
</Suspense>
218223
</StrictMode>,
219-
)
220-
})
224+
),
225+
)
221226

222-
expect(await screen.findByText('loading')).toBeInTheDocument()
227+
expect(screen.getByText('loading')).toBeInTheDocument()
223228

224-
resolve.splice(0).forEach((fn) => fn())
225-
expect(await screen.findByText('count: 0')).toBeInTheDocument()
229+
await act(() => vi.advanceTimersByTimeAsync(100))
230+
expect(screen.getByText('count: 0')).toBeInTheDocument()
226231

227-
await userEvent.click(screen.getByText('button'))
228-
await userEvent.click(screen.getByText('button'))
229-
resolve.splice(0).forEach((fn) => fn())
230-
expect(await screen.findByText('count: 2')).toBeInTheDocument()
232+
await act(() => fireEvent.click(screen.getByText('button')))
233+
expect(screen.getByText('loading')).toBeInTheDocument()
234+
await act(() => fireEvent.click(screen.getByText('button')))
235+
expect(screen.getByText('loading')).toBeInTheDocument()
236+
await act(() => vi.advanceTimersByTimeAsync(100))
237+
expect(screen.getByText('count: 2')).toBeInTheDocument()
231238

232-
await userEvent.click(screen.getByText('button'))
233-
resolve.splice(0).forEach((fn) => fn())
234-
expect(await screen.findByText('count: 3')).toBeInTheDocument()
239+
await act(() => fireEvent.click(screen.getByText('button')))
240+
expect(screen.getByText('loading')).toBeInTheDocument()
241+
await act(() => vi.advanceTimersByTimeAsync(100))
242+
expect(screen.getByText('count: 3')).toBeInTheDocument()
235243
})
236244
})

0 commit comments

Comments
 (0)