it('warns if calculateChangedBits returns larger than a 31-bit integer', () => { spyOnDev(console, 'error'); const Context = React.unstable_createContext( 0, (a, b) => Math.pow(2, 32) - 1, // Return 32 bit int ); function Provider(props) { return Context.provide(props.value, props.children); } ReactNoop.render(<Provider value={1} />); ReactNoop.flush(); // Update ReactNoop.render(<Provider value={2} />); ReactNoop.flush(); if (__DEV__) { expect(console.error.calls.count()).toBe(1); expect(console.error.calls.argsFor(0)[0]).toContain( 'calculateChangedBits: Expected the return value to be a 31-bit ' + 'integer. Instead received: 4294967295', ); } });
it('compares context values with Object.is semantics', () => { const Context = React.unstable_createContext(1); function Provider(props) { ReactNoop.yield('Provider'); return Context.provide(props.value, props.children); } function Consumer(props) { ReactNoop.yield('Consumer'); return Context.consume(value => { ReactNoop.yield('Consumer render prop'); return <span prop={'Result: ' + value} />; }); } class Indirection extends React.Component { shouldComponentUpdate() { return false; } render() { ReactNoop.yield('Indirection'); return this.props.children; } } function App(props) { ReactNoop.yield('App'); return ( <Provider value={props.value}> <Indirection> <Indirection> <Consumer /> </Indirection> </Indirection> </Provider> ); } ReactNoop.render(<App value={NaN} />); expect(ReactNoop.flush()).toEqual([ 'App', 'Provider', 'Indirection', 'Indirection', 'Consumer', 'Consumer render prop', ]); expect(ReactNoop.getChildren()).toEqual([span('Result: NaN')]); // Update ReactNoop.render(<App value={NaN} />); expect(ReactNoop.flush()).toEqual([ 'App', 'Provider', // Consumer should not re-render again // 'Consumer render prop', ]); expect(ReactNoop.getChildren()).toEqual([span('Result: NaN')]); });
it('propagates through shouldComponentUpdate false', () => { const Context = React.unstable_createContext(1); function Provider(props) { ReactNoop.yield('Provider'); return Context.provide(props.value, props.children); } function Consumer(props) { ReactNoop.yield('Consumer'); return Context.consume(value => { ReactNoop.yield('Consumer render prop'); return <span prop={'Result: ' + value} />; }); } class Indirection extends React.Component { shouldComponentUpdate() { return false; } render() { ReactNoop.yield('Indirection'); return this.props.children; } } function App(props) { ReactNoop.yield('App'); return ( <Provider value={props.value}> <Indirection> <Indirection> <Consumer /> </Indirection> </Indirection> </Provider> ); } ReactNoop.render(<App value={2} />); expect(ReactNoop.flush()).toEqual([ 'App', 'Provider', 'Indirection', 'Indirection', 'Consumer', 'Consumer render prop', ]); expect(ReactNoop.getChildren()).toEqual([span('Result: 2')]); // Update ReactNoop.render(<App value={3} />); expect(ReactNoop.flush()).toEqual([ 'App', 'Provider', 'Consumer render prop', ]); expect(ReactNoop.getChildren()).toEqual([span('Result: 3')]); });
it('context unwinds when interrupted', () => { const Context = React.unstable_createContext('Default'); function Provider(props) { return Context.provide(props.value, props.children); } function Consumer(props) { return Context.consume(value => { return <span prop={'Result: ' + value} />; }); } 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 ( <React.Fragment> <Provider value="Does not unwind"> <ErrorBoundary> <Provider value="Unwinds after BadRender throws"> <BadRender /> </Provider> </ErrorBoundary> <Consumer /> </Provider> </React.Fragment> ); } ReactNoop.render(<App value="A" />); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([ // The second provider should use the default value. This proves the span('Result: Does not unwind'), ]); });
it('nested providers', () => { const Context = React.unstable_createContext(1); function Provider(props) { return Context.consume(contextValue => // Multiply previous context value by 2, unless prop overrides Context.provide(props.value || contextValue * 2, props.children), ); } function Consumer(props) { return Context.consume(value => { return <span prop={'Result: ' + value} />; }); } 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 /> </Indirection> </Provider> </Indirection> </Provider> </Indirection> </Provider> ); } ReactNoop.render(<App value={2} />); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Result: 8')]); // Update ReactNoop.render(<App value={3} />); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Result: 12')]); });
it('warns if multiple renderers concurrently render the same context', () => { spyOnDev(console, 'error'); const Context = React.unstable_createContext(0); function Foo(props) { ReactNoop.yield('Foo'); return null; } function Provider(props) { return Context.provide(props.value, props.children); } function App(props) { return ( <Provider value={props.value}> <Foo /> <Foo /> </Provider> ); } ReactNoop.render(<App value={1} />); // Render past the Provider, but don't commit yet ReactNoop.flushThrough(['Foo']); // Get a new copy of ReactNoop jest.resetModules(); ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.enableNewContextAPI = true; React = require('react'); ReactNoop = require('react-noop-renderer'); // Render the provider again using a different renderer ReactNoop.render(<App value={1} />); ReactNoop.flush(); if (__DEV__) { expect(console.error.calls.argsFor(0)[0]).toContain( 'Detected multiple renderers concurrently rendering the same ' + 'context provider. This is currently unsupported', ); } });
it('simple mount and update', () => { const Context = React.unstable_createContext(1); function Provider(props) { return Context.provide(props.value, props.children); } function Consumer(props) { return Context.consume(value => { return <span prop={'Result: ' + value} />; }); } const Indirection = React.Fragment; function App(props) { return ( <Provider value={props.value}> <Indirection> <Indirection> <Consumer /> </Indirection> </Indirection> </Provider> ); } ReactNoop.render(<App value={2} />); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Result: 2')]); // Update ReactNoop.render(<App value={3} />); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Result: 3')]); });
contextKeys.map(key => { const Context = React.unstable_createContext(0); Context.displayName = 'Context' + key; return [key, Context]; }),
it('can skip consumers with bitmask', () => { const Context = React.unstable_createContext({foo: 0, bar: 0}, (a, b) => { let result = 0; if (a.foo !== b.foo) { result |= 0b01; } if (a.bar !== b.bar) { result |= 0b10; } return result; }); function Provider(props) { return Context.provide({foo: props.foo, bar: props.bar}, props.children); } function Foo() { return Context.consume(value => { ReactNoop.yield('Foo'); return <span prop={'Foo: ' + value.foo} />; }, 0b01); } function Bar() { return Context.consume(value => { ReactNoop.yield('Bar'); return <span prop={'Bar: ' + value.bar} />; }, 0b10); } class Indirection extends React.Component { shouldComponentUpdate() { return false; } render() { return this.props.children; } } function App(props) { return ( <Provider foo={props.foo} bar={props.bar}> <Indirection> <Indirection> <Foo /> </Indirection> <Indirection> <Bar /> </Indirection> </Indirection> </Provider> ); } ReactNoop.render(<App foo={1} bar={1} />); expect(ReactNoop.flush()).toEqual(['Foo', 'Bar']); expect(ReactNoop.getChildren()).toEqual([span('Foo: 1'), span('Bar: 1')]); // Update only foo ReactNoop.render(<App foo={2} bar={1} />); expect(ReactNoop.flush()).toEqual(['Foo']); expect(ReactNoop.getChildren()).toEqual([span('Foo: 2'), span('Bar: 1')]); // Update only bar ReactNoop.render(<App foo={2} bar={2} />); expect(ReactNoop.flush()).toEqual(['Bar']); expect(ReactNoop.getChildren()).toEqual([span('Foo: 2'), span('Bar: 2')]); // Update both ReactNoop.render(<App foo={3} bar={3} />); expect(ReactNoop.flush()).toEqual(['Foo', 'Bar']); expect(ReactNoop.getChildren()).toEqual([span('Foo: 3'), span('Bar: 3')]); });