Allow multiple root children in test renderer traversal API (#13017)
Dan Abramov committed
Jun 11, 2018 at 20:03 UTC
30bc8ef79295c71f5a4fc459c5bbd4b271f6f390
2 files changed
+101
-34
packages/react-test-renderer/src/ReactTestRenderer.js
+57
-34
@@ -200,8 +200,45 @@ const validWrapperTypes = new Set([
200
ClassComponent,
201
HostComponent,
202
ForwardRef,
203
+ // Normally skipped, but used when there's more than one root child.
204
+ HostRoot,
205
]);
206
207
+function getChildren(parent: Fiber) {
208
+ const children = [];
209
+ const startingNode = parent;
210
+ let node: Fiber = startingNode;
211
+ if (node.child === null) {
212
+ return children;
213
+ }
214
+ node.child.return = node;
215
+ node = node.child;
216
+ outer: while (true) {
217
+ let descend = false;
218
+ if (validWrapperTypes.has(node.tag)) {
219
+ children.push(wrapFiber(node));
220
+ } else if (node.tag === HostText) {
221
+ children.push('' + node.memoizedProps);
222
+ } else {
223
+ descend = true;
224
+ }
225
+ if (descend && node.child !== null) {
226
+ node.child.return = node;
227
+ node = node.child;
228
+ continue;
229
+ }
230
+ while (node.sibling === null) {
231
+ if (node.return === startingNode) {
232
+ break outer;
233
+ }
234
+ node = (node.return: any);
235
+ }
236
+ (node.sibling: any).return = node.return;
237
+ node = (node.sibling: any);
238
+ }
239
+ return children;
240
+}
241
+
242
class ReactTestInstance {
243
_fiber: Fiber;
244
@@ -246,6 +283,13 @@ class ReactTestInstance {
283
let parent = this._fiber.return;
284
while (parent !== null) {
285
if (validWrapperTypes.has(parent.tag)) {
286
+ if (parent.tag === HostRoot) {
287
+ // Special case: we only "materialize" instances for roots
288
+ // if they have more than a single child. So we'll check that now.
289
+ if (getChildren(parent).length < 2) {
290
+ return null;
291
+ }
292
+ }
293
return wrapFiber(parent);
294
}
295
parent = parent.return;
@@ -254,38 +298,7 @@ class ReactTestInstance {
298
}
299
300
get children(): Array<ReactTestInstance | string> {
257
- const children = [];
258
- const startingNode = this._currentFiber();
259
- let node: Fiber = startingNode;
260
- if (node.child === null) {
261
- return children;
262
- }
263
- node.child.return = node;
264
- node = node.child;
265
- outer: while (true) {
266
- let descend = false;
267
- if (validWrapperTypes.has(node.tag)) {
268
- children.push(wrapFiber(node));
269
- } else if (node.tag === HostText) {
270
- children.push('' + node.memoizedProps);
271
- } else {
272
- descend = true;
273
- }
274
- if (descend && node.child !== null) {
275
- node.child.return = node;
276
- node = node.child;
277
- continue;
278
- }
279
- while (node.sibling === null) {
280
- if (node.return === startingNode) {
281
- break outer;
282
- }
283
- node = (node.return: any);
284
- }
285
- (node.sibling: any).return = node.return;
286
- node = (node.sibling: any);
287
- }
288
- return children;
301
+ return getChildren(this._currentFiber());
302
}
303
304
// Custom search functions
@@ -469,10 +482,20 @@ const ReactTestRendererFiber = {
482
configurable: true,
483
enumerable: true,
484
get: function() {
472
- if (root === null || root.current.child === null) {
485
+ if (root === null) {
486
+ throw new Error("Can't access .root on unmounted test renderer");
487
+ }
488
+ const children = getChildren(root.current);
489
+ if (children.length === 0) {
490
throw new Error("Can't access .root on unmounted test renderer");
491
+ } else if (children.length === 1) {
492
+ // Normally, we skip the root and just give you the child.
493
+ return children[0];
494
+ } else {
495
+ // However, we give you the root if there's more than one root child.
496
+ // We could make this the behavior for all cases but it would be a breaking change.
497
+ return wrapFiber(root.current);
498
}
475
- return wrapFiber(root.current.child);
499
},
500
}: Object),
501
);
packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js
+44
@@ -199,4 +199,48 @@ describe('ReactTestRendererTraversal', () => {
199
expect(nestedViews[1].parent).toBe(expectedParent);
200
expect(nestedViews[2].parent).toBe(expectedParent);
201
});
202
+
203
+ it('can have special nodes as roots', () => {
204
+ const FR = React.forwardRef(props => <section {...props} />);
205
+ expect(
206
+ ReactTestRenderer.create(
207
+ <FR>
208
+ <div />
209
+ <div />
210
+ </FR>,
211
+ ).root.findAllByType('div').length,
212
+ ).toBe(2);
213
+ expect(
214
+ ReactTestRenderer.create(
215
+ <React.Fragment>
216
+ <div />
217
+ <div />
218
+ </React.Fragment>,
219
+ ).root.findAllByType('div').length,
220
+ ).toBe(2);
221
+ expect(
222
+ ReactTestRenderer.create(
223
+ <React.Fragment key="foo">
224
+ <div />
225
+ <div />
226
+ </React.Fragment>,
227
+ ).root.findAllByType('div').length,
228
+ ).toBe(2);
229
+ expect(
230
+ ReactTestRenderer.create(
231
+ <React.StrictMode>
232
+ <div />
233
+ <div />
234
+ </React.StrictMode>,
235
+ ).root.findAllByType('div').length,
236
+ ).toBe(2);
237
+ expect(
238
+ ReactTestRenderer.create(
239
+ <Context.Provider>
240
+ <div />
241
+ <div />
242
+ </Context.Provider>,
243
+ ).root.findAllByType('div').length,
244
+ ).toBe(2);
245
+ });
246
});