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