@samitouri / QOS-React / commits / 2da35fcae8

[Fiber] Implement test renderer (#8628)

* ReactTestRenderer move current impl to stack dir * ReactTestRenderer on fiber: commence! * ReactTestRenderer: most non-ref/non-public-instance tests are passing * Move ReactTestFiberComponent functions from Renderer to Component file * test renderer: get rid of private root containers and root Maps * TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber * ReactTestRenderer: inline component creation * ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference) * TestRendererFiber: use a simple class as TestComponentInstances * Add `getPublicInstance` to support TestRenderer `createNodeMock` * Rename files to end. Update for `mountContainer->createContainer` change * test renderer return same object to prevent unnecessary context pushing/popping * Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer * appease flow * Initial cleanup from sleepy work * unstable_batchedUpdates * Stack test renderer: cache nodeMock to not call on unmount * add public instance type parameter to the reconciler * test renderer: set _nodeMock when mounted * More cleanup * Add test cases for root fragments and (maybe?) root text nodes * Fix the npm package build Explicitly require the Stack version by default. Add a separate entry point for Fiber. We don't add fiber.js to the package yet since it's considered internal until React 16. * Relax the ref type from Object to mixed This seems like the most straightforward way to support getPublicInstance for test renderer. * Remove accidental newline * test renderer: unify TestComponent and TestContainer, handle root updates * Remove string/number serialization attempts since Fiber ensures all textInstances are strings * Return full fragments in toJSON * Test Renderer remove TestComponent instances for simple objects * Update babylon for exact object type syntax * Use $$typeof because clarity > punching ducks. * Minor Flow annotation tweaks * Tweak style, types, and naming * Fix typo

Dustan Kasten committed Jan 11, 2017 at 14:19 UTC 2da35fcae888752948454dd04e9573cc7bdba8f5
24 files changed +630 -174
package.json
+1 -1
@@ -33,7 +33,7 @@
33 "babel-plugin-transform-react-jsx-source": "^6.8.0",
34 "babel-preset-react": "^6.5.0",
35 "babel-traverse": "^6.9.0",
36 - "babylon": "6.8.0",
36 + "babylon": "6.15.0",
37 "browserify": "^13.0.0",
38 "bundle-collapser": "^1.1.1",
39 "coffee-script": "^1.8.0",
packages/react-test-renderer/fiber.js new
+3
@@ -0,0 +1,3 @@
1 +'use strict';
2 +
3 +module.exports = require('./lib/ReactTestRendererFiber');
packages/react-test-renderer/index.js
+1 -1
@@ -1,3 +1,3 @@
1 'use strict';
2
3 -module.exports = require('./lib/ReactTestRenderer');
3 +module.exports = require('./lib/ReactTestRendererStack');
scripts/fiber/tests-passing.txt
+4
@@ -1675,6 +1675,7 @@ src/renderers/testing/__tests__/ReactTestRenderer-test.js
1675 * renders a simple component
1676 * renders a top-level empty component
1677 * exposes a type flag
1678 +* can render a composite component
1679 * renders some basics with an update
1680 * exposes the instance
1681 * updates types
@@ -1687,6 +1688,9 @@ src/renderers/testing/__tests__/ReactTestRenderer-test.js
1688 * supports unmounting inner instances
1689 * supports updates when using refs
1690 * supports error boundaries
1691 +* can update text nodes
1692 +* can update text nodes when rendered as root
1693 +* can render and update root fragments
1694
1695 src/shared/utils/__tests__/KeyEscapeUtils-test.js
1696 * should properly escape and wrap user defined keys
src/renderers/art/ReactARTFiber.js
+4
@@ -464,6 +464,10 @@ const ARTRenderer = ReactFiberReconciler({
464 return false;
465 },
466
467 + getPublicInstance(instance) {
468 + return instance;
469 + },
470 +
471 insertBefore(parentInstance, child, beforeChild) {
472 invariant(
473 child !== beforeChild,
src/renderers/dom/fiber/ReactDOMFiber.js
+4
@@ -137,6 +137,10 @@ var DOMRenderer = ReactFiberReconciler({
137 return getChildNamespace(parentNamespace, type);
138 },
139
140 + getPublicInstance(instance) {
141 + return instance;
142 + },
143 +
144 prepareForCommit() : void {
145 eventsEnabled = ReactBrowserEventEmitter.isEnabled();
146 selectionInformation = ReactInputSelection.getSelectionInformation();
src/renderers/native/ReactNativeFiber.js
+4
@@ -232,6 +232,10 @@ const NativeRenderer = ReactFiberReconciler({
232 return emptyObject;
233 },
234
235 + getPublicInstance(instance) {
236 + return instance;
237 + },
238 +
239 insertBefore(
240 parentInstance : Instance | Container,
241 child : Instance | TextInstance,
src/renderers/noop/ReactNoop.js
+4
@@ -54,6 +54,10 @@ var NoopRenderer = ReactFiberReconciler({
54 return emptyObject;
55 },
56
57 + getPublicInstance(instance) {
58 + return instance;
59 + },
60 +
61 createInstance(type : string, props : Props) : Instance {
62 const inst = {
63 id: instanceCounter++,
src/renderers/shared/fiber/ReactFiber.js
+1 -1
@@ -102,7 +102,7 @@ export type Fiber = {
102
103 // The ref last used to attach this node.
104 // I'll avoid adding an owner field for prod and model that as functions.
105 - ref: null | (((handle : ?Object) => void) & { _stringRef: ?string }),
105 + ref: null | (((handle : mixed) => void) & { _stringRef: ?string }),
106
107 // Input is the data coming into process this fiber. Arguments. Props.
108 pendingProps: any, // This type will be more specific once we overload the tag.
src/renderers/shared/fiber/ReactFiberBeginWork.js
+2 -2
@@ -71,8 +71,8 @@ if (__DEV__) {
71 var warnedAboutStatelessRefs = {};
72 }
73
74 -module.exports = function<T, P, I, TI, C, CX>(
75 - config : HostConfig<T, P, I, TI, C, CX>,
74 +module.exports = function<T, P, I, TI, PI, C, CX>(
75 + config : HostConfig<T, P, I, TI, PI, C, CX>,
76 hostContext : HostContext<C, CX>,
77 scheduleUpdate : (fiber : Fiber, priorityLevel : PriorityLevel) => void,
78 getPriorityContext : () => PriorityLevel,
src/renderers/shared/fiber/ReactFiberCommitWork.js
+4 -3
@@ -34,8 +34,8 @@ var {
34 ContentReset,
35 } = require('ReactTypeOfSideEffect');
36
37 -module.exports = function<T, P, I, TI, C, CX>(
38 - config : HostConfig<T, P, I, TI, C, CX>,
37 +module.exports = function<T, P, I, TI, PI, C, CX>(
38 + config : HostConfig<T, P, I, TI, PI, C, CX>,
39 hostContext : HostContext<C, CX>,
40 captureError : (failedFiber : Fiber, error: Error) => ?Fiber
41 ) {
@@ -48,6 +48,7 @@ module.exports = function<T, P, I, TI, C, CX>(
48 appendChild,
49 insertBefore,
50 removeChild,
51 + getPublicInstance,
52 } = config;
53
54 const {
@@ -462,7 +463,7 @@ module.exports = function<T, P, I, TI, C, CX>(
463 }
464 const ref = finishedWork.ref;
465 if (ref) {
465 - const instance = finishedWork.stateNode;
466 + const instance = getPublicInstance(finishedWork.stateNode);
467 ref(instance);
468 }
469 }
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+2 -2
@@ -46,8 +46,8 @@ if (__DEV__) {
46 var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
47 }
48
49 -module.exports = function<T, P, I, TI, C, CX>(
50 - config : HostConfig<T, P, I, TI, C, CX>,
49 +module.exports = function<T, P, I, TI, PI, C, CX>(
50 + config : HostConfig<T, P, I, TI, PI, C, CX>,
51 hostContext : HostContext<C, CX>,
52 ) {
53 const {
src/renderers/shared/fiber/ReactFiberHostContext.js
+2 -2
@@ -34,8 +34,8 @@ export type HostContext<C, CX> = {
34 resetHostContainer() : void,
35 };
36
37 -module.exports = function<T, P, I, TI, C, CX>(
38 - config : HostConfig<T, P, I, TI, C, CX>
37 +module.exports = function<T, P, I, TI, PI, C, CX>(
38 + config : HostConfig<T, P, I, TI, PI, C, CX>
39 ) : HostContext<C, CX> {
40 const {
41 getChildHostContext,
src/renderers/shared/fiber/ReactFiberReconciler.js
+3 -2
@@ -43,10 +43,11 @@ export type Deadline = {
43
44 type OpaqueNode = Fiber;
45
46 -export type HostConfig<T, P, I, TI, C, CX> = {
46 +export type HostConfig<T, P, I, TI, PI, C, CX> = {
47
48 getRootHostContext(rootContainerInstance : C) : CX,
49 getChildHostContext(parentHostContext : CX, type : T) : CX,
50 + getPublicInstance(instance : I | TI) : PI,
51
52 createInstance(type : T, props : P, rootContainerInstance : C, hostContext : CX, internalInstanceHandle : OpaqueNode) : I,
53 appendInitialChild(parentInstance : I, child : I | TI) : void,
@@ -101,7 +102,7 @@ getContextForSubtree._injectFiber(function(fiber : Fiber) {
102 parentContext;
103 });
104
104 -module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) : Reconciler<C, I, TI> {
105 +module.exports = function<T, P, I, TI, PI, C, CX>(config : HostConfig<T, P, I, TI, PI, C, CX>) : Reconciler<C, I, TI> {
106
107 var {
108 scheduleUpdate,
src/renderers/shared/fiber/ReactFiberScheduler.js
+1 -1
@@ -74,7 +74,7 @@ if (__DEV__) {
74
75 var timeHeuristicForUnitOfWork = 1;
76
77 -module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) {
77 +module.exports = function<T, P, I, TI, PI, C, CX>(config : HostConfig<T, P, I, TI, PI, C, CX>) {
78 const hostContext = ReactFiberHostContext(config);
79 const { popHostContainer, popHostContext, resetHostContainer } = hostContext;
80 const { beginWork, beginFailedWork } = ReactFiberBeginWork(
src/renderers/testing/ReactTestRenderer.js
+5 -144
@@ -1,5 +1,5 @@
1 /**
2 - * Copyright 2013-present, Facebook, Inc.
2 + * Copyright (c) 2013-present Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
@@ -7,151 +7,12 @@
7 * of patent rights can be found in the PATENTS file in the same directory.
8 *
9 * @providesModule ReactTestRenderer
10 - * @preventMunge
11 - * @flow
10 */
11
12 'use strict';
13
16 -var ReactComponentEnvironment = require('ReactComponentEnvironment');
17 -var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
18 -var ReactEmptyComponent = require('ReactEmptyComponent');
19 -var ReactMultiChild = require('ReactMultiChild');
20 -var ReactHostComponent = require('ReactHostComponent');
21 -var ReactTestMount = require('ReactTestMount');
22 -var ReactTestReconcileTransaction = require('ReactTestReconcileTransaction');
23 -var ReactUpdates = require('ReactUpdates');
24 -var ReactTestTextComponent = require('ReactTestTextComponent');
25 -var ReactTestEmptyComponent = require('ReactTestEmptyComponent');
26 -var invariant = require('invariant');
14 +const ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
15
28 -import type { ReactElement } from 'ReactElementType';
29 -import type { ReactInstance } from 'ReactInstanceType';
30 -
31 -type ReactTestRendererJSON = {
32 - type: string,
33 - props: { [propName: string]: string },
34 - children: null | Array<string | ReactTestRendererJSON>,
35 - $$typeof?: any
36 -}
37 -
38 -/**
39 - * Drill down (through composites and empty components) until we get a native or
40 - * native text component.
41 - *
42 - * This is pretty polymorphic but unavoidable with the current structure we have
43 - * for `_renderedChildren`.
44 - */
45 -function getRenderedHostOrTextFromComponent(component) {
46 - var rendered;
47 - while ((rendered = component._renderedComponent)) {
48 - component = rendered;
49 - }
50 - return component;
51 -}
52 -
53 -class ReactTestComponent {
54 - _currentElement: ReactElement;
55 - _renderedChildren: null | Object;
56 - _topLevelWrapper: null | ReactInstance;
57 - _hostContainerInfo: null | Object;
58 -
59 - constructor(element: ReactElement) {
60 - this._currentElement = element;
61 - this._renderedChildren = null;
62 - this._topLevelWrapper = null;
63 - this._hostContainerInfo = null;
64 - }
65 -
66 - mountComponent(
67 - transaction: ReactTestReconcileTransaction,
68 - nativeParent: null | ReactTestComponent,
69 - hostContainerInfo: Object,
70 - context: Object,
71 - ) {
72 - var element = this._currentElement;
73 - this._hostContainerInfo = hostContainerInfo;
74 - // $FlowFixMe https://github.com/facebook/flow/issues/1805
75 - this.mountChildren(element.props.children, transaction, context);
76 - }
77 -
78 - receiveComponent(
79 - nextElement: ReactElement,
80 - transaction: ReactTestReconcileTransaction,
81 - context: Object,
82 - ) {
83 - this._currentElement = nextElement;
84 - // $FlowFixMe https://github.com/facebook/flow/issues/1805
85 - this.updateChildren(nextElement.props.children, transaction, context);
86 - }
87 -
88 - getPublicInstance(): Object {
89 - var element = this._currentElement;
90 - var hostContainerInfo = this._hostContainerInfo;
91 - invariant(
92 - hostContainerInfo,
93 - 'hostContainerInfo should be populated before ' +
94 - 'getPublicInstance is called.'
95 - );
96 - return hostContainerInfo.createNodeMock(element);
97 - }
98 -
99 - toJSON(): ReactTestRendererJSON {
100 - // not using `children`, but I don't want to rewrite without destructuring
101 - // eslint-disable-next-line no-unused-vars
102 - var {children, ...props} = this._currentElement.props;
103 - var childrenJSON = [];
104 - for (var key in this._renderedChildren) {
105 - var inst = this._renderedChildren[key];
106 - inst = getRenderedHostOrTextFromComponent(inst);
107 - var json = inst.toJSON();
108 - if (json !== undefined) {
109 - childrenJSON.push(json);
110 - }
111 - }
112 - var object: ReactTestRendererJSON = {
113 - type: this._currentElement.type,
114 - props: props,
115 - children: childrenJSON.length ? childrenJSON : null,
116 - };
117 - Object.defineProperty(object, '$$typeof', {
118 - value: Symbol.for('react.test.json'),
119 - });
120 - return object;
121 - }
122 -
123 - getHostNode(): void {}
124 - unmountComponent(safely, skipLifecycle): void {
125 - // $FlowFixMe https://github.com/facebook/flow/issues/1805
126 - this.unmountChildren(safely, skipLifecycle);
127 - }
128 -}
129 -
130 -Object.assign(ReactTestComponent.prototype, ReactMultiChild);
131 -
132 -// =============================================================================
133 -
134 -ReactUpdates.injection.injectReconcileTransaction(
135 - ReactTestReconcileTransaction
136 -);
137 -ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
138 -
139 -ReactHostComponent.injection.injectGenericComponentClass(ReactTestComponent);
140 -ReactHostComponent.injection.injectTextComponentClass(ReactTestTextComponent);
141 -ReactEmptyComponent.injection.injectEmptyComponentFactory(function() {
142 - return new ReactTestEmptyComponent();
143 -});
144 -
145 -ReactComponentEnvironment.injection.injectEnvironment({
146 - processChildrenUpdates: function() {},
147 - replaceNodeWithMarkup: function() {},
148 -});
149 -
150 -var ReactTestRenderer = {
151 - create: ReactTestMount.render,
152 - /* eslint-disable camelcase */
153 - unstable_batchedUpdates: ReactUpdates.batchedUpdates,
154 - /* eslint-enable camelcase */
155 -};
156 -
157 -module.exports = ReactTestRenderer;
16 +module.exports = ReactDOMFeatureFlags.useFiber
17 + ? require('ReactTestRendererFiber')
18 + : require('ReactTestRendererStack');
src/renderers/testing/ReactTestRendererFiber.js new
+293
@@ -0,0 +1,293 @@
1 +/**
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactTestRendererFiber
10 + * @preventMunge
11 + * @flow
12 + */
13 +
14 +'use strict';
15 +
16 +var ReactFiberReconciler = require('ReactFiberReconciler');
17 +var ReactGenericBatching = require('ReactGenericBatching');
18 +var emptyObject = require('emptyObject');
19 +
20 +import type { TestRendererOptions } from 'ReactTestMount';
21 +
22 +type ReactTestRendererJSON = {|
23 + type : string,
24 + props : {[propName: string] : string },
25 + children : null | Array<ReactTestRendererNode>,
26 + $$typeof ?: Symbol, // Optional because we add it with defineProperty().
27 +|};
28 +type ReactTestRendererNode = ReactTestRendererJSON | string;
29 +
30 +type Container = {|
31 + children : Array<Instance | TextInstance>,
32 + createNodeMock : Function,
33 + tag : 'CONTAINER',
34 +|};
35 +
36 +type Props = Object;
37 +type Instance = {|
38 + type : string,
39 + props : Object,
40 + children : Array<Instance | TextInstance>,
41 + rootContainerInstance : Container,
42 + tag : 'INSTANCE',
43 +|};
44 +
45 +type TextInstance = {|
46 + text : string,
47 + tag : 'TEXT',
48 +|};
49 +
50 +var TestRenderer = ReactFiberReconciler({
51 + getRootHostContext() {
52 + return emptyObject;
53 + },
54 +
55 + getChildHostContext() {
56 + return emptyObject;
57 + },
58 +
59 + prepareForCommit() : void {
60 + // noop
61 + },
62 +
63 + resetAfterCommit() : void {
64 + // noop
65 + },
66 +
67 + createInstance(
68 + type : string,
69 + props : Props,
70 + rootContainerInstance : Container,
71 + hostContext : Object,
72 + internalInstanceHandle : Object,
73 + ) : Instance {
74 + return {
75 + type,
76 + props,
77 + children: [],
78 + rootContainerInstance,
79 + tag: 'INSTANCE',
80 + };
81 + },
82 +
83 + appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void {
84 + const index = parentInstance.children.indexOf(child);
85 + if (index !== -1) {
86 + parentInstance.children.splice(index, 1);
87 + }
88 + parentInstance.children.push(child);
89 + },
90 +
91 + finalizeInitialChildren(
92 + testElement : Instance,
93 + type : string,
94 + props : Props,
95 + rootContainerInstance : Container,
96 + ) : boolean {
97 + return false;
98 + },
99 +
100 + prepareUpdate(
101 + testElement : Instance,
102 + type : string,
103 + oldProps : Props,
104 + newProps : Props,
105 + hostContext : Object,
106 + ) : boolean {
107 + return true;
108 + },
109 +
110 + commitUpdate(
111 + instance : Instance,
112 + type : string,
113 + oldProps : Props,
114 + newProps : Props,
115 + rootContainerInstance : Container,
116 + internalInstanceHandle : Object,
117 + ) : void {
118 + instance.type = type;
119 + instance.props = newProps;
120 + },
121 +
122 + commitMount(
123 + instance : Instance,
124 + type : string,
125 + newProps : Props,
126 + rootContainerInstance : Object,
127 + internalInstanceHandle : Object
128 + ) : void {
129 + // noop
130 + },
131 +
132 + shouldSetTextContent(props : Props) : boolean {
133 + return false;
134 + },
135 +
136 + resetTextContent(testElement : Instance) : void {
137 + // noop
138 + },
139 +
140 + createTextInstance(
141 + text : string,
142 + rootContainerInstance : Container,
143 + hostContext : Object,
144 + internalInstanceHandle : Object
145 + ) : TextInstance {
146 + return {
147 + text,
148 + tag: 'TEXT',
149 + };
150 + },
151 +
152 + commitTextUpdate(textInstance : TextInstance, oldText : string, newText : string) : void {
153 + textInstance.text = newText;
154 + },
155 +
156 + appendChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
157 + const index = parentInstance.children.indexOf(child);
158 + if (index !== -1) {
159 + parentInstance.children.splice(index, 1);
160 + }
161 + parentInstance.children.push(child);
162 + },
163 +
164 + insertBefore(
165 + parentInstance : Instance | Container,
166 + child : Instance | TextInstance,
167 + beforeChild : Instance | TextInstance
168 + ) : void {
169 + const index = parentInstance.children.indexOf(child);
170 + if (index !== -1) {
171 + parentInstance.children.splice(index, 1);
172 + }
173 + const beforeIndex = parentInstance.children.indexOf(beforeChild);
174 + parentInstance.children.splice(beforeIndex, 0, child);
175 + },
176 +
177 + removeChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
178 + const index = parentInstance.children.indexOf(child);
179 + parentInstance.children.splice(index, 1);
180 + },
181 +
182 + scheduleAnimationCallback(fn : Function) : void {
183 + setTimeout(fn);
184 + },
185 +
186 + scheduleDeferredCallback(fn : Function) : void {
187 + setTimeout(fn, 0, {timeRemaining: Infinity});
188 + },
189 +
190 + useSyncScheduling: true,
191 +
192 + getPublicInstance(inst) {
193 + switch (inst.tag) {
194 + case 'INSTANCE':
195 + const createNodeMock = inst.rootContainerInstance.createNodeMock;
196 + return createNodeMock({
197 + type: inst.type,
198 + props: inst.props,
199 + });
200 + default:
201 + return inst;
202 + }
203 + },
204 +});
205 +
206 +var defaultTestOptions = {
207 + createNodeMock: function() {
208 + return null;
209 + },
210 +};
211 +
212 +function toJSON(inst : Instance | TextInstance) : ReactTestRendererNode {
213 + switch (inst.tag) {
214 + case 'TEXT':
215 + return inst.text;
216 + case 'INSTANCE':
217 + /* eslint-disable no-unused-vars */
218 + // We don't include the `children` prop in JSON.
219 + // Instead, we will include the actual rendered children.
220 + const {children, ...props} = inst.props;
221 + /* eslint-enable */
222 + let renderedChildren = null;
223 + if (inst.children && inst.children.length) {
224 + renderedChildren = inst.children.map(toJSON);
225 + }
226 + const json : ReactTestRendererJSON = {
227 + type: inst.type,
228 + props: props,
229 + children: renderedChildren,
230 + };
231 + Object.defineProperty(json, '$$typeof', {value: Symbol.for('react.test.json')});
232 + return json;
233 + default:
234 + throw new Error(`Unexpected node type in toJSON: ${inst.tag}`);
235 + }
236 +}
237 +
238 +var ReactTestFiberRenderer = {
239 + create(element : ReactElement<any>, options : TestRendererOptions) {
240 + var createNodeMock = defaultTestOptions.createNodeMock;
241 + if (options && typeof options.createNodeMock === 'function') {
242 + createNodeMock = options.createNodeMock;
243 + }
244 + var container = {
245 + children: [],
246 + createNodeMock,
247 + tag: 'CONTAINER',
248 + };
249 + var root = TestRenderer.createContainer(container);
250 + TestRenderer.updateContainer(element, root, null, null);
251 +
252 + return {
253 + toJSON() {
254 + if (root == null || container == null) {
255 + return null;
256 + }
257 + if (container.children.length === 0) {
258 + return null;
259 + }
260 + if (container.children.length === 1) {
261 + return toJSON(container.children[0]);
262 + }
263 + return container.children.map(toJSON);
264 + },
265 + update(newElement : ReactElement<any>) {
266 + if (root == null) {
267 + return;
268 + }
269 + TestRenderer.updateContainer(newElement, root, null, null);
270 + },
271 + unmount() {
272 + if (root == null) {
273 + return;
274 + }
275 + TestRenderer.updateContainer(null, root, null);
276 + container = null;
277 + root = null;
278 + },
279 + getInstance() {
280 + if (root == null) {
281 + return null;
282 + }
283 + return TestRenderer.getPublicRootInstance(root);
284 + },
285 + };
286 + },
287 +
288 + /* eslint-disable camelcase */
289 + unstable_batchedUpdates: ReactGenericBatching.batchedUpdates,
290 + /* eslint-enable camelcase */
291 +};
292 +
293 +module.exports = ReactTestFiberRenderer;
src/renderers/testing/__tests__/ReactTestRenderer-test.js
+130 -10
@@ -13,6 +13,7 @@
13
14 var React = require('React');
15 var ReactTestRenderer = require('ReactTestRenderer');
16 +var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
17
18 describe('ReactTestRenderer', () => {
19 function normalizeCodeLocInfo(str) {
@@ -55,6 +56,31 @@ describe('ReactTestRenderer', () => {
56 }
57 });
58
59 + it('can render a composite component', () => {
60 + class Component extends React.Component {
61 + render() {
62 + return (
63 + <div className="purple">
64 + <Child />
65 + </div>
66 + );
67 + }
68 + }
69 +
70 + var Child = () => {
71 + return <moo />;
72 + };
73 +
74 + var renderer = ReactTestRenderer.create(<Component />);
75 + expect(renderer.toJSON()).toEqual({
76 + type: 'div',
77 + props: { className: 'purple' },
78 + children: [
79 + { type: 'moo', props: {}, children: null },
80 + ],
81 + });
82 + });
83 +
84 it('renders some basics with an update', () => {
85 var renders = 0;
86
@@ -92,7 +118,7 @@ describe('ReactTestRenderer', () => {
118 type: 'div',
119 props: { className: 'purple' },
120 children: [
95 - 7,
121 + ReactDOMFeatureFlags.useFiber ? '7' : 7,
122 { type: 'moo', props: {}, children: null },
123 ],
124 });
@@ -124,8 +150,8 @@ describe('ReactTestRenderer', () => {
150 mouse.handleMoose();
151 expect(renderer.toJSON()).toEqual({
152 type: 'div',
127 - props: {},
153 children: ['moose'],
154 + props: {},
155 });
156 });
157
@@ -366,8 +392,7 @@ describe('ReactTestRenderer', () => {
392 {createNodeMock}
393 );
394 inst.update(<Foo useDiv={false} />);
369 - // It's called with 'div' twice (mounting and unmounting)
370 - expect(log).toEqual(['div', 'div', 'span']);
395 + expect(log).toEqual(['div', 'span']);
396 });
397
398 it('supports error boundaries', () => {
@@ -420,12 +445,107 @@ describe('ReactTestRenderer', () => {
445 props: {},
446 children: ['Happy Birthday!'],
447 });
423 - expect(log).toEqual([
424 - 'Boundary render',
425 - 'Angry render',
426 - 'Boundary render',
427 - 'Boundary componentDidMount',
428 - ]);
448 + if (ReactDOMFeatureFlags.useFiber) {
449 + expect(log).toEqual([
450 + 'Boundary render',
451 + 'Angry render',
452 + 'Boundary componentDidMount',
453 + 'Boundary render',
454 + ]);
455 + } else {
456 + expect(log).toEqual([
457 + 'Boundary render',
458 + 'Angry render',
459 + 'Boundary render',
460 + 'Boundary componentDidMount',
461 + ]);
462 + }
463 });
464
465 + it('can update text nodes', () => {
466 + class Component extends React.Component {
467 + render() {
468 + return (
469 + <div>
470 + {this.props.children}
471 + </div>
472 + );
473 + }
474 + }
475 +
476 + var renderer = ReactTestRenderer.create(<Component>Hi</Component>);
477 + expect(renderer.toJSON()).toEqual({
478 + type: 'div',
479 + children: ['Hi'],
480 + props: {},
481 + });
482 + renderer.update(<Component>{['Hi', 'Bye']}</Component>);
483 + expect(renderer.toJSON()).toEqual({
484 + type: 'div',
485 + children: ['Hi', 'Bye'],
486 + props: {},
487 + });
488 + renderer.update(<Component>Bye</Component>);
489 + expect(renderer.toJSON()).toEqual({
490 + type: 'div',
491 + children: ['Bye'],
492 + props: {},
493 + });
494 + renderer.update(<Component>{42}</Component>);
495 + expect(renderer.toJSON()).toEqual({
496 + type: 'div',
497 + children: [
498 + ReactDOMFeatureFlags.useFiber ? '42' : 42,
499 + ],
500 + props: {},
501 + });
502 + renderer.update(<Component><div /></Component>);
503 + expect(renderer.toJSON()).toEqual({
504 + type: 'div',
505 + children: [{
506 + type: 'div',
507 + children: null,
508 + props: {},
509 + }],
510 + props: {},
511 + });
512 + });
513 +
514 + if (ReactDOMFeatureFlags.useFiber) {
515 + it('can update text nodes when rendered as root', () => {
516 + var renderer = ReactTestRenderer.create(['Hello', 'world']);
517 + expect(renderer.toJSON()).toEqual(['Hello', 'world']);
518 + renderer.update(42);
519 + expect(renderer.toJSON()).toEqual('42');
520 + renderer.update([42, 'world']);
521 + expect(renderer.toJSON()).toEqual(['42', 'world']);
522 + });
523 +
524 + it('can render and update root fragments', () => {
525 + var Component = (props) => props.children;
526 +
527 + var renderer = ReactTestRenderer.create([
528 + <Component>Hi</Component>,
529 + <Component>Bye</Component>,
530 + ]);
531 + expect(renderer.toJSON()).toEqual(['Hi', 'Bye']);
532 + renderer.update(<div />);
533 + expect(renderer.toJSON()).toEqual({
534 + type: 'div',
535 + children: null,
536 + props: {},
537 + });
538 + renderer.update([<div>goodbye</div>, 'world']);
539 + expect(renderer.toJSON()).toEqual([
540 + {
541 + type: 'div',
542 + children: [
543 + 'goodbye',
544 + ],
545 + props: {},
546 + },
547 + 'world',
548 + ]);
549 + });
550 + }
551 });
src/renderers/testing/stack/ReactTestEmptyComponent.js renamed
src/renderers/testing/stack/ReactTestMount.js renamed
src/renderers/testing/stack/ReactTestReconcileTransaction.js renamed
src/renderers/testing/stack/ReactTestRendererStack.js new
+159
@@ -0,0 +1,159 @@
1 +/**
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactTestRendererStack
10 + * @preventMunge
11 + * @flow
12 + */
13 +
14 +'use strict';
15 +
16 +var ReactComponentEnvironment = require('ReactComponentEnvironment');
17 +var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
18 +var ReactEmptyComponent = require('ReactEmptyComponent');
19 +var ReactMultiChild = require('ReactMultiChild');
20 +var ReactHostComponent = require('ReactHostComponent');
21 +var ReactTestMount = require('ReactTestMount');
22 +var ReactTestReconcileTransaction = require('ReactTestReconcileTransaction');
23 +var ReactUpdates = require('ReactUpdates');
24 +var ReactTestTextComponent = require('ReactTestTextComponent');
25 +var ReactTestEmptyComponent = require('ReactTestEmptyComponent');
26 +var invariant = require('invariant');
27 +
28 +import type { ReactElement } from 'ReactElementType';
29 +import type { ReactInstance } from 'ReactInstanceType';
30 +
31 +type ReactTestRendererJSON = {
32 + type: string,
33 + props: { [propName: string]: string },
34 + children: null | Array<string | ReactTestRendererJSON>,
35 + $$typeof?: any
36 +}
37 +
38 +/**
39 + * Drill down (through composites and empty components) until we get a native or
40 + * native text component.
41 + *
42 + * This is pretty polymorphic but unavoidable with the current structure we have
43 + * for `_renderedChildren`.
44 + */
45 +function getRenderedHostOrTextFromComponent(component) {
46 + var rendered;
47 + while ((rendered = component._renderedComponent)) {
48 + component = rendered;
49 + }
50 + return component;
51 +}
52 +
53 +var UNSET = {};
54 +
55 +class ReactTestComponent {
56 + _currentElement: ReactElement;
57 + _renderedChildren: null | Object;
58 + _topLevelWrapper: null | ReactInstance;
59 + _hostContainerInfo: null | Object;
60 + _nodeMock: Object;
61 +
62 + constructor(element: ReactElement) {
63 + this._currentElement = element;
64 + this._renderedChildren = null;
65 + this._topLevelWrapper = null;
66 + this._hostContainerInfo = null;
67 + this._nodeMock = UNSET;
68 + }
69 +
70 + mountComponent(
71 + transaction: ReactTestReconcileTransaction,
72 + nativeParent: null | ReactTestComponent,
73 + hostContainerInfo: Object,
74 + context: Object,
75 + ) {
76 + var element = this._currentElement;
77 + this._hostContainerInfo = hostContainerInfo;
78 + this._nodeMock = hostContainerInfo.createNodeMock(element);
79 + // $FlowFixMe https://github.com/facebook/flow/issues/1805
80 + this.mountChildren(element.props.children, transaction, context);
81 + }
82 +
83 + receiveComponent(
84 + nextElement: ReactElement,
85 + transaction: ReactTestReconcileTransaction,
86 + context: Object,
87 + ) {
88 + this._currentElement = nextElement;
89 + // $FlowFixMe https://github.com/facebook/flow/issues/1805
90 + this.updateChildren(nextElement.props.children, transaction, context);
91 + }
92 +
93 + getPublicInstance(): Object {
94 + invariant(
95 + this._nodeMock !== UNSET,
96 + 'getPublicInstance should not be called before component is mounted.'
97 + );
98 + return this._nodeMock;
99 + }
100 +
101 + toJSON(): ReactTestRendererJSON {
102 + // not using `children`, but I don't want to rewrite without destructuring
103 + // eslint-disable-next-line no-unused-vars
104 + var {children, ...props} = this._currentElement.props;
105 + var childrenJSON = [];
106 + for (var key in this._renderedChildren) {
107 + var inst = this._renderedChildren[key];
108 + inst = getRenderedHostOrTextFromComponent(inst);
109 + var json = inst.toJSON();
110 + if (json !== undefined) {
111 + childrenJSON.push(json);
112 + }
113 + }
114 + var object: ReactTestRendererJSON = {
115 + type: this._currentElement.type,
116 + props: props,
117 + children: childrenJSON.length ? childrenJSON : null,
118 + };
119 + Object.defineProperty(object, '$$typeof', {
120 + value: Symbol.for('react.test.json'),
121 + });
122 + return object;
123 + }
124 +
125 + getHostNode(): void {}
126 + unmountComponent(safely, skipLifecycle): void {
127 + // $FlowFixMe https://github.com/facebook/flow/issues/1805
128 + this.unmountChildren(safely, skipLifecycle);
129 + }
130 +}
131 +
132 +Object.assign(ReactTestComponent.prototype, ReactMultiChild);
133 +
134 +// =============================================================================
135 +
136 +ReactUpdates.injection.injectReconcileTransaction(
137 + ReactTestReconcileTransaction
138 +);
139 +ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
140 +
141 +ReactHostComponent.injection.injectGenericComponentClass(ReactTestComponent);
142 +ReactHostComponent.injection.injectTextComponentClass(ReactTestTextComponent);
143 +ReactEmptyComponent.injection.injectEmptyComponentFactory(function() {
144 + return new ReactTestEmptyComponent();
145 +});
146 +
147 +ReactComponentEnvironment.injection.injectEnvironment({
148 + processChildrenUpdates: function() {},
149 + replaceNodeWithMarkup: function() {},
150 +});
151 +
152 +var ReactTestRenderer = {
153 + create: ReactTestMount.render,
154 + /* eslint-disable camelcase */
155 + unstable_batchedUpdates: ReactUpdates.batchedUpdates,
156 + /* eslint-enable camelcase */
157 +};
158 +
159 +module.exports = ReactTestRenderer;
src/renderers/testing/stack/ReactTestTextComponent.js renamed
yarn.lock
+3 -5
@@ -859,11 +859,9 @@ babel@^5.4.7:
859 slash "^1.0.0"
860 source-map "^0.5.0"
861
862 -babylon@6.8.0:
863 - version "6.8.0"
864 - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.8.0.tgz#c1057f7bf703620dc04ddb69cd59ade961b87cb0"
865 - dependencies:
866 - babel-runtime "^6.0.0"
862 +babylon@6.15.0:
863 + version "6.15.0"
864 + resolved "http://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e"
865
866 babylon@^5.8.38:
867 version "5.8.38"