/**
* 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 ./scripts/jest/ReactDOMServerIntegrationEnvironment
*/
'use strict';
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let PropTypes;
let React;
let ReactDOM;
let ReactDOMServer;
let ReactTestUtils;
function initModules() {
// Reset warning cache.
jest.resetModules();
PropTypes = require('prop-types');
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
// Make them available to the helpers.
return {
ReactDOM,
ReactDOMServer,
ReactTestUtils,
};
}
const {resetModules, itRenders, itThrowsWhenRendering} =
ReactDOMServerIntegrationUtils(initModules);
describe('ReactDOMServerIntegration', () => {
beforeEach(() => {
resetModules();
});
describe('legacy context', function () {
// The `itRenders` test abstraction doesn't work with @gate so we have
// to do this instead.
if (gate(flags => flags.disableLegacyContext)) {
test('empty test to stop Jest from being a complainy complainer', () => {});
return;
}
let PurpleContext, RedContext;
beforeEach(() => {
class Parent extends React.Component {
getChildContext() {
return {text: this.props.text};
}
render() {
return this.props.children;
}
}
Parent.childContextTypes = {text: PropTypes.string};
PurpleContext = props => <Parent text="purple">{props.children}</Parent>;
RedContext = props => <Parent text="red">{props.children}</Parent>;
});
itRenders('class child with context', async render => {
class ClassChildWithContext extends React.Component {
render() {
return <div>{this.context.text}</div>;
}
}
ClassChildWithContext.contextTypes = {text: PropTypes.string};
const e = await render(
<PurpleContext>
<ClassChildWithContext />
</PurpleContext>,
);
expect(e.textContent).toBe('purple');
});
itRenders('stateless child with context', async render => {
function FunctionChildWithContext(props, context) {
return <div>{context.text}</div>;
}
FunctionChildWithContext.contextTypes = {text: PropTypes.string};
const e = await render(
<PurpleContext>
<FunctionChildWithContext />
</PurpleContext>,
);
expect(e.textContent).toBe('purple');
});
itRenders('class child without context', async render => {
class ClassChildWithoutContext extends React.Component {
render() {
// this should render blank; context isn't passed to this component.
return <div>{this.context.text}</div>;
}
}
const e = await render(
<PurpleContext>
<ClassChildWithoutContext />
</PurpleContext>,
);
expect(e.textContent).toBe('');
});
itRenders('stateless child without context', async render => {
function FunctionChildWithoutContext(props, context) {
// this should render blank; context isn't passed to this component.
return <div>{context.text}</div>;
}
const e = await render(
<PurpleContext>
<FunctionChildWithoutContext />
</PurpleContext>,
);
expect(e.textContent).toBe('');
});
itRenders('class child with wrong context', async render => {
class ClassChildWithWrongContext extends React.Component {
render() {
// this should render blank; context.text isn't passed to this component.
return <div id="classWrongChild">{this.context.text}</div>;
}
}
ClassChildWithWrongContext.contextTypes = {foo: PropTypes.string};
const e = await render(
<PurpleContext>
<ClassChildWithWrongContext />
</PurpleContext>,
);
expect(e.textContent).toBe('');
});
itRenders('stateless child with wrong context', async render => {
function FunctionChildWithWrongContext(props, context) {
// this should render blank; context.text isn't passed to this component.
return <div id="statelessWrongChild">{context.text}</div>;
}
FunctionChildWithWrongContext.contextTypes = {
foo: PropTypes.string,
};
const e = await render(
<PurpleContext>
<FunctionChildWithWrongContext />
</PurpleContext>,
);
expect(e.textContent).toBe('');
});
itRenders('with context passed through to a grandchild', async render => {
function Grandchild(props, context) {
return <div>{context.text}</div>;
}
Grandchild.contextTypes = {text: PropTypes.string};
const Child = props => <Grandchild />;
const e = await render(
<PurpleContext>
<Child />
</PurpleContext>,
);
expect(e.textContent).toBe('purple');
});
itRenders('a child context overriding a parent context', async render => {
const Grandchild = (props, context) => {
return <div>{context.text}</div>;
};
Grandchild.contextTypes = {text: PropTypes.string};
const e = await render(
<PurpleContext>
<RedContext>
<Grandchild />
</RedContext>
</PurpleContext>,
);
expect(e.textContent).toBe('red');
});
itRenders('a child context merged with a parent context', async render => {
class Parent extends React.Component {
getChildContext() {
return {text1: 'purple'};
}
render() {
return <Child />;
}
}
Parent.childContextTypes = {text1: PropTypes.string};
class Child extends React.Component {
getChildContext() {
return {text2: 'red'};
}
render() {
return <Grandchild />;
}
}
Child.childContextTypes = {text2: PropTypes.string};
const Grandchild = (props, context) => {
return (
<div>
<div id="first">{context.text1}</div>
<div id="second">{context.text2}</div>
</div>
);
};
Grandchild.contextTypes = {
text1: PropTypes.string,
text2: PropTypes.string,
};
const e = await render(<Parent />);
expect(e.querySelector('#first').textContent).toBe('purple');
expect(e.querySelector('#second').textContent).toBe('red');
});
itRenders(
'with a call to componentWillMount before getChildContext',
async render => {
class WillMountContext extends React.Component {
getChildContext() {
return {text: this.state.text};
}
UNSAFE_componentWillMount() {
this.setState({text: 'foo'});
}
render() {
return <Child />;
}
}
WillMountContext.childContextTypes = {text: PropTypes.string};
const Child = (props, context) => {
return <div>{context.text}</div>;
};
Child.contextTypes = {text: PropTypes.string};
const e = await render(<WillMountContext />);
expect(e.textContent).toBe('foo');
},
);
itRenders(
'if getChildContext exists but childContextTypes is missing with a warning',
async render => {
function HopefulChild(props, context) {
return context.foo || 'nope';
}
HopefulChild.contextTypes = {
foo: PropTypes.string,
};
class ForgetfulParent extends React.Component {
render() {
return <HopefulChild />;
}
getChildContext() {
return {foo: 'bar'};
}
}
const e = await render(<ForgetfulParent />, 1);
expect(e.textContent).toBe('nope');
},
);
itThrowsWhenRendering(
'if getChildContext returns a value not in childContextTypes',
render => {
class MyComponent extends React.Component {
render() {
return <div />;
}
getChildContext() {
return {value1: 'foo', value2: 'bar'};
}
}
MyComponent.childContextTypes = {value1: PropTypes.string};
return render(<MyComponent />);
},
'MyComponent.getChildContext(): key "value2" is not defined in childContextTypes.',
);
it('warns when childContextTypes is not defined', () => {
class MyComponent extends React.Component {
render() {
return <div />;
}
getChildContext() {
return {value1: 'foo', value2: 'bar'};
}
}
expect(() => {
ReactDOMServer.renderToString(<MyComponent />);
}).toErrorDev(
'Warning: MyComponent.getChildContext(): childContextTypes must be defined in order to use getChildContext().\n' +
' in MyComponent (at **)',
);
});
});
});