/*** 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 ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
const React = require('react');
const ReactTestRenderer = require('react-test-renderer');
const {format: prettyFormat} = require('pretty-format');
// Isolate noop rendererjest.resetModules();
const ReactNoop = require('react-noop-renderer');
const InternalTestUtils = require('internal-test-utils');
const waitForAll = InternalTestUtils.waitForAll;
// Kind of hacky, but we nullify all the instances to test the tree structure// with jasmine's deep equality function, and test the instances separate. We// also delete children props because testing them is more annoying and not// really important to verify.function cleanNodeOrArray(node) {
if (!node) {
return;
}if (Array.isArray(node)) {
node.forEach(cleanNodeOrArray);
return;
}if (node && node.instance) {
node.instance = null;
}if (node && node.props && node.props.children) {
// eslint-disable-next-line no-unused-vars
const {children, ...props} = node.props;
node.props = props;
}if (Array.isArray(node.rendered)) {
node.rendered.forEach(cleanNodeOrArray);
} else if (typeof node.rendered === 'object') {
cleanNodeOrArray(node.rendered);
}}describe('ReactTestRenderer', () => {
it('renders a simple component', () => {
function Link() {
return <a role="link" />;
}const renderer = ReactTestRenderer.create(<Link />);
expect(renderer.toJSON()).toEqual({
type: 'a',
props: {role: 'link'},
children: null,
});});it('renders a top-level empty component', () => {
function Empty() {
return null;
}const renderer = ReactTestRenderer.create(<Empty />);
expect(renderer.toJSON()).toEqual(null);
});it('exposes a type flag', () => {
function Link() {
return <a role="link" />;
}const renderer = ReactTestRenderer.create(<Link />);
const object = renderer.toJSON();
expect(object.$$typeof).toBe(Symbol.for('react.test.json'));
// $$typeof should not be enumerable.
for (const key in object) {
if (object.hasOwnProperty(key)) {
expect(key).not.toBe('$$typeof');
}}});it('can render a composite component', () => {
class Component extends React.Component {
render() {
return (
<div className="purple">
<Child />
</div>
);}}const Child = () => {return <moo />;
};const renderer = ReactTestRenderer.create(<Component />);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {className: 'purple'},
children: [{type: 'moo', props: {}, children: null}],
});});it('renders some basics with an update', () => {
let renders = 0;
class Component extends React.Component {
state = {x: 3};render() {
renders++;
return (
<div className="purple">
{this.state.x}
<Child />
<Null />
</div>
);}componentDidMount() {this.setState({x: 7});
}}const Child = () => {renders++;
return <moo />;
};const Null = () => {
renders++;
return null;
};const renderer = ReactTestRenderer.create(<Component />);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {className: 'purple'},
children: ['7', {type: 'moo', props: {}, children: null}],
});expect(renders).toBe(6);
});it('exposes the instance', () => {
class Mouse extends React.Component {
constructor() {
super();
this.state = {mouse: 'mouse'};
}handleMoose() {
this.setState({mouse: 'moose'});
}render() {
return <div>{this.state.mouse}</div>;
}}const renderer = ReactTestRenderer.create(<Mouse />);
expect(renderer.toJSON()).toEqual({
type: 'div',props: {},children: ['mouse'],
});const mouse = renderer.getInstance();
mouse.handleMoose();
expect(renderer.toJSON()).toEqual({
type: 'div',children: ['moose'],
props: {},});});it('updates types', () => {const renderer = ReactTestRenderer.create(<div>mouse</div>);
expect(renderer.toJSON()).toEqual({
type: 'div',props: {},children: ['mouse'],
});renderer.update(<span>mice</span>);
expect(renderer.toJSON()).toEqual({
type: 'span',props: {},children: ['mice'],
});});it('updates children', () => {const renderer = ReactTestRenderer.create(
<div><span key="a">A</span><span key="b">B</span><span key="c">C</span></div>,);expect(renderer.toJSON()).toEqual({
type: 'div',props: {},children: [
{type: 'span', props: {}, children: ['A']},
{type: 'span', props: {}, children: ['B']},
{type: 'span', props: {}, children: ['C']},
],});renderer.update(
<div><span key="d">D</span><span key="c">C</span><span key="b">B</span></div>,);expect(renderer.toJSON()).toEqual({
type: 'div',props: {},children: [
{type: 'span', props: {}, children: ['D']},
{type: 'span', props: {}, children: ['C']},
{type: 'span', props: {}, children: ['B']},
],});});it('does the full lifecycle', () => {const log = [];
class Log extends React.Component {
render() {log.push('render ' + this.props.name);
return <div />;}componentDidMount() {log.push('mount ' + this.props.name);
}componentWillUnmount() {log.push('unmount ' + this.props.name);
}}const renderer = ReactTestRenderer.create(<Log key="foo" name="Foo" />);
renderer.update(<Log key="bar" name="Bar" />);
renderer.unmount();
expect(log).toEqual([
'render Foo','mount Foo','render Bar','unmount Foo','mount Bar','unmount Bar',]);
});it('gives a ref to native components', () => {const log = [];
ReactTestRenderer.create(<div ref={r => log.push(r)} />);
expect(log).toEqual([null]);
});it('warns correctly for refs on SFCs', () => {function Bar() {return <div>Hello, world</div>;}class Foo extends React.Component {
fooRef = React.createRef();
render() {return <Bar ref={this.fooRef} />;
}}class Baz extends React.Component {
bazRef = React.createRef();
render() {return <div ref={this.bazRef} />;
}}ReactTestRenderer.create(<Baz />);
expect(() => ReactTestRenderer.create(<Foo />)).toErrorDev(
'Warning: Function components cannot be given refs. Attempts ' +
'to access this ref will fail. ' +
'Did you mean to use React.forwardRef()?\n\n' +
'Check the render method of `Foo`.\n' +
' in Bar (at **)\n' +
' in Foo (at **)',
);});it('allows an optional createNodeMock function', () => {const mockDivInstance = {appendChild: () => {}};const mockInputInstance = {focus: () => {}};const mockListItemInstance = {click: () => {}};const mockAnchorInstance = {hover: () => {}};const log = [];
class Foo extends React.Component {
barRef = React.createRef();
componentDidMount() {log.push(this.barRef.current);
}render() {return <a ref={this.barRef}>Hello, world</a>;
}}function createNodeMock(element) {switch (element.type) {
case 'div':return mockDivInstance;case 'input':return mockInputInstance;case 'li':return mockListItemInstance;case 'a':return mockAnchorInstance;default:return {};}}ReactTestRenderer.create(<div ref={r => log.push(r)} />, {createNodeMock});
ReactTestRenderer.create(<input ref={r => log.push(r)} />, {
createNodeMock,});ReactTestRenderer.create(
<div><span><ul><li ref={r => log.push(r)} />
</ul><ul><li ref={r => log.push(r)} />
<li ref={r => log.push(r)} />
</ul></span></div>,{createNodeMock, foobar: true},);ReactTestRenderer.create(<Foo />, {createNodeMock});
ReactTestRenderer.create(<div ref={r => log.push(r)} />);
ReactTestRenderer.create(<div ref={r => log.push(r)} />, {});
expect(log).toEqual([
mockDivInstance,mockInputInstance,mockListItemInstance,mockListItemInstance,mockListItemInstance,mockAnchorInstance,null,null,]);
});it('supports unmounting when using refs', () => {class Foo extends React.Component {
render() {return <div ref={React.createRef()} />;
}}const inst = ReactTestRenderer.create(<Foo />, {
createNodeMock: () => 'foo',});expect(() => inst.unmount()).not.toThrow();
});it('supports unmounting inner instances', () => {let count = 0;class Foo extends React.Component {
componentWillUnmount() {count++;
}render() {return <div />;}}const inst = ReactTestRenderer.create(
<div><Foo /></div>,{createNodeMock: () => 'foo',},);expect(() => inst.unmount()).not.toThrow();
expect(count).toEqual(1);
});it('supports updates when using refs', () => {const log = [];
const createNodeMock = element => {log.push(element.type);
return element.type;
};class Foo extends React.Component {
render() {return this.props.useDiv ? (
<div ref={React.createRef()} />
) : (<span ref={React.createRef()} />
);}}const inst = ReactTestRenderer.create(<Foo useDiv={true} />, {
createNodeMock,});inst.update(<Foo useDiv={false} />);
expect(log).toEqual(['div', 'span']);
});it('supports error boundaries', () => {const log = [];
class Angry extends React.Component {
render() {log.push('Angry render');
throw new Error('Please, do not render me.');
}componentDidMount() {log.push('Angry componentDidMount');
}componentWillUnmount() {log.push('Angry componentWillUnmount');
}}class Boundary extends React.Component {
constructor(props) {super(props);this.state = {error: false};
}render() {log.push('Boundary render');
if (!this.state.error) {
return (<div><button onClick={this.onClick}>ClickMe</button>
<Angry /></div>);} else {return <div>Happy Birthday!</div>;}}componentDidMount() {log.push('Boundary componentDidMount');
}componentWillUnmount() {log.push('Boundary componentWillUnmount');
}onClick() {/* do nothing */
}componentDidCatch() {log.push('Boundary componentDidCatch');
this.setState({error: true});
}}const renderer = ReactTestRenderer.create(<Boundary />);
expect(renderer.toJSON()).toEqual({
type: 'div',props: {},children: ['Happy Birthday!'],
});expect(log).toEqual([
'Boundary render','Angry render','Boundary componentDidMount','Boundary componentDidCatch','Boundary render',]);
});it('can update text nodes', () => {class Component extends React.Component {
render() {return <div>{this.props.children}</div>;
}}const renderer = ReactTestRenderer.create(<Component>Hi</Component>);
expect(renderer.toJSON()).toEqual({
type: 'div',children: ['Hi'],
props: {},});renderer.update(<Component>{['Hi', 'Bye']}</Component>);
expect(renderer.toJSON()).toEqual({
type: 'div',children: ['Hi', 'Bye'],
props: {},});renderer.update(<Component>Bye</Component>);
expect(renderer.toJSON()).toEqual({
type: 'div',children: ['Bye'],
props: {},});renderer.update(<Component>{42}</Component>);
expect(renderer.toJSON()).toEqual({
type: 'div',children: ['42'],
props: {},});renderer.update(
<Component><div /></Component>,);expect(renderer.toJSON()).toEqual({
type: 'div',children: [
{type: 'div',children: null,props: {},},],
props: {},});});it('toTree() renders simple components returning host components', () => {const Qoo = () => <span className="Qoo">Hello World!</span>;const renderer = ReactTestRenderer.create(<Qoo />);
const tree = renderer.toTree();
cleanNodeOrArray(tree);expect(prettyFormat(tree)).toEqual(
prettyFormat({nodeType: 'component',type: Qoo,props: {},instance: null,rendered: {nodeType: 'host',type: 'span',props: {className: 'Qoo'},instance: null,rendered: ['Hello World!'],
},}),);});it('toTree() handles nested Fragments', () => {const Foo = () => (<><>foo</></>);const renderer = ReactTestRenderer.create(<Foo />);
const tree = renderer.toTree();
cleanNodeOrArray(tree);expect(prettyFormat(tree)).toEqual(
prettyFormat({nodeType: 'component',type: Foo,instance: null,props: {},rendered: 'foo',}),);});it('toTree() handles null rendering components', () => {class Foo extends React.Component {
render() {return null;}}const renderer = ReactTestRenderer.create(<Foo />);
const tree = renderer.toTree();
expect(tree.instance).toBeInstanceOf(Foo);
cleanNodeOrArray(tree);expect(tree).toEqual({
type: Foo,nodeType: 'component',props: {},instance: null,rendered: null,});});it('toTree() handles simple components that return arrays', () => {const Foo = ({children}) => children;const renderer = ReactTestRenderer.create(
<Foo><div>One</div><div>Two</div></Foo>,);const tree = renderer.toTree();
cleanNodeOrArray(tree);expect(prettyFormat(tree)).toEqual(
prettyFormat({type: Foo,nodeType: 'component',props: {},instance: null,rendered: [
{instance: null,nodeType: 'host',props: {},rendered: ['One'],
type: 'div',},{instance: null,nodeType: 'host',props: {},rendered: ['Two'],
type: 'div',},],}),);});it('toTree() handles complicated tree of arrays', () => {class Foo extends React.Component {
render() {return this.props.children;
}}const renderer = ReactTestRenderer.create(
<div><Foo><div>One</div><div>Two</div><Foo><div>Three</div></Foo></Foo><div>Four</div></div>,);const tree = renderer.toTree();
cleanNodeOrArray(tree);expect(prettyFormat(tree)).toEqual(
prettyFormat({type: 'div',instance: null,nodeType: 'host',props: {},rendered: [
{type: Foo,nodeType: 'component',props: {},instance: null,rendered: [{type: 'div',nodeType: 'host',props: {},instance: null,rendered: ['One'],
},{type: 'div',nodeType: 'host',props: {},instance: null,rendered: ['Two'],
},{type: Foo,nodeType: 'component',props: {},instance: null,rendered: {type: 'div',nodeType: 'host',props: {},instance: null,rendered: ['Three'],
},},],},{type: 'div',nodeType: 'host',props: {},instance: null,rendered: ['Four'],
},],}),);});it('toTree() handles complicated tree of fragments', () => {const renderer = ReactTestRenderer.create(
<><><div>One</div><div>Two</div><><div>Three</div></></><div>Four</div></>,);const tree = renderer.toTree();
cleanNodeOrArray(tree);expect(prettyFormat(tree)).toEqual(
prettyFormat([
{type: 'div',nodeType: 'host',props: {},instance: null,rendered: ['One'],
},{type: 'div',nodeType: 'host',props: {},instance: null,rendered: ['Two'],
},{type: 'div',nodeType: 'host',props: {},instance: null,rendered: ['Three'],
},{type: 'div',nodeType: 'host',props: {},instance: null,rendered: ['Four'],
},]),);});it('root instance and createNodeMock ref return the same value', () => {const createNodeMock = ref => ({node: ref});let refInst = null;const renderer = ReactTestRenderer.create(
<div ref={ref => (refInst = ref)} />,{createNodeMock},);const root = renderer.getInstance();
expect(root).toEqual(refInst);
});it('toTree() renders complicated trees of composites and hosts', () => {// SFC returning host. no children props.
const Qoo = () => <span className="Qoo">Hello World!</span>;// SFC returning host. passes through children.
const Foo = ({className, children}) => (<div className={'Foo ' + className}>
<span className="Foo2">Literal</span>{children}</div>);// class composite returning composite. passes through children.
class Bar extends React.Component {
render() {const {special, children} = this.props;
return <Foo className={special ? 'special' : 'normal'}>{children}</Foo>;
}}// class composite return composite. no children props.
class Bam extends React.Component {
render() {return (<Bar special={true}><Qoo /></Bar>);}}const renderer = ReactTestRenderer.create(<Bam />);
const tree = renderer.toTree();
// we test for the presence of instances before nulling them outexpect(tree.instance).toBeInstanceOf(Bam);
expect(tree.rendered.instance).toBeInstanceOf(Bar);
cleanNodeOrArray(tree);expect(prettyFormat(tree)).toEqual(
prettyFormat({type: Bam,nodeType: 'component',props: {},instance: null,rendered: {type: Bar,nodeType: 'component',props: {special: true},instance: null,rendered: {type: Foo,nodeType: 'component',props: {className: 'special'},instance: null,rendered: {type: 'div',nodeType: 'host',props: {className: 'Foo special'},instance: null,rendered: [
{type: 'span',nodeType: 'host',props: {className: 'Foo2'},instance: null,rendered: ['Literal'],
},{type: Qoo,nodeType: 'component',props: {},instance: null,rendered: {type: 'span',nodeType: 'host',props: {className: 'Qoo'},instance: null,rendered: ['Hello World!'],
},},],},},},}),);});it('can update text nodes when rendered as root', () => {const renderer = ReactTestRenderer.create(['Hello', 'world']);
expect(renderer.toJSON()).toEqual(['Hello', 'world']);
renderer.update(42);
expect(renderer.toJSON()).toEqual('42');
renderer.update([42, 'world']);
expect(renderer.toJSON()).toEqual(['42', 'world']);
});it('can render and update root fragments', () => {const Component = props => props.children;
const renderer = ReactTestRenderer.create([
<Component key="a">Hi</Component>,<Component key="b">Bye</Component>,]);
expect(renderer.toJSON()).toEqual(['Hi', 'Bye']);
renderer.update(<div />);
expect(renderer.toJSON()).toEqual({
type: 'div',children: null,props: {},});renderer.update([<div key="a">goodbye</div>, 'world']);
expect(renderer.toJSON()).toEqual([
{type: 'div',children: ['goodbye'],
props: {},},'world',]);});it('supports context providers and consumers', () => {const {Consumer, Provider} = React.createContext('a');
function Child(props) {return props.value;
}function App() {return (<Provider value="b"><Consumer>{value => <Child value={value} />}</Consumer></Provider>);}const renderer = ReactTestRenderer.create(<App />);
const child = renderer.root.findByType(Child);
expect(child.children).toEqual(['b']);
expect(prettyFormat(renderer.toTree())).toEqual(
prettyFormat({instance: null,nodeType: 'component',props: {},rendered: {instance: null,nodeType: 'component',props: {value: 'b',},rendered: 'b',type: Child,},type: App,}),);});it('supports modes', () => {function Child(props) {return props.value;
}function App(props) {return (<React.StrictMode>
<Child value={props.value} />
</React.StrictMode>
);}const renderer = ReactTestRenderer.create(<App value="a" />);
const child = renderer.root.findByType(Child);
expect(child.children).toEqual(['a']);
expect(prettyFormat(renderer.toTree())).toEqual(
prettyFormat({instance: null,nodeType: 'component',props: {value: 'a',},rendered: {instance: null,nodeType: 'component',props: {value: 'a',},rendered: 'a',type: Child,},type: App,}),);});it('supports forwardRef', () => {const InnerRefed = React.forwardRef((props, ref) => (
<div><span ref={ref} /></div>));class App extends React.Component {
render() {return <InnerRefed ref={r => (this.ref = r)} />;
}}const renderer = ReactTestRenderer.create(<App />);
const tree = renderer.toTree();
cleanNodeOrArray(tree);expect(prettyFormat(tree)).toEqual(
prettyFormat({instance: null,nodeType: 'component',props: {},rendered: {instance: null,nodeType: 'host',props: {},rendered: [
{instance: null,nodeType: 'host',props: {},rendered: [],
type: 'span',},],type: 'div',},type: App,}),);});it('can concurrently render context with a "primary" renderer', async () => {const Context = React.createContext(null);
const Indirection = React.Fragment;
const App = () => (<Context.Provider value={null}>
<Indirection><Context.Consumer>{() => null}</Context.Consumer>
</Indirection></Context.Provider>
);ReactNoop.render(<App />);
await waitForAll([]);
ReactTestRenderer.create(<App />);
});it('calling findByType() with an invalid component will fall back to "Unknown" for component name', () => {const App = () => null;const renderer = ReactTestRenderer.create(<App />);
const NonComponent = {};expect(() => {renderer.root.findByType(NonComponent);
}).toThrowError(`No instances found with node type: "Unknown"`);
});});