@@ -62,7 +62,77 @@ describe('ReactIncrementalTriangle', () => {
62
62
} ;
63
63
}
64
64
65
- function TriangleSimulator ( ) {
65
+ const STOP = 'STOP' ;
66
+
67
+ function randomInteger ( min , max ) {
68
+ min = Math . ceil ( min ) ;
69
+ max = Math . floor ( max ) ;
70
+ return Math . floor ( Math . random ( ) * ( max - min ) ) + min ;
71
+ }
72
+
73
+ function formatAction ( action ) {
74
+ switch ( action . type ) {
75
+ case FLUSH :
76
+ return `flush(${ action . unitsOfWork } )` ;
77
+ case STEP :
78
+ return `step(${ action . counter } )` ;
79
+ case INTERRUPT :
80
+ return 'interrupt()' ;
81
+ case TOGGLE :
82
+ return `toggle(${ action . childIndex } )` ;
83
+ case EXPIRE :
84
+ return `expire(${ action . ms } )` ;
85
+ default :
86
+ throw new Error ( 'Switch statement should be exhaustive' ) ;
87
+ }
88
+ }
89
+
90
+ function formatActions ( actions ) {
91
+ let result = 'simulate(' ;
92
+ for ( let i = 0 ; i < actions . length ; i ++ ) {
93
+ const action = actions [ i ] ;
94
+ result += formatAction ( action ) ;
95
+ if ( i !== actions . length - 1 ) {
96
+ result += ', ' ;
97
+ }
98
+ }
99
+ result += ')' ;
100
+ return result ;
101
+ }
102
+
103
+ const MAX_DEPTH = 3 ;
104
+ const TOTAL_CHILDREN = Math . pow ( 3 , MAX_DEPTH ) ;
105
+ let TOTAL_TRIANGLES = 0 ;
106
+ for ( let i = 0 ; i <= MAX_DEPTH ; i ++ ) {
107
+ TOTAL_TRIANGLES += Math . pow ( 3 , i ) ;
108
+ }
109
+
110
+ function randomAction ( ) {
111
+ switch ( randomInteger ( 0 , 5 ) ) {
112
+ case 0 :
113
+ return flush ( randomInteger ( 0 , TOTAL_TRIANGLES * 1.5 ) ) ;
114
+ case 1 :
115
+ return step ( randomInteger ( 0 , 10 ) ) ;
116
+ case 2 :
117
+ return interrupt ( ) ;
118
+ case 3 :
119
+ return toggle ( randomInteger ( 0 , TOTAL_CHILDREN ) ) ;
120
+ case 4 :
121
+ return expire ( randomInteger ( 0 , 1500 ) ) ;
122
+ default :
123
+ throw new Error ( 'Switch statement should be exhaustive' ) ;
124
+ }
125
+ }
126
+
127
+ function randomActions ( n ) {
128
+ let actions = [ ] ;
129
+ for ( let i = 0 ; i < n ; i ++ ) {
130
+ actions . push ( randomAction ( ) ) ;
131
+ }
132
+ return actions ;
133
+ }
134
+
135
+ function TriangleSimulator ( rootID ) {
66
136
let triangles = [ ] ;
67
137
let leafTriangles = [ ] ;
68
138
let yieldAfterEachRender = false ;
@@ -132,27 +202,35 @@ describe('ReactIncrementalTriangle', () => {
132
202
}
133
203
}
134
204
135
- const depth = 3 ;
136
-
137
205
let keyCounter = 0 ;
138
206
function reset ( nextStep = 0 ) {
139
207
triangles = [ ] ;
140
208
leafTriangles = [ ] ;
141
209
// Remounts the whole tree by changing the key
142
- ReactNoop . render ( < App depth = { depth } key = { keyCounter ++ } /> ) ;
210
+ if ( rootID ) {
211
+ ReactNoop . renderToRootWithID (
212
+ < App depth = { MAX_DEPTH } key = { keyCounter ++ } /> ,
213
+ rootID ,
214
+ ) ;
215
+ } else {
216
+ ReactNoop . render ( < App depth = { MAX_DEPTH } key = { keyCounter ++ } /> ) ;
217
+ }
143
218
ReactNoop . flush ( ) ;
144
219
assertConsistentTree ( ) ;
145
220
return appInstance ;
146
221
}
147
222
148
223
reset ( ) ;
149
- const totalChildren = leafTriangles . length ;
150
- const totalTriangles = triangles . length ;
151
224
152
225
function assertConsistentTree ( activeTriangle , counter ) {
153
226
const activeIndex = activeTriangle ? activeTriangle . leafIndex : - 1 ;
154
227
155
- const children = ReactNoop . getChildren ( ) ;
228
+ const children = ReactNoop . getChildren ( rootID ) ;
229
+
230
+ if ( children . length !== TOTAL_CHILDREN ) {
231
+ throw new Error ( 'Wrong number of children.' ) ;
232
+ }
233
+
156
234
for ( let i = 0 ; i < children . length ; i ++ ) {
157
235
let child = children [ i ] ;
158
236
let num = child . prop ;
@@ -183,14 +261,17 @@ describe('ReactIncrementalTriangle', () => {
183
261
}
184
262
}
185
263
186
- function simulate ( ... actions ) {
264
+ function * simulateAndYield ( ) {
187
265
const app = reset ( ) ;
188
266
let expectedCounterAtEnd = app . state . counter ;
189
267
190
268
let activeTriangle = null ;
191
- for ( var i = 0 ; i < actions . length ; i ++ ) {
269
+ while ( true ) {
270
+ var action = yield ;
271
+ if ( action === STOP ) {
272
+ break ;
273
+ }
192
274
ReactNoop . flushSync ( ( ) => {
193
- const action = actions [ i ] ;
194
275
switch ( action . type ) {
195
276
case FLUSH :
196
277
ReactNoop . flushUnitsOfWork ( action . unitsOfWork ) ;
@@ -234,84 +315,83 @@ describe('ReactIncrementalTriangle', () => {
234
315
assertConsistentTree ( activeTriangle , expectedCounterAtEnd ) ;
235
316
}
236
317
237
- return { simulate, totalChildren, totalTriangles} ;
238
- }
318
+ function simulate ( ...actions ) {
319
+ const gen = simulateAndYield ( ) ;
320
+ for ( let action of actions ) {
321
+ gen . next ( action ) ;
322
+ }
323
+ gen . next ( STOP ) ;
324
+ }
239
325
240
- it ( 'renders the triangle demo without inconsistencies' , ( ) => {
241
- const { simulate} = TriangleSimulator ( ) ;
242
- simulate ( step ( 1 ) ) ;
243
- simulate ( toggle ( 0 ) , step ( 1 ) , toggle ( 0 ) ) ;
244
- simulate ( step ( 1 ) , toggle ( 0 ) , flush ( 2 ) , step ( 2 ) , toggle ( 0 ) ) ;
245
- simulate ( step ( 1 ) , flush ( 3 ) , toggle ( 0 ) , step ( 0 ) ) ;
246
- simulate ( step ( 1 ) , flush ( 3 ) , toggle ( 18 ) , step ( 0 ) ) ;
247
- simulate ( step ( 4 ) , flush ( 52 ) , expire ( 1476 ) , flush ( 17 ) , step ( 0 ) ) ;
248
- simulate ( interrupt ( ) , toggle ( 10 ) , step ( 2 ) , expire ( 990 ) , flush ( 46 ) ) ;
249
- simulate ( interrupt ( ) , step ( 6 ) , step ( 7 ) , toggle ( 6 ) , interrupt ( ) ) ;
250
- } ) ;
326
+ return {
327
+ simulateAndYield,
328
+ simulate,
329
+ randomAction,
330
+ randomActions,
331
+ } ;
332
+ }
251
333
252
- it ( 'fuzz tester ', ( ) => {
253
- // This test is not deterministic because the inputs are randomized. It runs
254
- // a limited number of tests on every run. If it fails, it will output the
255
- // case that led to the failure. Add the failing case to the test above
334
+ describe ( 'single root ', ( ) => {
335
+ // These tests are not deterministic because the inputs are randomized. It
336
+ // runs a limited number of tests on every run. If it fails, it will output
337
+ // the case that led to the failure. Add the failing case to the test above
256
338
// to prevent future regressions.
257
- const { simulate, totalTriangles, totalChildren} = TriangleSimulator ( ) ;
339
+ it ( 'hard-coded tests' , ( ) => {
340
+ const { simulate} = TriangleSimulator ( ) ;
341
+ simulate ( step ( 1 ) ) ;
342
+ simulate ( toggle ( 0 ) , step ( 1 ) , toggle ( 0 ) ) ;
343
+ simulate ( step ( 1 ) , toggle ( 0 ) , flush ( 2 ) , step ( 2 ) , toggle ( 0 ) ) ;
344
+ simulate ( step ( 1 ) , flush ( 3 ) , toggle ( 0 ) , step ( 0 ) ) ;
345
+ simulate ( step ( 1 ) , flush ( 3 ) , toggle ( 18 ) , step ( 0 ) ) ;
346
+ simulate ( step ( 4 ) , flush ( 52 ) , expire ( 1476 ) , flush ( 17 ) , step ( 0 ) ) ;
347
+ simulate ( interrupt ( ) , toggle ( 10 ) , step ( 2 ) , expire ( 990 ) , flush ( 46 ) ) ;
348
+ simulate ( interrupt ( ) , step ( 6 ) , step ( 7 ) , toggle ( 6 ) , interrupt ( ) ) ;
349
+ } ) ;
258
350
259
- const limit = 1000 ;
351
+ it ( 'generative tests' , ( ) => {
352
+ const { simulate} = TriangleSimulator ( ) ;
260
353
261
- function randomInteger ( min , max ) {
262
- min = Math . ceil ( min ) ;
263
- max = Math . floor ( max ) ;
264
- return Math . floor ( Math . random ( ) * ( max - min ) ) + min ;
265
- }
354
+ const limit = 1000 ;
355
+
356
+ for ( let i = 0 ; i < limit ; i ++ ) {
357
+ const actions = randomActions ( 5 ) ;
358
+ try {
359
+ simulate ( ...actions ) ;
360
+ } catch ( e ) {
361
+ console . error (
362
+ `Triangle fuzz tester error! Copy and paste the following line into the test suite:
363
+ ${ formatActions ( actions ) }
364
+ ` ,
365
+ ) ;
366
+ throw e ;
367
+ }
368
+ }
369
+ } ) ;
370
+ } ) ;
266
371
267
- function randomAction ( ) {
268
- switch ( randomInteger ( 0 , 5 ) ) {
269
- case 0 :
270
- return flush ( randomInteger ( 0 , totalTriangles * 1.5 ) ) ;
271
- case 1 :
272
- return step ( randomInteger ( 0 , 10 ) ) ;
273
- case 2 :
274
- return interrupt ( ) ;
275
- case 3 :
276
- return toggle ( randomInteger ( 0 , totalChildren ) ) ;
277
- case 4 :
278
- return expire ( randomInteger ( 0 , 1500 ) ) ;
279
- default :
280
- throw new Error ( 'Switch statement should be exhaustive' ) ;
372
+ describe ( 'multiple roots' , ( ) => {
373
+ const rootIDs = [ 'a' , 'b' , 'c' ] ;
374
+
375
+ function randomActionsPerRoot ( ) {
376
+ function randomRootID ( ) {
377
+ const index = randomInteger ( 0 , rootIDs . length ) ;
378
+ return rootIDs [ index ] ;
281
379
}
282
- }
283
380
284
- function randomActions ( n ) {
285
- let actions = [ ] ;
286
- for ( let i = 0 ; i < n ; i ++ ) {
287
- actions . push ( randomAction ( ) ) ;
381
+ const actions = [ ] ;
382
+ for ( let i = 0 ; i < 10 ; i ++ ) {
383
+ const rootID = randomRootID ( ) ;
384
+ const action = randomAction ( ) ;
385
+ actions . push ( [ rootID , action ] ) ;
288
386
}
289
387
return actions ;
290
388
}
291
389
292
- function formatActions ( actions ) {
293
- let result = 'simulate (' ;
390
+ function formatActionsPerRoot ( actions ) {
391
+ let result = 'simulateMultipleRoots (' ;
294
392
for ( let i = 0 ; i < actions . length ; i ++ ) {
295
- const action = actions [ i ] ;
296
- switch ( action . type ) {
297
- case FLUSH :
298
- result += `flush(${ action . unitsOfWork } )` ;
299
- break ;
300
- case STEP :
301
- result += `step(${ action . counter } )` ;
302
- break ;
303
- case INTERRUPT :
304
- result += 'interrupt()' ;
305
- break ;
306
- case TOGGLE :
307
- result += `toggle(${ action . childIndex } )` ;
308
- break ;
309
- case EXPIRE :
310
- result += `expire(${ action . ms } )` ;
311
- break ;
312
- default :
313
- throw new Error ( 'Switch statement should be exhaustive' ) ;
314
- }
393
+ const [ rootID , action ] = actions [ i ] ;
394
+ result += `['${ rootID } ', ${ formatAction ( action ) } ]` ;
315
395
if ( i !== actions . length - 1 ) {
316
396
result += ', ' ;
317
397
}
@@ -320,19 +400,52 @@ describe('ReactIncrementalTriangle', () => {
320
400
return result ;
321
401
}
322
402
323
- for ( let i = 0 ; i < limit ; i ++ ) {
324
- const actions = randomActions ( 5 ) ;
325
- try {
326
- simulate ( ...actions ) ;
327
- } catch ( e ) {
328
- console . error (
329
- `
330
- Triangle fuzz tester error! Copy and paste the following line into the test suite:
331
- ${ formatActions ( actions ) }
332
- ` ,
333
- ) ;
334
- throw e ;
403
+ function simulateMultipleRoots ( ...actions ) {
404
+ const roots = new Map ( ) ;
405
+ for ( let rootID of rootIDs ) {
406
+ const simulator = TriangleSimulator ( rootID ) ;
407
+ const generator = simulator . simulateAndYield ( ) ;
408
+ // Call this once to prepare the generator
409
+ generator . next ( ) ;
410
+ roots . set ( rootID , generator ) ;
335
411
}
412
+
413
+ actions . forEach ( ( [ rootID , action ] ) => {
414
+ const generator = roots . get ( rootID ) ;
415
+ generator . next ( action ) ;
416
+ } ) ;
417
+ roots . forEach ( generator => {
418
+ generator . next ( STOP ) ;
419
+ } ) ;
336
420
}
421
+
422
+ it ( 'hard-coded tests' , ( ) => {
423
+ simulateMultipleRoots (
424
+ [ 'b' , interrupt ( ) ] ,
425
+ [ 'a' , toggle ( 22 ) ] ,
426
+ [ 'c' , step ( 4 ) ] ,
427
+ [ 'a' , expire ( 10 ) ] ,
428
+ [ 'a' , interrupt ( ) ] ,
429
+ [ 'c' , step ( 2 ) ] ,
430
+ [ 'b' , interrupt ( ) ] ,
431
+ ) ;
432
+ } ) ;
433
+
434
+ it ( 'generative tests' , ( ) => {
435
+ const limit = 100 ;
436
+ for ( let i = 0 ; i < limit ; i ++ ) {
437
+ const actions = randomActionsPerRoot ( ) ;
438
+ try {
439
+ simulateMultipleRoots ( ...actions ) ;
440
+ } catch ( e ) {
441
+ console . error (
442
+ `Triangle fuzz tester error! Copy and paste the following line into the test suite:
443
+ ${ formatActionsPerRoot ( actions ) }
444
+ ` ,
445
+ ) ;
446
+ throw e ;
447
+ }
448
+ }
449
+ } ) ;
337
450
} ) ;
338
451
} ) ;
0 commit comments