'use strict';
let PropTypes;
let React;
let ReactDOMClient;
let act;
function FunctionComponent(props) {
return <div>{props.name}</div>;
}
describe('ReactFunctionComponent', () => {
beforeEach(() => {
jest.resetModules();
PropTypes = require('prop-types');
React = require('react');
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;
});
it('should render stateless component', async () => {
const el = document.createElement('div');
const root = ReactDOMClient.createRoot(el);
await act(() => {
root.render(<FunctionComponent name="A" />);
});
expect(el.textContent).toBe('A');
});
it('should update stateless component', async () => {
class Parent extends React.Component {
render() {
return <FunctionComponent {...this.props} />;
}
}
const el = document.createElement('div');
const root = ReactDOMClient.createRoot(el);
await act(() => {
root.render(<Parent name="A" />);
});
expect(el.textContent).toBe('A');
await act(() => {
root.render(<Parent name="B" />);
});
expect(el.textContent).toBe('B');
});
it('should unmount stateless component', async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<FunctionComponent name="A" />);
});
expect(container.textContent).toBe('A');
root.unmount();
expect(container.textContent).toBe('');
});
it('should pass context thru stateless component', async () => {
class Child extends React.Component {
static contextTypes = {
test: PropTypes.string.isRequired,
};
render() {
return <div>{this.context.test}</div>;
}
}
function Parent() {
return <Child />;
}
class GrandParent extends React.Component {
static childContextTypes = {
test: PropTypes.string.isRequired,
};
getChildContext() {
return {test: this.props.test};
}
render() {
return <Parent />;
}
}
const el = document.createElement('div');
const root = ReactDOMClient.createRoot(el);
await act(() => {
root.render(<GrandParent test="test" />);
});
expect(el.textContent).toBe('test');
await act(() => {
root.render(<GrandParent test="mest" />);
});
expect(el.textContent).toBe('mest');
});
it('should warn for getDerivedStateFromProps on a function component', async () => {
function FunctionComponentWithChildContext() {
return null;
}
FunctionComponentWithChildContext.getDerivedStateFromProps = function () {};
const container = document.createElement('div');
await expect(async () => {
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<FunctionComponentWithChildContext />);
});
}).toErrorDev(
'FunctionComponentWithChildContext: Function ' +
'components do not support getDerivedStateFromProps.',
);
});
it('should warn for childContextTypes on a function component', async () => {
function FunctionComponentWithChildContext(props) {
return <div>{props.name}</div>;
}
FunctionComponentWithChildContext.childContextTypes = {
foo: PropTypes.string,
};
const container = document.createElement('div');
await expect(async () => {
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<FunctionComponentWithChildContext name="A" />);
});
}).toErrorDev(
'childContextTypes cannot ' + 'be defined on a function component.',
);
});
it('should not throw when stateless component returns undefined', async () => {
function NotAComponent() {}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await expect(
act(() => {
root.render(
<div>
<NotAComponent />
</div>,
);
}),
).resolves.not.toThrowError();
});
it('should throw on string refs in pure functions', async () => {
function Child() {
return <div ref="me" />;
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await expect(
act(() => {
root.render(<Child test="test" />);
}),
)
.rejects.toThrowError();
});
it('should warn when given a string ref', async () => {
function Indirection(props) {
return <div>{props.children}</div>;
}
class ParentUsingStringRef extends React.Component {
render() {
return (
<Indirection>
<FunctionComponent name="A" ref="stateless" />
</Indirection>
);
}
}
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<ParentUsingStringRef />);
});
}).toErrorDev(
'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 `ParentUsingStringRef`.\n' +
' in FunctionComponent (at **)\n' +
' in div (at **)\n' +
' in Indirection (at **)\n' +
' in ParentUsingStringRef (at **)',
);
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<ParentUsingStringRef />);
});
});
it('should warn when given a function ref', async () => {
function Indirection(props) {
return <div>{props.children}</div>;
}
const ref = jest.fn();
class ParentUsingFunctionRef extends React.Component {
render() {
return (
<Indirection>
<FunctionComponent name="A" ref={ref} />
</Indirection>
);
}
}
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<ParentUsingFunctionRef />);
});
}).toErrorDev(
'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 `ParentUsingFunctionRef`.\n' +
' in FunctionComponent (at **)\n' +
' in div (at **)\n' +
' in Indirection (at **)\n' +
' in ParentUsingFunctionRef (at **)',
);
expect(ref).not.toHaveBeenCalled();
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<ParentUsingFunctionRef />);
});
});
it('deduplicates ref warnings based on element or owner', async () => {
class AnonymousParentUsingJSX extends React.Component {
render() {
return <FunctionComponent name="A" ref={() => {}} />;
}
}
let instance1;
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<AnonymousParentUsingJSX ref={current => (instance1 = current)} />,
);
});
}).toErrorDev('Function components cannot be given refs.');
instance1.forceUpdate();
let container = document.createElement('div');
let root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<AnonymousParentUsingJSX />);
});
class AnonymousParentNotUsingJSX extends React.Component {
render() {
return React.createElement(FunctionComponent, {
name: 'A',
ref: () => {},
});
}
}
let instance2;
await expect(async () => {
container = document.createElement('div');
root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<AnonymousParentNotUsingJSX ref={current => (instance2 = current)} />,
);
});
}).toErrorDev('Function components cannot be given refs.');
instance2.forceUpdate();
container = document.createElement('div');
root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<AnonymousParentNotUsingJSX />);
});
class NamedParentNotUsingJSX extends React.Component {
render() {
return React.createElement(FunctionComponent, {
name: 'A',
ref: () => {},
});
}
}
let instance3;
await expect(async () => {
container = document.createElement('div');
root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<NamedParentNotUsingJSX ref={current => (instance3 = current)} />,
);
});
}).toErrorDev('Function components cannot be given refs.');
instance3.forceUpdate();
container = document.createElement('div');
root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<NamedParentNotUsingJSX />);
});
});
it('should warn when giving a function ref with context', async () => {
function Child() {
return null;
}
Child.contextTypes = {
foo: PropTypes.string,
};
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string,
};
getChildContext() {
return {
foo: 'bar',
};
}
render() {
return <Child ref={function () {}} />;
}
}
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Parent />);
});
}).toErrorDev(
'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 `Parent`.\n' +
' in Child (at **)\n' +
' in Parent (at **)',
);
});
it('should use correct name in key warning', async () => {
function Child() {
return <div>{[<span />]}</div>;
}
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Child />);
});
}).toErrorDev(
'Each child in a list should have a unique "key" prop.\n\n' +
'Check the render method of `Child`.',
);
});
it('should support default props', async () => {
function Child(props) {
return <div>{props.test}</div>;
}
Child.defaultProps = {test: 2};
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Child />);
});
expect(container.textContent).toBe('2');
}).toErrorDev([
'Child: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
]);
});
it('should receive context', async () => {
class Parent extends React.Component {
static childContextTypes = {
lang: PropTypes.string,
};
getChildContext() {
return {lang: 'en'};
}
render() {
return <Child />;
}
}
function Child(props, context) {
return <div>{context.lang}</div>;
}
Child.contextTypes = {lang: PropTypes.string};
const el = document.createElement('div');
const root = ReactDOMClient.createRoot(el);
await act(() => {
root.render(<Parent />);
});
expect(el.textContent).toBe('en');
});
it('should work with arrow functions', async () => {
let Child = function () {
return <div />;
};
Child = Child.bind(this);
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Child />);
});
}).not.toThrow();
});
it('should allow simple functions to return null', async () => {
const Child = function () {
return null;
};
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Child />);
});
}).not.toThrow();
});
it('should allow simple functions to return false', async () => {
function Child() {
return false;
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await expect(
act(() => {
root.render(<Child />);
}),
).resolves.not.toThrow();
});
});