Skip to content

Commit 4100bfa

Browse files
committed
add renderHookToSnapshotStream tests
1 parent cf0459d commit 4100bfa

File tree

6 files changed

+172
-18
lines changed

6 files changed

+172
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {disableActWarnings} from '../renderStream/disableActWarnings.js'
2+
3+
export function withDisabledActWarnings<T>(cb: () => T): T {
4+
const disabledActWarnings = disableActWarnings()
5+
let result: T
6+
try {
7+
result = cb()
8+
return result instanceof Promise
9+
? (result.finally(disabledActWarnings.cleanup) as T)
10+
: result
11+
} finally {
12+
disabledActWarnings.cleanup()
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/* eslint-disable no-await-in-loop */
2+
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
3+
import {EventEmitter} from 'node:events'
4+
import {test, expect} from '@jest/globals'
5+
import {renderHookToSnapshotStream} from '@testing-library/react-render-stream'
6+
import * as React from 'react'
7+
import {withDisabledActWarnings} from '../__testHelpers__/withDisabledActWarnings.js'
8+
9+
const testEvents = new EventEmitter<{
10+
rerenderWithValue: [unknown]
11+
}>()
12+
13+
function useRerenderEvents(initialValue: unknown) {
14+
const lastValueRef = React.useRef(initialValue)
15+
return React.useSyncExternalStore(
16+
onChange => {
17+
const cb = (value: unknown) => {
18+
lastValueRef.current = value
19+
withDisabledActWarnings(onChange)
20+
}
21+
testEvents.addListener('rerenderWithValue', cb)
22+
return () => {
23+
testEvents.removeListener('rerenderWithValue', cb)
24+
}
25+
},
26+
() => {
27+
return lastValueRef.current
28+
},
29+
)
30+
}
31+
32+
test('basic functionality', async () => {
33+
const {takeSnapshot} = renderHookToSnapshotStream(useRerenderEvents, {
34+
initialProps: 'initial',
35+
})
36+
testEvents.emit('rerenderWithValue', 'value')
37+
await Promise.resolve()
38+
testEvents.emit('rerenderWithValue', 'value2')
39+
{
40+
const snapshot = await takeSnapshot()
41+
expect(snapshot).toBe('initial')
42+
}
43+
{
44+
const snapshot = await takeSnapshot()
45+
expect(snapshot).toBe('value')
46+
}
47+
{
48+
const snapshot = await takeSnapshot()
49+
expect(snapshot).toBe('value2')
50+
}
51+
})
52+
53+
test.each<[type: string, initialValue: unknown, ...nextValues: unknown[]]>([
54+
['string', 'initial', 'value', 'value2'],
55+
['number', 0, 1, 2],
56+
['functions', () => {}, () => {}, function named() {}],
57+
['objects', {a: 1}, {a: 2}, {foo: 'bar'}],
58+
['arrays', [1], [1, 2], [2]],
59+
['null/undefined', null, undefined, null],
60+
['undefined/null', undefined, null, undefined],
61+
])('works with %s', async (_, initialValue, ...nextValues) => {
62+
const {takeSnapshot} = renderHookToSnapshotStream(useRerenderEvents, {
63+
initialProps: initialValue,
64+
})
65+
for (const nextValue of nextValues) {
66+
testEvents.emit('rerenderWithValue', nextValue)
67+
// allow for a render to happen
68+
await Promise.resolve()
69+
}
70+
expect(await takeSnapshot()).toBe(initialValue)
71+
for (const nextValue of nextValues) {
72+
expect(await takeSnapshot()).toBe(nextValue)
73+
}
74+
})

src/jest/__tests__/renderStreamMatchers.test.tsx

+75-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
22
import {EventEmitter} from 'node:events'
33
import {describe, test, expect} from '@jest/globals'
4-
import {createRenderStream} from '@testing-library/react-render-stream'
4+
import {
5+
createRenderStream,
6+
renderHookToSnapshotStream,
7+
} from '@testing-library/react-render-stream'
58
import * as React from 'react'
69
import {
710
RenderStreamMatchers,
811
toRenderExactlyTimes,
912
toRerender,
1013
} from '../renderStreamMatchers.js'
1114
import {getExpectErrorMessage} from '../../__testHelpers__/getCleanedErrorMessage.js'
15+
import {withDisabledActWarnings} from '../../__testHelpers__/withDisabledActWarnings.js'
1216

1317
expect.extend({
1418
toRerender,
@@ -25,21 +29,20 @@ const testEvents = new EventEmitter<{
2529
rerender: []
2630
}>()
2731

28-
function RerenderingComponent() {
32+
function useRerender() {
2933
const [, rerender] = React.useReducer(c => c + 1, 0)
3034
React.useEffect(() => {
31-
function cb() {
32-
const anyThis = globalThis as any as {IS_REACT_ACT_ENVIRONMENT?: boolean}
33-
const prev = anyThis.IS_REACT_ACT_ENVIRONMENT
34-
anyThis.IS_REACT_ACT_ENVIRONMENT = false
35-
rerender()
36-
anyThis.IS_REACT_ACT_ENVIRONMENT = prev
37-
}
35+
const cb = () => void withDisabledActWarnings(rerender)
36+
3837
testEvents.addListener('rerender', cb)
3938
return () => {
4039
testEvents.removeListener('rerender', cb)
4140
}
4241
}, [])
42+
}
43+
44+
function RerenderingComponent() {
45+
useRerender()
4346
return null
4447
}
4548

@@ -58,6 +61,46 @@ describe('toRerender', () => {
5861
await expect(takeRender).not.toRerender()
5962
})
6063

64+
test('works with renderStream object', async () => {
65+
const renderStream = createRenderStream({})
66+
67+
renderStream.render(<RerenderingComponent />)
68+
await expect(renderStream).toRerender()
69+
await renderStream.takeRender()
70+
71+
testEvents.emit('rerender')
72+
await expect(renderStream).toRerender()
73+
await renderStream.takeRender()
74+
75+
await expect(renderStream).not.toRerender()
76+
})
77+
78+
test('works with takeSnapshot function', async () => {
79+
const {takeSnapshot} = renderHookToSnapshotStream(() => useRerender())
80+
81+
await expect(takeSnapshot).toRerender()
82+
await takeSnapshot()
83+
84+
testEvents.emit('rerender')
85+
await expect(takeSnapshot).toRerender()
86+
await takeSnapshot()
87+
88+
await expect(takeSnapshot).not.toRerender()
89+
})
90+
91+
test('works with snapshotStream', async () => {
92+
const snapshotStream = renderHookToSnapshotStream(() => useRerender())
93+
94+
await expect(snapshotStream).toRerender()
95+
await snapshotStream.takeSnapshot()
96+
97+
testEvents.emit('rerender')
98+
await expect(snapshotStream).toRerender()
99+
await snapshotStream.takeSnapshot()
100+
101+
await expect(snapshotStream).not.toRerender()
102+
})
103+
61104
test("errors when it rerenders, but shouldn't", async () => {
62105
const {takeRender, render} = createRenderStream({})
63106

@@ -102,6 +145,29 @@ describe('toRenderExactlyTimes', () => {
102145
await expect(takeRender).toRenderExactlyTimes(2)
103146
})
104147

148+
test('works with renderStream object', async () => {
149+
const renderStream = createRenderStream({})
150+
151+
renderStream.render(<RerenderingComponent />)
152+
testEvents.emit('rerender')
153+
154+
await expect(renderStream).toRenderExactlyTimes(2)
155+
})
156+
157+
test('works with takeSnapshot function', async () => {
158+
const {takeSnapshot} = renderHookToSnapshotStream(() => useRerender())
159+
testEvents.emit('rerender')
160+
161+
await expect(takeSnapshot).toRenderExactlyTimes(2)
162+
})
163+
164+
test('works with snapshotStream', async () => {
165+
const snapshotStream = renderHookToSnapshotStream(() => useRerender())
166+
testEvents.emit('rerender')
167+
168+
await expect(snapshotStream).toRenderExactlyTimes(2)
169+
})
170+
105171
test('errors when the count of rerenders is wrong', async () => {
106172
const {takeRender, render} = createRenderStream({})
107173

src/renderHookToSnapshotStream.tsx

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import {RenderHookOptions} from '@testing-library/react'
2-
import {createElement} from 'rehackt'
2+
import React from 'rehackt'
33
import {createRenderStream} from './renderStream/createRenderStream.js'
44
import {type NextRenderOptions} from './renderStream/createRenderStream.js'
5-
65
import {Render} from './renderStream/Render.js'
76
import {Assertable, assertableSymbol, markAssertable} from './assertable.js'
87

@@ -46,24 +45,24 @@ export interface SnapshotStream<Snapshot, Props> extends Assertable {
4645
unmount: () => void
4746
}
4847

49-
export function renderHookToSnapshotStream<ReturnValue, Props extends {}>(
48+
export function renderHookToSnapshotStream<ReturnValue, Props>(
5049
renderCallback: (props: Props) => ReturnValue,
5150
{initialProps, ...renderOptions}: RenderHookOptions<Props> = {},
5251
): SnapshotStream<ReturnValue, Props> {
5352
const {render, ...stream} = createRenderStream<{value: ReturnValue}>()
5453

55-
const HookComponent: React.FC<Props> = props => {
56-
stream.replaceSnapshot({value: renderCallback(props)})
54+
const HookComponent: React.FC<{arg: Props}> = props => {
55+
stream.replaceSnapshot({value: renderCallback(props.arg)})
5756
return null
5857
}
5958

6059
const {rerender: baseRerender, unmount} = render(
61-
createElement(HookComponent, initialProps),
60+
<HookComponent arg={initialProps!} />,
6261
renderOptions,
6362
)
6463

6564
function rerender(rerenderCallbackProps: Props) {
66-
return baseRerender(createElement(HookComponent, rerenderCallbackProps))
65+
return baseRerender(<HookComponent arg={rerenderCallbackProps} />)
6766
}
6867

6968
return {

src/renderStream/createRenderStream.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ export function createRenderStream<Snapshot extends ValidSnapshot = void>({
272272
// In many cases we do not control the resolution of the suspended
273273
// promise which results in noisy tests when the profiler due to
274274
// repeated act warnings.
275-
using _disabledActWarnings = disableActWarnings()
275+
const disabledActWarnings = disableActWarnings()
276276

277277
let error: unknown
278278

@@ -290,6 +290,7 @@ export function createRenderStream<Snapshot extends ValidSnapshot = void>({
290290
if (!(error && error instanceof WaitForRenderTimeoutError)) {
291291
iteratorPosition++
292292
}
293+
disabledActWarnings.cleanup()
293294
}
294295
}, stream),
295296
getCurrentRender() {

src/renderStream/disableActWarnings.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function disableActWarnings() {
99
anyThis.IS_REACT_ACT_ENVIRONMENT = false
1010

1111
return {
12-
[Symbol.dispose]() {
12+
cleanup: () => {
1313
anyThis.IS_REACT_ACT_ENVIRONMENT = prevActEnv
1414
},
1515
}

0 commit comments

Comments
 (0)