@@ -15,7 +15,7 @@ var React;
1515var ReactNoop ;
1616var ReactFeatureFlags ;
1717
18- describe ( 'ReactConcurrency ' , ( ) => {
18+ describe ( 'ReactIncrementalTriangle ' , ( ) => {
1919 beforeEach ( ( ) => {
2020 jest . resetModules ( ) ;
2121 React = require ( 'react' ) ;
@@ -33,7 +33,6 @@ describe('ReactConcurrency', () => {
3333 let triangles = [ ] ;
3434 let leafTriangles = [ ] ;
3535 let yieldAfterEachRender = false ;
36- let lastRenderedTriangle = null ;
3736 class Triangle extends React . Component {
3837 constructor ( props ) {
3938 super ( ) ;
@@ -64,9 +63,8 @@ describe('ReactConcurrency', () => {
6463 ) ;
6564 }
6665 render ( ) {
67- lastRenderedTriangle = this ;
6866 if ( yieldAfterEachRender ) {
69- ReactNoop . yieldBeforeNextUnitOfWork ( ) ;
67+ ReactNoop . yield ( this ) ;
7068 }
7169 const { counter, depth} = this . props ;
7270 if ( depth === 0 ) {
@@ -83,7 +81,7 @@ describe('ReactConcurrency', () => {
8381 }
8482 }
8583
86- let app ;
84+ let appInstance ;
8785 class App extends React . Component {
8886 state = { counter : 0 } ;
8987 interrupt ( ) {
@@ -98,16 +96,32 @@ describe('ReactConcurrency', () => {
9896 return currentCounter ;
9997 }
10098 render ( ) {
101- app = this ;
99+ appInstance = this ;
102100 return < Triangle counter = { this . state . counter } depth = { 3 } /> ;
103101 }
104102 }
105103
106104 const depth = 3 ;
107- ReactNoop . render ( < App depth = { depth } /> ) ;
108- ReactNoop . flush ( ) ;
109- // Check initial mount
110- treeIsConsistent ( 0 ) ;
105+
106+ function reset ( nextStep = 0 ) {
107+ triangles = [ ] ;
108+ leafTriangles = [ ] ;
109+
110+ const previousYieldAfterEachRender = yieldAfterEachRender ;
111+ yieldAfterEachRender = false ;
112+ ReactNoop . render ( [ ] ) ;
113+ ReactNoop . flush ( ) ;
114+ ReactNoop . render ( < App depth = { depth } /> ) ;
115+ ReactNoop . flush ( ) ;
116+ // Check initial mount
117+ treeIsConsistent ( nextStep ) ;
118+ yieldAfterEachRender = previousYieldAfterEachRender ;
119+ return appInstance ;
120+ }
121+
122+ reset ( ) ;
123+ const totalChildren = leafTriangles . length ;
124+ const totalTriangles = triangles . length ;
111125
112126 function treeIsConsistent ( counter , activeTriangles = new Set ( ) ) {
113127 let activeIndices = [ ] ;
@@ -136,15 +150,9 @@ describe('ReactConcurrency', () => {
136150 }
137151 }
138152
139- function renderNextTriangle ( ) {
140- yieldAfterEachRender = true ;
141- lastRenderedTriangle = null ;
142- ReactNoop . flush ( ) ;
143- yieldAfterEachRender = false ;
144- return lastRenderedTriangle ;
145- }
146-
147153 function step ( nextCounter , ...configs ) {
154+ const app = reset ( ) ;
155+
148156 const currentCounter = app . setCounter ( nextCounter ) ;
149157
150158 const onKeyframes = new Map ( ) ;
@@ -156,25 +164,28 @@ describe('ReactConcurrency', () => {
156164 throw new Error ( 'targetIndex should be the index of a leaf triangle' ) ;
157165 }
158166
159- const onTriangles = onKeyframes . get ( targetIndex ) || new Set ( ) ;
160- onTriangles . add ( targetTriangle ) ;
161- onKeyframes . set ( onKeyframe , onTriangles ) ;
167+ if ( onKeyframe >= 0 && onKeyframe < totalTriangles ) {
168+ const onTriangles = onKeyframes . get ( targetIndex ) || new Set ( ) ;
169+ onTriangles . add ( targetTriangle ) ;
170+ onKeyframes . set ( onKeyframe , onTriangles ) ;
171+ }
162172
163- const offTriangles = offKeyframes . get ( targetIndex ) || new Set ( ) ;
164- offTriangles . add ( targetTriangle ) ;
165- offKeyframes . set ( offKeyframe , offTriangles ) ;
173+ if ( offKeyframe >= 0 && offKeyframe < totalTriangles ) {
174+ const offTriangles = offKeyframes . get ( targetIndex ) || new Set ( ) ;
175+ offTriangles . add ( targetTriangle ) ;
176+ offKeyframes . set ( offKeyframe , offTriangles ) ;
177+ }
166178 }
167179
168180 const activeTriangles = new Set ( ) ;
169181
182+ yieldAfterEachRender = true ;
170183 let i = 0 ;
171- let renderedTriangle = renderNextTriangle ( ) ;
172- while ( renderedTriangle !== null ) {
184+ for ( var renderedTriangle of ReactNoop . flushAndYield ( ) ) {
173185 if ( i ++ > 999 ) {
174186 throw new Error ( 'Infinite loop' ) ;
175187 }
176188 treeIsConsistent ( currentCounter , activeTriangles ) ;
177-
178189 var onTriangles = onKeyframes . get ( renderedTriangle . index ) ;
179190 if ( onTriangles ) {
180191 onTriangles . forEach ( targetTriangle => {
@@ -184,6 +195,7 @@ describe('ReactConcurrency', () => {
184195 activeTriangles . add ( targetTriangle ) ;
185196 onTriangles . delete ( targetTriangle ) ;
186197 } ) ;
198+ onKeyframes . delete ( renderedTriangle . index ) ;
187199 }
188200
189201 var offTriangles = offKeyframes . get ( renderedTriangle . index ) ;
@@ -195,19 +207,24 @@ describe('ReactConcurrency', () => {
195207 activeTriangles . delete ( targetTriangle ) ;
196208 offTriangles . delete ( targetTriangle ) ;
197209 } ) ;
210+ offKeyframes . delete ( renderedTriangle . index ) ;
198211 }
199212
200213 app . interrupt ( ) ;
201214 ReactNoop . flushAnimationPri ( ) ;
202215 treeIsConsistent ( currentCounter , activeTriangles ) ;
216+ }
217+ yieldAfterEachRender = false ;
203218
204- renderedTriangle = renderNextTriangle ( ) ;
219+ if ( onKeyframes . size !== 0 || offKeyframes . size !== 0 ) {
220+ onKeyframes . forEach ( k => console . log ( k . size ) ) ;
221+ throw new Error ( 'Some keyframes were not fired.' ) ;
205222 }
206223
207224 treeIsConsistent ( nextCounter , activeTriangles ) ;
208225 }
209226
210- return { step} ;
227+ return { step, totalChildren , totalTriangles } ;
211228 }
212229
213230 it ( 'works' , ( ) => {
@@ -224,12 +241,55 @@ describe('ReactConcurrency', () => {
224241 // hi-pri update to "deactivate" that same node.
225242 step ( 2 , [ 5 , 5 , 10 ] ) ;
226243 step ( 3 , [ 22 , 20 , 22 ] ) ;
227- step ( 4 , [ 13 , 10 , 30 ] ) ;
228- step ( 5 , [ 7 , 35 , 38 ] ) ;
229- step ( 6 , [ 17 , 8 , 14 ] ) ;
230244
231245 // Simulate multiple hover effects in the same step.
232- step ( 7 , [ 3 , 4 , 21 ] , [ 17 , 8 , 14 ] , [ 19 , 7 , 12 ] ) ;
233- step ( 8 , [ 3 , 4 , 39 ] , [ 17 , 8 , 14 ] , [ 19 , 7 , 12 ] ) ;
246+ step ( 4 , [ 3 , 4 , 21 ] , [ 17 , 8 , 14 ] , [ 19 , 7 , 12 ] ) ;
247+ step ( 5 , [ 3 , 4 , 39 ] , [ 17 , 8 , 14 ] , [ 19 , 7 , 12 ] ) ;
248+
249+ // The following tests are test cases that at one point or another failed.
250+ // Don't touch them (unless there's a good reason).
251+ step ( 6 , [ 23 , 11 , 6 ] ) ;
252+ step ( 7 , [ 7 , 4 , 3 ] , [ 4 , 14 , 6 ] ) ;
253+ } ) ;
254+
255+ it ( 'fuzz tester' , ( ) => {
256+ // This test is not deterministic because the inputs are randomized. It runs
257+ // a limited number of tests on every run. If it fails, it will output the
258+ // inputs that led to the failure. Add the failing case to the test above
259+ // to prevent future regressions.
260+ const limit = 100 ;
261+
262+ const { step, totalChildren, totalTriangles} = TriangleTester ( ) ;
263+
264+ function randomInteger ( min , max ) {
265+ min = Math . ceil ( min ) ;
266+ max = Math . floor ( max ) ;
267+ return Math . floor ( Math . random ( ) * ( max - min ) ) + min ;
268+ }
269+
270+ function randomConfig ( ) {
271+ const targetIndex = randomInteger ( 0 , totalChildren ) ;
272+ const onKeyframe = randomInteger ( 0 , totalTriangles ) ;
273+ // Allow for end keyframe to happen after entire tree has flushed
274+ const offKeyframe = randomInteger ( 0 , totalTriangles * 2 ) ;
275+ return [ targetIndex , onKeyframe , offKeyframe ] ;
276+ }
277+
278+ for ( let nextStep = 1 ; nextStep <= limit ; nextStep ++ ) {
279+ const configs = [ randomConfig ( ) , randomConfig ( ) , randomConfig ( ) ] ;
280+ try {
281+ step ( nextStep , ...configs ) ;
282+ } catch ( error ) {
283+ console . error ( error ) ;
284+ const configStr = configs
285+ . map ( config => `[${ config . join ( ', ' ) } ]` )
286+ . join ( ', ' ) ;
287+ throw new Error (
288+ `Triangle fuzz tester failure. Add this the ReactIncrementalTriangle-test suite and fix it:
289+ step(n, ${ configStr } )
290+ ` ,
291+ ) ;
292+ }
293+ }
234294 } ) ;
235295} ) ;
0 commit comments