From f900429319df5a0b57c2ac9941a2c6ef0a25a0b3 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Mon, 29 Jan 2024 09:11:02 +0100 Subject: [PATCH] Convert ReactDOMTextComponent to createRoot (#28141) --- .../__tests__/ReactDOMTextComponent-test.js | 398 ++++++++++-------- 1 file changed, 224 insertions(+), 174 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMTextComponent-test.js b/packages/react-dom/src/__tests__/ReactDOMTextComponent-test.js index 419d312cbc671..4ec6ba2c7d23a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMTextComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMTextComponent-test.js @@ -10,94 +10,99 @@ 'use strict'; let React; -let ReactDOM; +let ReactDOMClient; let ReactDOMServer; - -// In standard React, TextComponent keeps track of different Text templates -// using comments. However, in React Fiber, those comments are not outputted due -// to the way Fiber keeps track of the templates. -// This function "Normalizes" childNodes lists to avoid the presence of comments -// and make the child list identical in standard React and Fiber -function filterOutComments(nodeList) { - return [].slice.call(nodeList).filter(node => !(node instanceof Comment)); -} +let act; describe('ReactDOMTextComponent', () => { beforeEach(() => { React = require('react'); - ReactDOM = require('react-dom'); + ReactDOMClient = require('react-dom/client'); ReactDOMServer = require('react-dom/server'); + act = require('internal-test-utils').act; }); - it('updates a mounted text component in place', () => { - const el = document.createElement('div'); - let inst = ReactDOM.render( -
- - {'foo'} - {'bar'} -
, - el, - ); - let nodes = filterOutComments(inst.childNodes); + it('updates a mounted text component in place', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+ + {'foo'} + {'bar'} +
, + ); + }); + let inst = container.firstChild; + let nodes = inst.childNodes; const foo = nodes[1]; const bar = nodes[2]; expect(foo.data).toBe('foo'); expect(bar.data).toBe('bar'); - inst = ReactDOM.render( -
- - {'baz'} - {'qux'} -
, - el, - ); + await act(() => { + root.render( +
+ + {'baz'} + {'qux'} +
, + ); + }); + inst = container.firstChild; // After the update, the text nodes should have stayed in place (as opposed // to getting unmounted and remounted) - nodes = filterOutComments(inst.childNodes); + nodes = inst.childNodes; expect(nodes[1]).toBe(foo); expect(nodes[2]).toBe(bar); expect(foo.data).toBe('baz'); expect(bar.data).toBe('qux'); }); - it('can be toggled in and out of the markup', () => { - const el = document.createElement('div'); - let inst = ReactDOM.render( -
- {'foo'} -
- {'bar'} -
, - el, - ); + it('can be toggled in and out of the markup', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+ {'foo'} +
+ {'bar'} +
, + ); + }); + let inst = container.firstChild; - let childNodes = filterOutComments(inst.childNodes); + let childNodes = inst.childNodes; const childDiv = childNodes[1]; - inst = ReactDOM.render( -
- {null} -
- {null} -
, - el, - ); - childNodes = filterOutComments(inst.childNodes); + await act(() => { + root.render( +
+ {null} +
+ {null} +
, + ); + }); + inst = container.firstChild; + childNodes = inst.childNodes; expect(childNodes.length).toBe(1); expect(childNodes[0]).toBe(childDiv); - inst = ReactDOM.render( -
- {'foo'} -
- {'bar'} -
, - el, - ); - childNodes = filterOutComments(inst.childNodes); + await act(() => { + root.render( +
+ {'foo'} +
+ {'bar'} +
, + ); + }); + inst = container.firstChild; + childNodes = inst.childNodes; expect(childNodes.length).toBe(3); expect(childNodes[0].data).toBe('foo'); expect(childNodes[1]).toBe(childDiv); @@ -106,101 +111,125 @@ describe('ReactDOMTextComponent', () => { /** * The following Node.normalize() tests are intentionally failing. - * See #9836 tracking whether we'll need to fix this or if it's unnecessary. + * See https://github.com/facebook/react/issues/9836 tracking whether we'll need to fix this or if it's unnecessary. */ + // @gate TODO + it('can reconcile text merged by Node.normalize() alongside other elements', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+ {'foo'} + {'bar'} + {'baz'} + + {'qux'} +
, + ); + }); - xit('can reconcile text merged by Node.normalize() alongside other elements', () => { - const el = document.createElement('div'); - let inst = ReactDOM.render( -
- {'foo'} - {'bar'} - {'baz'} - - {'qux'} -
, - el, - ); + const inst = container.firstChild; inst.normalize(); - inst = ReactDOM.render( -
- {'bar'} - {'baz'} - {'qux'} - - {'foo'} -
, - el, - ); + await act(() => { + root.render( +
+ {'bar'} + {'baz'} + {'qux'} + + {'foo'} +
, + container, + ); + }); expect(inst.textContent).toBe('barbazquxfoo'); }); - xit('can reconcile text merged by Node.normalize()', () => { - const el = document.createElement('div'); - let inst = ReactDOM.render( -
- {'foo'} - {'bar'} - {'baz'} -
, - el, - ); + // @gate TODO + it('can reconcile text merged by Node.normalize()', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+ {'foo'} + {'bar'} + {'baz'} +
, + ); + }); + let inst = container.firstChild; inst.normalize(); - inst = ReactDOM.render( -
- {'bar'} - {'baz'} - {'qux'} -
, - el, - ); + await act(() => { + root.render( +
+ {'bar'} + {'baz'} + {'qux'} +
, + container, + ); + }); + inst = container.firstChild; expect(inst.textContent).toBe('barbazqux'); }); - it('can reconcile text from pre-rendered markup', () => { - const el = document.createElement('div'); - let reactEl = ( + it('can reconcile text from pre-rendered markup', async () => { + const container = document.createElement('div'); + let children = (
{'foo'} {'bar'} {'baz'}
); - el.innerHTML = ReactDOMServer.renderToString(reactEl); + container.innerHTML = ReactDOMServer.renderToString(children); - ReactDOM.hydrate(reactEl, el); - expect(el.textContent).toBe('foobarbaz'); + const root = await act(() => { + return ReactDOMClient.hydrateRoot(container, children); + }); + expect(container.textContent).toBe('foobarbaz'); - ReactDOM.unmountComponentAtNode(el); + await act(() => { + root.unmount(); + }); - reactEl = ( + children = (
{''} {''} {''}
); - el.innerHTML = ReactDOMServer.renderToString(reactEl); + container.innerHTML = ReactDOMServer.renderToString(children); - ReactDOM.hydrate(reactEl, el); - expect(el.textContent).toBe(''); + await act(() => { + ReactDOMClient.hydrateRoot(container, children); + }); + expect(container.textContent).toBe(''); }); - xit('can reconcile text arbitrarily split into multiple nodes', () => { - const el = document.createElement('div'); - let inst = ReactDOM.render( -
- - {'foobarbaz'} -
, - el, - ); + // @gate TODO + it('can reconcile text arbitrarily split into multiple nodes', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); - const childNodes = filterOutComments(inst.childNodes); + await act(() => { + root.render( +
+ + {'foobarbaz'} +
, + ); + }); + let inst = container.firstChild; + + const childNodes = inst.childNodes; const textNode = childNodes[1]; textNode.textContent = 'foo'; inst.insertBefore( @@ -212,32 +241,40 @@ describe('ReactDOMTextComponent', () => { childNodes[1].nextSibling, ); - inst = ReactDOM.render( -
- - {'barbazqux'} -
, - el, - ); + await act(() => { + root.render( +
+ + {'barbazqux'} +
, + container, + ); + }); + inst = container.firstChild; expect(inst.textContent).toBe('barbazqux'); }); - xit('can reconcile text arbitrarily split into multiple nodes on some substitutions only', () => { - const el = document.createElement('div'); - let inst = ReactDOM.render( -
- - {'bar'} - - {'foobarbaz'} - {'foo'} - {'barfoo'} - -
, - el, - ); + // @gate TODO + it('can reconcile text arbitrarily split into multiple nodes on some substitutions only', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+ + {'bar'} + + {'foobarbaz'} + {'foo'} + {'barfoo'} + +
, + ); + }); - const childNodes = filterOutComments(inst.childNodes); + let inst = container.firstChild; + + const childNodes = inst.childNodes; const textNode = childNodes[3]; textNode.textContent = 'foo'; inst.insertBefore( @@ -255,38 +292,48 @@ describe('ReactDOMTextComponent', () => { childNodes[5].nextSibling, ); - inst = ReactDOM.render( -
- - {'baz'} - - {'barbazqux'} - {'bar'} - {'bazbar'} - -
, - el, - ); + await act(() => { + root.render( +
+ + {'baz'} + + {'barbazqux'} + {'bar'} + {'bazbar'} + +
, + container, + ); + }); + inst = container.firstChild; expect(inst.textContent).toBe('bazbarbazquxbarbazbar'); }); - xit('can unmount normalized text nodes', () => { - const el = document.createElement('div'); - ReactDOM.render( -
- {''} - {'foo'} - {'bar'} -
, - el, - ); - el.normalize(); - ReactDOM.render(
, el); - expect(el.innerHTML).toBe('
'); + // @gate TODO + it('can unmount normalized text nodes', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
+ {''} + {'foo'} + {'bar'} +
, + ); + }); + + container.normalize(); + await act(() => { + root.render(
); + }); + + expect(container.innerHTML).toBe('
'); }); - it('throws for Temporal-like text nodes', () => { - const el = document.createElement('div'); + it('throws for Temporal-like text nodes', async () => { + const container = document.createElement('div'); class TemporalLike { valueOf() { // Throwing here is the behavior of ECMAScript "Temporal" date/time API. @@ -297,9 +344,12 @@ describe('ReactDOMTextComponent', () => { return '2020-01-01'; } } - expect(() => - ReactDOM.render(
{new TemporalLike()}
, el), - ).toThrowError( + const root = ReactDOMClient.createRoot(container); + await expect( + act(() => { + root.render(
{new TemporalLike()}
); + }), + ).rejects.toThrowError( new Error( 'Objects are not valid as a React child (found: object with keys {}).' + ' If you meant to render a collection of children, use an array instead.',