1
+/**
2
+ * This is a renderer of React that doesn't have a render target output.
3
+ * It is used to test that the react-reconciler package doesn't blow up.
4
+ *
5
+ * @flow
6
+ */
7
+'use strict';
8
+
9
+var React = require('react');
10
+var assert = require('assert');
11
+var ReactFiberReconciler = require('react-reconciler');
12
+var emptyObject = require('fbjs/lib/emptyObject');
13
+var assert = require('assert');
14
+
15
+const UPDATE_SIGNAL = {};
16
+
17
+var scheduledCallback = null;
18
+
19
+type Container = {rootID: string, children: Array<Instance | TextInstance>};
20
+type Props = {prop: any, hidden?: boolean};
21
+type Instance = {|
22
+ type: string,
23
+ id: number,
24
+ children: Array<Instance | TextInstance>,
25
+ prop: any,
26
+|};
27
+type TextInstance = {|text: string, id: number|};
28
+
29
+var instanceCounter = 0;
30
+
31
+function appendChild(
32
+ parentInstance: Instance | Container,
33
+ child: Instance | TextInstance
34
+): void {
35
+ const index = parentInstance.children.indexOf(child);
36
+ if (index !== -1) {
37
+ parentInstance.children.splice(index, 1);
38
+ }
39
+ parentInstance.children.push(child);
40
+}
41
+
42
+function insertBefore(
43
+ parentInstance: Instance | Container,
44
+ child: Instance | TextInstance,
45
+ beforeChild: Instance | TextInstance
46
+): void {
47
+ const index = parentInstance.children.indexOf(child);
48
+ if (index !== -1) {
49
+ parentInstance.children.splice(index, 1);
50
+ }
51
+ const beforeIndex = parentInstance.children.indexOf(beforeChild);
52
+ if (beforeIndex === -1) {
53
+ throw new Error('This child does not exist.');
54
+ }
55
+ parentInstance.children.splice(beforeIndex, 0, child);
56
+}
57
+
58
+function removeChild(
59
+ parentInstance: Instance | Container,
60
+ child: Instance | TextInstance
61
+): void {
62
+ const index = parentInstance.children.indexOf(child);
63
+ if (index === -1) {
64
+ throw new Error('This child does not exist.');
65
+ }
66
+ parentInstance.children.splice(index, 1);
67
+}
68
+
69
+var NoopRenderer = ReactFiberReconciler({
70
+ getRootHostContext() {
71
+ return emptyObject;
72
+ },
73
+
74
+ getChildHostContext() {
75
+ return emptyObject;
76
+ },
77
+
78
+ getPublicInstance(instance) {
79
+ return instance;
80
+ },
81
+
82
+ createInstance(type: string, props: Props): Instance {
83
+ const inst = {
84
+ id: instanceCounter++,
85
+ type: type,
86
+ children: [],
87
+ prop: props.prop,
88
+ };
89
+ // Hide from unit tests
90
+ Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
91
+ return inst;
92
+ },
93
+
94
+ appendInitialChild(
95
+ parentInstance: Instance,
96
+ child: Instance | TextInstance
97
+ ): void {
98
+ parentInstance.children.push(child);
99
+ },
100
+
101
+ finalizeInitialChildren(
102
+ domElement: Instance,
103
+ type: string,
104
+ props: Props
105
+ ): boolean {
106
+ return false;
107
+ },
108
+
109
+ prepareUpdate(
110
+ instance: Instance,
111
+ type: string,
112
+ oldProps: Props,
113
+ newProps: Props
114
+ ): null | {} {
115
+ return UPDATE_SIGNAL;
116
+ },
117
+
118
+ commitMount(instance: Instance, type: string, newProps: Props): void {
119
+ // Noop
120
+ },
121
+
122
+ commitUpdate(
123
+ instance: Instance,
124
+ updatePayload: Object,
125
+ type: string,
126
+ oldProps: Props,
127
+ newProps: Props
128
+ ): void {
129
+ instance.prop = newProps.prop;
130
+ },
131
+
132
+ shouldSetTextContent(type: string, props: Props): boolean {
133
+ return (
134
+ typeof props.children === 'string' || typeof props.children === 'number'
135
+ );
136
+ },
137
+
138
+ resetTextContent(instance: Instance): void {},
139
+
140
+ shouldDeprioritizeSubtree(type: string, props: Props): boolean {
141
+ return !!props.hidden;
142
+ },
143
+
144
+ now: Date.now,
145
+
146
+ createTextInstance(
147
+ text: string,
148
+ rootContainerInstance: Container,
149
+ hostContext: Object,
150
+ internalInstanceHandle: Object
151
+ ): TextInstance {
152
+ var inst = {text: text, id: instanceCounter++};
153
+ // Hide from unit tests
154
+ Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
155
+ return inst;
156
+ },
157
+
158
+ commitTextUpdate(
159
+ textInstance: TextInstance,
160
+ oldText: string,
161
+ newText: string
162
+ ): void {
163
+ textInstance.text = newText;
164
+ },
165
+
166
+ appendChild: appendChild,
167
+ appendChildToContainer: appendChild,
168
+ insertBefore: insertBefore,
169
+ insertInContainerBefore: insertBefore,
170
+ removeChild: removeChild,
171
+ removeChildFromContainer: removeChild,
172
+
173
+ scheduleDeferredCallback(callback) {
174
+ if (scheduledCallback) {
175
+ throw new Error(
176
+ 'Scheduling a callback twice is excessive. Instead, keep track of ' +
177
+ 'whether the callback has already been scheduled.'
178
+ );
179
+ }
180
+ scheduledCallback = callback;
181
+ },
182
+
183
+ prepareForCommit(): void {},
184
+
185
+ resetAfterCommit(): void {},
186
+});
187
+
188
+var rootContainers = new Map();
189
+var roots = new Map();
190
+var DEFAULT_ROOT_ID = '<default>';
191
+
192
+let yieldedValues = null;
193
+
194
+function* flushUnitsOfWork(n: number): Generator<Array<mixed>, void, void> {
195
+ var didStop = false;
196
+ while (!didStop && scheduledCallback !== null) {
197
+ var cb = scheduledCallback;
198
+ scheduledCallback = null;
199
+ yieldedValues = null;
200
+ var unitsRemaining = n;
201
+ cb({
202
+ timeRemaining() {
203
+ if (yieldedValues !== null) {
204
+ return 0;
205
+ }
206
+ if (unitsRemaining-- > 0) {
207
+ return 999;
208
+ }
209
+ didStop = true;
210
+ return 0;
211
+ },
212
+ });
213
+
214
+ if (yieldedValues !== null) {
215
+ const values = yieldedValues;
216
+ yieldedValues = null;
217
+ yield values;
218
+ }
219
+ }
220
+}
221
+
222
+var Noop = {
223
+ getChildren(rootID: string = DEFAULT_ROOT_ID) {
224
+ const container = rootContainers.get(rootID);
225
+ if (container) {
226
+ return container.children;
227
+ } else {
228
+ return null;
229
+ }
230
+ },
231
+
232
+ // Shortcut for testing a single root
233
+ render(element: React$Element<any>, callback: ?Function) {
234
+ Noop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback);
235
+ },
236
+
237
+ renderToRootWithID(
238
+ element: React$Element<any>,
239
+ rootID: string,
240
+ callback: ?Function
241
+ ) {
242
+ let root = roots.get(rootID);
243
+ if (!root) {
244
+ const container = {rootID: rootID, children: []};
245
+ rootContainers.set(rootID, container);
246
+ root = NoopRenderer.createContainer(container);
247
+ roots.set(rootID, root);
248
+ }
249
+ NoopRenderer.updateContainer(element, root, null, callback);
250
+ },
251
+
252
+ flush(): Array<mixed> {
253
+ return Noop.flushUnitsOfWork(Infinity);
254
+ },
255
+
256
+ flushUnitsOfWork(n: number): Array<mixed> {
257
+ let values = [];
258
+ for (const value of flushUnitsOfWork(n)) {
259
+ values.push(...value);
260
+ }
261
+ return values;
262
+ },
263
+
264
+ batchedUpdates: NoopRenderer.batchedUpdates,
265
+
266
+ deferredUpdates: NoopRenderer.deferredUpdates,
267
+
268
+ unbatchedUpdates: NoopRenderer.unbatchedUpdates,
269
+
270
+ flushSync: NoopRenderer.flushSync,
271
+};
272
+
273
+type TestProps = {|
274
+ active: boolean,
275
+|};
276
+type TestState = {|
277
+ counter: number,
278
+|};
279
+
280
+let instance = null;
281
+class Test extends React.Component<TestProps, TestState> {
282
+ state = {counter: 0};
283
+ increment() {
284
+ this.setState(({counter}) => ({
285
+ counter: counter + 1,
286
+ }));
287
+ }
288
+ render() {
289
+ return [this.props.active ? 'Active' : 'Inactive', this.state.counter];
290
+ }
291
+}
292
+const Children = props => props.children;
293
+Noop.render(
294
+ <main>
295
+ <div>Hello</div>
296
+ <Children>
297
+ Hello world
298
+ <span>{'Number '}{42}</span>
299
+ <Test active={true} ref={t => (instance = t)} />
300
+ </Children>
301
+ </main>
302
+);
303
+Noop.flush();
304
+const actual1 = Noop.getChildren();
305
+const expected1 = [
306
+ {
307
+ type: 'main',
308
+ children: [
309
+ {type: 'div', children: [], prop: undefined},
310
+ {text: 'Hello world'},
311
+ {
312
+ type: 'span',
313
+ children: [{text: 'Number '}, {text: '42'}],
314
+ prop: undefined,
315
+ },
316
+ {text: 'Active'},
317
+ {text: '0'},
318
+ ],
319
+ prop: undefined,
320
+ },
321
+];
322
+assert.deepEqual(
323
+ actual1,
324
+ expected1,
325
+ 'Error. Noop.getChildren() returned unexpected value.\nExpected:\ ' +
326
+ JSON.stringify(expected1, null, 2) +
327
+ '\n\nActual:\n ' +
328
+ JSON.stringify(actual1, null, 2)
329
+);
330
+
331
+if (instance === null) {
332
+ throw new Error('Expected instance to exist.');
333
+}
334
+
335
+instance.increment();
336
+Noop.flush();
337
+const actual2 = Noop.getChildren();
338
+const expected2 = [
339
+ {
340
+ type: 'main',
341
+ children: [
342
+ {type: 'div', children: [], prop: undefined},
343
+ {text: 'Hello world'},
344
+ {
345
+ type: 'span',
346
+ children: [{text: 'Number '}, {text: '42'}],
347
+ prop: undefined,
348
+ },
349
+ {text: 'Active'},
350
+ {text: '1'},
351
+ ],
352
+ prop: undefined,
353
+ },
354
+];
355
+assert.deepEqual(
356
+ actual2,
357
+ expected2,
358
+ 'Error. Noop.getChildren() returned unexpected value.\nExpected:\ ' +
359
+ JSON.stringify(expected2, null, 2) +
360
+ '\n\nActual:\n ' +
361
+ JSON.stringify(actual2, null, 2)
362
+);
363
+
364
+const beginGreen = '\u001b[32m';
365
+const endGreen = '\u001b[39m';
366
+console.log(beginGreen + 'Reconciler package is OK!' + endGreen);