Skip to content

Commit

Permalink
Add selective hydration test
Browse files Browse the repository at this point in the history
Demonstrates that selective hydration works and ids are preserved even
after subsequent client updates.
  • Loading branch information
acdlite committed Nov 1, 2021
1 parent 2a4e9c9 commit 371302f
Showing 1 changed file with 109 additions and 51 deletions.
160 changes: 109 additions & 51 deletions packages/react-dom/src/__tests__/ReactDOMUseId-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,6 @@ describe('useId', () => {
}
}

function Text({text}) {
Scheduler.unstable_yieldValue(text);
return text;
}

function normalizeTreeIdForTesting(id) {
const [serverClientPrefix, base32, hookIndex] = id.split(':');
if (serverClientPrefix === 'r') {
Expand Down Expand Up @@ -359,96 +354,159 @@ describe('useId', () => {
`);
});

test('inserting a sibling before a dehydrated Suspense boundary', async () => {
test('inserting/deleting siblings outside a dehydrated Suspense boundary', async () => {
const span = React.createRef(null);
function App({showMore}) {
// Note: Using a dynamic array so this is treated as an insertion instead
// of an update, because Fiber currently allocates a node even for
// empty children.
const children = [<Text key="A" text="A" />];
if (showMore) {
// These are client-only nodes. They aren't not included in the initial
// server render.
children.push(<Text key="B" text="B" />, <DivWithId key="C" />);
}
children.push(
function App({swap}) {
// Note: Using a dynamic array so these are treated as insertions and
// deletions instead of updates, because Fiber currently allocates a node
// even for empty children.
const children = [
<DivWithId key="A" />,
swap ? <DivWithId key="C" /> : <DivWithId key="B" />,
<DivWithId key="D" />,
];
return (
<>
{children}
<Suspense key="boundary" fallback="Loading...">
<DivWithId />
<span ref={span} />
</Suspense>
</>
);
}

await serverAct(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
pipe(writable);
});
const dehydratedSpan = container.getElementsByTagName('span')[0];
await clientAct(async () => {
const root = ReactDOM.hydrateRoot(container, <App />);
expect(Scheduler).toFlushUntilNextPaint([]);
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<div
id="101"
/>
<div
id="1001"
/>
<div
id="1101"
/>
<!--$-->
<div
id="110"
/>
<span />
<!--/$-->
</div>
`);

// The inner boundary hasn't hydrated yet
expect(span.current).toBe(null);

// Swap B for C
root.render(<App swap={true} />);
});
// The swap should not have caused a mismatch.
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<div
id="101"
/>
<div
id="CLIENT_GENERATED_ID"
/>
<div
id="1101"
/>
<!--$-->
<div
id="110"
/>
<span />
<!--/$-->
</div>
`);
// Should have hydrated successfully
expect(span.current).toBe(dehydratedSpan);
});

test('inserting/deleting siblings inside a dehydrated Suspense boundary', async () => {
const span = React.createRef(null);
function App({swap}) {
// Note: Using a dynamic array so these are treated as insertions and
// deletions instead of updates, because Fiber currently allocates a node
// even for empty children.
const children = [
<DivWithId key="A" />,
swap ? <DivWithId key="C" /> : <DivWithId key="B" />,
<DivWithId key="D" />,
];
return (
<Suspense key="boundary" fallback="Loading...">
<DivWithId />
<DivWithId />
{children}
<span ref={span} />
</Suspense>,
<DivWithId key="after" />,
</Suspense>
);

return children;
}

await serverAct(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
pipe(writable);
});
expect(Scheduler).toHaveYielded(['A']);
const dehydratedSpan = container.getElementsByTagName('span')[0];
await clientAct(async () => {
const root = ReactDOM.hydrateRoot(container, <App />);
expect(Scheduler).toFlushUntilNextPaint(['A']);
expect(Scheduler).toFlushUntilNextPaint([]);
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
A
<!-- -->
<!--$-->
<div
id="110"
id="101"
/>
<div
id="1010"
id="1001"
/>
<span />
<!--/$-->
<div
id="11"
id="1101"
/>
<span />
<!--/$-->
</div>
`);

// The inner boundary hasn't hydrated yet
expect(span.current).toBe(null);

// Insert another sibling before the Suspense boundary
root.render(<App showMore={true} />);
// Swap B for C
root.render(<App swap={true} />);
});
expect(Scheduler).toHaveYielded([
'A',
'B',
// The update triggers selective hydration so we render again
'A',
'B',
]);
// The insertions should not cause a mismatch.
// The swap should not have caused a mismatch.
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
A
<!-- -->
<!--$-->
B
<div
id="CLIENT_GENERATED_ID"
id="101"
/>
<div
id="110"
id="CLIENT_GENERATED_ID"
/>
<div
id="1010"
id="1101"
/>
<span />
<!--/$-->
<div
id="11"
/>
</div>
`);
// Should have hydrated successfully
Expand Down

0 comments on commit 371302f

Please sign in to comment.