@@ -23,15 +23,27 @@ export interface NextRenderOptions {
23
23
export interface ProfiledComponent < Props , Snapshot >
24
24
extends React . FC < Props > ,
25
25
ProfiledComponentFields < Props , Snapshot > ,
26
- ProfiledComponenOnlyFields < Props , Snapshot > { }
26
+ ProfiledComponentOnlyFields < Props , Snapshot > { }
27
27
28
- interface UpdateSnapshot < Snapshot > {
28
+ interface ReplaceSnapshot < Snapshot > {
29
29
( newSnapshot : Snapshot ) : void ;
30
30
( updateSnapshot : ( lastSnapshot : Readonly < Snapshot > ) => Snapshot ) : void ;
31
31
}
32
32
33
- interface ProfiledComponenOnlyFields < Props , Snapshot > {
34
- updateSnapshot : UpdateSnapshot < Snapshot > ;
33
+ interface MergeSnapshot < Snapshot > {
34
+ ( partialSnapshot : Partial < Snapshot > ) : void ;
35
+ (
36
+ updatePartialSnapshot : (
37
+ lastSnapshot : Readonly < Snapshot >
38
+ ) => Partial < Snapshot >
39
+ ) : void ;
40
+ }
41
+
42
+ interface ProfiledComponentOnlyFields < Props , Snapshot > {
43
+ // Allows for partial updating of the snapshot by shallow merging the results
44
+ mergeSnapshot : MergeSnapshot < Snapshot > ;
45
+ // Performs a full replacement of the snapshot
46
+ replaceSnapshot : ReplaceSnapshot < Snapshot > ;
35
47
}
36
48
interface ProfiledComponentFields < Props , Snapshot > {
37
49
/**
@@ -54,21 +66,14 @@ interface ProfiledComponentFields<Props, Snapshot> {
54
66
*/
55
67
takeRender ( options ?: NextRenderOptions ) : Promise < Render < Snapshot > > ;
56
68
/**
57
- * Returns the current render count .
69
+ * Returns the total number of renders .
58
70
*/
59
- currentRenderCount ( ) : number ;
71
+ totalRenderCount ( ) : number ;
60
72
/**
61
73
* Returns the current render.
62
74
* @throws {Error } if no render has happened yet
63
75
*/
64
76
getCurrentRender ( ) : Render < Snapshot > ;
65
- /**
66
- * Iterates the renders until the render count is reached.
67
- */
68
- takeUntilRenderCount (
69
- count : number ,
70
- optionsPerRender ?: NextRenderOptions
71
- ) : Promise < void > ;
72
77
/**
73
78
* Waits for the next render to happen.
74
79
* Does not advance the render iterator.
@@ -90,18 +95,18 @@ export function profile<
90
95
onRender ?: (
91
96
info : BaseRender & {
92
97
snapshot : Snapshot ;
93
- updateSnapshot : UpdateSnapshot < Snapshot > ;
98
+ replaceSnapshot : ReplaceSnapshot < Snapshot > ;
99
+ mergeSnapshot : MergeSnapshot < Snapshot > ;
94
100
}
95
101
) => void ;
96
102
snapshotDOM ?: boolean ;
97
103
initialSnapshot ?: Snapshot ;
98
104
} ) {
99
- let currentRender : Render < Snapshot > | undefined ;
100
105
let nextRender : Promise < Render < Snapshot > > | undefined ;
101
106
let resolveNextRender : ( ( render : Render < Snapshot > ) => void ) | undefined ;
102
107
let rejectNextRender : ( ( error : unknown ) => void ) | undefined ;
103
108
const snapshotRef = { current : initialSnapshot } ;
104
- const updateSnapshot : UpdateSnapshot < Snapshot > = ( snap ) => {
109
+ const replaceSnapshot : ReplaceSnapshot < Snapshot > = ( snap ) => {
105
110
if ( typeof snap === "function" ) {
106
111
if ( ! initialSnapshot ) {
107
112
throw new Error (
@@ -118,6 +123,16 @@ export function profile<
118
123
snapshotRef . current = snap ;
119
124
}
120
125
} ;
126
+
127
+ const mergeSnapshot : MergeSnapshot < Snapshot > = ( partialSnapshot ) => {
128
+ replaceSnapshot ( ( snapshot ) => ( {
129
+ ...snapshot ,
130
+ ...( typeof partialSnapshot === "function"
131
+ ? partialSnapshot ( snapshot )
132
+ : partialSnapshot ) ,
133
+ } ) ) ;
134
+ } ;
135
+
121
136
const profilerOnRender : React . ProfilerOnRenderCallback = (
122
137
id ,
123
138
phase ,
@@ -145,7 +160,8 @@ export function profile<
145
160
*/
146
161
onRender ?.( {
147
162
...baseRender ,
148
- updateSnapshot,
163
+ replaceSnapshot,
164
+ mergeSnapshot,
149
165
snapshot : snapshotRef . current ! ,
150
166
} ) ;
151
167
@@ -154,8 +170,6 @@ export function profile<
154
170
? window . document . body . innerHTML
155
171
: undefined ;
156
172
const render = new RenderInstance ( baseRender , snapshot , domSnapshot ) ;
157
- // eslint-disable-next-line testing-library/render-result-naming-convention
158
- currentRender = render ;
159
173
Profiled . renders . push ( render ) ;
160
174
resolveNextRender ?.( render ) ;
161
175
} catch ( error ) {
@@ -178,29 +192,31 @@ export function profile<
178
192
</ React . Profiler >
179
193
) ,
180
194
{
181
- updateSnapshot,
182
- } satisfies ProfiledComponenOnlyFields < Props , Snapshot > ,
195
+ replaceSnapshot,
196
+ mergeSnapshot,
197
+ } satisfies ProfiledComponentOnlyFields < Props , Snapshot > ,
183
198
{
184
199
renders : new Array <
185
200
| Render < Snapshot >
186
201
| { phase : "snapshotError" ; count : number ; error : unknown }
187
202
> ( ) ,
188
- currentRenderCount ( ) {
203
+ totalRenderCount ( ) {
189
204
return Profiled . renders . length ;
190
205
} ,
191
206
async peekRender ( options : NextRenderOptions = { } ) {
192
207
if ( iteratorPosition < Profiled . renders . length ) {
193
208
const render = Profiled . renders [ iteratorPosition ] ;
209
+
194
210
if ( render . phase === "snapshotError" ) {
195
211
throw render . error ;
196
212
}
213
+
197
214
return render ;
198
215
}
199
- const render = Profiled . waitForNextRender ( {
216
+ return Profiled . waitForNextRender ( {
200
217
[ _stackTrace ] : captureStackTrace ( Profiled . peekRender ) ,
201
218
...options ,
202
219
} ) ;
203
- return render ;
204
220
} ,
205
221
async takeRender ( options : NextRenderOptions = { } ) {
206
222
let error : unknown = undefined ;
@@ -219,18 +235,25 @@ export function profile<
219
235
}
220
236
} ,
221
237
getCurrentRender ( ) {
222
- if ( ! currentRender ) {
223
- throw new Error ( "Has not been rendered yet!" ) ;
238
+ // The "current" render should point at the same render that the most
239
+ // recent `takeRender` call returned, so we need to get the "previous"
240
+ // iterator position, otherwise `takeRender` advances the iterator
241
+ // to the next render. This means we need to call `takeRender` at least
242
+ // once before we can get a current render.
243
+ const currentPosition = iteratorPosition - 1 ;
244
+
245
+ if ( currentPosition < 0 ) {
246
+ throw new Error (
247
+ "No current render available. You need to call `takeRender` before you can get the current render."
248
+ ) ;
224
249
}
225
- return currentRender ;
226
- } ,
227
- async takeUntilRenderCount (
228
- count : number ,
229
- optionsPerRender ?: NextRenderOptions
230
- ) {
231
- while ( Profiled . renders . length < count ) {
232
- await Profiled . takeRender ( optionsPerRender ) ;
250
+
251
+ const render = Profiled . renders [ currentPosition ] ;
252
+
253
+ if ( render . phase === "snapshotError" ) {
254
+ throw render . error ;
233
255
}
256
+ return render ;
234
257
} ,
235
258
waitForNextRender ( {
236
259
timeout = 1000 ,
@@ -306,7 +329,7 @@ export function profileHook<ReturnValue extends ValidSnapshot, Props>(
306
329
) : ProfiledHook < Props , ReturnValue > {
307
330
let returnValue : ReturnValue ;
308
331
const Component = ( props : Props ) => {
309
- ProfiledComponent . updateSnapshot ( renderCallback ( props ) ) ;
332
+ ProfiledComponent . replaceSnapshot ( renderCallback ( props ) ) ;
310
333
return null ;
311
334
} ;
312
335
const ProfiledComponent = profile < ReturnValue , Props > ( {
@@ -322,7 +345,7 @@ export function profileHook<ReturnValue extends ValidSnapshot, Props>(
322
345
} ,
323
346
{
324
347
renders : ProfiledComponent . renders ,
325
- currentSnapshotCount : ProfiledComponent . currentRenderCount ,
348
+ totalSnapshotCount : ProfiledComponent . totalRenderCount ,
326
349
async peekSnapshot ( options ) {
327
350
return ( await ProfiledComponent . peekRender ( options ) ) . snapshot ;
328
351
} ,
@@ -332,7 +355,6 @@ export function profileHook<ReturnValue extends ValidSnapshot, Props>(
332
355
getCurrentSnapshot ( ) {
333
356
return ProfiledComponent . getCurrentRender ( ) . snapshot ;
334
357
} ,
335
- takeUntilSnapshotCount : ProfiledComponent . takeUntilRenderCount ,
336
358
async waitForNextSnapshot ( options ) {
337
359
return ( await ProfiledComponent . waitForNextRender ( options ) ) . snapshot ;
338
360
} ,
0 commit comments