@samitouri / QOS-React / commits / 0470854f55

Split ReactNoop into normal and persistent exports (#12793)

* Copy-paste ReactNoop into ReactNoopPersistent * Split ReactNoop into normal and persistent exports * ReactNoopShared -> createReactNoop

Dan Abramov committed May 14, 2018 at 13:57 UTC 0470854f5522dde521d46049bcac894b2e86d280
8 files changed +671 -590
packages/react-noop-renderer/npm/persistent.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-noop-renderer-persistent.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-noop-renderer-persistent.development.js');
7 +}
packages/react-noop-renderer/package.json
+1
@@ -20,6 +20,7 @@
20 "LICENSE",
21 "README.md",
22 "index.js",
23 + "persistent.js",
24 "cjs/"
25 ]
26 }
packages/react-noop-renderer/persistent.js new
+18
@@ -0,0 +1,18 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +'use strict';
11 +
12 +const ReactNoopPersistent = require('./src/ReactNoopPersistent');
13 +
14 +// TODO: decide on the top-level export form.
15 +// This is hacky but makes it work with both Rollup and Jest.
16 +module.exports = ReactNoopPersistent.default
17 + ? ReactNoopPersistent.default
18 + : ReactNoopPersistent;
packages/react-noop-renderer/src/ReactNoop.js
+4 -575
@@ -14,581 +14,10 @@
14 * environment.
15 */
16
17 -import type {Fiber} from 'react-reconciler/src/ReactFiber';
18 -import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
19 -import type {ReactNodeList} from 'shared/ReactTypes';
20 -import ReactFiberReconciler from 'react-reconciler';
21 -import {enablePersistentReconciler} from 'shared/ReactFeatureFlags';
22 -import * as ReactPortal from 'shared/ReactPortal';
23 -import emptyObject from 'fbjs/lib/emptyObject';
24 -import expect from 'expect';
17 +import createReactNoop from './createReactNoop';
18
26 -const UPDATE_SIGNAL = {};
27 -
28 -let scheduledCallback = null;
29 -
30 -type Container = {rootID: string, children: Array<Instance | TextInstance>};
31 -type Props = {prop: any, hidden?: boolean};
32 -type Instance = {|
33 - type: string,
34 - id: number,
35 - children: Array<Instance | TextInstance>,
36 - prop: any,
37 -|};
38 -type TextInstance = {|text: string, id: number|};
39 -
40 -let instanceCounter = 0;
41 -let failInBeginPhase = false;
42 -
43 -function appendChild(
44 - parentInstance: Instance | Container,
45 - child: Instance | TextInstance,
46 -): void {
47 - const index = parentInstance.children.indexOf(child);
48 - if (index !== -1) {
49 - parentInstance.children.splice(index, 1);
50 - }
51 - parentInstance.children.push(child);
52 -}
53 -
54 -function insertBefore(
55 - parentInstance: Instance | Container,
56 - child: Instance | TextInstance,
57 - beforeChild: Instance | TextInstance,
58 -): void {
59 - const index = parentInstance.children.indexOf(child);
60 - if (index !== -1) {
61 - parentInstance.children.splice(index, 1);
62 - }
63 - const beforeIndex = parentInstance.children.indexOf(beforeChild);
64 - if (beforeIndex === -1) {
65 - throw new Error('This child does not exist.');
66 - }
67 - parentInstance.children.splice(beforeIndex, 0, child);
68 -}
69 -
70 -function removeChild(
71 - parentInstance: Instance | Container,
72 - child: Instance | TextInstance,
73 -): void {
74 - const index = parentInstance.children.indexOf(child);
75 - if (index === -1) {
76 - throw new Error('This child does not exist.');
77 - }
78 - parentInstance.children.splice(index, 1);
79 -}
80 -
81 -let elapsedTimeInMs = 0;
82 -
83 -let SharedHostConfig = {
84 - getRootHostContext() {
85 - if (failInBeginPhase) {
86 - throw new Error('Error in host config.');
87 - }
88 - return emptyObject;
89 - },
90 -
91 - getChildHostContext() {
92 - return emptyObject;
93 - },
94 -
95 - getPublicInstance(instance) {
96 - return instance;
97 - },
98 -
99 - createInstance(type: string, props: Props): Instance {
100 - const inst = {
101 - id: instanceCounter++,
102 - type: type,
103 - children: [],
104 - prop: props.prop,
105 - };
106 - // Hide from unit tests
107 - Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
108 - return inst;
109 - },
110 -
111 - appendInitialChild(
112 - parentInstance: Instance,
113 - child: Instance | TextInstance,
114 - ): void {
115 - parentInstance.children.push(child);
116 - },
117 -
118 - finalizeInitialChildren(
119 - domElement: Instance,
120 - type: string,
121 - props: Props,
122 - ): boolean {
123 - return false;
124 - },
125 -
126 - prepareUpdate(
127 - instance: Instance,
128 - type: string,
129 - oldProps: Props,
130 - newProps: Props,
131 - ): null | {} {
132 - if (oldProps === null) {
133 - throw new Error('Should have old props');
134 - }
135 - if (newProps === null) {
136 - throw new Error('Should have new props');
137 - }
138 - return UPDATE_SIGNAL;
139 - },
140 -
141 - shouldSetTextContent(type: string, props: Props): boolean {
142 - return (
143 - typeof props.children === 'string' || typeof props.children === 'number'
144 - );
145 - },
146 -
147 - shouldDeprioritizeSubtree(type: string, props: Props): boolean {
148 - return !!props.hidden;
149 - },
150 -
151 - createTextInstance(
152 - text: string,
153 - rootContainerInstance: Container,
154 - hostContext: Object,
155 - internalInstanceHandle: Object,
156 - ): TextInstance {
157 - const inst = {text: text, id: instanceCounter++};
158 - // Hide from unit tests
159 - Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
160 - return inst;
161 - },
162 -
163 - scheduleDeferredCallback(callback) {
164 - if (scheduledCallback) {
165 - throw new Error(
166 - 'Scheduling a callback twice is excessive. Instead, keep track of ' +
167 - 'whether the callback has already been scheduled.',
168 - );
169 - }
170 - scheduledCallback = callback;
171 - return 0;
172 - },
173 -
174 - cancelDeferredCallback() {
175 - if (scheduledCallback === null) {
176 - throw new Error('No callback is scheduled.');
177 - }
178 - scheduledCallback = null;
179 - },
180 -
181 - prepareForCommit(): void {},
182 -
183 - resetAfterCommit(): void {},
184 -
185 - now(): number {
186 - return elapsedTimeInMs;
187 - },
188 -
189 - isPrimaryRenderer: true,
190 -};
191 -
192 -const NoopRenderer = ReactFiberReconciler({
193 - ...SharedHostConfig,
194 - mutation: {
195 - commitMount(instance: Instance, type: string, newProps: Props): void {
196 - // Noop
197 - },
198 -
199 - commitUpdate(
200 - instance: Instance,
201 - updatePayload: Object,
202 - type: string,
203 - oldProps: Props,
204 - newProps: Props,
205 - ): void {
206 - if (oldProps === null) {
207 - throw new Error('Should have old props');
208 - }
209 - instance.prop = newProps.prop;
210 - },
211 -
212 - commitTextUpdate(
213 - textInstance: TextInstance,
214 - oldText: string,
215 - newText: string,
216 - ): void {
217 - textInstance.text = newText;
218 - },
219 -
220 - appendChild: appendChild,
221 - appendChildToContainer: appendChild,
222 - insertBefore: insertBefore,
223 - insertInContainerBefore: insertBefore,
224 - removeChild: removeChild,
225 - removeChildFromContainer: removeChild,
226 -
227 - resetTextContent(instance: Instance): void {},
228 - },
229 -});
230 -
231 -const PersistentNoopRenderer = enablePersistentReconciler
232 - ? ReactFiberReconciler({
233 - ...SharedHostConfig,
234 - persistence: {
235 - cloneInstance(
236 - instance: Instance,
237 - updatePayload: null | Object,
238 - type: string,
239 - oldProps: Props,
240 - newProps: Props,
241 - internalInstanceHandle: Object,
242 - keepChildren: boolean,
243 - recyclableInstance: null | Instance,
244 - ): Instance {
245 - const clone = {
246 - id: instance.id,
247 - type: type,
248 - children: keepChildren ? instance.children : [],
249 - prop: newProps.prop,
250 - };
251 - Object.defineProperty(clone, 'id', {
252 - value: clone.id,
253 - enumerable: false,
254 - });
255 - return clone;
256 - },
257 -
258 - createContainerChildSet(
259 - container: Container,
260 - ): Array<Instance | TextInstance> {
261 - return [];
262 - },
263 -
264 - appendChildToContainerChildSet(
265 - childSet: Array<Instance | TextInstance>,
266 - child: Instance | TextInstance,
267 - ): void {
268 - childSet.push(child);
269 - },
270 -
271 - finalizeContainerChildren(
272 - container: Container,
273 - newChildren: Array<Instance | TextInstance>,
274 - ): void {},
275 -
276 - replaceContainerChildren(
277 - container: Container,
278 - newChildren: Array<Instance | TextInstance>,
279 - ): void {
280 - container.children = newChildren;
281 - },
282 - },
283 - })
284 - : null;
285 -
286 -const rootContainers = new Map();
287 -const roots = new Map();
288 -const persistentRoots = new Map();
289 -const DEFAULT_ROOT_ID = '<default>';
290 -
291 -let yieldedValues = null;
292 -
293 -let unitsRemaining;
294 -
295 -function* flushUnitsOfWork(n: number): Generator<Array<mixed>, void, void> {
296 - let didStop = false;
297 - while (!didStop && scheduledCallback !== null) {
298 - let cb = scheduledCallback;
299 - scheduledCallback = null;
300 - unitsRemaining = n;
301 - cb({
302 - timeRemaining() {
303 - if (yieldedValues !== null) {
304 - return 0;
305 - }
306 - if (unitsRemaining-- > 0) {
307 - return 999;
308 - }
309 - didStop = true;
310 - return 0;
311 - },
312 - // React's scheduler has its own way of keeping track of expired
313 - // work and doesn't read this, so don't bother setting it to the
314 - // correct value.
315 - didTimeout: false,
316 - });
317 -
318 - if (yieldedValues !== null) {
319 - const values = yieldedValues;
320 - yieldedValues = null;
321 - yield values;
322 - }
323 - }
324 -}
325 -
326 -const ReactNoop = {
327 - getChildren(rootID: string = DEFAULT_ROOT_ID) {
328 - const container = rootContainers.get(rootID);
329 - if (container) {
330 - return container.children;
331 - } else {
332 - return null;
333 - }
334 - },
335 -
336 - createPortal(
337 - children: ReactNodeList,
338 - container: Container,
339 - key: ?string = null,
340 - ) {
341 - return ReactPortal.createPortal(children, container, null, key);
342 - },
343 -
344 - // Shortcut for testing a single root
345 - render(element: React$Element<any>, callback: ?Function) {
346 - ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback);
347 - },
348 -
349 - renderToRootWithID(
350 - element: React$Element<any>,
351 - rootID: string,
352 - callback: ?Function,
353 - ) {
354 - let root = roots.get(rootID);
355 - if (!root) {
356 - const container = {rootID: rootID, children: []};
357 - rootContainers.set(rootID, container);
358 - root = NoopRenderer.createContainer(container, true, false);
359 - roots.set(rootID, root);
360 - }
361 - NoopRenderer.updateContainer(element, root, null, callback);
362 - },
363 -
364 - renderToPersistentRootWithID(
365 - element: React$Element<any>,
366 - rootID: string,
367 - callback: ?Function,
368 - ) {
369 - if (PersistentNoopRenderer === null) {
370 - throw new Error(
371 - 'Enable ReactFeatureFlags.enablePersistentReconciler to use it in tests.',
372 - );
373 - }
374 - let root = persistentRoots.get(rootID);
375 - if (!root) {
376 - const container = {rootID: rootID, children: []};
377 - rootContainers.set(rootID, container);
378 - root = PersistentNoopRenderer.createContainer(container, true, false);
379 - persistentRoots.set(rootID, root);
380 - }
381 - PersistentNoopRenderer.updateContainer(element, root, null, callback);
382 - },
383 -
384 - unmountRootWithID(rootID: string) {
385 - const root = roots.get(rootID);
386 - if (root) {
387 - NoopRenderer.updateContainer(null, root, null, () => {
388 - roots.delete(rootID);
389 - rootContainers.delete(rootID);
390 - });
391 - }
392 - },
393 -
394 - findInstance(
395 - componentOrElement: Element | ?React$Component<any, any>,
396 - ): null | Instance | TextInstance {
397 - if (componentOrElement == null) {
398 - return null;
399 - }
400 - // Unsound duck typing.
401 - const component = (componentOrElement: any);
402 - if (typeof component.id === 'number') {
403 - return component;
404 - }
405 - return NoopRenderer.findHostInstance(component);
406 - },
407 -
408 - flushDeferredPri(timeout: number = Infinity): Array<mixed> {
409 - // The legacy version of this function decremented the timeout before
410 - // returning the new time.
411 - // TODO: Convert tests to use flushUnitsOfWork or flushAndYield instead.
412 - const n = timeout / 5 - 1;
413 -
414 - let values = [];
415 - // eslint-disable-next-line no-for-of-loops/no-for-of-loops
416 - for (const value of flushUnitsOfWork(n)) {
417 - values.push(...value);
418 - }
419 - return values;
420 - },
421 -
422 - flush(): Array<mixed> {
423 - return ReactNoop.flushUnitsOfWork(Infinity);
424 - },
425 -
426 - flushAndYield(
427 - unitsOfWork: number = Infinity,
428 - ): Generator<Array<mixed>, void, void> {
429 - return flushUnitsOfWork(unitsOfWork);
430 - },
431 -
432 - flushUnitsOfWork(n: number): Array<mixed> {
433 - let values = yieldedValues || [];
434 - yieldedValues = null;
435 - // eslint-disable-next-line no-for-of-loops/no-for-of-loops
436 - for (const value of flushUnitsOfWork(n)) {
437 - values.push(...value);
438 - }
439 - return values;
440 - },
441 -
442 - flushThrough(expected: Array<mixed>): void {
443 - let actual = [];
444 - if (expected.length !== 0) {
445 - // eslint-disable-next-line no-for-of-loops/no-for-of-loops
446 - for (const value of flushUnitsOfWork(Infinity)) {
447 - actual.push(...value);
448 - if (actual.length >= expected.length) {
449 - break;
450 - }
451 - }
452 - }
453 - expect(actual).toEqual(expected);
454 - },
455 -
456 - expire(ms: number): void {
457 - elapsedTimeInMs += ms;
458 - },
459 -
460 - flushExpired(): Array<mixed> {
461 - return ReactNoop.flushUnitsOfWork(0);
462 - },
463 -
464 - yield(value: mixed) {
465 - if (yieldedValues === null) {
466 - yieldedValues = [value];
467 - } else {
468 - yieldedValues.push(value);
469 - }
470 - },
471 -
472 - clearYields() {
473 - const values = yieldedValues;
474 - yieldedValues = null;
475 - return values;
476 - },
477 -
478 - hasScheduledCallback() {
479 - return !!scheduledCallback;
480 - },
481 -
482 - batchedUpdates: NoopRenderer.batchedUpdates,
483 -
484 - deferredUpdates: NoopRenderer.deferredUpdates,
485 -
486 - unbatchedUpdates: NoopRenderer.unbatchedUpdates,
487 -
488 - interactiveUpdates: NoopRenderer.interactiveUpdates,
489 -
490 - flushSync(fn: () => mixed) {
491 - yieldedValues = [];
492 - NoopRenderer.flushSync(fn);
493 - return yieldedValues;
494 - },
495 -
496 - // Logs the current state of the tree.
497 - dumpTree(rootID: string = DEFAULT_ROOT_ID) {
498 - const root = roots.get(rootID);
499 - const rootContainer = rootContainers.get(rootID);
500 - if (!root || !rootContainer) {
501 - console.log('Nothing rendered yet.');
502 - return;
503 - }
504 -
505 - let bufferedLog = [];
506 - function log(...args) {
507 - bufferedLog.push(...args, '\n');
508 - }
509 -
510 - function logHostInstances(children: Array<Instance | TextInstance>, depth) {
511 - for (let i = 0; i < children.length; i++) {
512 - const child = children[i];
513 - const indent = ' '.repeat(depth);
514 - if (typeof child.text === 'string') {
515 - log(indent + '- ' + child.text);
516 - } else {
517 - // $FlowFixMe - The child should've been refined now.
518 - log(indent + '- ' + child.type + '#' + child.id);
519 - // $FlowFixMe - The child should've been refined now.
520 - logHostInstances(child.children, depth + 1);
521 - }
522 - }
523 - }
524 - function logContainer(container: Container, depth) {
525 - log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
526 - logHostInstances(container.children, depth + 1);
527 - }
528 -
529 - function logUpdateQueue(updateQueue: UpdateQueue<mixed>, depth) {
530 - log(' '.repeat(depth + 1) + 'QUEUED UPDATES');
531 - const firstUpdate = updateQueue.firstUpdate;
532 - if (!firstUpdate) {
533 - return;
534 - }
535 -
536 - log(' '.repeat(depth + 1) + '~', '[' + firstUpdate.expirationTime + ']');
537 - while (firstUpdate.next) {
538 - log(
539 - ' '.repeat(depth + 1) + '~',
540 - '[' + firstUpdate.expirationTime + ']',
541 - );
542 - }
543 - }
544 -
545 - function logFiber(fiber: Fiber, depth) {
546 - log(
547 - ' '.repeat(depth) +
548 - '- ' +
549 - // need to explicitly coerce Symbol to a string
550 - (fiber.type ? fiber.type.name || fiber.type.toString() : '[root]'),
551 - '[' + fiber.expirationTime + (fiber.pendingProps ? '*' : '') + ']',
552 - );
553 - if (fiber.updateQueue) {
554 - logUpdateQueue(fiber.updateQueue, depth);
555 - }
556 - // const childInProgress = fiber.progressedChild;
557 - // if (childInProgress && childInProgress !== fiber.child) {
558 - // log(
559 - // ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority,
560 - // );
561 - // logFiber(childInProgress, depth + 1);
562 - // if (fiber.child) {
563 - // log(' '.repeat(depth + 1) + 'CURRENT');
564 - // }
565 - // } else if (fiber.child && fiber.updateQueue) {
566 - // log(' '.repeat(depth + 1) + 'CHILDREN');
567 - // }
568 - if (fiber.child) {
569 - logFiber(fiber.child, depth + 1);
570 - }
571 - if (fiber.sibling) {
572 - logFiber(fiber.sibling, depth);
573 - }
574 - }
575 -
576 - log('HOST INSTANCES:');
577 - logContainer(rootContainer, 0);
578 - log('FIBERS:');
579 - logFiber(root.current, 0);
580 -
581 - console.log(...bufferedLog);
582 - },
583 -
584 - simulateErrorInHostConfig(fn: () => void) {
585 - failInBeginPhase = true;
586 - try {
587 - fn();
588 - } finally {
589 - failInBeginPhase = false;
590 - }
591 - },
592 -};
19 +const ReactNoop = createReactNoop(
20 + true, // useMutation
21 +);
22
23 export default ReactNoop;
packages/react-noop-renderer/src/ReactNoopPersistent.js new
+23
@@ -0,0 +1,23 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +/**
11 + * This is a renderer of React that doesn't have a render target output.
12 + * It is useful to demonstrate the internals of the reconciler in isolation
13 + * and for testing semantics of reconciliation separate from the host
14 + * environment.
15 + */
16 +
17 +import createReactNoop from './createReactNoop';
18 +
19 +const ReactNoopPersistent = createReactNoop(
20 + false, // useMutation
21 +);
22 +
23 +export default ReactNoopPersistent;
packages/react-noop-renderer/src/createReactNoop.js new
+582
@@ -0,0 +1,582 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +/**
11 + * This is a renderer of React that doesn't have a render target output.
12 + * It is useful to demonstrate the internals of the reconciler in isolation
13 + * and for testing semantics of reconciliation separate from the host
14 + * environment.
15 + */
16 +
17 +import type {Fiber} from 'react-reconciler/src/ReactFiber';
18 +import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
19 +import type {ReactNodeList} from 'shared/ReactTypes';
20 +
21 +import ReactFiberReconciler from 'react-reconciler';
22 +import * as ReactPortal from 'shared/ReactPortal';
23 +import emptyObject from 'fbjs/lib/emptyObject';
24 +import expect from 'expect';
25 +
26 +type Container = {rootID: string, children: Array<Instance | TextInstance>};
27 +type Props = {prop: any, hidden?: boolean};
28 +type Instance = {|
29 + type: string,
30 + id: number,
31 + children: Array<Instance | TextInstance>,
32 + prop: any,
33 +|};
34 +type TextInstance = {|text: string, id: number|};
35 +
36 +function createReactNoop(useMutation: boolean) {
37 + const UPDATE_SIGNAL = {};
38 + let scheduledCallback = null;
39 +
40 + let instanceCounter = 0;
41 + let failInBeginPhase = false;
42 +
43 + function appendChild(
44 + parentInstance: Instance | Container,
45 + child: Instance | TextInstance,
46 + ): void {
47 + const index = parentInstance.children.indexOf(child);
48 + if (index !== -1) {
49 + parentInstance.children.splice(index, 1);
50 + }
51 + parentInstance.children.push(child);
52 + }
53 +
54 + function insertBefore(
55 + parentInstance: Instance | Container,
56 + child: Instance | TextInstance,
57 + beforeChild: Instance | TextInstance,
58 + ): void {
59 + const index = parentInstance.children.indexOf(child);
60 + if (index !== -1) {
61 + parentInstance.children.splice(index, 1);
62 + }
63 + const beforeIndex = parentInstance.children.indexOf(beforeChild);
64 + if (beforeIndex === -1) {
65 + throw new Error('This child does not exist.');
66 + }
67 + parentInstance.children.splice(beforeIndex, 0, child);
68 + }
69 +
70 + function removeChild(
71 + parentInstance: Instance | Container,
72 + child: Instance | TextInstance,
73 + ): void {
74 + const index = parentInstance.children.indexOf(child);
75 + if (index === -1) {
76 + throw new Error('This child does not exist.');
77 + }
78 + parentInstance.children.splice(index, 1);
79 + }
80 +
81 + let elapsedTimeInMs = 0;
82 +
83 + const sharedHostConfig = {
84 + getRootHostContext() {
85 + if (failInBeginPhase) {
86 + throw new Error('Error in host config.');
87 + }
88 + return emptyObject;
89 + },
90 +
91 + getChildHostContext() {
92 + return emptyObject;
93 + },
94 +
95 + getPublicInstance(instance) {
96 + return instance;
97 + },
98 +
99 + createInstance(type: string, props: Props): Instance {
100 + const inst = {
101 + id: instanceCounter++,
102 + type: type,
103 + children: [],
104 + prop: props.prop,
105 + };
106 + // Hide from unit tests
107 + Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
108 + return inst;
109 + },
110 +
111 + appendInitialChild(
112 + parentInstance: Instance,
113 + child: Instance | TextInstance,
114 + ): void {
115 + parentInstance.children.push(child);
116 + },
117 +
118 + finalizeInitialChildren(
119 + domElement: Instance,
120 + type: string,
121 + props: Props,
122 + ): boolean {
123 + return false;
124 + },
125 +
126 + prepareUpdate(
127 + instance: Instance,
128 + type: string,
129 + oldProps: Props,
130 + newProps: Props,
131 + ): null | {} {
132 + if (oldProps === null) {
133 + throw new Error('Should have old props');
134 + }
135 + if (newProps === null) {
136 + throw new Error('Should have new props');
137 + }
138 + return UPDATE_SIGNAL;
139 + },
140 +
141 + shouldSetTextContent(type: string, props: Props): boolean {
142 + return (
143 + typeof props.children === 'string' || typeof props.children === 'number'
144 + );
145 + },
146 +
147 + shouldDeprioritizeSubtree(type: string, props: Props): boolean {
148 + return !!props.hidden;
149 + },
150 +
151 + createTextInstance(
152 + text: string,
153 + rootContainerInstance: Container,
154 + hostContext: Object,
155 + internalInstanceHandle: Object,
156 + ): TextInstance {
157 + const inst = {text: text, id: instanceCounter++};
158 + // Hide from unit tests
159 + Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
160 + return inst;
161 + },
162 +
163 + scheduleDeferredCallback(callback) {
164 + if (scheduledCallback) {
165 + throw new Error(
166 + 'Scheduling a callback twice is excessive. Instead, keep track of ' +
167 + 'whether the callback has already been scheduled.',
168 + );
169 + }
170 + scheduledCallback = callback;
171 + return 0;
172 + },
173 +
174 + cancelDeferredCallback() {
175 + if (scheduledCallback === null) {
176 + throw new Error('No callback is scheduled.');
177 + }
178 + scheduledCallback = null;
179 + },
180 +
181 + prepareForCommit(): void {},
182 +
183 + resetAfterCommit(): void {},
184 +
185 + now(): number {
186 + return elapsedTimeInMs;
187 + },
188 +
189 + isPrimaryRenderer: true,
190 + };
191 +
192 + const hostConfig = useMutation
193 + ? {
194 + ...sharedHostConfig,
195 + mutation: {
196 + commitMount(instance: Instance, type: string, newProps: Props): void {
197 + // Noop
198 + },
199 +
200 + commitUpdate(
201 + instance: Instance,
202 + updatePayload: Object,
203 + type: string,
204 + oldProps: Props,
205 + newProps: Props,
206 + ): void {
207 + if (oldProps === null) {
208 + throw new Error('Should have old props');
209 + }
210 + instance.prop = newProps.prop;
211 + },
212 +
213 + commitTextUpdate(
214 + textInstance: TextInstance,
215 + oldText: string,
216 + newText: string,
217 + ): void {
218 + textInstance.text = newText;
219 + },
220 +
221 + appendChild: appendChild,
222 + appendChildToContainer: appendChild,
223 + insertBefore: insertBefore,
224 + insertInContainerBefore: insertBefore,
225 + removeChild: removeChild,
226 + removeChildFromContainer: removeChild,
227 +
228 + resetTextContent(instance: Instance): void {},
229 + },
230 + }
231 + : {
232 + ...sharedHostConfig,
233 + persistence: {
234 + cloneInstance(
235 + instance: Instance,
236 + updatePayload: null | Object,
237 + type: string,
238 + oldProps: Props,
239 + newProps: Props,
240 + internalInstanceHandle: Object,
241 + keepChildren: boolean,
242 + recyclableInstance: null | Instance,
243 + ): Instance {
244 + const clone = {
245 + id: instance.id,
246 + type: type,
247 + children: keepChildren ? instance.children : [],
248 + prop: newProps.prop,
249 + };
250 + Object.defineProperty(clone, 'id', {
251 + value: clone.id,
252 + enumerable: false,
253 + });
254 + return clone;
255 + },
256 +
257 + createContainerChildSet(
258 + container: Container,
259 + ): Array<Instance | TextInstance> {
260 + return [];
261 + },
262 +
263 + appendChildToContainerChildSet(
264 + childSet: Array<Instance | TextInstance>,
265 + child: Instance | TextInstance,
266 + ): void {
267 + childSet.push(child);
268 + },
269 +
270 + finalizeContainerChildren(
271 + container: Container,
272 + newChildren: Array<Instance | TextInstance>,
273 + ): void {},
274 +
275 + replaceContainerChildren(
276 + container: Container,
277 + newChildren: Array<Instance | TextInstance>,
278 + ): void {
279 + container.children = newChildren;
280 + },
281 + },
282 + };
283 +
284 + const NoopRenderer = ReactFiberReconciler(hostConfig);
285 +
286 + const rootContainers = new Map();
287 + const roots = new Map();
288 + const DEFAULT_ROOT_ID = '<default>';
289 +
290 + let yieldedValues = null;
291 +
292 + let unitsRemaining;
293 +
294 + function* flushUnitsOfWork(n: number): Generator<Array<mixed>, void, void> {
295 + let didStop = false;
296 + while (!didStop && scheduledCallback !== null) {
297 + let cb = scheduledCallback;
298 + scheduledCallback = null;
299 + unitsRemaining = n;
300 + cb({
301 + timeRemaining() {
302 + if (yieldedValues !== null) {
303 + return 0;
304 + }
305 + if (unitsRemaining-- > 0) {
306 + return 999;
307 + }
308 + didStop = true;
309 + return 0;
310 + },
311 + // React's scheduler has its own way of keeping track of expired
312 + // work and doesn't read this, so don't bother setting it to the
313 + // correct value.
314 + didTimeout: false,
315 + });
316 +
317 + if (yieldedValues !== null) {
318 + const values = yieldedValues;
319 + yieldedValues = null;
320 + yield values;
321 + }
322 + }
323 + }
324 +
325 + const ReactNoop = {
326 + getChildren(rootID: string = DEFAULT_ROOT_ID) {
327 + const container = rootContainers.get(rootID);
328 + if (container) {
329 + return container.children;
330 + } else {
331 + return null;
332 + }
333 + },
334 +
335 + createPortal(
336 + children: ReactNodeList,
337 + container: Container,
338 + key: ?string = null,
339 + ) {
340 + return ReactPortal.createPortal(children, container, null, key);
341 + },
342 +
343 + // Shortcut for testing a single root
344 + render(element: React$Element<any>, callback: ?Function) {
345 + ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback);
346 + },
347 +
348 + renderToRootWithID(
349 + element: React$Element<any>,
350 + rootID: string,
351 + callback: ?Function,
352 + ) {
353 + let root = roots.get(rootID);
354 + if (!root) {
355 + const container = {rootID: rootID, children: []};
356 + rootContainers.set(rootID, container);
357 + root = NoopRenderer.createContainer(container, true, false);
358 + roots.set(rootID, root);
359 + }
360 + NoopRenderer.updateContainer(element, root, null, callback);
361 + },
362 +
363 + unmountRootWithID(rootID: string) {
364 + const root = roots.get(rootID);
365 + if (root) {
366 + NoopRenderer.updateContainer(null, root, null, () => {
367 + roots.delete(rootID);
368 + rootContainers.delete(rootID);
369 + });
370 + }
371 + },
372 +
373 + findInstance(
374 + componentOrElement: Element | ?React$Component<any, any>,
375 + ): null | Instance | TextInstance {
376 + if (componentOrElement == null) {
377 + return null;
378 + }
379 + // Unsound duck typing.
380 + const component = (componentOrElement: any);
381 + if (typeof component.id === 'number') {
382 + return component;
383 + }
384 + return NoopRenderer.findHostInstance(component);
385 + },
386 +
387 + flushDeferredPri(timeout: number = Infinity): Array<mixed> {
388 + // The legacy version of this function decremented the timeout before
389 + // returning the new time.
390 + // TODO: Convert tests to use flushUnitsOfWork or flushAndYield instead.
391 + const n = timeout / 5 - 1;
392 +
393 + let values = [];
394 + // eslint-disable-next-line no-for-of-loops/no-for-of-loops
395 + for (const value of flushUnitsOfWork(n)) {
396 + values.push(...value);
397 + }
398 + return values;
399 + },
400 +
401 + flush(): Array<mixed> {
402 + return ReactNoop.flushUnitsOfWork(Infinity);
403 + },
404 +
405 + flushAndYield(
406 + unitsOfWork: number = Infinity,
407 + ): Generator<Array<mixed>, void, void> {
408 + return flushUnitsOfWork(unitsOfWork);
409 + },
410 +
411 + flushUnitsOfWork(n: number): Array<mixed> {
412 + let values = yieldedValues || [];
413 + yieldedValues = null;
414 + // eslint-disable-next-line no-for-of-loops/no-for-of-loops
415 + for (const value of flushUnitsOfWork(n)) {
416 + values.push(...value);
417 + }
418 + return values;
419 + },
420 +
421 + flushThrough(expected: Array<mixed>): void {
422 + let actual = [];
423 + if (expected.length !== 0) {
424 + // eslint-disable-next-line no-for-of-loops/no-for-of-loops
425 + for (const value of flushUnitsOfWork(Infinity)) {
426 + actual.push(...value);
427 + if (actual.length >= expected.length) {
428 + break;
429 + }
430 + }
431 + }
432 + expect(actual).toEqual(expected);
433 + },
434 +
435 + expire(ms: number): void {
436 + elapsedTimeInMs += ms;
437 + },
438 +
439 + flushExpired(): Array<mixed> {
440 + return ReactNoop.flushUnitsOfWork(0);
441 + },
442 +
443 + yield(value: mixed) {
444 + if (yieldedValues === null) {
445 + yieldedValues = [value];
446 + } else {
447 + yieldedValues.push(value);
448 + }
449 + },
450 +
451 + clearYields() {
452 + const values = yieldedValues;
453 + yieldedValues = null;
454 + return values;
455 + },
456 +
457 + hasScheduledCallback() {
458 + return !!scheduledCallback;
459 + },
460 +
461 + batchedUpdates: NoopRenderer.batchedUpdates,
462 +
463 + deferredUpdates: NoopRenderer.deferredUpdates,
464 +
465 + unbatchedUpdates: NoopRenderer.unbatchedUpdates,
466 +
467 + interactiveUpdates: NoopRenderer.interactiveUpdates,
468 +
469 + flushSync(fn: () => mixed) {
470 + yieldedValues = [];
471 + NoopRenderer.flushSync(fn);
472 + return yieldedValues;
473 + },
474 +
475 + // Logs the current state of the tree.
476 + dumpTree(rootID: string = DEFAULT_ROOT_ID) {
477 + const root = roots.get(rootID);
478 + const rootContainer = rootContainers.get(rootID);
479 + if (!root || !rootContainer) {
480 + console.log('Nothing rendered yet.');
481 + return;
482 + }
483 +
484 + let bufferedLog = [];
485 + function log(...args) {
486 + bufferedLog.push(...args, '\n');
487 + }
488 +
489 + function logHostInstances(
490 + children: Array<Instance | TextInstance>,
491 + depth,
492 + ) {
493 + for (let i = 0; i < children.length; i++) {
494 + const child = children[i];
495 + const indent = ' '.repeat(depth);
496 + if (typeof child.text === 'string') {
497 + log(indent + '- ' + child.text);
498 + } else {
499 + // $FlowFixMe - The child should've been refined now.
500 + log(indent + '- ' + child.type + '#' + child.id);
501 + // $FlowFixMe - The child should've been refined now.
502 + logHostInstances(child.children, depth + 1);
503 + }
504 + }
505 + }
506 + function logContainer(container: Container, depth) {
507 + log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
508 + logHostInstances(container.children, depth + 1);
509 + }
510 +
511 + function logUpdateQueue(updateQueue: UpdateQueue<mixed>, depth) {
512 + log(' '.repeat(depth + 1) + 'QUEUED UPDATES');
513 + const firstUpdate = updateQueue.firstUpdate;
514 + if (!firstUpdate) {
515 + return;
516 + }
517 +
518 + log(
519 + ' '.repeat(depth + 1) + '~',
520 + '[' + firstUpdate.expirationTime + ']',
521 + );
522 + while (firstUpdate.next) {
523 + log(
524 + ' '.repeat(depth + 1) + '~',
525 + '[' + firstUpdate.expirationTime + ']',
526 + );
527 + }
528 + }
529 +
530 + function logFiber(fiber: Fiber, depth) {
531 + log(
532 + ' '.repeat(depth) +
533 + '- ' +
534 + // need to explicitly coerce Symbol to a string
535 + (fiber.type ? fiber.type.name || fiber.type.toString() : '[root]'),
536 + '[' + fiber.expirationTime + (fiber.pendingProps ? '*' : '') + ']',
537 + );
538 + if (fiber.updateQueue) {
539 + logUpdateQueue(fiber.updateQueue, depth);
540 + }
541 + // const childInProgress = fiber.progressedChild;
542 + // if (childInProgress && childInProgress !== fiber.child) {
543 + // log(
544 + // ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority,
545 + // );
546 + // logFiber(childInProgress, depth + 1);
547 + // if (fiber.child) {
548 + // log(' '.repeat(depth + 1) + 'CURRENT');
549 + // }
550 + // } else if (fiber.child && fiber.updateQueue) {
551 + // log(' '.repeat(depth + 1) + 'CHILDREN');
552 + // }
553 + if (fiber.child) {
554 + logFiber(fiber.child, depth + 1);
555 + }
556 + if (fiber.sibling) {
557 + logFiber(fiber.sibling, depth);
558 + }
559 + }
560 +
561 + log('HOST INSTANCES:');
562 + logContainer(rootContainer, 0);
563 + log('FIBERS:');
564 + logFiber(root.current, 0);
565 +
566 + console.log(...bufferedLog);
567 + },
568 +
569 + simulateErrorInHostConfig(fn: () => void) {
570 + failInBeginPhase = true;
571 + try {
572 + fn();
573 + } finally {
574 + failInBeginPhase = false;
575 + }
576 + },
577 + };
578 +
579 + return ReactNoop;
580 +}
581 +
582 +export default createReactNoop;
packages/react-reconciler/src/__tests__/ReactPersistent-test.internal.js
+13 -15
@@ -11,7 +11,7 @@
11 'use strict';
12
13 let React;
14 -let ReactNoop;
14 +let ReactNoopPersistent;
15 let ReactPortal;
16
17 describe('ReactPersistent', () => {
@@ -24,14 +24,12 @@ describe('ReactPersistent', () => {
24 ReactFeatureFlags.enableNoopReconciler = false;
25
26 React = require('react');
27 - ReactNoop = require('react-noop-renderer');
27 + ReactNoopPersistent = require('react-noop-renderer/persistent');
28 ReactPortal = require('shared/ReactPortal');
29 });
30
31 - const DEFAULT_ROOT_ID = 'persistent-test';
32 -
31 function render(element) {
34 - ReactNoop.renderToPersistentRootWithID(element, DEFAULT_ROOT_ID);
32 + ReactNoopPersistent.render(element);
33 }
34
35 function div(...children) {
@@ -44,7 +42,7 @@ describe('ReactPersistent', () => {
42 }
43
44 function getChildren() {
47 - return ReactNoop.getChildren(DEFAULT_ROOT_ID);
45 + return ReactNoopPersistent.getChildren();
46 }
47
48 it('can update child nodes of a host instance', () => {
@@ -62,12 +60,12 @@ describe('ReactPersistent', () => {
60 }
61
62 render(<Foo text="Hello" />);
65 - ReactNoop.flush();
63 + ReactNoopPersistent.flush();
64 const originalChildren = getChildren();
65 expect(originalChildren).toEqual([div(span())]);
66
67 render(<Foo text="World" />);
70 - ReactNoop.flush();
68 + ReactNoopPersistent.flush();
69 const newChildren = getChildren();
70 expect(newChildren).toEqual([div(span(), span())]);
71
@@ -96,12 +94,12 @@ describe('ReactPersistent', () => {
94 }
95
96 render(<Foo text="Hello" />);
99 - ReactNoop.flush();
97 + ReactNoopPersistent.flush();
98 const originalChildren = getChildren();
99 expect(originalChildren).toEqual([div(span('Hello'))]);
100
101 render(<Foo text="World" />);
104 - ReactNoop.flush();
102 + ReactNoopPersistent.flush();
103 const newChildren = getChildren();
104 expect(newChildren).toEqual([div(span('Hello'), span('World'))]);
105
@@ -122,12 +120,12 @@ describe('ReactPersistent', () => {
120 }
121
122 render(<Foo text="Hello" />);
125 - ReactNoop.flush();
123 + ReactNoopPersistent.flush();
124 const originalChildren = getChildren();
125 expect(originalChildren).toEqual([div('Hello', span())]);
126
127 render(<Foo text="World" />);
130 - ReactNoop.flush();
128 + ReactNoopPersistent.flush();
129 const newChildren = getChildren();
130 expect(newChildren).toEqual([div('World', span())]);
131
@@ -167,7 +165,7 @@ describe('ReactPersistent', () => {
165 {ReactPortal.createPortal(<Child />, portalContainer, null)}
166 </Parent>,
167 );
170 - ReactNoop.flush();
168 + ReactNoopPersistent.flush();
169
170 expect(emptyPortalChildSet).toEqual([]);
171
@@ -185,7 +183,7 @@ describe('ReactPersistent', () => {
183 )}
184 </Parent>,
185 );
188 - ReactNoop.flush();
186 + ReactNoopPersistent.flush();
187
188 const newChildren = getChildren();
189 expect(newChildren).toEqual([div()]);
@@ -202,7 +200,7 @@ describe('ReactPersistent', () => {
200
201 // Deleting the Portal, should clear its children
202 render(<Parent />);
205 - ReactNoop.flush();
203 + ReactNoopPersistent.flush();
204
205 const clearedPortalChildren = portalContainer.children;
206 expect(clearedPortalChildren).toEqual([]);
scripts/rollup/bundles.js
+23
@@ -275,6 +275,29 @@ const bundles = [
275 }),
276 },
277
278 + /******* React Noop Persistent Renderer (used for tests) *******/
279 + {
280 + label: 'noop-persistent',
281 + bundleTypes: [NODE_DEV, NODE_PROD],
282 + moduleType: RENDERER,
283 + entry: 'react-noop-renderer/persistent',
284 + global: 'ReactNoopRendererPersistent',
285 + externals: ['react', 'expect'],
286 + // React Noop uses generators. However GCC currently
287 + // breaks when we attempt to use them in the output.
288 + // So we precompile them with regenerator, and include
289 + // it as a runtime dependency of React Noop. In practice
290 + // this isn't an issue because React Noop is only used
291 + // in our tests. We wouldn't want to do this for any
292 + // public package though.
293 + babel: opts =>
294 + Object.assign({}, opts, {
295 + plugins: opts.plugins.concat([
296 + require.resolve('babel-plugin-transform-regenerator'),
297 + ]),
298 + }),
299 + },
300 +
301 /******* React Reconciler *******/
302 {
303 label: 'react-reconciler',