@samitouri / QOS-React-2 / commits / 8b0be68eee

Progressed work

Sebastian Markbage committed Sep 1, 2016 at 21:04 UTC 8b0be68eee8b3a05c0b4d64e9a01059af7aa8237
7 files changed +213 -142
src/renderers/noop/ReactNoop.js
+6 -10
@@ -170,16 +170,12 @@ var ReactNoop = {
170 ' '.repeat(depth) + '- ' + (fiber.type ? fiber.type.name || fiber.type : '[root]'),
171 '[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']'
172 );
173 - const childInProgress = fiber.childInProgress;
174 - if (childInProgress) {
175 - if (childInProgress === fiber.child) {
176 - console.log(' '.repeat(depth + 1) + 'ERROR: IN PROGRESS == CURRENT');
177 - } else {
178 - console.log(' '.repeat(depth + 1) + 'IN PROGRESS');
179 - logFiber(childInProgress, depth + 1);
180 - if (fiber.child) {
181 - console.log(' '.repeat(depth + 1) + 'CURRENT');
182 - }
173 + const childInProgress = fiber.progressedChild;
174 + if (childInProgress && childInProgress !== fiber.child) {
175 + console.log(' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.progressedPriority);
176 + logFiber(childInProgress, depth + 1);
177 + if (fiber.child) {
178 + console.log(' '.repeat(depth + 1) + 'CURRENT');
179 }
180 }
181 if (fiber.child) {
src/renderers/shared/fiber/ReactChildFiber.js
+4 -7
@@ -67,7 +67,7 @@ function ChildReconciler(shouldClone) {
67 clone.pendingWorkPriority = priority;
68 }
69 clone.pendingProps = element.props;
70 - clone.child = existingChild.child;
70 + // clone.child = existingChild.child;
71 clone.sibling = null;
72 clone.return = returnFiber;
73 previousSibling.sibling = clone;
@@ -139,7 +139,7 @@ function ChildReconciler(shouldClone) {
139 clone.pendingWorkPriority = priority;
140 }
141 clone.pendingProps = element.props;
142 - clone.child = existingChild.child;
142 + // clone.child = existingChild.child;
143 clone.sibling = null;
144 clone.return = returnFiber;
145 return clone;
@@ -246,9 +246,6 @@ exports.cloneChildFibers = function(workInProgress : Fiber) {
246 // If the children of the alternate fiber is a different set, then we don't
247 // need to clone. We need to reset the return fiber though since we'll
248 // traverse down into them.
249 - // TODO: I don't think it is actually possible for them to be anything but
250 - // equal at this point because this fiber was just cloned. Can we skip this
251 - // check? Similar question about the return fiber.
249 let child = workInProgress.child;
250 while (child) {
251 child.return = workInProgress;
@@ -265,7 +262,7 @@ exports.cloneChildFibers = function(workInProgress : Fiber) {
262 // than the next sibling. At that point we should add tests that catches
263 // this.
264
268 - const currentChild = current.child;
265 + const currentChild = workInProgress.child;
266 if (!currentChild) {
267 return;
268 }
@@ -274,4 +271,4 @@ exports.cloneChildFibers = function(workInProgress : Fiber) {
271 currentChild.pendingWorkPriority
272 );
273 cloneSiblings(currentChild, workInProgress.child, workInProgress);
277 -}
274 +};
src/renderers/shared/fiber/ReactFiber.js
+30 -4
@@ -92,6 +92,17 @@ export type Fiber = Instance & {
92 // This will be used to quickly determine if a subtree has no pending changes.
93 pendingWorkPriority: PriorityLevel,
94
95 + // This value represents the priority level that was last used to process this
96 + // component. This indicates whether it is better to continue from the
97 + // progressed work or if it is better to continue from the current state.
98 + progressedPriority: PriorityLevel,
99 +
100 + // If work bails out on a Fiber that already had some work started at a lower
101 + // priority, then we need to store the progressed work somewhere. This holds
102 + // the started child set until we need to get back to working on it. It may
103 + // or may not be the same as the "current" child.
104 + progressedChild: ?Fiber,
105 +
106 // This is a pooled version of a Fiber. Every fiber that gets updated will
107 // eventually have a pair. There are cases when we can clean up pairs to save
108 // memory if we need to.
@@ -147,6 +158,8 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
158 lastEffect: null,
159
160 pendingWorkPriority: NoWork,
161 + progressedPriority: NoWork,
162 + progressedChild: null,
163
164 alternate: null,
165
@@ -158,7 +171,16 @@ function shouldConstruct(Component) {
171 }
172
173 // This is used to create an alternate fiber to do work on.
174 +// TODO: Rename to createWorkInProgressFiber or something like that.
175 exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fiber {
176 + // We clone to get a work in progress. That means that this fiber is the
177 + // current. To make it safe to reuse that fiber later on as work in progress
178 + // we need to reset its work in progress flag now. We don't have an
179 + // opportunity to do this earlier since we don't traverse the tree when
180 + // the work in progress tree becomes the current tree.
181 + // fiber.progressedPriority = NoWork;
182 + // fiber.progressedChild = null;
183 +
184 // We use a double buffering pooling technique because we know that we'll only
185 // ever need at most two versions of a tree. We pool the "other" unused node
186 // that we're free to reuse. This is lazily created to avoid allocating extra
@@ -167,12 +189,12 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
189 let alt = fiber.alternate;
190 if (alt) {
191 alt.stateNode = fiber.stateNode;
170 - alt.child = fiber.child;
171 - alt.sibling = fiber.sibling;
192 + alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
193 alt.ref = fiber.ref;
173 - alt.pendingProps = fiber.pendingProps;
194 + alt.pendingProps = fiber.pendingProps; // TODO: Pass as argument.
195 alt.pendingWorkPriority = priorityLevel;
196
197 + alt.child = fiber.child;
198 alt.memoizedProps = fiber.memoizedProps;
199 alt.output = fiber.output;
200
@@ -190,15 +212,19 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
212 alt.type = fiber.type;
213 alt.stateNode = fiber.stateNode;
214 alt.child = fiber.child;
193 - alt.sibling = fiber.sibling;
215 + alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
216 alt.ref = fiber.ref;
217 // pendingProps is here for symmetry but is unnecessary in practice for now.
218 + // TODO: Pass in the new pendingProps as an argument maybe?
219 alt.pendingProps = fiber.pendingProps;
220 alt.pendingWorkPriority = priorityLevel;
221
222 alt.memoizedProps = fiber.memoizedProps;
223 alt.output = fiber.output;
224
225 + alt.progressedChild = fiber.progressedChild;
226 + alt.progressedPriority = fiber.progressedPriority;
227 +
228 alt.alternate = fiber;
229 fiber.alternate = alt;
230 return alt;
src/renderers/shared/fiber/ReactFiberBeginWork.js
+72 -99
@@ -15,6 +15,7 @@
15 import type { ReactCoroutine } from 'ReactCoroutine';
16 import type { Fiber } from 'ReactFiber';
17 import type { HostConfig } from 'ReactFiberReconciler';
18 +import type { PriorityLevel } from 'ReactPriorityLevel';
19
20 var {
21 reconcileChildFibers,
@@ -39,8 +40,24 @@ var {
40
41 module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
42
43 + function markChildAsProgressed(current, workInProgress, priorityLevel) {
44 + // We now have clones. Let's store them as the currently progressed work.
45 + workInProgress.progressedChild = workInProgress.child;
46 + workInProgress.progressedPriority = priorityLevel;
47 + if (current) {
48 + // We also store it on the current. When the alternate swaps in we can
49 + // continue from this point.
50 + current.progressedChild = workInProgress.progressedChild;
51 + current.progressedPriority = workInProgress.progressedPriority;
52 + }
53 + }
54 +
55 function reconcileChildren(current, workInProgress, nextChildren) {
56 const priorityLevel = workInProgress.pendingWorkPriority;
57 + reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel);
58 + }
59 +
60 + function reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel) {
61 // At this point any memoization is no longer valid since we'll have changed
62 // the children.
63 workInProgress.memoizedProps = null;
@@ -66,6 +83,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
83 priorityLevel
84 );
85 }
86 + markChildAsProgressed(current, workInProgress, priorityLevel);
87 }
88
89 function updateFunctionalComponent(current, workInProgress) {
@@ -94,20 +112,13 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
112 var ctor = workInProgress.type;
113 workInProgress.stateNode = instance = new ctor(props);
114 } else if (typeof instance.shouldComponentUpdate === 'function') {
97 - if (current && current.memoizedProps) {
98 - // Revert to the last flushed props, incase we aborted an update.
99 - instance.props = current.memoizedProps;
100 - if (!instance.shouldComponentUpdate(props)) {
101 - return bailoutOnCurrent(current, workInProgress);
102 - }
103 - }
104 - if (workInProgress.memoizedProps) {
115 + if (workInProgress.memoizedProps !== null) {
116 // Reset the props, in case this is a ping-pong case rather than a
117 // completed update case. For the completed update case, the instance
118 // props will already be the memoizedProps.
119 instance.props = workInProgress.memoizedProps;
120 if (!instance.shouldComponentUpdate(props)) {
110 - // return bailoutOnAlreadyFinishedWork(current, workInProgress);
121 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
122 }
123 }
124 }
@@ -120,14 +131,29 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
131 }
132
133 function updateHostComponent(current, workInProgress) {
134 + const nextChildren = workInProgress.pendingProps.children;
135 if (workInProgress.pendingProps.hidden &&
136 workInProgress.pendingWorkPriority !== OffscreenPriority) {
125 - // If this host component is hidden, we can bail out and ignore this.
126 - // We'll rerender it later at the lower priority.
127 - workInProgress.pendingWorkPriority = OffscreenPriority;
128 - return bailoutOnLowPriority(current, workInProgress);
137 + // If this host component is hidden, we can bail out on the children.
138 + // We'll rerender the children later at the lower priority.
139 +
140 + // It is unfortunate that we have to do the reconciliation of these
141 + // children already since that will add them to the tree even though
142 + // they are not actually done yet. If this is a large set it is also
143 + // confusing that this takes time to do right now instead of later.
144 +
145 + if (workInProgress.progressedPriority === OffscreenPriority) {
146 + // If we already made some progress on the offscreen priority before,
147 + // then we should continue from where we left off.
148 + workInProgress.child = workInProgress.progressedChild;
149 + }
150 +
151 + // Reconcile the children and stash them for later work.
152 + reconcileChildrenAtPriority(current, workInProgress, nextChildren, OffscreenPriority);
153 + workInProgress.child = current ? current.child : null;
154 + // Abort and don't process children yet.
155 + return null;
156 } else {
130 - var nextChildren = workInProgress.pendingProps.children;
157 reconcileChildren(current, workInProgress, nextChildren);
158 return workInProgress.child;
159 }
@@ -163,31 +189,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
189 reconcileChildren(current, workInProgress, coroutine.children);
190 }
191
166 - function reuseChildren(returnFiber : Fiber, firstChild : Fiber) {
167 - // TODO on the TODO: Is this not necessary anymore because I moved the
168 - // priority reset?
169 - // TODO: None of this should be necessary if structured better.
170 - // The returnFiber pointer only needs to be updated when we walk into this child
171 - // which we don't do right now. If the pending work priority indicated only
172 - // if a child has work rather than if the node has work, then we would know
173 - // by a single lookup on workInProgress rather than having to go through
174 - // each child.
175 - let child = firstChild;
176 - do {
177 - // Update the returnFiber of the child to the newest fiber.
178 - child.return = returnFiber;
179 - // Retain the priority if there's any work left to do in the children.
180 - /*if (child.pendingWorkPriority !== NoWork &&
181 - (returnFiber.pendingWorkPriority === NoWork ||
182 - returnFiber.pendingWorkPriority > child.pendingWorkPriority)) {
183 - returnFiber.pendingWorkPriority = child.pendingWorkPriority;
184 - }*/
185 - if (!child.pendingProps && !child.memoizedProps) {
186 - throw new Error('Should have memoized props by now');
187 - }
188 - } while (child = child.sibling);
189 - }
190 -
192 + /*
193 function reuseChildrenEffects(returnFiber : Fiber, firstChild : Fiber) {
194 let child = firstChild;
195 do {
@@ -204,57 +206,29 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
206 }
207 } while (child = child.sibling);
208 }
209 + */
210
208 -/*
209 - function bailoutOnCurrent(current : Fiber, workInProgress : Fiber) : ?Fiber {
210 - // The most likely scenario is that the previous copy of the tree contains
211 - // the same props as the new one. In that case, we can just copy the output
212 - // and children from that node.
213 - workInProgress.memoizedProps = workInProgress.pendingProps;
214 - workInProgress.output = current.output;
211 + function bailoutOnAlreadyFinishedWork(current, workInProgress : Fiber) : ?Fiber {
212 const priorityLevel = workInProgress.pendingWorkPriority;
216 - // workInProgress.pendingProps = null;
217 - workInProgress.stateNode = current.stateNode;
218 -
219 - workInProgress.nextEffect = null;
220 - workInProgress.firstEffect = null;
221 - workInProgress.lastEffect = null;
213
223 - workInProgress.child = current.child;
214 + // TODO: We should ideally be able to bail out early if the children have no
215 + // more work to do. However, since we don't have a separation of this
216 + // Fiber's priority and its children yet - we don't know without doing lots
217 + // of the same work we do anyway. Once we have that separation we can just
218 + // bail out here if the children has no more work at this priority level.
219 + // if (workInProgress.priorityOfChildren <= priorityLevel) {
220 + // // If there are side-effects in these children that have not yet been
221 + // // committed we need to ensure that they get properly transferred up.
222 + // if (current && current.child !== workInProgress.child) {
223 + // reuseChildrenEffects(workInProgress, child);
224 + // }
225 + // return null;
226 + // }
227
228 cloneChildFibers(workInProgress);
226 -
227 - // TODO: Maybe bailout with null if the children priority flag indicate
228 - // that there is no nested work.
229 + markChildAsProgressed(current, workInProgress, priorityLevel);
230 return workInProgress.child;
231 }
231 -*/
232 -
233 - function bailoutOnAlreadyFinishedWork(current, workInProgress : Fiber) : ?Fiber {
234 - // If we started this work before, and finished it, or if we're in a
235 - // ping-pong update scenario, this version could already be what we're
236 - // looking for. In that case, we should be able to just bail out.
237 - const priorityLevel = workInProgress.pendingWorkPriority;
238 - // workInProgress.pendingProps = null;
239 -
240 - workInProgress.firstEffect = null;
241 - workInProgress.nextEffect = null;
242 - workInProgress.lastEffect = null;
243 -
244 - const child = workInProgress.child;
245 - if (child) {
246 - // Ensure that the effects of reused work are preserved.
247 - reuseChildrenEffects(workInProgress, child);
248 - // If we bail out but still has work with the current priority in this
249 - // subtree, we need to go find it right now. If we don't, we won't flush
250 - // it until the next tick.
251 - reuseChildren(workInProgress, child);
252 - // TODO: Maybe bailout with null if the children priority flag indicate
253 - // that there is no nested work.
254 - return workInProgress.child;
255 - }
256 - return null;
257 - }
232
233 function bailoutOnLowPriority(current, workInProgress) {
234 if (current) {
@@ -265,26 +239,22 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
239 return null;
240 }
241
268 - function beginWork(current : ?Fiber, workInProgress : Fiber, priorityLevel) : ?Fiber {
269 - if (!workInProgress.pendingProps) {
270 - throw new Error('should have pending props here');
271 - }
272 -
242 + function beginWork(current : ?Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
243 if (workInProgress.pendingWorkPriority === NoWork ||
244 workInProgress.pendingWorkPriority > priorityLevel) {
245 return bailoutOnLowPriority(current, workInProgress);
246 }
247
278 - // The current, flushed, state of this fiber is the alternate.
279 - // Ideally nothing should rely on this, but relying on it here
280 - // means that we don't need an additional field on the work in
281 - // progress.
282 - if (current && workInProgress.pendingProps === current.memoizedProps) {
283 - return bailoutOnCurrent(current, workInProgress);
248 + if (workInProgress.progressedPriority === priorityLevel) {
249 + // If we have progressed work on this priority level already, we can
250 + // proceed this that as the child.
251 + workInProgress.child = workInProgress.progressedChild;
252 }
253
286 - if (workInProgress.memoizedProps &&
287 - workInProgress.pendingProps === workInProgress.memoizedProps) {
254 + if (workInProgress.pendingProps === null || (
255 + workInProgress.memoizedProps !== null &&
256 + workInProgress.pendingProps === workInProgress.memoizedProps
257 + )) {
258 return bailoutOnAlreadyFinishedWork(current, workInProgress);
259 }
260
@@ -302,7 +272,8 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
272 if (workInProgress.child) {
273 return beginWork(
274 workInProgress.child.alternate,
305 - workInProgress.child
275 + workInProgress.child,
276 + priorityLevel
277 );
278 }
279 return null;
@@ -322,7 +293,8 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
293 if (workInProgress.child) {
294 return beginWork(
295 workInProgress.child.alternate,
325 - workInProgress.child
296 + workInProgress.child,
297 + priorityLevel
298 );
299 }
300 return workInProgress.child;
@@ -332,7 +304,8 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
304 if (workInProgress.sibling) {
305 return beginWork(
306 workInProgress.sibling.alternate,
335 - workInProgress.sibling
307 + workInProgress.sibling,
308 + priorityLevel
309 );
310 }
311 return null;
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+7 -1
@@ -162,10 +162,16 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
162 // This returns true if there was something to update.
163 markForPreEffect(workInProgress);
164 }
165 + // TODO: Is this actually ever going to change? Why set it every time?
166 workInProgress.output = instance;
167 } else {
168 if (!newProps) {
168 - throw new Error('We must have new props for new mounts.');
169 + if (workInProgress.stateNode === null) {
170 + throw new Error('We must have new props for new mounts.');
171 + } else {
172 + // This can happen when we abort work.
173 + return null;
174 + }
175 }
176 const instance = createInstance(workInProgress.type, newProps, children);
177 // TODO: This seems like unnecessary duplication.
src/renderers/shared/fiber/ReactFiberScheduler.js
+15 -21
@@ -25,9 +25,6 @@ var { cloneFiber } = require('ReactFiber');
25
26 var {
27 NoWork,
28 - HighPriority,
29 - LowPriority,
30 - OffscreenPriority,
28 } = require('ReactPriorityLevel');
29
30 var timeHeuristicForUnitOfWork = 1;
@@ -105,7 +102,10 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
102
103 function resetWorkPriority(workInProgress : Fiber) {
104 let newPriority = NoWork;
108 - let child = workInProgress.child;
105 + // progressedChild is going to be the child set with the highest priority.
106 + // Either it is the same as child, or it just bailed out because it choose
107 + // not to do the work.
108 + let child = workInProgress.progressedChild;
109 while (child) {
110 // Ensure that remaining work priority bubbles up.
111 if (child.pendingWorkPriority !== NoWork &&
@@ -125,25 +125,13 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
125 // means that we don't need an additional field on the work in
126 // progress.
127 const current = workInProgress.alternate;
128 - let next = null;
128 + const next = completeWork(current, workInProgress);
129
130 - // If this bailed at a lower priority.
131 - // TODO: This branch is currently needed if a particular type of component
132 - // ends up being a priority lowering. We should probably know that already
133 - // before entering begin work.
134 - if (workInProgress.pendingWorkPriority === NoWork ||
135 - workInProgress.pendingWorkPriority > nextPriorityLevel) {
136 - // This fiber was ignored. We need to fall through to the next fiber
137 - // and leave the pending props and work untouched on this fiber.
138 - } else {
139 - next = completeWork(current, workInProgress);
140 -
141 - resetWorkPriority(workInProgress);
130 + resetWorkPriority(workInProgress);
131
143 - // The work is now done. We don't need this anymore. This flags
144 - // to the system not to redo any work here.
145 - workInProgress.pendingProps = null;
146 - }
132 + // The work is now done. We don't need this anymore. This flags
133 + // to the system not to redo any work here.
134 + workInProgress.pendingProps = null;
135
136 const returnFiber = workInProgress.return;
137
@@ -175,6 +163,12 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
163 } else {
164 // If we're at the root, there's no more work to do. We can flush it.
165 const root : FiberRoot = (workInProgress.stateNode : any);
166 + if (root.current === workInProgress) {
167 + throw new Error(
168 + 'Cannot commit the same tree as before. This is probably a bug ' +
169 + 'related to the return field.'
170 + );
171 + }
172 root.current = workInProgress;
173 // TODO: We can be smarter here and only look for more work in the
174 // "next" scheduled work since we've already scanned passed. That
src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
+79
@@ -270,6 +270,85 @@ describe('ReactIncrementalSideEffects', () => {
270 ]);
271 });
272
273 + it('can defer side-effects and resume them later on', function() {
274 + class Bar extends React.Component {
275 + shouldComponentUpdate(nextProps) {
276 + return this.props.idx !== nextProps;
277 + }
278 + render() {
279 + return <span prop={this.props.idx} />;
280 + }
281 + }
282 + function Foo(props) {
283 + return (
284 + <div>
285 + <span prop={props.tick} />
286 + <div hidden={true}>
287 + <Bar idx={props.idx} />
288 + <Bar idx={props.idx + 1} />
289 + </div>
290 + </div>
291 + );
292 + }
293 + ReactNoop.render(<Foo tick={0} idx={0} />);
294 + ReactNoop.flushLowPri(40 + 25);
295 + expect(ReactNoop.root.children).toEqual([
296 + div(
297 + span(0),
298 + div(/*the spans are down-prioritized and not rendered yet*/)
299 + ),
300 + ]);
301 + ReactNoop.render(<Foo tick={1} idx={0} />);
302 + ReactNoop.flushLowPri(35 + 25);
303 + expect(ReactNoop.root.children).toEqual([
304 + div(
305 + span(1),
306 + div(/*still not rendered yet*/)
307 + ),
308 + ]);
309 + ReactNoop.flushLowPri(30 + 25);
310 + expect(ReactNoop.root.children).toEqual([
311 + div(
312 + span(1),
313 + div(
314 + // Now we had enough time to finish the spans.
315 + span(0),
316 + span(1)
317 + )
318 + ),
319 + ]);
320 + var innerSpanA = ReactNoop.root.children[0].children[1].children[1];
321 + ReactNoop.render(<Foo tick={2} idx={1} />);
322 + ReactNoop.flushLowPri(30 + 25);
323 + expect(ReactNoop.root.children).toEqual([
324 + div(
325 + span(2),
326 + div(
327 + // Still same old numbers.
328 + span(0),
329 + span(1)
330 + )
331 + ),
332 + ]);
333 + ReactNoop.flushLowPri(30);
334 + expect(ReactNoop.root.children).toEqual([
335 + div(
336 + span(2),
337 + div(
338 + // New numbers.
339 + span(1),
340 + span(2)
341 + )
342 + ),
343 + ]);
344 +
345 + var innerSpanB = ReactNoop.root.children[0].children[1].children[1];
346 + // This should have been an update to an existing instance, not recreation.
347 + // We verify that by ensuring that the child instance was the same as
348 + // before.
349 + expect(innerSpanA).toBe(innerSpanB);
350 + });
351 +
352
353 // TODO: Test that side-effects are not cut off when a work in progress node
354 // moves to "current" without flushing due to having lower priority. Does this