Skip to content

Commit

Permalink
Run Placeholder tests in persistent mode, too (#15013)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
acdlite committed Mar 9, 2019
1 parent d0289c7 commit 3f4852f
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 167 deletions.
13 changes: 10 additions & 3 deletions packages/react-native-renderer/src/ReactFabricHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,10 +406,17 @@ export function cloneUnhiddenInstance(
};
}

export function createHiddenTextInstance(
export function cloneHiddenTextInstance(
instance: Instance,
text: string,
internalInstanceHandle: Object,
): TextInstance {
throw new Error('Not yet implemented.');
}

export function cloneUnhiddenTextInstance(
instance: Instance,
text: string,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
throw new Error('Not yet implemented.');
Expand Down
112 changes: 96 additions & 16 deletions packages/react-noop-renderer/src/createReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,18 @@ type Instance = {|
text: string | null,
prop: any,
hidden: boolean,
context: HostContext,
|};
type TextInstance = {|text: string, id: number, hidden: boolean|};
type TextInstance = {|
text: string,
id: number,
hidden: boolean,
context: HostContext,
|};
type HostContext = Object;

const NO_CONTEXT = {};
const UPPERCASE_CONTEXT = {};
const UPDATE_SIGNAL = {};
if (__DEV__) {
Object.freeze(NO_CONTEXT);
Expand Down Expand Up @@ -190,10 +198,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
type: type,
children: keepChildren ? instance.children : [],
text: shouldSetTextContent(type, newProps)
? (newProps.children: any) + ''
? computeText((newProps.children: any) + '', instance.context)
: null,
prop: newProps.prop,
hidden: newProps.hidden === true,
context: instance.context,
};
Object.defineProperty(clone, 'id', {
value: clone.id,
Expand All @@ -203,6 +212,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
value: clone.text,
enumerable: false,
});
Object.defineProperty(clone, 'context', {
value: clone.context,
enumerable: false,
});
hostCloneCounter++;
return clone;
}
Expand All @@ -216,20 +229,36 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
);
}

function computeText(rawText, hostContext) {
return hostContext === UPPERCASE_CONTEXT ? rawText.toUpperCase() : rawText;
}

const sharedHostConfig = {
getRootHostContext() {
return NO_CONTEXT;
},

getChildHostContext() {
getChildHostContext(
parentHostContext: HostContext,
type: string,
rootcontainerInstance: Container,
) {
if (type === 'uppercase') {
return UPPERCASE_CONTEXT;
}
return NO_CONTEXT;
},

getPublicInstance(instance) {
return instance;
},

createInstance(type: string, props: Props): Instance {
createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): Instance {
if (type === 'errorInCompletePhase') {
throw new Error('Error in host config.');
}
Expand All @@ -238,17 +267,22 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
type: type,
children: [],
text: shouldSetTextContent(type, props)
? (props.children: any) + ''
? computeText((props.children: any) + '', hostContext)
: null,
prop: props.prop,
hidden: props.hidden === true,
context: hostContext,
};
// Hide from unit tests
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
Object.defineProperty(inst, 'text', {
value: inst.text,
enumerable: false,
});
Object.defineProperty(inst, 'context', {
value: inst.context,
enumerable: false,
});
return inst;
},

Expand Down Expand Up @@ -298,9 +332,21 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
hostContext: Object,
internalInstanceHandle: Object,
): TextInstance {
const inst = {text: text, id: instanceCounter++, hidden: false};
if (hostContext === UPPERCASE_CONTEXT) {
text = text.toUpperCase();
}
const inst = {
text: text,
id: instanceCounter++,
hidden: false,
context: hostContext,
};
// Hide from unit tests
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
Object.defineProperty(inst, 'context', {
value: inst.context,
enumerable: false,
});
return inst;
},

Expand Down Expand Up @@ -343,7 +389,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
instance.prop = newProps.prop;
instance.hidden = newProps.hidden === true;
if (shouldSetTextContent(type, newProps)) {
instance.text = (newProps.children: any) + '';
instance.text = computeText(
(newProps.children: any) + '',
instance.context,
);
}
},

Expand All @@ -353,7 +402,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
newText: string,
): void {
hostUpdateCounter++;
textInstance.text = newText;
textInstance.text = computeText(newText, textInstance.context);
},

appendChild,
Expand Down Expand Up @@ -453,23 +502,54 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
true,
null,
);
clone.hidden = props.hidden;
clone.hidden = props.hidden === true;
return clone;
},

cloneHiddenTextInstance(
instance: TextInstance,
text: string,
internalInstanceHandle: Object,
): TextInstance {
const clone = {
text: instance.text,
id: instanceCounter++,
hidden: true,
context: instance.context,
};
// Hide from unit tests
Object.defineProperty(clone, 'id', {
value: clone.id,
enumerable: false,
});
Object.defineProperty(clone, 'context', {
value: clone.context,
enumerable: false,
});
return clone;
},

createHiddenTextInstance(
cloneUnhiddenTextInstance(
instance: TextInstance,
text: string,
rootContainerInstance: Container,
hostContext: Object,
internalInstanceHandle: Object,
): TextInstance {
const inst = {text: text, id: instanceCounter++, hidden: true};
const clone = {
text: instance.text,
id: instanceCounter++,
hidden: false,
context: instance.context,
};
// Hide from unit tests
Object.defineProperty(inst, 'id', {
value: inst.id,
Object.defineProperty(clone, 'id', {
value: clone.id,
enumerable: false,
});
Object.defineProperty(clone, 'context', {
value: clone.context,
enumerable: false,
});
return inst;
return clone;
},
};

Expand Down
98 changes: 54 additions & 44 deletions packages/react-reconciler/src/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,13 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
return;
}
case Profiler: {
return;
}
case SuspenseComponent: {
commitSuspenseComponent(finishedWork);
return;
}
}

commitContainer(finishedWork);
Expand Down Expand Up @@ -1199,50 +1206,7 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
return;
}
case SuspenseComponent: {
let newState: SuspenseState | null = finishedWork.memoizedState;

let newDidTimeout;
let primaryChildParent = finishedWork;
if (newState === null) {
newDidTimeout = false;
} else {
newDidTimeout = true;
primaryChildParent = finishedWork.child;
if (newState.timedOutAt === NoWork) {
// If the children had not already timed out, record the time.
// This is used to compute the elapsed time during subsequent
// attempts to render the children.
newState.timedOutAt = requestCurrentTime();
}
}

if (primaryChildParent !== null) {
hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);
}

// If this boundary just timed out, then it will have a set of thenables.
// For each thenable, attach a listener so that when it resolves, React
// attempts to re-render the boundary in the primary (pre-timeout) state.
const thenables: Set<Thenable> | null = (finishedWork.updateQueue: any);
if (thenables !== null) {
finishedWork.updateQueue = null;
let retryCache = finishedWork.stateNode;
if (retryCache === null) {
retryCache = finishedWork.stateNode = new PossiblyWeakSet();
}
thenables.forEach(thenable => {
// Memoize using the boundary fiber to prevent redundant listeners.
let retry = resolveRetryThenable.bind(null, finishedWork, thenable);
if (enableSchedulerTracing) {
retry = Schedule_tracing_wrap(retry);
}
if (!retryCache.has(thenable)) {
retryCache.add(thenable);
thenable.then(retry, retry);
}
});
}

commitSuspenseComponent(finishedWork);
return;
}
case IncompleteClassComponent: {
Expand All @@ -1258,6 +1222,52 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
}
}

function commitSuspenseComponent(finishedWork: Fiber) {
let newState: SuspenseState | null = finishedWork.memoizedState;

let newDidTimeout;
let primaryChildParent = finishedWork;
if (newState === null) {
newDidTimeout = false;
} else {
newDidTimeout = true;
primaryChildParent = finishedWork.child;
if (newState.timedOutAt === NoWork) {
// If the children had not already timed out, record the time.
// This is used to compute the elapsed time during subsequent
// attempts to render the children.
newState.timedOutAt = requestCurrentTime();
}
}

if (supportsMutation && primaryChildParent !== null) {
hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);
}

// If this boundary just timed out, then it will have a set of thenables.
// For each thenable, attach a listener so that when it resolves, React
// attempts to re-render the boundary in the primary (pre-timeout) state.
const thenables: Set<Thenable> | null = (finishedWork.updateQueue: any);
if (thenables !== null) {
finishedWork.updateQueue = null;
let retryCache = finishedWork.stateNode;
if (retryCache === null) {
retryCache = finishedWork.stateNode = new PossiblyWeakSet();
}
thenables.forEach(thenable => {
// Memoize using the boundary fiber to prevent redundant listeners.
let retry = resolveRetryThenable.bind(null, finishedWork, thenable);
if (enableSchedulerTracing) {
retry = Schedule_tracing_wrap(retry);
}
if (!retryCache.has(thenable)) {
retryCache.add(thenable);
thenable.then(retry, retry);
}
});
}
}

function commitResetTextContent(current: Fiber) {
if (!supportsMutation) {
return;
Expand Down
Loading

0 comments on commit 3f4852f

Please sign in to comment.