/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
const React = require('react');
let ReactTestRenderer;
let Context;
const RCTView = 'RCTView';
const View = props => <RCTView {...props} />;
describe('ReactTestRendererTraversal', () => {
beforeEach(() => {
jest.resetModules();
ReactTestRenderer = require('react-test-renderer');
Context = React.createContext(null);
});
class Example extends React.Component {
render() {
return (
<View>
<View foo="foo">
<View bar="bar" />
<View bar="bar" baz="baz" itself="itself" />
<View />
<ExampleSpread bar="bar" />
<ExampleFn bar="bar" bing="bing" />
<ExampleNull bar="bar" />
<ExampleNull null="null">
<View void="void" />
<View void="void" />
</ExampleNull>
<React.Profiler id="test" onRender={() => {}}>
<ExampleForwardRef qux="qux" />
</React.Profiler>
<>
<>
<Context.Provider value={null}>
<Context.Consumer>
{() => <View nested={true} />}
</Context.Consumer>
</Context.Provider>
</>
<View nested={true} />
<View nested={true} />
</>
</View>
</View>
);
}
}
class ExampleSpread extends React.Component {
render = () => <View {...this.props} />;
}
const ExampleFn = props => <View baz="baz" />;
const ExampleNull = props => null;
const ExampleForwardRef = React.forwardRef((props, ref) => (
<View {...props} ref={ref} />
));
it('initializes', () => {
const render = ReactTestRenderer.create(<Example />);
const hasFooProp = node => node.props.hasOwnProperty('foo');
// assert .props, .type and .parent attributes
const foo = render.root.find(hasFooProp);
expect(foo.props.children).toHaveLength(9);
expect(foo.type).toBe(View);
expect(render.root.parent).toBe(null);
expect(foo.children[0].parent).toBe(foo);
});
it('searches via .find() / .findAll()', () => {
const render = ReactTestRenderer.create(<Example />);
const hasFooProp = node => node.props.hasOwnProperty('foo');
const hasBarProp = node => node.props.hasOwnProperty('bar');
const hasBazProp = node => node.props.hasOwnProperty('baz');
const hasBingProp = node => node.props.hasOwnProperty('bing');
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
expect(() => render.root.find(hasBazProp)).toThrow(); // >1 matches
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);
expect(render.root.findAll(hasBarProp, {deep: false})).toHaveLength(5);
expect(render.root.findAll(hasBazProp, {deep: false})).toHaveLength(2);
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
expect(render.root.findAll(hasFooProp)).toHaveLength(2);
expect(render.root.findAll(hasBarProp)).toHaveLength(9);
expect(render.root.findAll(hasBazProp)).toHaveLength(4);
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);
expect(bing.find(hasBingProp)).toBe(bing);
expect(bing.findAll(hasBazProp, {deep: false})).toHaveLength(1);
expect(bing.findAll(hasBazProp)).toHaveLength(2);
const foo = render.root.find(hasFooProp);
expect(foo.findAll(hasFooProp, {deep: false})).toHaveLength(1);
expect(foo.findAll(hasFooProp)).toHaveLength(2);
const itself = foo.find(hasItselfProp);
expect(itself.find(hasBarProp)).toBe(itself);
expect(itself.find(hasBazProp)).toBe(itself);
expect(itself.findAll(hasBazProp, {deep: false})).toHaveLength(1);
expect(itself.findAll(hasBazProp)).toHaveLength(2);
});
it('searches via .findByType() / .findAllByType()', () => {
const render = ReactTestRenderer.create(<Example />);
expect(() => render.root.findByType(ExampleFn)).not.toThrow(); // 1 match
expect(() => render.root.findByType(View)).not.toThrow(); // 1 match
expect(() => render.root.findByType(ExampleForwardRef)).not.toThrow(); // 1 match
// note: there are clearly multiple <View /> in general, but there
// is only one being rendered at root node level
expect(() => render.root.findByType(ExampleNull)).toThrow(); // 2 matches
expect(render.root.findAllByType(ExampleFn)).toHaveLength(1);
expect(render.root.findAllByType(View, {deep: false})).toHaveLength(1);
expect(render.root.findAllByType(View)).toHaveLength(11);
expect(render.root.findAllByType(ExampleNull)).toHaveLength(2);
expect(render.root.findAllByType(ExampleForwardRef)).toHaveLength(1);
const nulls = render.root.findAllByType(ExampleNull);
expect(nulls[0].findAllByType(View)).toHaveLength(0);
expect(nulls[1].findAllByType(View)).toHaveLength(0);
const fn = render.root.findAllByType(ExampleFn);
expect(fn[0].findAllByType(View)).toHaveLength(1);
});
it('searches via .findByProps() / .findAllByProps()', () => {
const render = ReactTestRenderer.create(<Example />);
const foo = 'foo';
const bar = 'bar';
const baz = 'baz';
const qux = 'qux';
expect(() => render.root.findByProps({foo})).not.toThrow(); // 1 match
expect(() => render.root.findByProps({bar})).toThrow(); // >1 matches
expect(() => render.root.findByProps({baz})).toThrow(); // >1 matches
expect(() => render.root.findByProps({qux})).not.toThrow(); // 1 match
expect(render.root.findAllByProps({foo}, {deep: false})).toHaveLength(1);
expect(render.root.findAllByProps({bar}, {deep: false})).toHaveLength(5);
expect(render.root.findAllByProps({baz}, {deep: false})).toHaveLength(2);
expect(render.root.findAllByProps({qux}, {deep: false})).toHaveLength(1);
expect(render.root.findAllByProps({foo})).toHaveLength(2);
expect(render.root.findAllByProps({bar})).toHaveLength(9);
expect(render.root.findAllByProps({baz})).toHaveLength(4);
expect(render.root.findAllByProps({qux})).toHaveLength(3);
});
it('skips special nodes', () => {
const render = ReactTestRenderer.create(<Example />);
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);
});
it('can have special nodes as roots', () => {
const FR = React.forwardRef((props, ref) => <section {...props} />);
expect(
ReactTestRenderer.create(
<FR>
<div />
<div />
</FR>,
).root.findAllByType('div').length,
).toBe(2);
expect(
ReactTestRenderer.create(
<>
<div />
<div />
</>,
).root.findAllByType('div').length,
).toBe(2);
expect(
ReactTestRenderer.create(
<React.Fragment key="foo">
<div />
<div />
</React.Fragment>,
).root.findAllByType('div').length,
).toBe(2);
expect(
ReactTestRenderer.create(
<React.StrictMode>
<div />
<div />
</React.StrictMode>,
).root.findAllByType('div').length,
).toBe(2);
expect(
ReactTestRenderer.create(
<Context.Provider value={null}>
<div />
<div />
</Context.Provider>,
).root.findAllByType('div').length,
).toBe(2);
});
});