/** * Copyright (c) Facebook, Inc. and its 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'; const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils'); let React; let ReactDOM; let ReactDOMServer; let ReactTestUtils; function initModules() { // Reset warning cache. jest.resetModuleRegistry(); 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} = ReactDOMServerIntegrationUtils(initModules); describe('ReactDOMServerIntegration', () => { beforeEach(() => { resetModules(); }); describe('context', function() { let Context, PurpleContextProvider, RedContextProvider, Consumer; beforeEach(() => { Context = React.createContext('none'); class Parent extends React.Component { render() { return ( {this.props.children} ); } } Consumer = Context.Consumer; PurpleContextProvider = props => ( {props.children} ); RedContextProvider = props => ( {props.children} ); }); itRenders('class child with context', async render => { class ClassChildWithContext extends React.Component { render() { return (
{text => text}
); } } const e = await render( , ); expect(e.textContent).toBe('purple'); }); itRenders('stateless child with context', async render => { function FunctionChildWithContext(props) { return {text => text}; } const e = await render( , ); expect(e.textContent).toBe('purple'); }); itRenders('class child with default context', async render => { class ClassChildWithWrongContext extends React.Component { render() { return (
{text => text}
); } } const e = await render(); expect(e.textContent).toBe('none'); }); itRenders('stateless child with wrong context', async render => { function FunctionChildWithWrongContext(props) { return (
{text => text}
); } const e = await render(); expect(e.textContent).toBe('none'); }); itRenders('with context passed through to a grandchild', async render => { function Grandchild(props) { return (
{text => text}
); } const Child = props => ; const e = await render( , ); expect(e.textContent).toBe('purple'); }); itRenders('a child context overriding a parent context', async render => { const Grandchild = props => { return (
{text => text}
); }; const e = await render( , ); expect(e.textContent).toBe('red'); }); itRenders('readContext() in different components', async render => { function readContext(Ctx, observedBits) { const dispatcher = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED .ReactCurrentDispatcher.current; return dispatcher.readContext(Ctx, observedBits); } class Cls extends React.Component { render() { return readContext(Context); } } function Fn() { return readContext(Context); } const Memo = React.memo(() => { return readContext(Context); }); const FwdRef = React.forwardRef((props, ref) => { return readContext(Context); }); const e = await render( {() => readContext(Context)} , ); expect(e.textContent).toBe('redredredredred'); }); itRenders('multiple contexts', async render => { const Theme = React.createContext('dark'); const Language = React.createContext('french'); class Parent extends React.Component { render() { return ( ); } } function Child() { return ( ); } const Grandchild = props => { return (
{theme =>
{theme}
}
{language =>
{language}
}
); }; const e = await render(); expect(e.querySelector('#theme').textContent).toBe('light'); expect(e.querySelector('#language').textContent).toBe('english'); }); itRenders('nested context unwinding', async render => { const Theme = React.createContext('dark'); const Language = React.createContext('french'); const App = () => (
{theme =>
{theme}
}
{theme =>
{theme}
}
{() => ( {language =>
{language}
}
)}
{language => ( <> {theme =>
{theme}
}
{language}
)}
{language =>
{language}
}
); const e = await render(); expect(e.querySelector('#theme1').textContent).toBe('dark'); expect(e.querySelector('#theme2').textContent).toBe('light'); expect(e.querySelector('#theme3').textContent).toBe('blue'); expect(e.querySelector('#language1').textContent).toBe('chinese'); expect(e.querySelector('#language2').textContent).toBe('sanskrit'); expect(e.querySelector('#language3').textContent).toBe('french'); }); itRenders( 'should warn with an error message when using Context as consumer in DEV', async render => { const Theme = React.createContext('dark'); const Language = React.createContext('french'); const App = () => (
{theme =>
{theme}
}
); // We expect 1 error. await render(, 1); }, ); // False positive regression test. itRenders( 'should not warn when using Consumer from React < 16.6 with newer renderer', async render => { const Theme = React.createContext('dark'); const Language = React.createContext('french'); // React 16.5 and earlier didn't have a separate object. Theme.Consumer = Theme; const App = () => (
{theme =>
{theme}
}
); // We expect 0 errors. await render(, 0); }, ); itRenders( 'should warn with an error message when using nested context consumers in DEV', async render => { const App = () => { const Theme = React.createContext('dark'); const Language = React.createContext('french'); return (
{theme =>
{theme}
}
); }; // We expect 1 error. await render(, 1); }, ); itRenders( 'should warn with an error message when using Context.Consumer.Provider DEV', async render => { const App = () => { const Theme = React.createContext('dark'); const Language = React.createContext('french'); return (
{theme =>
{theme}
}
); }; // We expect 1 error. await render(, 1); }, ); it('does not pollute parallel node streams', () => { const LoggedInUser = React.createContext(); const AppWithUser = user => (
{whoAmI => whoAmI}
{whoAmI => whoAmI}
); const streamAmy = ReactDOMServer.renderToNodeStream( AppWithUser('Amy'), ).setEncoding('utf8'); const streamBob = ReactDOMServer.renderToNodeStream( AppWithUser('Bob'), ).setEncoding('utf8'); // Testing by filling the buffer using internal _read() with a small // number of bytes to avoid a test case which needs to align to a // highWaterMark boundary of 2^14 chars. streamAmy._read(20); streamBob._read(20); streamAmy._read(20); streamBob._read(20); expect(streamAmy.read()).toBe('
Amy
Amy
'); expect(streamBob.read()).toBe('
Bob
Bob
'); }); it('does not pollute parallel node streams when many are used', () => { const CurrentIndex = React.createContext(); const NthRender = index => (
{idx => idx}
{idx => idx}
); const streams = []; // Test with more than 32 streams to test that growing the thread count // works properly. const streamCount = 34; for (let i = 0; i < streamCount; i++) { streams[i] = ReactDOMServer.renderToNodeStream( NthRender(i % 2 === 0 ? 'Expected to be recreated' : i), ).setEncoding('utf8'); } // Testing by filling the buffer using internal _read() with a small // number of bytes to avoid a test case which needs to align to a // highWaterMark boundary of 2^14 chars. for (let i = 0; i < streamCount; i++) { streams[i]._read(20); } // Early destroy every other stream for (let i = 0; i < streamCount; i += 2) { streams[i].destroy(); } // Recreate those same streams. for (let i = 0; i < streamCount; i += 2) { streams[i] = ReactDOMServer.renderToNodeStream( NthRender(i), ).setEncoding('utf8'); } // Read a bit from all streams again. for (let i = 0; i < streamCount; i++) { streams[i]._read(20); } // Assert that all stream rendered the expected output. for (let i = 0; i < streamCount; i++) { expect(streams[i].read()).toBe( '
' + i + '
' + i + '
', ); } }); // Regression test for https://github.com/facebook/react/issues/14705 it('does not pollute later renders when stream destroyed', () => { const LoggedInUser = React.createContext('default'); const AppWithUser = user => (
{whoAmI => whoAmI}
); const stream = ReactDOMServer.renderToNodeStream( AppWithUser('Amy'), ).setEncoding('utf8'); // This is an implementation detail because we test a memory leak const {threadID} = stream.partialRenderer; // Read enough to render Provider but not enough for it to be exited stream._read(10); expect(LoggedInUser[threadID]).toBe('Amy'); stream.destroy(); const AppWithUserNoProvider = () => ( {whoAmI => whoAmI} ); const stream2 = ReactDOMServer.renderToNodeStream( AppWithUserNoProvider(), ).setEncoding('utf8'); // Sanity check to ensure 2nd render has same threadID as 1st render, // otherwise this test is not testing what it's meant to expect(stream2.partialRenderer.threadID).toBe(threadID); const markup = stream2.read(Infinity); expect(markup).toBe('default'); }); // Regression test for https://github.com/facebook/react/issues/14705 it('frees context value reference when stream destroyed', () => { const LoggedInUser = React.createContext('default'); const AppWithUser = user => (
{whoAmI => whoAmI}
); const stream = ReactDOMServer.renderToNodeStream( AppWithUser('Amy'), ).setEncoding('utf8'); // This is an implementation detail because we test a memory leak const {threadID} = stream.partialRenderer; // Read enough to render Provider but not enough for it to be exited stream._read(10); expect(LoggedInUser[threadID]).toBe('Amy'); stream.destroy(); expect(LoggedInUser[threadID]).toBe('default'); }); it('does not pollute sync renders after an error', () => { const LoggedInUser = React.createContext('default'); const Crash = () => { throw new Error('Boo!'); }; const AppWithUser = user => ( {whoAmI => whoAmI} ); expect(() => { ReactDOMServer.renderToString(AppWithUser('Casper')); }).toThrow('Boo'); // Should not report a value from failed render expect( ReactDOMServer.renderToString( {whoAmI => whoAmI}, ), ).toBe('default'); }); }); });