@samitouri / QOS-React / commits / 30bc8ef792

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 });