diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index 5fab9af606fbe..029c2605800e3 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -245,10 +245,14 @@ class ReactTestInstance { } get parent(): ?ReactTestInstance { - const parent = this._fiber.return; - return parent === null || parent.tag === HostRoot - ? null - : wrapFiber(parent); + let parent = this._fiber.return; + while (parent !== null) { + if (validWrapperTypes.has(parent.tag)) { + return wrapFiber(parent); + } + parent = parent.return; + } + return null; } get children(): Array { @@ -262,30 +266,12 @@ class ReactTestInstance { node = node.child; outer: while (true) { let descend = false; - switch (node.tag) { - case FunctionalComponent: - case ClassComponent: - case HostComponent: - case ForwardRef: - children.push(wrapFiber(node)); - break; - case HostText: - children.push('' + node.memoizedProps); - break; - case Fragment: - case ContextProvider: - case ContextConsumer: - case Mode: - case Profiler: - descend = true; - break; - default: - invariant( - false, - 'Unsupported component type %s in test renderer. ' + - 'This is probably a bug in React.', - node.tag, - ); + if (validWrapperTypes.has(node.tag)) { + children.push(wrapFiber(node)); + } else if (node.tag === HostText) { + children.push('' + node.memoizedProps); + } else { + descend = true; } if (descend && node.child !== null) { node.child.return = node; diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js index cefef5657c8e6..9fb91e1f35b83 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js @@ -12,6 +12,7 @@ const React = require('react'); let ReactTestRenderer; +let Context; const RCTView = 'RCTView'; const View = props => ; @@ -20,6 +21,7 @@ describe('ReactTestRendererTraversal', () => { beforeEach(() => { jest.resetModules(); ReactTestRenderer = require('react-test-renderer'); + Context = React.createContext(null); }); class Example extends React.Component { @@ -40,6 +42,17 @@ describe('ReactTestRendererTraversal', () => { {}}> + + + + + {() => } + + + + + + ); @@ -61,7 +74,7 @@ describe('ReactTestRendererTraversal', () => { // assert .props, .type and .parent attributes const foo = render.root.find(hasFooProp); - expect(foo.props.children).toHaveLength(8); + expect(foo.props.children).toHaveLength(9); expect(foo.type).toBe(View); expect(render.root.parent).toBe(null); expect(foo.children[0].parent).toBe(foo); @@ -76,6 +89,7 @@ describe('ReactTestRendererTraversal', () => { const hasNullProp = node => node.props.hasOwnProperty('null'); const hasVoidProp = node => node.props.hasOwnProperty('void'); const hasItselfProp = node => node.props.hasOwnProperty('itself'); + const hasNestedProp = node => node.props.hasOwnProperty('nested'); expect(() => render.root.find(hasFooProp)).not.toThrow(); // 1 match expect(() => render.root.find(hasBarProp)).toThrow(); // >1 matches @@ -83,6 +97,7 @@ describe('ReactTestRendererTraversal', () => { expect(() => render.root.find(hasBingProp)).not.toThrow(); // 1 match expect(() => render.root.find(hasNullProp)).not.toThrow(); // 1 match expect(() => render.root.find(hasVoidProp)).toThrow(); // 0 matches + expect(() => render.root.find(hasNestedProp)).toThrow(); // >1 matches // same assertion as .find(), but confirm length expect(render.root.findAll(hasFooProp, {deep: false})).toHaveLength(1); @@ -91,6 +106,7 @@ describe('ReactTestRendererTraversal', () => { expect(render.root.findAll(hasBingProp, {deep: false})).toHaveLength(1); expect(render.root.findAll(hasNullProp, {deep: false})).toHaveLength(1); expect(render.root.findAll(hasVoidProp, {deep: false})).toHaveLength(0); + expect(render.root.findAll(hasNestedProp, {deep: false})).toHaveLength(3); // note: with {deep: true}, .findAll() will continue to // search children, even after finding a match @@ -100,6 +116,7 @@ describe('ReactTestRendererTraversal', () => { expect(render.root.findAll(hasBingProp)).toHaveLength(1); // no spread expect(render.root.findAll(hasNullProp)).toHaveLength(1); // no spread expect(render.root.findAll(hasVoidProp)).toHaveLength(0); + expect(render.root.findAll(hasNestedProp, {deep: false})).toHaveLength(3); const bing = render.root.find(hasBingProp); expect(bing.find(hasBarProp)).toBe(bing); @@ -130,7 +147,7 @@ describe('ReactTestRendererTraversal', () => { expect(render.root.findAllByType(ExampleFn)).toHaveLength(1); expect(render.root.findAllByType(View, {deep: false})).toHaveLength(1); - expect(render.root.findAllByType(View)).toHaveLength(8); + expect(render.root.findAllByType(View)).toHaveLength(11); expect(render.root.findAllByType(ExampleNull)).toHaveLength(2); expect(render.root.findAllByType(ExampleForwardRef)).toHaveLength(1); @@ -164,4 +181,22 @@ describe('ReactTestRendererTraversal', () => { expect(render.root.findAllByProps({baz})).toHaveLength(4); expect(render.root.findAllByProps({qux})).toHaveLength(3); }); + + it('skips special nodes', () => { + const render = ReactTestRenderer.create(); + expect(render.root.findAllByType(React.Fragment)).toHaveLength(0); + expect(render.root.findAllByType(Context.Consumer)).toHaveLength(0); + expect(render.root.findAllByType(Context.Provider)).toHaveLength(0); + + const expectedParent = render.root.findByProps({foo: 'foo'}, {deep: false}) + .children[0]; + const nestedViews = render.root.findAllByProps( + {nested: true}, + {deep: false}, + ); + expect(nestedViews.length).toBe(3); + expect(nestedViews[0].parent).toBe(expectedParent); + expect(nestedViews[1].parent).toBe(expectedParent); + expect(nestedViews[2].parent).toBe(expectedParent); + }); });