/
githubmr
/
facebook-react
Обзор
Документация
Войти
/
githubmr
/
facebook-react
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
packages/react-dom/src/__tests__/ReactDOM-test.js
963 строки
32 KB
Ricky
[flags] remove enableOwnerStacks (#32426)
04 мар 2025, 20:34
Не верифицирован
04 мар 2025, 20:34
e0fe347
Код
Авторство
О чём код?
/** * 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; let ReactDOM; let findDOMNode; let ReactDOMClient; let ReactDOMServer; let assertConsoleErrorDev; let act; describe('ReactDOM', () => { beforeEach(() => { jest.resetModules(); React = require('react'); ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); ReactDOMServer = require('react-dom/server'); findDOMNode = ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE .findDOMNode; ({act, assertConsoleErrorDev} = require('internal-test-utils')); }); it('should bubble onSubmit', async () => { const container = document.createElement('div'); let count = 0; let buttonRef; function Parent() { return ( <div onSubmit={event => { event.preventDefault(); count++; }}> <Child /> </div> ); } function Child() { return ( <form> <input type="submit" ref={button => (buttonRef = button)} /> </form> ); } document.body.appendChild(container); const root = ReactDOMClient.createRoot(container); try { await act(() => { root.render(<Parent />); }); buttonRef.click(); expect(count).toBe(1); } finally { document.body.removeChild(container); } }); it('allows a DOM element to be used with a string', async () => { const element = React.createElement('div', {className: 'foo'}); const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(element); }); const node = container.firstChild; expect(node.tagName).toBe('DIV'); }); it('should allow children to be passed as an argument', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(React.createElement('div', null, 'child')); }); const argNode = container.firstChild; expect(argNode.innerHTML).toBe('child'); }); it('should overwrite props.children with children argument', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(React.createElement('div', {children: 'fakechild'}, 'child')); }); const conflictNode = container.firstChild; expect(conflictNode.innerHTML).toBe('child'); }); /** * We need to make sure that updates occur to the actual node that's in the * DOM, instead of a stale cache. */ it('should purge the DOM cache when removing nodes', async () => { let container = document.createElement('div'); let root = ReactDOMClient.createRoot(container); await act(() => { root.render( <div> <div key="theDog" className="dog" />, <div key="theBird" className="bird" /> </div>, ); }); // Warm the cache with theDog container = document.createElement('div'); root = ReactDOMClient.createRoot(container); await act(() => { root.render( <div> <div key="theDog" className="dogbeforedelete" />, <div key="theBird" className="bird" />, </div>, ); }); // Remove theDog - this should purge the cache container = document.createElement('div'); root = ReactDOMClient.createRoot(container); await act(() => { root.render( <div> <div key="theBird" className="bird" />, </div>, ); }); // Now, put theDog back. It's now a different DOM node. container = document.createElement('div'); root = ReactDOMClient.createRoot(container); await act(() => { root.render( <div> <div key="theDog" className="dog" />, <div key="theBird" className="bird" />, </div>, ); }); // Change the className of theDog. It will use the same element container = document.createElement('div'); root = ReactDOMClient.createRoot(container); await act(() => { root.render( <div> <div key="theDog" className="bigdog" />, <div key="theBird" className="bird" />, </div>, ); }); const myDiv = container.firstChild; const dog = myDiv.childNodes[0]; expect(dog.className).toBe('bigdog'); }); // @gate !disableLegacyMode it('throws in render() if the mount callback in legacy roots is not a function', async () => { spyOnDev(console, 'warn'); spyOnDev(console, 'error'); function Foo() { this.a = 1; this.b = 2; } class A extends React.Component { state = {}; render() { return <div />; } } const myDiv = document.createElement('div'); await expect(async () => { await act(() => { ReactDOM.render(<A />, myDiv, 'no'); }); }).rejects.toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: no', ); assertConsoleErrorDev( [ 'Expected the last optional `callback` argument to be a function. Instead received: no.', 'Expected the last optional `callback` argument to be a function. Instead received: no.', ], {withoutStack: 2}, ); await expect(async () => { await act(() => { ReactDOM.render(<A />, myDiv, {foo: 'bar'}); }); }).rejects.toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); assertConsoleErrorDev( [ "Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }", "Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }.", ], {withoutStack: 2}, ); await expect(async () => { await act(() => { ReactDOM.render(<A />, myDiv, new Foo()); }); }).rejects.toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); assertConsoleErrorDev( [ 'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.', 'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.', ], {withoutStack: 2}, ); }); // @gate !disableLegacyMode it('throws in render() if the update callback in legacy roots is not a function', async () => { function Foo() { this.a = 1; this.b = 2; } class A extends React.Component { state = {}; render() { return <div />; } } const myDiv = document.createElement('div'); ReactDOM.render(<A />, myDiv); await expect(async () => { await act(() => { ReactDOM.render(<A />, myDiv, 'no'); }); }).rejects.toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: no', ); assertConsoleErrorDev( [ 'Expected the last optional `callback` argument to be a function. Instead received: no.', 'Expected the last optional `callback` argument to be a function. Instead received: no.', ], {withoutStack: 2}, ); ReactDOM.render(<A />, myDiv); // Re-mount await expect(async () => { await act(() => { ReactDOM.render(<A />, myDiv, {foo: 'bar'}); }); }).rejects.toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); assertConsoleErrorDev( [ "Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }.", "Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }.", ], {withoutStack: 2}, ); ReactDOM.render(<A />, myDiv); // Re-mount await expect(async () => { await act(() => { ReactDOM.render(<A />, myDiv, new Foo()); }); }).rejects.toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); assertConsoleErrorDev( [ 'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.', 'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.', ], {withoutStack: 2}, ); }); it('preserves focus', async () => { let input; let input2; class A extends React.Component { render() { return ( <div> <input id="one" ref={r => (input = input || r)} /> {this.props.showTwo && ( <input id="two" ref={r => (input2 = input2 || r)} /> )} </div> ); } componentDidUpdate() { // Focus should have been restored to the original input expect(document.activeElement.id).toBe('one'); input2.focus(); expect(document.activeElement.id).toBe('two'); log.push('input2 focused'); } } const log = []; const container = document.createElement('div'); document.body.appendChild(container); const root = ReactDOMClient.createRoot(container); try { await act(() => { root.render(<A showTwo={false} />); }); input.focus(); // When the second input is added, let's simulate losing focus, which is // something that could happen when manipulating DOM nodes (but is hard to // deterministically force without relying intensely on React DOM // implementation details) const div = container.firstChild; ['appendChild', 'insertBefore'].forEach(name => { const mutator = div[name]; div[name] = function () { if (input) { input.blur(); expect(document.activeElement.tagName).toBe('BODY'); log.push('input2 inserted'); } return mutator.apply(this, arguments); }; }); expect(document.activeElement.id).toBe('one'); await act(() => { root.render(<A showTwo={true} />); }); // input2 gets added, which causes input to get blurred. Then // componentDidUpdate focuses input2 and that should make it down to here, // not get overwritten by focus restoration. expect(document.activeElement.id).toBe('two'); expect(log).toEqual(['input2 inserted', 'input2 focused']); } finally { document.body.removeChild(container); } }); it('calls focus() on autoFocus elements after they have been mounted to the DOM', async () => { const originalFocus = HTMLElement.prototype.focus; try { let focusedElement; let inputFocusedAfterMount = false; // This test needs to determine that focus is called after mount. // Can't check document.activeElement because PhantomJS is too permissive; // It doesn't require element to be in the DOM to be focused. HTMLElement.prototype.focus = function () { focusedElement = this; inputFocusedAfterMount = !!this.parentNode; }; const container = document.createElement('div'); document.body.appendChild(container); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( <div> <h1>Auto-focus Test</h1> <input autoFocus={true} /> <p>The above input should be focused after mount.</p> </div>, ); }); expect(inputFocusedAfterMount).toBe(true); expect(focusedElement.tagName).toBe('INPUT'); } finally { HTMLElement.prototype.focus = originalFocus; } }); it("shouldn't fire duplicate event handler while handling other nested dispatch", async () => { const actual = []; class Wrapper extends React.Component { componentDidMount() { this.ref1.click(); } render() { return ( <div> <div onClick={() => { actual.push('1st node clicked'); this.ref2.click(); }} ref={ref => (this.ref1 = ref)} /> <div onClick={ref => { actual.push("2nd node clicked imperatively from 1st's handler"); }} ref={ref => (this.ref2 = ref)} /> </div> ); } } const container = document.createElement('div'); document.body.appendChild(container); const root = ReactDOMClient.createRoot(container); try { await act(() => { root.render(<Wrapper />); }); const expected = [ '1st node clicked', "2nd node clicked imperatively from 1st's handler", ]; expect(actual).toEqual(expected); } finally { document.body.removeChild(container); } }); it('should not crash with devtools installed', async () => { try { global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { inject: function () {}, onCommitFiberRoot: function () {}, onCommitFiberUnmount: function () {}, supportsFiber: true, }; jest.resetModules(); React = require('react'); ReactDOM = require('react-dom'); class Component extends React.Component { render() { return <div />; } } const root = ReactDOMClient.createRoot(document.createElement('div')); await act(() => { root.render(<Component />); }); } finally { delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__; } }); it('should not crash calling findDOMNode inside a function component', async () => { class Component extends React.Component { render() { return <div />; } } const container = document.createElement('div'); let root = ReactDOMClient.createRoot(container); let instance; await act(() => { root.render(<Component ref={current => (instance = current)} />); }); const App = () => { findDOMNode(instance); return <div />; }; if (__DEV__) { root = ReactDOMClient.createRoot(document.createElement('div')); await act(() => { root.render(<App />); }); } }); it('reports stacks with re-entrant renderToString() calls on the client', async () => { function Child2(props) { return <span ariaTypo3="no">{props.children}</span>; } function App2() { return ( <Child2> {ReactDOMServer.renderToString(<blink ariaTypo2="no" />)} </Child2> ); } function Child() { return ( <span ariaTypo4="no">{ReactDOMServer.renderToString(<App2 />)}</span> ); } function ServerEntry() { return ReactDOMServer.renderToString(<Child />); } function App() { return ( <div> <span ariaTypo="no" /> <ServerEntry /> <font ariaTypo5="no" /> </div> ); } const root = ReactDOMClient.createRoot(document.createElement('div')); await act(() => { root.render(<App />); }); assertConsoleErrorDev([ // ReactDOM(App > div > span) 'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + ' in span (at **)\n' + ' in App (at **)', // ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child) >>> ReactDOMServer(App2) >>> ReactDOMServer(blink) 'Invalid ARIA attribute `ariaTypo2`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + ' in blink (at **)', // ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child) >>> ReactDOMServer(App2 > Child2 > span) 'Invalid ARIA attribute `ariaTypo3`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + ' in span (at **)\n' + ' in Child2 (at **)\n' + ' in App2 (at **)', // ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child > span) 'Invalid ARIA attribute `ariaTypo4`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + ' in span (at **)\n' + ' in Child (at **)', // ReactDOM(App > div > font) 'Invalid ARIA attribute `ariaTypo5`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + ' in font (at **)\n' + ' in App (at **)', ]); }); it('should render root host components into body scope when the container is a Document', async () => { function App({phase}) { return ( <> {phase < 1 ? null : <div>..before</div>} {phase < 3 ? <div>before</div> : null} {phase < 2 ? null : <div>before..</div>} <html lang="en"> <head data-h=""> {phase < 1 ? null : <meta itemProp="" content="..head" />} {phase < 3 ? <meta itemProp="" content="head" /> : null} {phase < 2 ? null : <meta itemProp="" content="head.." />} </head> <body data-b=""> {phase < 1 ? null : <div>..inside</div>} {phase < 3 ? <div>inside</div> : null} {phase < 2 ? null : <div>inside..</div>} </body> </html> {phase < 1 ? null : <div>..after</div>} {phase < 3 ? <div>after</div> : null} {phase < 2 ? null : <div>after..</div>} </> ); } const root = ReactDOMClient.createRoot(document); await act(() => { root.render(<App phase={0} />); }); expect(document.documentElement.outerHTML).toBe( '<html lang="en"><head data-h=""><meta itemprop="" content="head"></head><body data-b=""><div>before</div><div>inside</div><div>after</div></body></html>', ); await act(() => { root.render(<App phase={1} />); }); expect(document.documentElement.outerHTML).toBe( '<html lang="en"><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head"></head><body data-b=""><div>..before</div><div>before</div><div>..inside</div><div>inside</div><div>..after</div><div>after</div></body></html>', ); await act(() => { root.render(<App phase={2} />); }); expect(document.documentElement.outerHTML).toBe( '<html lang="en"><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head"><meta itemprop="" content="head.."></head><body data-b=""><div>..before</div><div>before</div><div>before..</div><div>..inside</div><div>inside</div><div>inside..</div><div>..after</div><div>after</div><div>after..</div></body></html>', ); await act(() => { root.render(<App phase={3} />); }); expect(document.documentElement.outerHTML).toBe( '<html lang="en"><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head.."></head><body data-b=""><div>..before</div><div>before..</div><div>..inside</div><div>inside..</div><div>..after</div><div>after..</div></body></html>', ); await act(() => { root.unmount(); }); expect(document.documentElement.outerHTML).toBe( '<html><head></head><body></body></html>', ); }); it('should render root host components into body scope when the container is a the <html> tag', async () => { function App({phase}) { return ( <> {phase < 1 ? null : <div>..before</div>} {phase < 3 ? <div>before</div> : null} {phase < 2 ? null : <div>before..</div>} <head data-h=""> {phase < 1 ? null : <meta itemProp="" content="..head" />} {phase < 3 ? <meta itemProp="" content="head" /> : null} {phase < 2 ? null : <meta itemProp="" content="head.." />} </head> <body data-b=""> {phase < 1 ? null : <div>..inside</div>} {phase < 3 ? <div>inside</div> : null} {phase < 2 ? null : <div>inside..</div>} </body> {phase < 1 ? null : <div>..after</div>} {phase < 3 ? <div>after</div> : null} {phase < 2 ? null : <div>after..</div>} </> ); } const root = ReactDOMClient.createRoot(document.documentElement); await act(() => { root.render(<App phase={0} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="head"></head><body data-b=""><div>before</div><div>inside</div><div>after</div></body></html>', ); await act(() => { root.render(<App phase={1} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head"></head><body data-b=""><div>..before</div><div>before</div><div>..inside</div><div>inside</div><div>..after</div><div>after</div></body></html>', ); await act(() => { root.render(<App phase={2} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head"><meta itemprop="" content="head.."></head><body data-b=""><div>..before</div><div>before</div><div>before..</div><div>..inside</div><div>inside</div><div>inside..</div><div>..after</div><div>after</div><div>after..</div></body></html>', ); await act(() => { root.render(<App phase={3} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head.."></head><body data-b=""><div>..before</div><div>before..</div><div>..inside</div><div>inside..</div><div>..after</div><div>after..</div></body></html>', ); await act(() => { root.unmount(); }); expect(document.documentElement.outerHTML).toBe( '<html><head></head><body></body></html>', ); }); it('should render root host components into body scope when the container is a the <body> tag', async () => { function App({phase}) { return ( <> {phase < 1 ? null : <div>..before</div>} {phase < 3 ? <div>before</div> : null} {phase < 2 ? null : <div>before..</div>} <head data-h=""> {phase < 1 ? null : <meta itemProp="" content="..head" />} {phase < 3 ? <meta itemProp="" content="head" /> : null} {phase < 2 ? null : <meta itemProp="" content="head.." />} </head> {phase < 1 ? null : <div>..inside</div>} {phase < 3 ? <div>inside</div> : null} {phase < 2 ? null : <div>inside..</div>} {phase < 1 ? null : <div>..after</div>} {phase < 3 ? <div>after</div> : null} {phase < 2 ? null : <div>after..</div>} </> ); } const root = ReactDOMClient.createRoot(document.body); await act(() => { root.render(<App phase={0} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="head"></head><body><div>before</div><div>inside</div><div>after</div></body></html>', ); await act(() => { root.render(<App phase={1} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head"></head><body><div>..before</div><div>before</div><div>..inside</div><div>inside</div><div>..after</div><div>after</div></body></html>', ); await act(() => { root.render(<App phase={2} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head"><meta itemprop="" content="head.."></head><body><div>..before</div><div>before</div><div>before..</div><div>..inside</div><div>inside</div><div>inside..</div><div>..after</div><div>after</div><div>after..</div></body></html>', ); await act(() => { root.render(<App phase={3} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head.."></head><body><div>..before</div><div>before..</div><div>..inside</div><div>inside..</div><div>..after</div><div>after..</div></body></html>', ); await act(() => { root.unmount(); }); expect(document.documentElement.outerHTML).toBe( '<html><head></head><body></body></html>', ); }); it('should render children of <head> into the document head even when the container is inside the document body', async () => { function App({phase}) { return ( <> <div>before</div> <head data-h=""> {phase < 1 ? null : <meta itemProp="" content="..head" />} {phase < 3 ? <meta itemProp="" content="head" /> : null} {phase < 2 ? null : <meta itemProp="" content="head.." />} </head> <div>after</div> </> ); } const container = document.createElement('main'); document.body.append(container); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(<App phase={0} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="head"></head><body><main><div>before</div><div>after</div></main></body></html>', ); // @TODO remove this warning check when we loosen the tag nesting restrictions to allow arbitrary tags at the // root of the application assertConsoleErrorDev(['In HTML, <head> cannot be a child of <main>']); await act(() => { root.render(<App phase={1} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head"></head><body><main><div>before</div><div>after</div></main></body></html>', ); await act(() => { root.render(<App phase={2} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head"><meta itemprop="" content="head.."></head><body><main><div>before</div><div>after</div></main></body></html>', ); await act(() => { root.render(<App phase={3} />); }); expect(document.documentElement.outerHTML).toBe( '<html><head data-h=""><meta itemprop="" content="..head"><meta itemprop="" content="head.."></head><body><main><div>before</div><div>after</div></main></body></html>', ); await act(() => { root.unmount(); }); expect(document.documentElement.outerHTML).toBe( '<html><head></head><body><main></main></body></html>', ); }); it('can render a Suspense boundary above the <html> tag', async () => { let suspendOnNewPromise; let resolveCurrentPromise; let currentPromise; function createNewPromise() { currentPromise = new Promise(r => { resolveCurrentPromise = r; }); return currentPromise; } createNewPromise(); function Comp() { const [promise, setPromise] = React.useState(currentPromise); suspendOnNewPromise = () => { setPromise(createNewPromise()); }; React.use(promise); return null; } const fallback = ( <html data-fallback=""> <body data-fallback=""> <div>fallback</div> </body> </html> ); const main = ( <html lang="en"> <head> <meta itemProp="" content="primary" /> </head> <body> <div> <Message /> </div> </body> </html> ); let suspendOnNewMessage; let currentMessage; let resolveCurrentMessage; function createNewMessage() { currentMessage = new Promise(r => { resolveCurrentMessage = r; }); return currentMessage; } createNewMessage(); resolveCurrentMessage('hello world'); function Message() { const [pendingMessage, setPendingMessage] = React.useState(currentMessage); suspendOnNewMessage = () => { setPendingMessage(createNewMessage()); }; return React.use(pendingMessage); } function App() { return ( <React.Suspense fallback={fallback}> <Comp /> {main} </React.Suspense> ); } const root = ReactDOMClient.createRoot(document); await act(() => { root.render(<App />); }); // The initial render is blocked by promiseA so we see the fallback Document expect(document.documentElement.outerHTML).toBe( '<html data-fallback=""><head></head><body data-fallback=""><div>fallback</div></body></html>', ); await act(() => { resolveCurrentPromise(); }); // When promiseA resolves we see the primary Document expect(document.documentElement.outerHTML).toBe( '<html lang="en"><head><meta itemprop="" content="primary"></head><body><div>hello world</div></body></html>', ); await act(() => { suspendOnNewPromise(); }); // When we switch to rendering ComponentB synchronously we have to put the Document back into fallback // The primary content remains hidden until promiseB resolves expect(document.documentElement.outerHTML).toBe( '<html data-fallback=""><head><meta itemprop="" content="primary" style="display: none;"></head><body data-fallback=""><div style="display: none;">hello world</div><div>fallback</div></body></html>', ); await act(() => { resolveCurrentPromise(); }); // When promiseB resolves we see the new primary content inside the primary Document // style attributes stick around after being unhidden by the Suspense boundary expect(document.documentElement.outerHTML).toBe( '<html lang="en"><head><meta itemprop="" content="primary" style=""></head><body><div style="">hello world</div></body></html>', ); await act(() => { React.startTransition(() => { suspendOnNewPromise(); }); }); expect(document.documentElement.outerHTML).toBe( '<html lang="en"><head><meta itemprop="" content="primary" style=""></head><body><div style="">hello world</div></body></html>', ); await act(() => { resolveCurrentPromise(); }); expect(document.documentElement.outerHTML).toBe( '<html lang="en"><head><meta itemprop="" content="primary" style=""></head><body><div style="">hello world</div></body></html>', ); await act(() => { suspendOnNewMessage(); }); // When we update the message itself we will be causing updates on the primary content of the Suspense boundary. // The reason we also test for this is to make sure we don't double acquire the document singletons while // disappearing and reappearing layout effects expect(document.documentElement.outerHTML).toBe( '<html data-fallback=""><head><meta itemprop="" content="primary" style="display: none;"></head><body data-fallback=""><div style="display: none;">hello world</div><div>fallback</div></body></html>', ); await act(() => { resolveCurrentMessage('hello you!'); }); expect(document.documentElement.outerHTML).toBe( '<html lang="en"><head><meta itemprop="" content="primary" style=""></head><body><div style="">hello you!</div></body></html>', ); await act(() => { React.startTransition(() => { suspendOnNewMessage(); }); }); expect(document.documentElement.outerHTML).toBe( '<html lang="en"><head><meta itemprop="" content="primary" style=""></head><body><div style="">hello you!</div></body></html>', ); await act(() => { resolveCurrentMessage('goodbye!'); }); expect(document.documentElement.outerHTML).toBe( '<html lang="en"><head><meta itemprop="" content="primary" style=""></head><body><div style="">goodbye!</div></body></html>', ); }); });