Skip to content

Commit 01c1093

Browse files
committed
Moar fuzziness
Adds more fuzziness to the generated tests. Specifcally, introduces nested Suspense cases, where the fallback of a Suspense component also suspends. This flushed out a bug (yay!) whose test case I've hard coded.
1 parent 85d4c78 commit 01c1093

File tree

6 files changed

+73
-21
lines changed

6 files changed

+73
-21
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,7 +1178,6 @@ function updateSuspenseComponent(
11781178
currentPrimaryChildFragment.pendingProps,
11791179
NoWork,
11801180
);
1181-
primaryChildFragment.effectTag |= Placement;
11821181

11831182
if ((workInProgress.mode & ConcurrentMode) === NoContext) {
11841183
// Outside of concurrent mode, we commit the effects from the
@@ -1213,7 +1212,6 @@ function updateSuspenseComponent(
12131212
nextFallbackChildren,
12141213
currentFallbackChildFragment.expirationTime,
12151214
));
1216-
fallbackChildFragment.effectTag |= Placement;
12171215
child = primaryChildFragment;
12181216
primaryChildFragment.childExpirationTime = NoWork;
12191217
// Skip the primary children, and continue working on the
@@ -1257,11 +1255,14 @@ function updateSuspenseComponent(
12571255
NoWork,
12581256
null,
12591257
);
1260-
1261-
primaryChildFragment.effectTag |= Placement;
12621258
primaryChildFragment.child = currentPrimaryChild;
12631259
currentPrimaryChild.return = primaryChildFragment;
12641260

1261+
// Even though we're creating a new fiber, there are no new children,
1262+
// because we're reusing an already mounted tree. So we don't need to
1263+
// schedule a placement.
1264+
// primaryChildFragment.effectTag |= Placement;
1265+
12651266
if ((workInProgress.mode & ConcurrentMode) === NoContext) {
12661267
// Outside of concurrent mode, we commit the effects from the
12671268
// partially completed, timed-out tree, too.

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,16 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) {
558558
} else {
559559
unhideTextInstance(instance, node.memoizedProps);
560560
}
561+
} else if (
562+
node.tag === SuspenseComponent &&
563+
node.memoizedState !== null
564+
) {
565+
// Found a nested Suspense component that timed out. Skip over the
566+
// primary child fragment, which should remain hidden.
567+
const fallbackChildFragment: Fiber = (node.child: any).sibling;
568+
fallbackChildFragment.return = node;
569+
node = fallbackChildFragment;
570+
continue;
561571
} else if (node.child !== null) {
562572
node.child.return = node;
563573
node = node.child;

packages/react-reconciler/src/ReactFiberCompleteWork.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
Update,
4646
NoEffect,
4747
DidCapture,
48+
Deletion,
4849
} from 'shared/ReactSideEffectTags';
4950
import invariant from 'shared/invariant';
5051

@@ -82,7 +83,6 @@ import {
8283
popHydrationState,
8384
} from './ReactFiberHydrationContext';
8485
import {ConcurrentMode, NoContext} from './ReactTypeOfMode';
85-
import {reconcileChildFibers} from './ReactChildFiber';
8686

8787
function markUpdate(workInProgress: Fiber) {
8888
// Tag the fiber with an update effect. This turns a Placement into
@@ -715,12 +715,16 @@ function completeWork(
715715
// the stateNode during the begin phase?
716716
const currentFallbackChild: Fiber | null = (current.child: any).sibling;
717717
if (currentFallbackChild !== null) {
718-
reconcileChildFibers(
719-
workInProgress,
720-
currentFallbackChild,
721-
null,
722-
renderExpirationTime,
723-
);
718+
// Deletions go at the beginning of the return fiber's effect list
719+
const first = workInProgress.firstEffect;
720+
if (first !== null) {
721+
workInProgress.firstEffect = currentFallbackChild;
722+
currentFallbackChild.nextEffect = first;
723+
} else {
724+
workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChild;
725+
currentFallbackChild.nextEffect = null;
726+
}
727+
currentFallbackChild.effectTag = Deletion;
724728
}
725729
}
726730

packages/react-reconciler/src/ReactFiberUnwindWork.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ import {
7070
LOW_PRIORITY_EXPIRATION,
7171
} from './ReactFiberExpirationTime';
7272
import {findEarliestOutstandingPriorityLevel} from './ReactFiberPendingPriority';
73-
import {reconcileChildren} from './ReactFiberBeginWork';
7473

7574
function createRootErrorUpdate(
7675
fiber: Fiber,
@@ -238,14 +237,6 @@ function throwException(
238237
if ((workInProgress.mode & ConcurrentMode) === NoEffect) {
239238
workInProgress.effectTag |= DidCapture;
240239

241-
// Unmount the source fiber's children
242-
const nextChildren = null;
243-
reconcileChildren(
244-
sourceFiber.alternate,
245-
sourceFiber,
246-
nextChildren,
247-
renderExpirationTime,
248-
);
249240
sourceFiber.effectTag &= ~Incomplete;
250241

251242
// We're going to commit this fiber even though it didn't complete.

packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,28 @@ describe('ReactSuspenseFuzz', () => {
296296
{value: randomInteger(0, 5000), weight: 1},
297297
]);
298298

299-
return React.createElement(Suspense, {maxDuration}, ...children);
299+
const fallbackType = pickRandomWeighted([
300+
{value: 'none', weight: 1},
301+
{value: 'normal', weight: 1},
302+
{value: 'nested suspense', weight: 1},
303+
]);
304+
305+
let fallback;
306+
if (fallbackType === 'normal') {
307+
fallback = 'Loading...';
308+
} else if (fallbackType === 'nested suspense') {
309+
fallback = React.createElement(
310+
React.Fragment,
311+
null,
312+
...createRandomChildren(3),
313+
);
314+
}
315+
316+
return React.createElement(
317+
Suspense,
318+
{maxDuration, fallback},
319+
...children,
320+
);
300321
}
301322
case 'return':
302323
default:
@@ -333,6 +354,28 @@ describe('ReactSuspenseFuzz', () => {
333354
);
334355
});
335356

357+
it('hard-coded cases', () => {
358+
const {Text, testResolvedOutput} = createFuzzer();
359+
360+
testResolvedOutput(
361+
<React.Fragment>
362+
<Text
363+
initialDelay={20}
364+
text="A"
365+
updates={[{beginAfter: 10, suspendFor: 20}]}
366+
/>
367+
<Suspense fallback="Loading... (B)">
368+
<Text
369+
initialDelay={10}
370+
text="B"
371+
updates={[{beginAfter: 30, suspendFor: 50}]}
372+
/>
373+
<Text text="C" />
374+
</Suspense>
375+
</React.Fragment>,
376+
);
377+
});
378+
336379
it('generative tests', () => {
337380
const {generateTestCase, testResolvedOutput} = createFuzzer();
338381

packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
937937
]);
938938
expect(ReactNoop.getChildrenAsJSX()).toEqual(
939939
<React.Fragment>
940+
<span hidden={true} prop="Step: 1" />
940941
<span hidden={true} prop="Sibling" />
941942
<span prop="Loading (1)" />
942943
<span prop="Loading (2)" />
@@ -1060,6 +1061,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
10601061
expect(ReactNoop.getChildrenAsJSX()).toEqual(
10611062
<React.Fragment>
10621063
<span hidden={true} prop="Before" />
1064+
<span hidden={true} prop="Async: 1" />
10631065
<span hidden={true} prop="After" />
10641066
<span prop="Loading..." />
10651067

@@ -1194,6 +1196,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
11941196
expect(ReactNoop.getChildrenAsJSX()).toEqual(
11951197
<React.Fragment>
11961198
<span hidden={true} prop="Before" />
1199+
<span hidden={true} prop="Async: 1" />
11971200
<span hidden={true} prop="After" />
11981201
<span prop="Loading..." />
11991202

0 commit comments

Comments
 (0)