Skip to content

Commit 76a2765

Browse files
authored
fix: avoid act warnings (#46)
1 parent df16d16 commit 76a2765

5 files changed

Lines changed: 75 additions & 15 deletions

File tree

src/pure.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function ensureTestIdAttribute(element: HTMLElement) {
1717
let activeActs = 0
1818

1919
function setActEnvironment(env: boolean | undefined): void {
20-
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = env
20+
;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = env
2121
}
2222

2323
function updateActEnvironment(): void {
@@ -30,7 +30,7 @@ const _act = React.act || React.unstable_act
3030
// we call act only when rendering to flush any possible effects
3131
// usually the async nature of Vitest browser mode ensures consistency,
3232
// but rendering is sync and controlled by React directly
33-
const act = typeof _act !== 'function'
33+
const act: typeof React.act = typeof _act !== 'function'
3434
? async (cb: () => unknown) => { await cb() }
3535
: async (cb: () => unknown) => {
3636
activeActs++
@@ -115,7 +115,7 @@ export async function render(
115115
})
116116
}
117117

118-
await act(() => {
118+
await act(async () => {
119119
root!.render(
120120
strictModeIfNeeded(wrapUiIfNeeded(ui, WrapperComponent)),
121121
)
@@ -127,12 +127,12 @@ export async function render(
127127
locator: page.elementLocator(container),
128128
debug: (el, maxLength, options) => debug(el, maxLength, options),
129129
unmount: async () => {
130-
await act(() => {
130+
await act(async () => {
131131
root.unmount()
132132
})
133133
},
134134
rerender: async (newUi: React.ReactNode) => {
135-
await act(() => {
135+
await act(async () => {
136136
root.render(
137137
strictModeIfNeeded(wrapUiIfNeeded(newUi, WrapperComponent)),
138138
)
@@ -210,7 +210,7 @@ export async function renderHook<Props, Result>(renderCallback: (initialProps?:
210210

211211
export async function cleanup(): Promise<void> {
212212
for (const { root, container } of mountedRootEntries) {
213-
await act(() => {
213+
await act(async () => {
214214
root.unmount()
215215
})
216216
if (container.parentNode === document.body) {

test/no-act-error.test.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
import { page } from 'vitest/browser'
3+
import type { FC } from 'react'
4+
import { useEffect, useState } from 'react'
5+
import { render } from 'vitest-browser-react'
6+
7+
const fetchData = vi.fn()
8+
9+
const AsyncComponent: FC = () => {
10+
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading')
11+
const [data, setData] = useState<string | null>(null)
12+
13+
useEffect(() => {
14+
const load = async () => {
15+
try {
16+
const result = await fetchData()
17+
setData(result)
18+
setStatus('success')
19+
}
20+
catch {
21+
setStatus('error')
22+
}
23+
}
24+
void load()
25+
}, [])
26+
27+
if (status === 'loading') {
28+
return <div data-testid="status">loading</div>
29+
}
30+
31+
return (
32+
<div>
33+
<div data-testid="status">{status}</div>
34+
{data && <div data-testid="data">{data}</div>}
35+
</div>
36+
)
37+
}
38+
39+
beforeEach(() => {
40+
const spy = vi.spyOn(console, 'error')
41+
return () => {
42+
const calls = spy.mock.calls.flat()
43+
spy.mockRestore()
44+
expect(calls).not.toContainEqual(expect.stringContaining('act'))
45+
}
46+
})
47+
48+
describe('act() warning reproduction', () => {
49+
it('produces act() warning when async operation resolves', async () => {
50+
fetchData.mockResolvedValue('Hello World')
51+
52+
await render(<AsyncComponent />)
53+
54+
await expect.element(page.getByTestId('status')).toHaveTextContent('success')
55+
await expect.element(page.getByTestId('data')).toHaveTextContent('Hello World')
56+
})
57+
58+
it('produces act() warning when async operation rejects', async () => {
59+
fetchData.mockRejectedValue(new Error('Failed'))
60+
61+
await render(<AsyncComponent />)
62+
63+
await expect.element(page.getByTestId('status')).toHaveTextContent('error')
64+
})
65+
})

test/render-hook.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ test('allows rerendering', async () => {
3737

3838
expect(result.current).toEqual(['left', expect.any(Function)])
3939

40-
rerender({ branch: 'right' })
40+
await rerender({ branch: 'right' })
4141

4242
expect(result.current).toEqual(['right', expect.any(Function)])
4343
})

test/render.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,5 @@ test('waits for suspended boundaries', async ({ onTestFinished }) => {
4848
await expect.element(page.getByText('Suspended!')).toBeInTheDocument()
4949
vi.runAllTimers()
5050
await result
51-
expect(page.getByText('Hello Vitest')).toBeInTheDocument()
51+
await expect.element(page.getByText('Hello Vitest')).toBeInTheDocument()
5252
})

vitest.config.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,12 @@ export default defineConfig({
1111
projects: [
1212
{
1313
extends: true,
14-
test: { name: 'prod' },
15-
},
16-
{
17-
extends: true,
18-
test: { name: 'dev' },
19-
resolve: { conditions: ['vdev'] },
14+
test: { name: 'react' },
2015
},
2116
{
2217
extends: true,
2318
test: {
24-
name: 'selector-custom-attr',
19+
name: 'custom-attr',
2520
include: ['test/render-selector.test.tsx'],
2621
browser: {
2722
locators: {

0 commit comments

Comments
 (0)