-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathrenderHookToSnapshotStream.tsx
98 lines (91 loc) · 3.54 KB
/
renderHookToSnapshotStream.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import {type RenderHookOptions} from '@testing-library/react/pure.js'
import React from 'rehackt'
import {createRenderStream} from './renderStream/createRenderStream.js'
import {type NextRenderOptions} from './renderStream/createRenderStream.js'
import {Render} from './renderStream/Render.js'
import {Assertable, assertableSymbol, markAssertable} from './assertable.js'
export interface SnapshotStream<Snapshot, Props> extends Assertable {
/**
* An array of all renders that have happened so far.
* Errors thrown during component render will be captured here, too.
*/
renders: Array<
| Render<{value: Snapshot}, never>
| {phase: 'snapshotError'; count: number; error: unknown}
>
/**
* Peeks the next render from the current iterator position, without advancing the iterator.
* If no render has happened yet, it will wait for the next render to happen.
* @throws {WaitForRenderTimeoutError} if no render happens within the timeout
*/
peekSnapshot(options?: NextRenderOptions): Promise<Snapshot>
/**
* Iterates to the next render and returns it.
* If no render has happened yet, it will wait for the next render to happen.
* @throws {WaitForRenderTimeoutError} if no render happens within the timeout
*/
takeSnapshot: Assertable &
((options?: NextRenderOptions) => Promise<Snapshot>)
/**
* Returns the total number of renders.
*/
totalSnapshotCount(): number
/**
* Returns the current render.
* @throws {Error} if no render has happened yet
*/
getCurrentSnapshot(): Snapshot
/**
* Waits for the next render to happen.
* Does not advance the render iterator.
*/
waitForNextSnapshot(options?: NextRenderOptions): Promise<Snapshot>
rerender: (rerenderCallbackProps: VoidOptionalArg<Props>) => Promise<void>
unmount: () => void
}
/**
* if `Arg` can be `undefined`, replace it with `void` to make type represent an optional argument in a function argument position
*/
type VoidOptionalArg<Arg> = Arg extends any // distribute members of a potential `Props` union
? undefined extends Arg
? // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
void
: Arg
: Arg
export async function renderHookToSnapshotStream<ReturnValue, Props = void>(
renderCallback: (props: Props) => ReturnValue,
{initialProps, ...renderOptions}: RenderHookOptions<Props> = {},
): Promise<SnapshotStream<ReturnValue, Props>> {
const {render, ...stream} = createRenderStream<{value: ReturnValue}, never>()
const HookComponent: React.FC<{arg: Props}> = props => {
stream.replaceSnapshot({value: renderCallback(props.arg)})
return null
}
const {rerender: baseRerender, unmount} = await render(
<HookComponent arg={initialProps!} />,
renderOptions,
)
function rerender(rerenderCallbackProps: VoidOptionalArg<Props>) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
return baseRerender(<HookComponent arg={rerenderCallbackProps as any} />)
}
return {
[assertableSymbol]: stream,
renders: stream.renders,
totalSnapshotCount: stream.totalRenderCount,
async peekSnapshot(options) {
return (await stream.peekRender(options)).snapshot.value
},
takeSnapshot: markAssertable(async function takeSnapshot(options) {
return (await stream.takeRender(options)).snapshot.value
}, stream),
getCurrentSnapshot() {
return stream.getCurrentRender().snapshot.value
},
async waitForNextSnapshot(options) {
return (await stream.waitForNextRender(options)).snapshot.value
},
rerender,
unmount,
}
}