/*** 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*/'use strict';
let React = require('react');
let useContext;
let ReactNoop;
let Scheduler;
let gen;
let waitForAll;
let waitFor;
let waitForThrow;
describe('ReactNewContext', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
useContext = React.useContext;
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
gen = require('random-seed');
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
waitFor = InternalTestUtils.waitFor;
waitForThrow = InternalTestUtils.waitForThrow;
});afterEach(() => {
jest.restoreAllMocks();
});function Text(props) {
Scheduler.log(props.text);
return <span prop={props.text} />;
}function span(prop) {
return {type: 'span', children: [], prop, hidden: false};
}function readContext(Context) {
const dispatcher =
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.ReactCurrentDispatcher.current;return dispatcher.readContext(Context);
}// Note: This is based on a similar component we use in www. We can delete
// once the extra div wrapper is no longer necessary.
function LegacyHiddenDiv({children, mode}) {
return (
<div hidden={mode === 'hidden'}>
<React.unstable_LegacyHidden
mode={mode === 'hidden' ? 'unstable-defer-without-hiding' : mode}>
{children}
</React.unstable_LegacyHidden>
</div>
);
}// We have several ways of reading from context. sharedContextTests runs
// a suite of tests for a given context consumer implementation.
sharedContextTests('Context.Consumer', Context => Context.Consumer);
sharedContextTests(
'useContext inside function component',
Context =>
function Consumer(props) {
const contextValue = useContext(Context);
const render = props.children;
return render(contextValue);
},);sharedContextTests('useContext inside forwardRef component', Context =>
React.forwardRef(function Consumer(props, ref) {
const contextValue = useContext(Context);
const render = props.children;
return render(contextValue);
}),);sharedContextTests('useContext inside memoized function component', Context =>
React.memo(function Consumer(props) {
const contextValue = useContext(Context);
const render = props.children;
return render(contextValue);
}),);sharedContextTests(
'readContext(Context) inside class component',
Context =>
class Consumer extends React.Component {
render() {
const contextValue = readContext(Context);
const render = this.props.children;
return render(contextValue);
}},
);sharedContextTests(
'readContext(Context) inside pure class component',
Context =>
class Consumer extends React.PureComponent {
render() {
const contextValue = readContext(Context);
const render = this.props.children;
return render(contextValue);
}},
);function sharedContextTests(label, getConsumer) {
describe(`reading context with ${label}`, () => {
it('simple mount and update', async () => {
const Context = React.createContext(1);
const Consumer = getConsumer(Context);
const Indirection = React.Fragment;
function App(props) {
return (
<Context.Provider value={props.value}>
<Indirection>
<Indirection>
<Consumer>
{value => <span prop={'Result: ' + value} />}
</Consumer>
</Indirection>
</Indirection>
</Context.Provider>
);
}ReactNoop.render(<App value={2} />);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 2" />);
// Update
ReactNoop.render(<App value={3} />);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 3" />);
});it('propagates through shouldComponentUpdate false', async () => {
const Context = React.createContext(1);
const ContextConsumer = getConsumer(Context);
function Provider(props) {
Scheduler.log('Provider');
return (
<Context.Provider value={props.value}>
{props.children}
</Context.Provider>
);}function Consumer(props) {Scheduler.log('Consumer');
return (<ContextConsumer>{value => {Scheduler.log('Consumer render prop');
return <span prop={'Result: ' + value} />;
}}</ContextConsumer>);}class Indirection extends React.Component {
shouldComponentUpdate() {return false;}render() {Scheduler.log('Indirection');
return this.props.children;
}}function App(props) {Scheduler.log('App');
return (<Provider value={props.value}>
<Indirection><Indirection><Consumer /></Indirection></Indirection></Provider>);}ReactNoop.render(<App value={2} />);
await waitForAll([
'App','Provider','Indirection','Indirection','Consumer','Consumer render prop',]);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 2" />);
// Update
ReactNoop.render(<App value={3} />);
await waitForAll(['App', 'Provider', 'Consumer render prop']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 3" />);
});it('consumers bail out if context value is the same', async () => {
const Context = React.createContext(1);
const ContextConsumer = getConsumer(Context);
function Provider(props) {
Scheduler.log('Provider');
return (
<Context.Provider value={props.value}>
{props.children}
</Context.Provider>
);}function Consumer(props) {Scheduler.log('Consumer');
return (<ContextConsumer>{value => {Scheduler.log('Consumer render prop');
return <span prop={'Result: ' + value} />;
}}</ContextConsumer>);}class Indirection extends React.Component {
shouldComponentUpdate() {return false;}render() {Scheduler.log('Indirection');
return this.props.children;
}}function App(props) {Scheduler.log('App');
return (<Provider value={props.value}>
<Indirection><Indirection><Consumer /></Indirection></Indirection></Provider>);}ReactNoop.render(<App value={2} />);
await waitForAll([
'App','Provider','Indirection','Indirection','Consumer','Consumer render prop',]);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 2" />);
// Update with the same context value
ReactNoop.render(<App value={2} />);
await waitForAll([
'App',
'Provider',
// Don't call render prop again
]);expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 2" />);
});it('nested providers', async () => {
const Context = React.createContext(1);
const Consumer = getConsumer(Context);
function Provider(props) {
return (
<Consumer>
{contextValue => (// Multiply previous context value by 2, unless prop overrides
<Context.Provider value={props.value || contextValue * 2}>
{props.children}
</Context.Provider>
)}</Consumer>
);
}class Indirection extends React.Component {
shouldComponentUpdate() {
return false;
}render() {
return this.props.children;
}}function App(props) {
return (
<Provider value={props.value}>
<Indirection>
<Provider>
<Indirection>
<Provider>
<Indirection>
<Consumer>
{value => <span prop={'Result: ' + value} />}
</Consumer>
</Indirection>
</Provider>
</Indirection>
</Provider>
</Indirection>
</Provider>
);}ReactNoop.render(<App value={2} />);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 8" />);
// Update
ReactNoop.render(<App value={3} />);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 12" />);
});it('should provide the correct (default) values to consumers outside of a provider', async () => {
const FooContext = React.createContext({value: 'foo-initial'});
const BarContext = React.createContext({value: 'bar-initial'});
const FooConsumer = getConsumer(FooContext);
const BarConsumer = getConsumer(BarContext);
const Verify = ({actual, expected}) => {
expect(expected).toBe(actual);
return null;
};ReactNoop.render(
<><BarContext.Provider value={{value: 'bar-updated'}}>
<BarConsumer>
{({value}) => <Verify actual={value} expected="bar-updated" />}
</BarConsumer>
<FooContext.Provider value={{value: 'foo-updated'}}>
<FooConsumer>{({value}) => (<Verify actual={value} expected="foo-updated" />)}</FooConsumer>
</FooContext.Provider>
</BarContext.Provider>
<FooConsumer>
{({value}) => <Verify actual={value} expected="foo-initial" />}
</FooConsumer>
<BarConsumer>{({value}) => <Verify actual={value} expected="bar-initial" />}
</BarConsumer>
</>,
);await waitForAll([]);
});it('multiple consumers in different branches', async () => {
const Context = React.createContext(1);
const Consumer = getConsumer(Context);
function Provider(props) {
return (
<Context.Consumer>
{contextValue => (// Multiply previous context value by 2, unless prop overrides
<Context.Provider value={props.value || contextValue * 2}>
{props.children}
</Context.Provider>
)}</Context.Consumer>
);
}class Indirection extends React.Component {
shouldComponentUpdate() {
return false;
}render() {
return this.props.children;
}}function App(props) {
return (
<Provider value={props.value}>
<Indirection>
<Indirection>
<Provider>
<Consumer>
{value => <span prop={'Result: ' + value} />}
</Consumer>
</Provider>
</Indirection>
<Indirection><Consumer>{value => <span prop={'Result: ' + value} />}
</Consumer>
</Indirection>
</Indirection>
</Provider>
);
}ReactNoop.render(<App value={2} />);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(
<><span prop="Result: 4" />
<span prop="Result: 2" />
</>,
);// Update
ReactNoop.render(<App value={3} />);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(
<><span prop="Result: 6" />
<span prop="Result: 3" />
</>,
);// Another update
ReactNoop.render(<App value={4} />);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(
<><span prop="Result: 8" />
<span prop="Result: 4" />
</>,
);});it('compares context values with Object.is semantics', async () => {
const Context = React.createContext(1);
const ContextConsumer = getConsumer(Context);function Provider(props) {Scheduler.log('Provider');
return (<Context.Provider value={props.value}>
{props.children}
</Context.Provider>
);}function Consumer(props) {Scheduler.log('Consumer');
return (<ContextConsumer>{value => {Scheduler.log('Consumer render prop');
return <span prop={'Result: ' + value} />;
}}</ContextConsumer>);}class Indirection extends React.Component {
shouldComponentUpdate() {return false;}render() {Scheduler.log('Indirection');
return this.props.children;
}}function App(props) {Scheduler.log('App');
return (<Provider value={props.value}>
<Indirection><Indirection><Consumer /></Indirection></Indirection></Provider>);}ReactNoop.render(<App value={NaN} />);
await waitForAll([
'App','Provider','Indirection','Indirection','Consumer','Consumer render prop',]);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: NaN" />);
// UpdateReactNoop.render(<App value={NaN} />);
await waitForAll([
'App','Provider',// Consumer should not re-render again// 'Consumer render prop',]);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: NaN" />);
});it('context unwinds when interrupted', async () => {const Context = React.createContext('Default');
const ContextConsumer = getConsumer(Context);function Consumer(props) {return (<ContextConsumer>{value => <span prop={'Result: ' + value} />}
</ContextConsumer>);}function BadRender() {throw new Error('Bad render');}class ErrorBoundary extends React.Component {
state = {error: null};componentDidCatch(error) {this.setState({error});
}render() {if (this.state.error) {
return null;}return this.props.children;
}}function App(props) {return (<><Context.Provider value="Does not unwind">
<ErrorBoundary><Context.Provider value="Unwinds after BadRender throws">
<BadRender /></Context.Provider>
</ErrorBoundary><Consumer /></Context.Provider>
</>);}ReactNoop.render(<App value="A" />);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(
// The second provider should use the default value.
<span prop="Result: Does not unwind" />,);});it("does not re-render if there's an update in a child", async () => {const Context = React.createContext(0);
const Consumer = getConsumer(Context);let child;class Child extends React.Component {
state = {step: 0};render() {Scheduler.log('Child');
return (<spanprop={`Context: ${this.props.context}, Step: ${this.state.step}`}
/>);}}function App(props) {return (<Context.Provider value={props.value}>
<Consumer>{value => {Scheduler.log('Consumer render prop');
return <Child ref={inst => (child = inst)} context={value} />;}}</Consumer></Context.Provider>
);}// Initial mountReactNoop.render(<App value={1} />);
await waitForAll(['Consumer render prop', 'Child']);
expect(ReactNoop).toMatchRenderedOutput(
<span prop="Context: 1, Step: 0" />,);child.setState({step: 1});
await waitForAll(['Child']);
expect(ReactNoop).toMatchRenderedOutput(
<span prop="Context: 1, Step: 1" />,);});it('consumer bails out if value is unchanged and something above bailed out', async () => {const Context = React.createContext(0);
const Consumer = getConsumer(Context);function renderChildValue(value) {Scheduler.log('Consumer');
return <span prop={value} />;}function ChildWithInlineRenderCallback() {Scheduler.log('ChildWithInlineRenderCallback');
// Note: we are intentionally passing an inline arrow. Don't refactor.
return <Consumer>{value => renderChildValue(value)}</Consumer>;}function ChildWithCachedRenderCallback() {Scheduler.log('ChildWithCachedRenderCallback');
return <Consumer>{renderChildValue}</Consumer>;}class PureIndirection extends React.PureComponent {
render() {Scheduler.log('PureIndirection');
return (<><ChildWithInlineRenderCallback /><ChildWithCachedRenderCallback /></>);}}class App extends React.Component {
render() {Scheduler.log('App');
return (<Context.Provider value={this.props.value}>
<PureIndirection /></Context.Provider>
);}}// Initial mountReactNoop.render(<App value={1} />);
await waitForAll([
'App','PureIndirection','ChildWithInlineRenderCallback','Consumer','ChildWithCachedRenderCallback','Consumer',]);
expect(ReactNoop).toMatchRenderedOutput(
<><span prop={1} />
<span prop={1} />
</>,);// Update (bailout)ReactNoop.render(<App value={1} />);
await waitForAll(['App']);
expect(ReactNoop).toMatchRenderedOutput(
<><span prop={1} />
<span prop={1} />
</>,);// Update (no bailout)ReactNoop.render(<App value={2} />);
await waitForAll(['App', 'Consumer', 'Consumer']);
expect(ReactNoop).toMatchRenderedOutput(
<><span prop={2} />
<span prop={2} />
</>,);});// @gate www
it("context consumer doesn't bail out inside hidden subtree", async () => {
const Context = React.createContext('dark');
const Consumer = getConsumer(Context);
function App({theme}) {
return (
<Context.Provider value={theme}>
<LegacyHiddenDiv mode="hidden">
<Consumer>{value => <Text text={value} />}</Consumer>
</LegacyHiddenDiv>
</Context.Provider>
);}ReactNoop.render(<App theme="dark" />);
await waitForAll(['dark']);
expect(ReactNoop.getChildrenAsJSX()).toEqual(
<div hidden={true}><span prop="dark" /></div>,);ReactNoop.render(<App theme="light" />);
await waitForAll(['light']);
expect(ReactNoop.getChildrenAsJSX()).toEqual(
<div hidden={true}><span prop="light" /></div>,);});// This is a regression case for https://github.com/facebook/react/issues/12389.
it('does not run into an infinite loop', async () => {
const Context = React.createContext(null);
const Consumer = getConsumer(Context);
class App extends React.Component {
renderItem(id) {
return (
<span key={id}>
<Consumer>{() => <span>inner</span>}</Consumer>
<span>outer</span>
</span>
);}renderList() {const list = [1, 2].map(id => this.renderItem(id));
if (this.props.reverse) {
list.reverse();
}return list;}render() {return (<Context.Provider value={{}}>
{this.renderList()}
</Context.Provider>
);}}ReactNoop.render(<App reverse={false} />);
await waitForAll([]);
ReactNoop.render(<App reverse={true} />);
await waitForAll([]);
ReactNoop.render(<App reverse={false} />);
await waitForAll([]);
});// This is a regression case for https://github.com/facebook/react/issues/12686
it('does not skip some siblings', async () => {
const Context = React.createContext(0);
const ContextConsumer = getConsumer(Context);
class App extends React.Component {
state = {step: 0,};render() {
Scheduler.log('App');
return (
<Context.Provider value={this.state.step}>
<StaticContent />
{this.state.step > 0 && <Indirection />}
</Context.Provider>
);}}class StaticContent extends React.PureComponent {
render() {return (<><><span prop="static 1" /><span prop="static 2" /></></>);}}class Indirection extends React.PureComponent {
render() {return (<ContextConsumer>{value => {Scheduler.log('Consumer');
return <span prop={value} />;}}</ContextConsumer>);}}// Initial mount
let inst;
ReactNoop.render(<App ref={ref => (inst = ref)} />);
await waitForAll(['App']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span prop="static 1" />
<span prop="static 2" />
</>,
);// Update the first time
inst.setState({step: 1});
await waitForAll(['App', 'Consumer']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span prop="static 1" />
<span prop="static 2" />
<span prop={1} />
</>,
);// Update the second time
inst.setState({step: 2});
await waitForAll(['App', 'Consumer']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span prop="static 1" />
<span prop="static 2" />
<span prop={2} />
</>,
);});});}describe('Context.Provider', () => {
it('warns if no value prop provided', async () => {const Context = React.createContext();
ReactNoop.render(
<Context.Provider anyPropNameOtherThanValue="value could be anything" />,
);await expect(async () => await waitForAll([])).toErrorDev(
'The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?',
{withoutStack: true,},);});it('warns if multiple renderers concurrently render the same context', async () => {spyOnDev(console, 'error').mockImplementation(() => {});
const Context = React.createContext(0);
function Foo(props) {Scheduler.log('Foo');
return null;}function App(props) {return (<Context.Provider value={props.value}>
<Foo /><Foo /></Context.Provider>
);}React.startTransition(() => {
ReactNoop.render(<App value={1} />);
});// Render past the Provider, but don't commit yetawait waitFor(['Foo']);
// Get a new copy of ReactNoopjest.resetModules();
React = require('react');ReactNoop = require('react-noop-renderer');Scheduler = require('scheduler');const InternalTestUtils = require('internal-test-utils');waitForAll = InternalTestUtils.waitForAll;
waitFor = InternalTestUtils.waitFor;
// Render the provider again using a different rendererReactNoop.render(<App value={1} />);
await waitForAll(['Foo', 'Foo']);
if (__DEV__) {expect(console.error.mock.calls[0][0]).toContain(
'Detected multiple renderers concurrently rendering the same ' +
'context provider. This is currently unsupported',
);}});it('does not warn if multiple renderers use the same context sequentially', async () => {spyOnDev(console, 'error');const Context = React.createContext(0);
function Foo(props) {Scheduler.log('Foo');
return null;}function App(props) {return (<Context.Provider value={props.value}>
<Foo /><Foo /></Context.Provider>
);}React.startTransition(() => {
ReactNoop.render(<App value={1} />);
});await waitForAll(['Foo', 'Foo']);
// Get a new copy of ReactNoopjest.resetModules();
React = require('react');ReactNoop = require('react-noop-renderer');Scheduler = require('scheduler');const InternalTestUtils = require('internal-test-utils');waitForAll = InternalTestUtils.waitForAll;
waitFor = InternalTestUtils.waitFor;
// Render the provider again using a different rendererReactNoop.render(<App value={1} />);
await waitForAll(['Foo', 'Foo']);
if (__DEV__) {expect(console.error).not.toHaveBeenCalled();
}});it('provider bails out if children and value are unchanged (like sCU)', async () => {const Context = React.createContext(0);
function Child() {Scheduler.log('Child');
return <span prop="Child" />;}const children = <Child />;function App(props) {Scheduler.log('App');
return (<Context.Provider value={props.value}>{children}</Context.Provider>
);}// Initial mountReactNoop.render(<App value={1} />);
await waitForAll(['App', 'Child']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
// UpdateReactNoop.render(<App value={1} />);
await waitForAll([
'App',// Child does not re-render]);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
});// @gate !disableLegacyContextit('provider does not bail out if legacy context changed above', async () => {const Context = React.createContext(0);
function Child() {Scheduler.log('Child');
return <span prop="Child" />;}const children = <Child />;class LegacyProvider extends React.Component {
static childContextTypes = {legacyValue: () => {},};state = {legacyValue: 1};getChildContext() {return {legacyValue: this.state.legacyValue};
}render() {Scheduler.log('LegacyProvider');
return this.props.children;
}}class App extends React.Component {
state = {value: 1};render() {Scheduler.log('App');
return (<Context.Provider value={this.state.value}>
{this.props.children}
</Context.Provider>
);}}const legacyProviderRef = React.createRef();
const appRef = React.createRef();
// Initial mountReactNoop.render(
<LegacyProvider ref={legacyProviderRef}><App ref={appRef} value={1}>
{children}</App></LegacyProvider>,);await waitForAll(['LegacyProvider', 'App', 'Child']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
// Update App with same value (should bail out)appRef.current.setState({value: 1});
await waitForAll(['App']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
// Update LegacyProvider (should not bail out)legacyProviderRef.current.setState({value: 1});
await waitForAll(['LegacyProvider', 'App', 'Child']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
// Update App with same value (should bail out)appRef.current.setState({value: 1});
await waitForAll(['App']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
});});describe('Context.Consumer', () => {
it('warns if child is not a function', async () => {spyOnDev(console, 'error').mockImplementation(() => {});
const Context = React.createContext(0);
ReactNoop.render(<Context.Consumer />);
await waitForThrow('is not a function');if (__DEV__) {expect(console.error.mock.calls[0][0]).toContain(
'A context consumer was rendered with multiple children, or a child ' +
"that isn't a function",);}});it('can read other contexts inside consumer render prop', async () => {const FooContext = React.createContext(0);
const BarContext = React.createContext(0);
function FooAndBar() {return (<FooContext.Consumer>
{foo => {const bar = readContext(BarContext);return <Text text={`Foo: ${foo}, Bar: ${bar}`} />;
}}</FooContext.Consumer>
);}class Indirection extends React.Component {
shouldComponentUpdate() {return false;}render() {return this.props.children;
}}function App(props) {return (<FooContext.Provider value={props.foo}>
<BarContext.Provider value={props.bar}>
<Indirection><FooAndBar /></Indirection></BarContext.Provider>
</FooContext.Provider>
);}ReactNoop.render(<App foo={1} bar={1} />);
await waitForAll(['Foo: 1, Bar: 1']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 1, Bar: 1" />);
// Update fooReactNoop.render(<App foo={2} bar={1} />);
await waitForAll(['Foo: 2, Bar: 1']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 2, Bar: 1" />);
// Update barReactNoop.render(<App foo={2} bar={2} />);
await waitForAll(['Foo: 2, Bar: 2']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 2, Bar: 2" />);
});// Context consumer bails out on propagating "deep" updates when `value` hasn't changed.
// However, it doesn't bail out from rendering if the component above it re-rendered anyway.
// If we bailed out on referential equality, it would be confusing that you// can call this.setState(), but an autobound render callback "blocked" the update.
// https://github.com/facebook/react/pull/12470#issuecomment-376917711
it('consumer does not bail out if there were no bailouts above it', async () => {const Context = React.createContext(0);
const Consumer = Context.Consumer;
class App extends React.Component {
state = {text: 'hello',};renderConsumer = context => {Scheduler.log('App#renderConsumer');
return <span prop={this.state.text} />;
};render() {Scheduler.log('App');
return (<Context.Provider value={this.props.value}>
<Consumer>{this.renderConsumer}</Consumer>
</Context.Provider>
);}}// Initial mountlet inst;ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
await waitForAll(['App', 'App#renderConsumer']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
// Updateinst.setState({text: 'goodbye'});
await waitForAll(['App', 'App#renderConsumer']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
});});describe('readContext', () => {// Unstable changedBits API was removed. Port this test to context selectors
// once that exists.
// @gate FIXMEit('can read the same context multiple times in the same function', async () => {const Context = React.createContext({foo: 0, bar: 0, baz: 0}, (a, b) => {
let result = 0;if (a.foo !== b.foo) {
result |= 0b001;
}if (a.bar !== b.bar) {
result |= 0b010;
}if (a.baz !== b.baz) {
result |= 0b100;
}return result;});function Provider(props) {return (<Context.Provider
value={{foo: props.foo, bar: props.bar, baz: props.baz}}>
{props.children}
</Context.Provider>
);}function FooAndBar() {const {foo} = readContext(Context, 0b001);const {bar} = readContext(Context, 0b010);return <Text text={`Foo: ${foo}, Bar: ${bar}`} />;
}function Baz() {const {baz} = readContext(Context, 0b100);return <Text text={'Baz: ' + baz} />;
}class Indirection extends React.Component {
shouldComponentUpdate() {return false;}render() {return this.props.children;
}}function App(props) {return (<Provider foo={props.foo} bar={props.bar} baz={props.baz}>
<Indirection><Indirection><FooAndBar /></Indirection><Indirection><Baz /></Indirection></Indirection></Provider>);}ReactNoop.render(<App foo={1} bar={1} baz={1} />);
await waitForAll(['Foo: 1, Bar: 1', 'Baz: 1']);
expect(ReactNoop).toMatchRenderedOutput([
<span prop="Foo: 1, Bar: 1" />,<span prop="Baz: 1" />,]);
// Update only fooReactNoop.render(<App foo={2} bar={1} baz={1} />);
await waitForAll(['Foo: 2, Bar: 1']);
expect(ReactNoop).toMatchRenderedOutput([
<span prop="Foo: 2, Bar: 1" />,<span prop="Baz: 1" />,]);
// Update only barReactNoop.render(<App foo={2} bar={2} baz={1} />);
await waitForAll(['Foo: 2, Bar: 2']);
expect(ReactNoop).toMatchRenderedOutput([
<span prop="Foo: 2, Bar: 2" />,<span prop="Baz: 1" />,]);
// Update only bazReactNoop.render(<App foo={2} bar={2} baz={2} />);
await waitForAll(['Baz: 2']);
expect(ReactNoop).toMatchRenderedOutput([
<span prop="Foo: 2, Bar: 2" />,<span prop="Baz: 2" />,]);
});// Context consumer bails out on propagating "deep" updates when `value` hasn't changed.
// However, it doesn't bail out from rendering if the component above it re-rendered anyway.
// If we bailed out on referential equality, it would be confusing that you// can call this.setState(), but an autobound render callback "blocked" the update.
// https://github.com/facebook/react/pull/12470#issuecomment-376917711
it('does not bail out if there were no bailouts above it', async () => {const Context = React.createContext(0);
class Consumer extends React.Component {
render() {const contextValue = readContext(Context);return this.props.children(contextValue);
}}class App extends React.Component {
state = {text: 'hello',};renderConsumer = context => {Scheduler.log('App#renderConsumer');
return <span prop={this.state.text} />;
};render() {Scheduler.log('App');
return (<Context.Provider value={this.props.value}>
<Consumer>{this.renderConsumer}</Consumer>
</Context.Provider>
);}}// Initial mountlet inst;ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
await waitForAll(['App', 'App#renderConsumer']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
// Updateinst.setState({text: 'goodbye'});
await waitForAll(['App', 'App#renderConsumer']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
});it('warns when reading context inside render phase class setState updater', async () => {const ThemeContext = React.createContext('light');
class Cls extends React.Component {
state = {};render() {this.setState(() => {
readContext(ThemeContext);});return null;}}ReactNoop.render(<Cls />);
await expect(async () => await waitForAll([])).toErrorDev([
'Context can only be read while React is rendering','Cannot update during an existing state transition',]);
});});describe('useContext', () => {it('throws when used in a class component', async () => {const Context = React.createContext(0);
class Foo extends React.Component {
render() {return useContext(Context);}}ReactNoop.render(<Foo />);
await waitForThrow('Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen' +
' for one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
);});it('warns when passed a consumer', async () => {const Context = React.createContext(0);
function Foo() {return useContext(Context.Consumer);
}ReactNoop.render(<Foo />);
await expect(async () => await waitForAll([])).toErrorDev(
'Calling useContext(Context.Consumer) is not supported, may cause bugs, ' +
'and will be removed in a future major release. ' +
'Did you mean to call useContext(Context) instead?',
);});it('warns when passed a provider', async () => {const Context = React.createContext(0);
function Foo() {useContext(Context.Provider);
return null;}ReactNoop.render(<Foo />);
await expect(async () => await waitForAll([])).toErrorDev(
'Calling useContext(Context.Provider) is not supported. ' +
'Did you mean to call useContext(Context) instead?',
);});// Context consumer bails out on propagating "deep" updates when `value` hasn't changed.
// However, it doesn't bail out from rendering if the component above it re-rendered anyway.
// If we bailed out on referential equality, it would be confusing that you// can call this.setState(), but an autobound render callback "blocked" the update.
// https://github.com/facebook/react/pull/12470#issuecomment-376917711
it('does not bail out if there were no bailouts above it', async () => {const Context = React.createContext(0);
function Consumer({children}) {const contextValue = useContext(Context);return children(contextValue);}class App extends React.Component {
state = {text: 'hello',};renderConsumer = context => {Scheduler.log('App#renderConsumer');
return <span prop={this.state.text} />;
};render() {Scheduler.log('App');
return (<Context.Provider value={this.props.value}>
<Consumer>{this.renderConsumer}</Consumer>
</Context.Provider>
);}}// Initial mountlet inst;ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
await waitForAll(['App', 'App#renderConsumer']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
// Updateinst.setState({text: 'goodbye'});
await waitForAll(['App', 'App#renderConsumer']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
});});it('unwinds after errors in complete phase', async () => {const Context = React.createContext(0);
// This is a regression test for stack misalignment// caused by unwinding the context from wrong point.
ReactNoop.render(
<errorInCompletePhase><Context.Provider value={null} />
</errorInCompletePhase>,);await waitForThrow('Error in host config.');
ReactNoop.render(
<Context.Provider value={10}>
<Context.Consumer>{value => <span prop={value} />}</Context.Consumer>
</Context.Provider>,
);await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(<span prop={10} />);
});describe('fuzz test', () => {const contextKeys = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
const FLUSH_ALL = 'FLUSH_ALL';function flushAll() {return {type: FLUSH_ALL,toString() {return `flushAll()`;},};}const FLUSH = 'FLUSH';function flush(unitsOfWork) {return {type: FLUSH,unitsOfWork,toString() {return `flush(${unitsOfWork})`;
},};}const UPDATE = 'UPDATE';function update(key, value) {return {type: UPDATE,key,value,toString() {return `update('${key}', ${value})`;
},};}function randomInteger(min, max) {min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}function randomAction() {switch (randomInteger(0, 3)) {case 0:return flushAll();case 1:return flush(randomInteger(0, 500));case 2:const key = contextKeys[randomInteger(0, contextKeys.length)];
const value = randomInteger(1, 10);return update(key, value);default:throw new Error('Switch statement should be exhaustive');}}function randomActions(n) {const actions = [];
for (let i = 0; i < n; i++) {
actions.push(randomAction());
}return actions;}function ContextSimulator(maxDepth) {const contexts = new Map(contextKeys.map(key => {
const Context = React.createContext(0);
Context.displayName = 'Context' + key;
return [key, Context];
}),);class ConsumerTree extends React.Component {
shouldComponentUpdate() {return false;}render() {Scheduler.log();
if (this.props.depth >= this.props.maxDepth) {
return null;}const consumers = [0, 1, 2].map(i => {
const randomKey =contextKeys[
this.props.rand.intBetween(0, contextKeys.length - 1)];
const Context = contexts.get(randomKey);
return (<Context.Consumer key={i}>
{value => (<><span prop={`${randomKey}:${value}`} />
<ConsumerTreerand={this.props.rand}
depth={this.props.depth + 1}
maxDepth={this.props.maxDepth}
/></>)}</Context.Consumer>
);});return consumers;}}function Root(props) {return contextKeys.reduceRight(
(children, key) => {const Context = contexts.get(key);
const value = props.values[key];
return (<Context.Provider value={value}>{children}</Context.Provider>
);},<ConsumerTreerand={props.rand}
depth={0}
maxDepth={props.maxDepth}
/>,);}const initialValues = contextKeys.reduce(
(result, key, i) => ({...result, [key]: i + 1}),
{},);function assertConsistentTree(expectedValues = {}) {const jsx = ReactNoop.getChildrenAsJSX();
const children = jsx === null ? [] : jsx.props.children;
children.forEach(child => {
const text = child.props.prop;
const key = text[0];
const value = parseInt(text[2], 10);
const expectedValue = expectedValues[key];
if (expectedValue === undefined) {// If an expected value was not explicitly passed to this function,// use the first occurrence.
expectedValues[key] = value;
} else if (value !== expectedValue) {throw new Error(`Inconsistent value! Expected: ${key}:${expectedValue}. Actual: ${text}`,
);}});}function simulate(seed, actions) {const rand = gen.create(seed);
let finalExpectedValues = initialValues;function updateRoot() {ReactNoop.render(
<RootmaxDepth={maxDepth}rand={rand}values={finalExpectedValues}/>,);}updateRoot();actions.forEach(action => {
switch (action.type) {
case FLUSH_ALL:Scheduler.unstable_flushAllWithoutAsserting();
break;case FLUSH:Scheduler.unstable_flushNumberOfYields(action.unitsOfWork);
break;case UPDATE:finalExpectedValues = {...finalExpectedValues,
[action.key]: action.value,
};updateRoot();break;default:throw new Error('Switch statement should be exhaustive');}assertConsistentTree();});Scheduler.unstable_flushAllWithoutAsserting();
assertConsistentTree(finalExpectedValues);}return {simulate};}it('hard-coded tests', () => {const {simulate} = ContextSimulator(5);simulate('randomSeed', [flush(3), update('A', 4)]);
});it('generated tests', () => {const {simulate} = ContextSimulator(5);const LIMIT = 100;for (let i = 0; i < LIMIT; i++) {
const seed = Math.random().toString(36).slice(2, 7);
const actions = randomActions(5);try {simulate(seed, actions);} catch (error) {console.error(`
Context fuzz tester error! Copy and paste the following line into the test suite:simulate('${seed}', ${actions.join(', ')});
`);throw error;}}});});it('should warn with an error message when using context as a consumer in DEV', async () => {const BarContext = React.createContext({value: 'bar-initial'});
const BarConsumer = BarContext;function Component() {return (<><BarContext.Provider value={{value: 'bar-updated'}}>
<BarConsumer>{({value}) => <div actual={value} expected="bar-updated" />}</BarConsumer></BarContext.Provider>
</>);}await expect(async () => {ReactNoop.render(<Component />);
await waitForAll([]);
}).toErrorDev(
'Rendering <Context> directly is not supported and will be removed in ' +
'a future major release. Did you mean to render <Context.Consumer> instead?',
);});// False positive regression test.
it('should not warn when using Consumer from React < 16.6 with newer renderer', async () => {
const BarContext = React.createContext({value: 'bar-initial'});
// React 16.5 and earlier didn't have a separate object.
BarContext.Consumer = BarContext;
function Component() {
return (
<>
<BarContext.Provider value={{value: 'bar-updated'}}>
<BarContext.Consumer>
{({value}) => <div actual={value} expected="bar-updated" />}
</BarContext.Consumer>
</BarContext.Provider>
</>
);}ReactNoop.render(<Component />);
await waitForAll([]);
});it('should warn with an error message when using nested context consumers in DEV', async () => {const BarContext = React.createContext({value: 'bar-initial'});
const BarConsumer = BarContext;function Component() {return (<><BarContext.Provider value={{value: 'bar-updated'}}>
<BarConsumer.Consumer.Consumer>
{({value}) => <div actual={value} expected="bar-updated" />}</BarConsumer.Consumer.Consumer>
</BarContext.Provider>
</>);}await expect(async () => {ReactNoop.render(<Component />);
await waitForAll([]);
}).toErrorDev(
'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
'a future major release. Did you mean to render <Context.Consumer> instead?',
);});it('should warn with an error message when using Context.Consumer.Provider DEV', async () => {
const BarContext = React.createContext({value: 'bar-initial'});
function Component() {return (<><BarContext.Consumer.Provider value={{value: 'bar-updated'}}>
<BarContext.Consumer>
{({value}) => <div actual={value} expected="bar-updated" />}</BarContext.Consumer>
</BarContext.Consumer.Provider>
</>);}await expect(async () => {ReactNoop.render(<Component />);
await waitForAll([]);
}).toErrorDev(
'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
'a future major release. Did you mean to render <Context.Provider> instead?',
);});});