'use strict';
let PropTypes;
let React;
let ReactDOMClient;
let act;
let assertConsoleErrorDev;
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, assertConsoleErrorDev} = require('internal-test-utils'));
});
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" />);
});
assertConsoleErrorDev([
'GrandParent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
]);
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 />);
});
assertConsoleErrorDev([
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
'Child uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
]);
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();
});
});