Skip to content

Commit dd8ff20

Browse files
authored
Merge pull request #3 from phryneas/pr/queries
2 parents e7422ed + e5645c0 commit dd8ff20

8 files changed

+291
-68
lines changed

src/__tests__/renderToRenderStream.test.tsx

+31
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,37 @@ describe('snapshotDOM', () => {
5858
expect(input.value).toBe('2')
5959
}
6060
})
61+
62+
test('queries option', async () => {
63+
function Component() {
64+
return null
65+
}
66+
const queries = {
67+
foo: (_: any) => {
68+
return null
69+
},
70+
}
71+
const {takeRender, renderResultPromise} = renderToRenderStream(
72+
<Component />,
73+
{
74+
queries,
75+
snapshotDOM: true,
76+
},
77+
)
78+
const utils = await renderResultPromise
79+
expect(utils.foo()).toBe(null)
80+
const {withinDOM} = await takeRender()
81+
expect(withinDOM().foo()).toBe(null)
82+
function _typeTest() {
83+
// @ts-expect-error should not be present
84+
utils.getByText
85+
// @ts-expect-error should not be present
86+
withinDOM().getByText
87+
utils.debug()
88+
withinDOM().debug()
89+
const _str: string = withinDOM().logTestingPlaygroundURL()
90+
}
91+
})
6192
})
6293

6394
// for more tests, see the `createRenderStream` test suite, as `renderToRenderStream` is just a wrapper around that

src/assertable.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ export const assertableSymbol = Symbol.for(
1313
```
1414
*/
1515
export type Assertable = {
16-
[assertableSymbol]: RenderStream<any>
16+
[assertableSymbol]: RenderStream<any, any>
1717
}
1818

1919
export function markAssertable<T extends {}>(
2020
assertable: T,
21-
stream: RenderStream<any>,
21+
stream: RenderStream<any, any>,
2222
): T & Assertable {
2323
return Object.assign(assertable, {
2424
[assertableSymbol]: stream,

src/renderHookToSnapshotStream.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface SnapshotStream<Snapshot, Props> extends Assertable {
1111
* Errors thrown during component render will be captured here, too.
1212
*/
1313
renders: Array<
14-
| Render<{value: Snapshot}>
14+
| Render<{value: Snapshot}, never>
1515
| {phase: 'snapshotError'; count: number; error: unknown}
1616
>
1717
/**
@@ -49,7 +49,7 @@ export function renderHookToSnapshotStream<ReturnValue, Props>(
4949
renderCallback: (props: Props) => ReturnValue,
5050
{initialProps, ...renderOptions}: RenderHookOptions<Props> = {},
5151
): SnapshotStream<ReturnValue, Props> {
52-
const {render, ...stream} = createRenderStream<{value: ReturnValue}>()
52+
const {render, ...stream} = createRenderStream<{value: ReturnValue}, never>()
5353

5454
const HookComponent: React.FC<{arg: Props}> = props => {
5555
stream.replaceSnapshot({value: renderCallback(props.arg)})

src/renderStream/Render.tsx

+34-36
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
1-
/* istanbul ignore file */
2-
3-
/*
4-
Something in this file does not compile correctly while measuring code coverage
5-
and will lead to a
6-
Uncaught [ReferenceError: cov_1zb8w312au is not defined]
7-
if we do not ignore this file in code coverage.
8-
9-
As we only use this file in our internal tests, we can safely ignore it.
10-
*/
11-
12-
import {within, screen} from '@testing-library/dom'
1+
import {screen, getQueriesForElement, Screen} from '@testing-library/dom'
132
import {JSDOM, VirtualConsole} from 'jsdom'
3+
import {
4+
BoundSyncFunctions,
5+
type Queries,
6+
type SyncQueries,
7+
} from './syncQueries.js'
148

159
export interface BaseRender {
1610
id: string
@@ -25,18 +19,11 @@ export interface BaseRender {
2519
count: number
2620
}
2721

28-
type Screen = typeof screen
22+
export type SyncScreen<Q extends Queries = SyncQueries> =
23+
BoundSyncFunctions<Q> & Pick<Screen, 'debug' | 'logTestingPlaygroundURL'>
2924

30-
export type SyncScreen = {
31-
[K in keyof Screen]: K extends `find${string}`
32-
? {
33-
/** @deprecated A snapshot is static, so avoid async queries! */
34-
(...args: Parameters<Screen[K]>): ReturnType<Screen[K]>
35-
}
36-
: Screen[K]
37-
}
38-
39-
export interface Render<Snapshot> extends BaseRender {
25+
export interface Render<Snapshot, Q extends Queries = SyncQueries>
26+
extends BaseRender {
4027
/**
4128
* The snapshot, as returned by the `takeSnapshot` option of `createRenderStream`.
4229
*/
@@ -57,12 +44,14 @@ export interface Render<Snapshot> extends BaseRender {
5744
* +expect(withinDOM().getByText("foo")).toBeInTheDocument();
5845
* ```
5946
*/
60-
withinDOM: () => SyncScreen
47+
withinDOM: () => SyncScreen<Q>
6148

6249
renderedComponents: Array<string | React.ComponentType>
6350
}
6451

65-
export class RenderInstance<Snapshot> implements Render<Snapshot> {
52+
export class RenderInstance<Snapshot, Q extends Queries = SyncQueries>
53+
implements Render<Snapshot, Q>
54+
{
6655
id: string
6756
phase: 'mount' | 'update' | 'nested-update'
6857
actualDuration: number
@@ -73,12 +62,14 @@ export class RenderInstance<Snapshot> implements Render<Snapshot> {
7362
public snapshot: Snapshot
7463
private stringifiedDOM: string | undefined
7564
public renderedComponents: Array<string | React.ComponentType>
65+
private queries: Q
7666

7767
constructor(
7868
baseRender: BaseRender,
7969
snapshot: Snapshot,
8070
stringifiedDOM: string | undefined,
8171
renderedComponents: Array<string | React.ComponentType>,
72+
queries: Q,
8273
) {
8374
this.snapshot = snapshot
8475
this.stringifiedDOM = stringifiedDOM
@@ -90,6 +81,7 @@ export class RenderInstance<Snapshot> implements Render<Snapshot> {
9081
this.startTime = baseRender.startTime
9182
this.commitTime = baseRender.commitTime
9283
this.count = baseRender.count
84+
this.queries = queries
9385
}
9486

9587
private _domSnapshot: HTMLElement | undefined
@@ -124,17 +116,23 @@ export class RenderInstance<Snapshot> implements Render<Snapshot> {
124116
return (this._domSnapshot = body)
125117
}
126118

127-
get withinDOM(): () => SyncScreen {
128-
const snapScreen = Object.assign(within(this.domSnapshot), {
129-
debug: (
130-
...[dom = this.domSnapshot, ...rest]: Parameters<typeof screen.debug>
131-
) => screen.debug(dom, ...rest),
132-
logTestingPlaygroundURL: (
133-
...[dom = this.domSnapshot, ...rest]: Parameters<
134-
typeof screen.logTestingPlaygroundURL
135-
>
136-
) => screen.logTestingPlaygroundURL(dom, ...rest),
137-
})
119+
get withinDOM(): () => SyncScreen<Q> {
120+
const snapScreen = Object.assign(
121+
getQueriesForElement<Q>(
122+
this.domSnapshot,
123+
this.queries,
124+
) as any as BoundSyncFunctions<Q>,
125+
{
126+
debug: (
127+
...[dom = this.domSnapshot, ...rest]: Parameters<typeof screen.debug>
128+
) => screen.debug(dom, ...rest),
129+
logTestingPlaygroundURL: (
130+
...[dom = this.domSnapshot, ...rest]: Parameters<
131+
typeof screen.logTestingPlaygroundURL
132+
>
133+
) => screen.logTestingPlaygroundURL(dom, ...rest),
134+
},
135+
)
138136
return () => snapScreen
139137
}
140138
}

src/renderStream/__tests__/createRenderStream.test.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,32 @@ describe('snapshotDOM', () => {
8787
}
8888
}
8989
})
90+
91+
test('queries option', async () => {
92+
function Component() {
93+
return null
94+
}
95+
const queries = {
96+
foo: (_: any) => {
97+
return null
98+
},
99+
}
100+
101+
const {takeRender, render} = createRenderStream({
102+
snapshotDOM: true,
103+
queries,
104+
})
105+
render(<Component />)
106+
107+
const {withinDOM} = await takeRender()
108+
expect(withinDOM().foo()).toBe(null)
109+
function _typeTest() {
110+
// @ts-expect-error should not be present
111+
withinDOM().getByText
112+
withinDOM().debug()
113+
const _str: string = withinDOM().logTestingPlaygroundURL()
114+
}
115+
})
90116
})
91117

92118
describe('replaceSnapshot', () => {

src/renderStream/createRenderStream.tsx

+40-18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {RenderInstance, type Render, type BaseRender} from './Render.js'
66
import {type RenderStreamContextValue} from './context.js'
77
import {RenderStreamContextProvider} from './context.js'
88
import {disableActWarnings} from './disableActWarnings.js'
9+
import {syncQueries, type Queries, type SyncQueries} from './syncQueries.js'
910

1011
export type ValidSnapshot =
1112
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
@@ -29,7 +30,10 @@ interface MergeSnapshot<Snapshot> {
2930
): void
3031
}
3132

32-
export interface RenderStream<Snapshot extends ValidSnapshot> {
33+
export interface RenderStream<
34+
Snapshot extends ValidSnapshot,
35+
Q extends Queries = SyncQueries,
36+
> {
3337
// Allows for partial updating of the snapshot by shallow merging the results
3438
mergeSnapshot: MergeSnapshot<Snapshot>
3539
// Performs a full replacement of the snapshot
@@ -39,21 +43,22 @@ export interface RenderStream<Snapshot extends ValidSnapshot> {
3943
* Errors thrown during component render will be captured here, too.
4044
*/
4145
renders: Array<
42-
Render<Snapshot> | {phase: 'snapshotError'; count: number; error: unknown}
46+
| Render<Snapshot, Q>
47+
| {phase: 'snapshotError'; count: number; error: unknown}
4348
>
4449
/**
4550
* Peeks the next render from the current iterator position, without advancing the iterator.
4651
* If no render has happened yet, it will wait for the next render to happen.
4752
* @throws {WaitForRenderTimeoutError} if no render happens within the timeout
4853
*/
49-
peekRender: (options?: NextRenderOptions) => Promise<Render<Snapshot>>
54+
peekRender: (options?: NextRenderOptions) => Promise<Render<Snapshot, Q>>
5055
/**
5156
* Iterates to the next render and returns it.
5257
* If no render has happened yet, it will wait for the next render to happen.
5358
* @throws {WaitForRenderTimeoutError} if no render happens within the timeout
5459
*/
5560
takeRender: Assertable &
56-
((options?: NextRenderOptions) => Promise<Render<Snapshot>>)
61+
((options?: NextRenderOptions) => Promise<Render<Snapshot, Q>>)
5762
/**
5863
* Returns the total number of renders.
5964
*/
@@ -62,20 +67,27 @@ export interface RenderStream<Snapshot extends ValidSnapshot> {
6267
* Returns the current render.
6368
* @throws {Error} if no render has happened yet
6469
*/
65-
getCurrentRender: () => Render<Snapshot>
70+
getCurrentRender: () => Render<Snapshot, Q>
6671
/**
6772
* Waits for the next render to happen.
6873
* Does not advance the render iterator.
6974
*/
70-
waitForNextRender: (options?: NextRenderOptions) => Promise<Render<Snapshot>>
75+
waitForNextRender: (
76+
options?: NextRenderOptions,
77+
) => Promise<Render<Snapshot, Q>>
7178
}
7279

73-
export interface RenderStreamWithRenderFn<Snapshot extends ValidSnapshot>
74-
extends RenderStream<Snapshot> {
80+
export interface RenderStreamWithRenderFn<
81+
Snapshot extends ValidSnapshot,
82+
Q extends Queries = SyncQueries,
83+
> extends RenderStream<Snapshot, Q> {
7584
render: typeof baseRender
7685
}
7786

78-
export type RenderStreamOptions<Snapshot extends ValidSnapshot> = {
87+
export type RenderStreamOptions<
88+
Snapshot extends ValidSnapshot,
89+
Q extends Queries = SyncQueries,
90+
> = {
7991
onRender?: (
8092
info: BaseRender & {
8193
snapshot: Snapshot
@@ -90,6 +102,7 @@ export type RenderStreamOptions<Snapshot extends ValidSnapshot> = {
90102
* `useTrackRenders` occured.
91103
*/
92104
skipNonTrackingRenders?: boolean
105+
queries?: Q
93106
}
94107

95108
export class WaitForRenderTimeoutError extends Error {
@@ -100,19 +113,26 @@ export class WaitForRenderTimeoutError extends Error {
100113
}
101114
}
102115

103-
export function createRenderStream<Snapshot extends ValidSnapshot = void>({
116+
export function createRenderStream<
117+
Snapshot extends ValidSnapshot = void,
118+
Q extends Queries = SyncQueries,
119+
>({
104120
onRender,
105121
snapshotDOM = false,
106122
initialSnapshot,
107123
skipNonTrackingRenders,
108-
}: RenderStreamOptions<Snapshot> = {}): RenderStreamWithRenderFn<Snapshot> {
124+
queries = syncQueries as any as Q,
125+
}: RenderStreamOptions<Snapshot, Q> = {}): RenderStreamWithRenderFn<
126+
Snapshot,
127+
Q
128+
> {
109129
// creating the object first and then assigning in all the properties
110130
// allows keeping the object instance for reference while the members are
111131
// created, which is important for the `markAssertable` function
112-
const stream = {} as any as RenderStreamWithRenderFn<Snapshot>
132+
const stream = {} as any as RenderStreamWithRenderFn<Snapshot, Q>
113133

114-
let nextRender: Promise<Render<Snapshot>> | undefined,
115-
resolveNextRender: ((render: Render<Snapshot>) => void) | undefined,
134+
let nextRender: Promise<Render<Snapshot, Q>> | undefined,
135+
resolveNextRender: ((render: Render<Snapshot, Q>) => void) | undefined,
116136
rejectNextRender: ((error: unknown) => void) | undefined
117137
function resetNextRender() {
118138
nextRender = undefined
@@ -199,6 +219,7 @@ export function createRenderStream<Snapshot extends ValidSnapshot = void>({
199219
snapshot,
200220
domSnapshot,
201221
renderStreamContext.renderedComponents,
222+
queries,
202223
)
203224
renderStreamContext.renderedComponents = []
204225
stream.renders.push(render)
@@ -247,7 +268,8 @@ export function createRenderStream<Snapshot extends ValidSnapshot = void>({
247268
replaceSnapshot,
248269
mergeSnapshot,
249270
renders: new Array<
250-
Render<Snapshot> | {phase: 'snapshotError'; count: number; error: unknown}
271+
| Render<Snapshot, Q>
272+
| {phase: 'snapshotError'; count: number; error: unknown}
251273
>(),
252274
totalRenderCount() {
253275
return stream.renders.length
@@ -316,12 +338,12 @@ export function createRenderStream<Snapshot extends ValidSnapshot = void>({
316338
},
317339
waitForNextRender({timeout = 1000}: NextRenderOptions = {}) {
318340
if (!nextRender) {
319-
nextRender = Promise.race<Render<Snapshot>>([
320-
new Promise<Render<Snapshot>>((resolve, reject) => {
341+
nextRender = Promise.race<Render<Snapshot, Q>>([
342+
new Promise<Render<Snapshot, Q>>((resolve, reject) => {
321343
resolveNextRender = resolve
322344
rejectNextRender = reject
323345
}),
324-
new Promise<Render<Snapshot>>((_, reject) =>
346+
new Promise<Render<Snapshot, Q>>((_, reject) =>
325347
setTimeout(() => {
326348
const error = new WaitForRenderTimeoutError()
327349
Error.captureStackTrace(error, stream.waitForNextRender)

0 commit comments

Comments
 (0)