Skip to content

Commit 3f4852f

Browse files
authored
Run Placeholder tests in persistent mode, too (#15013)
* Convert ReactSuspensePlaceholder tests to use noop Instead of the test renderer, since test renderer does not support running in persistent mode. * Run Placeholder tests in persistent mode, too * Fix Flow and lint * Hidden text instances should have correct host context Adds a test for a subtle edge case that only occurs in persistent mode. * createHiddenTextInstance -> cloneHiddenTextInstance This sidesteps the problem where createHiddenTextInstance needs access to the host context.
1 parent d0289c7 commit 3f4852f

File tree

7 files changed

+328
-167
lines changed

7 files changed

+328
-167
lines changed

packages/react-native-renderer/src/ReactFabricHostConfig.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -406,10 +406,17 @@ export function cloneUnhiddenInstance(
406406
};
407407
}
408408

409-
export function createHiddenTextInstance(
409+
export function cloneHiddenTextInstance(
410+
instance: Instance,
411+
text: string,
412+
internalInstanceHandle: Object,
413+
): TextInstance {
414+
throw new Error('Not yet implemented.');
415+
}
416+
417+
export function cloneUnhiddenTextInstance(
418+
instance: Instance,
410419
text: string,
411-
rootContainerInstance: Container,
412-
hostContext: HostContext,
413420
internalInstanceHandle: Object,
414421
): TextInstance {
415422
throw new Error('Not yet implemented.');

packages/react-noop-renderer/src/createReactNoop.js

+96-16
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,18 @@ type Instance = {|
4141
text: string | null,
4242
prop: any,
4343
hidden: boolean,
44+
context: HostContext,
4445
|};
45-
type TextInstance = {|text: string, id: number, hidden: boolean|};
46+
type TextInstance = {|
47+
text: string,
48+
id: number,
49+
hidden: boolean,
50+
context: HostContext,
51+
|};
52+
type HostContext = Object;
4653

4754
const NO_CONTEXT = {};
55+
const UPPERCASE_CONTEXT = {};
4856
const UPDATE_SIGNAL = {};
4957
if (__DEV__) {
5058
Object.freeze(NO_CONTEXT);
@@ -190,10 +198,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
190198
type: type,
191199
children: keepChildren ? instance.children : [],
192200
text: shouldSetTextContent(type, newProps)
193-
? (newProps.children: any) + ''
201+
? computeText((newProps.children: any) + '', instance.context)
194202
: null,
195203
prop: newProps.prop,
196204
hidden: newProps.hidden === true,
205+
context: instance.context,
197206
};
198207
Object.defineProperty(clone, 'id', {
199208
value: clone.id,
@@ -203,6 +212,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
203212
value: clone.text,
204213
enumerable: false,
205214
});
215+
Object.defineProperty(clone, 'context', {
216+
value: clone.context,
217+
enumerable: false,
218+
});
206219
hostCloneCounter++;
207220
return clone;
208221
}
@@ -216,20 +229,36 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
216229
);
217230
}
218231

232+
function computeText(rawText, hostContext) {
233+
return hostContext === UPPERCASE_CONTEXT ? rawText.toUpperCase() : rawText;
234+
}
235+
219236
const sharedHostConfig = {
220237
getRootHostContext() {
221238
return NO_CONTEXT;
222239
},
223240

224-
getChildHostContext() {
241+
getChildHostContext(
242+
parentHostContext: HostContext,
243+
type: string,
244+
rootcontainerInstance: Container,
245+
) {
246+
if (type === 'uppercase') {
247+
return UPPERCASE_CONTEXT;
248+
}
225249
return NO_CONTEXT;
226250
},
227251

228252
getPublicInstance(instance) {
229253
return instance;
230254
},
231255

232-
createInstance(type: string, props: Props): Instance {
256+
createInstance(
257+
type: string,
258+
props: Props,
259+
rootContainerInstance: Container,
260+
hostContext: HostContext,
261+
): Instance {
233262
if (type === 'errorInCompletePhase') {
234263
throw new Error('Error in host config.');
235264
}
@@ -238,17 +267,22 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
238267
type: type,
239268
children: [],
240269
text: shouldSetTextContent(type, props)
241-
? (props.children: any) + ''
270+
? computeText((props.children: any) + '', hostContext)
242271
: null,
243272
prop: props.prop,
244273
hidden: props.hidden === true,
274+
context: hostContext,
245275
};
246276
// Hide from unit tests
247277
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
248278
Object.defineProperty(inst, 'text', {
249279
value: inst.text,
250280
enumerable: false,
251281
});
282+
Object.defineProperty(inst, 'context', {
283+
value: inst.context,
284+
enumerable: false,
285+
});
252286
return inst;
253287
},
254288

@@ -298,9 +332,21 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
298332
hostContext: Object,
299333
internalInstanceHandle: Object,
300334
): TextInstance {
301-
const inst = {text: text, id: instanceCounter++, hidden: false};
335+
if (hostContext === UPPERCASE_CONTEXT) {
336+
text = text.toUpperCase();
337+
}
338+
const inst = {
339+
text: text,
340+
id: instanceCounter++,
341+
hidden: false,
342+
context: hostContext,
343+
};
302344
// Hide from unit tests
303345
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
346+
Object.defineProperty(inst, 'context', {
347+
value: inst.context,
348+
enumerable: false,
349+
});
304350
return inst;
305351
},
306352

@@ -343,7 +389,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
343389
instance.prop = newProps.prop;
344390
instance.hidden = newProps.hidden === true;
345391
if (shouldSetTextContent(type, newProps)) {
346-
instance.text = (newProps.children: any) + '';
392+
instance.text = computeText(
393+
(newProps.children: any) + '',
394+
instance.context,
395+
);
347396
}
348397
},
349398

@@ -353,7 +402,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
353402
newText: string,
354403
): void {
355404
hostUpdateCounter++;
356-
textInstance.text = newText;
405+
textInstance.text = computeText(newText, textInstance.context);
357406
},
358407

359408
appendChild,
@@ -453,23 +502,54 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
453502
true,
454503
null,
455504
);
456-
clone.hidden = props.hidden;
505+
clone.hidden = props.hidden === true;
506+
return clone;
507+
},
508+
509+
cloneHiddenTextInstance(
510+
instance: TextInstance,
511+
text: string,
512+
internalInstanceHandle: Object,
513+
): TextInstance {
514+
const clone = {
515+
text: instance.text,
516+
id: instanceCounter++,
517+
hidden: true,
518+
context: instance.context,
519+
};
520+
// Hide from unit tests
521+
Object.defineProperty(clone, 'id', {
522+
value: clone.id,
523+
enumerable: false,
524+
});
525+
Object.defineProperty(clone, 'context', {
526+
value: clone.context,
527+
enumerable: false,
528+
});
457529
return clone;
458530
},
459531

460-
createHiddenTextInstance(
532+
cloneUnhiddenTextInstance(
533+
instance: TextInstance,
461534
text: string,
462-
rootContainerInstance: Container,
463-
hostContext: Object,
464535
internalInstanceHandle: Object,
465536
): TextInstance {
466-
const inst = {text: text, id: instanceCounter++, hidden: true};
537+
const clone = {
538+
text: instance.text,
539+
id: instanceCounter++,
540+
hidden: false,
541+
context: instance.context,
542+
};
467543
// Hide from unit tests
468-
Object.defineProperty(inst, 'id', {
469-
value: inst.id,
544+
Object.defineProperty(clone, 'id', {
545+
value: clone.id,
546+
enumerable: false,
547+
});
548+
Object.defineProperty(clone, 'context', {
549+
value: clone.context,
470550
enumerable: false,
471551
});
472-
return inst;
552+
return clone;
473553
},
474554
};
475555

packages/react-reconciler/src/ReactFiberCommitWork.js

+54-44
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,13 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
11311131
commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
11321132
return;
11331133
}
1134+
case Profiler: {
1135+
return;
1136+
}
1137+
case SuspenseComponent: {
1138+
commitSuspenseComponent(finishedWork);
1139+
return;
1140+
}
11341141
}
11351142

11361143
commitContainer(finishedWork);
@@ -1199,50 +1206,7 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
11991206
return;
12001207
}
12011208
case SuspenseComponent: {
1202-
let newState: SuspenseState | null = finishedWork.memoizedState;
1203-
1204-
let newDidTimeout;
1205-
let primaryChildParent = finishedWork;
1206-
if (newState === null) {
1207-
newDidTimeout = false;
1208-
} else {
1209-
newDidTimeout = true;
1210-
primaryChildParent = finishedWork.child;
1211-
if (newState.timedOutAt === NoWork) {
1212-
// If the children had not already timed out, record the time.
1213-
// This is used to compute the elapsed time during subsequent
1214-
// attempts to render the children.
1215-
newState.timedOutAt = requestCurrentTime();
1216-
}
1217-
}
1218-
1219-
if (primaryChildParent !== null) {
1220-
hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);
1221-
}
1222-
1223-
// If this boundary just timed out, then it will have a set of thenables.
1224-
// For each thenable, attach a listener so that when it resolves, React
1225-
// attempts to re-render the boundary in the primary (pre-timeout) state.
1226-
const thenables: Set<Thenable> | null = (finishedWork.updateQueue: any);
1227-
if (thenables !== null) {
1228-
finishedWork.updateQueue = null;
1229-
let retryCache = finishedWork.stateNode;
1230-
if (retryCache === null) {
1231-
retryCache = finishedWork.stateNode = new PossiblyWeakSet();
1232-
}
1233-
thenables.forEach(thenable => {
1234-
// Memoize using the boundary fiber to prevent redundant listeners.
1235-
let retry = resolveRetryThenable.bind(null, finishedWork, thenable);
1236-
if (enableSchedulerTracing) {
1237-
retry = Schedule_tracing_wrap(retry);
1238-
}
1239-
if (!retryCache.has(thenable)) {
1240-
retryCache.add(thenable);
1241-
thenable.then(retry, retry);
1242-
}
1243-
});
1244-
}
1245-
1209+
commitSuspenseComponent(finishedWork);
12461210
return;
12471211
}
12481212
case IncompleteClassComponent: {
@@ -1258,6 +1222,52 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
12581222
}
12591223
}
12601224

1225+
function commitSuspenseComponent(finishedWork: Fiber) {
1226+
let newState: SuspenseState | null = finishedWork.memoizedState;
1227+
1228+
let newDidTimeout;
1229+
let primaryChildParent = finishedWork;
1230+
if (newState === null) {
1231+
newDidTimeout = false;
1232+
} else {
1233+
newDidTimeout = true;
1234+
primaryChildParent = finishedWork.child;
1235+
if (newState.timedOutAt === NoWork) {
1236+
// If the children had not already timed out, record the time.
1237+
// This is used to compute the elapsed time during subsequent
1238+
// attempts to render the children.
1239+
newState.timedOutAt = requestCurrentTime();
1240+
}
1241+
}
1242+
1243+
if (supportsMutation && primaryChildParent !== null) {
1244+
hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);
1245+
}
1246+
1247+
// If this boundary just timed out, then it will have a set of thenables.
1248+
// For each thenable, attach a listener so that when it resolves, React
1249+
// attempts to re-render the boundary in the primary (pre-timeout) state.
1250+
const thenables: Set<Thenable> | null = (finishedWork.updateQueue: any);
1251+
if (thenables !== null) {
1252+
finishedWork.updateQueue = null;
1253+
let retryCache = finishedWork.stateNode;
1254+
if (retryCache === null) {
1255+
retryCache = finishedWork.stateNode = new PossiblyWeakSet();
1256+
}
1257+
thenables.forEach(thenable => {
1258+
// Memoize using the boundary fiber to prevent redundant listeners.
1259+
let retry = resolveRetryThenable.bind(null, finishedWork, thenable);
1260+
if (enableSchedulerTracing) {
1261+
retry = Schedule_tracing_wrap(retry);
1262+
}
1263+
if (!retryCache.has(thenable)) {
1264+
retryCache.add(thenable);
1265+
thenable.then(retry, retry);
1266+
}
1267+
});
1268+
}
1269+
}
1270+
12611271
function commitResetTextContent(current: Fiber) {
12621272
if (!supportsMutation) {
12631273
return;

0 commit comments

Comments
 (0)