Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fizz] Improve text separator byte efficiency #24630

Merged
merged 10 commits into from May 28, 2022
221 changes: 221 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Expand Up @@ -234,6 +234,11 @@ describe('ReactDOMFizzServer', () => {
return readText(text);
}

function AsyncTextWrapped({as, text}) {
let As = as;
return <As>{readText(text)}</As>;
}

// @gate experimental
it('should asynchronously load a lazy component', async () => {
let resolveA;
Expand Down Expand Up @@ -3577,4 +3582,220 @@ describe('ReactDOMFizzServer', () => {
</div>,
);
});

describe('text separators', () => {
it('it only includes separators between adjacent text nodes', async () => {
function App({name}) {
return (
<div>
hello<b>world, {name}</b>!
</div>
);
}

await act(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<App name="Foo" />,
);
pipe(writable);
});

expect(container.innerHTML).toEqual(
'<div>hello<b>world, <!-- -->Foo</b>!</div>',
);
});

it('it inserts text separators even when adjacent text is in a delayed segment', async () => {
function App({name}) {
return (
<Suspense fallback={'loading...'}>
<div id="app-div">
hello
<b>
world, <AsyncText text={name} />
</b>
!
</div>
</Suspense>
);
}

await act(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<App name="Foo" />,
);
pipe(writable);
});

expect(document.getElementById('app-div').outerHTML).toEqual(
'<div id="app-div">hello<b>world, <template id="P:1"></template></b>!</div>',
);

await act(() => resolveText('Foo'));

expect(container.firstElementChild.outerHTML).toEqual(
'<div id="app-div">hello<b>world, <!-- -->Foo<!-- --></b>!</div>',
);
});

it('it works with multiple adjacent segments', async () => {
function App() {
return (
<Suspense fallback={'loading...'}>
<div id="app-div">
h<AsyncText text={'ello'} />
w<AsyncText text={'orld'} />
</div>
</Suspense>
);
}

await act(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
pipe(writable);
});

expect(document.getElementById('app-div').outerHTML).toEqual(
'<div id="app-div">h<template id="P:1"></template>w<template id="P:2"></template></div>',
);

await act(() => resolveText('orld'));

expect(document.getElementById('app-div').outerHTML).toEqual(
'<div id="app-div">h<template id="P:1"></template>w<!-- -->orld<!-- --></div>',
);

await act(() => resolveText('ello'));
expect(container.firstElementChild.outerHTML).toEqual(
'<div id="app-div">h<!-- -->ello<!-- -->w<!-- -->orld<!-- --></div>',
);
});

it('it works when some segments are flushed and others are patched', async () => {
function App() {
return (
<Suspense fallback={'loading...'}>
<div id="app-div">
h<AsyncText text={'ello'} />
w<AsyncText text={'orld'} />
</div>
</Suspense>
);
}

await act(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
await act(() => resolveText('ello'));
pipe(writable);
});

expect(document.getElementById('app-div').outerHTML).toEqual(
'<div id="app-div">h<!-- -->ello<!-- -->w<template id="P:1"></template></div>',
);

await act(() => resolveText('orld'));

expect(container.firstElementChild.outerHTML).toEqual(
'<div id="app-div">h<!-- -->ello<!-- -->w<!-- -->orld<!-- --></div>',
);
});

it('it does not prepend a text separators if the segment follows a non-Text Node', async () => {
function App() {
return (
<Suspense fallback={'loading...'}>
<div>
hello
<b>
<AsyncText text={'world'} />
</b>
</div>
</Suspense>
);
}

await act(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
await act(() => resolveText('world'));
pipe(writable);
});

expect(container.firstElementChild.outerHTML).toEqual(
'<div>hello<b>world</b></div>',
);
});

it('it does not prepend a text separators if the segments first emission is a non-Text Node', async () => {
function App() {
return (
<Suspense fallback={'loading...'}>
<div>
hello
<AsyncTextWrapped as={'b'} text={'world'} />
</div>
</Suspense>
);
}

await act(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
await act(() => resolveText('world'));
pipe(writable);
});

expect(container.firstElementChild.outerHTML).toEqual(
'<div>hello<b>world</b></div>',
);
});

it('should not insert separators for text inside Suspense boundaries even if they would otherwise be considered text-embedded', async () => {
function App() {
return (
<Suspense fallback={'loading...'}>
<div id="app-div">
start
<Suspense fallback={'[loading first]'}>
firststart
<AsyncText text={'first suspended'} />
firstend
</Suspense>
<Suspense fallback={'[loading second]'}>
secondstart
<b>
<AsyncText text={'second suspended'} />
</b>
</Suspense>
end
</div>
</Suspense>
);
}

await act(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
await act(() => resolveText('world'));
pipe(writable);
});

expect(document.getElementById('app-div').outerHTML).toEqual(
'<div id="app-div">start<!--$?--><template id="B:0"></template>[loading first]<!--/$--><!--$?--><template id="B:1"></template>[loading second]<!--/$-->end</div>',
);

await act(async () => {
resolveText('first suspended');
});

expect(document.getElementById('app-div').outerHTML).toEqual(
'<div id="app-div">start<!--$-->firststart<!-- -->first suspended<!-- -->firstend<!--/$--><!--$?--><template id="B:1"></template>[loading second]<!--/$-->end</div>',
);

await act(async () => {
resolveText('second suspended');
});

expect(container.firstElementChild.outerHTML).toEqual(
'<div id="app-div">start<!--$-->firststart<!-- -->first suspended<!-- -->firstend<!--/$--><!--$-->secondstart<b>second suspended<!-- --></b><!--/$-->end</div>',
);
});
});
});