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

[DevTools] Support Server Components in Tree #30684

Merged
merged 8 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3150,4 +3150,35 @@ describe('InspectedElement', () => {
<Child> ⚠
`);
});

// @reactVersion > 18.2
it('should inspect server components', async () => {
const ChildPromise = Promise.resolve(<div />);
ChildPromise._debugInfo = [
{
name: 'ServerComponent',
env: 'Server',
owner: null,
},
];
const Parent = () => ChildPromise;

await utils.actAsync(() => {
modernRender(<Parent />);
});

const inspectedElement = await inspectElementAtIndex(1);
expect(inspectedElement).toMatchInlineSnapshot(`
{
"context": null,
"events": undefined,
"hooks": null,
"id": 3,
"owners": null,
"props": null,
"rootType": "createRoot()",
"state": null,
}
`);
});
});
276 changes: 276 additions & 0 deletions packages/react-devtools-shared/src/__tests__/store-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2212,4 +2212,280 @@ describe('Store', () => {
`);
});
});

// @reactVersion > 18.2
it('does not show server components without any children reified children', async () => {
// A Server Component that doesn't render into anything on the client doesn't show up.
const ServerPromise = Promise.resolve(null);
ServerPromise._debugInfo = [
{
name: 'ServerComponent',
env: 'Server',
owner: null,
},
];
const App = () => ServerPromise;

await actAsync(() => render(<App />));
expect(store).toMatchInlineSnapshot(`
[root]
<App>
`);
});

// @reactVersion > 18.2
it('does show a server component that renders into a filtered node', async () => {
const ServerPromise = Promise.resolve(<div />);
ServerPromise._debugInfo = [
{
name: 'ServerComponent',
env: 'Server',
owner: null,
},
];
const App = () => ServerPromise;

await actAsync(() => render(<App />));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<ServerComponent> [Server]
`);
});

it('can render the same server component twice', async () => {
function ClientComponent() {
return <div />;
}
const ServerPromise = Promise.resolve(<ClientComponent />);
ServerPromise._debugInfo = [
{
name: 'ServerComponent',
env: 'Server',
owner: null,
},
];
const App = () => (
<>
{ServerPromise}
<ClientComponent />
{ServerPromise}
</>
);

await actAsync(() => render(<App />));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <ServerComponent> [Server]
<ClientComponent>
<ClientComponent>
▾ <ServerComponent> [Server]
<ClientComponent>
`);
});

// @reactVersion > 18.2
it('collapses multiple parent server components into one', async () => {
function ClientComponent() {
return <div />;
}
const ServerPromise = Promise.resolve(<ClientComponent />);
ServerPromise._debugInfo = [
{
name: 'ServerComponent',
env: 'Server',
owner: null,
},
];
const ServerPromise2 = Promise.resolve(<ClientComponent />);
ServerPromise2._debugInfo = [
{
name: 'ServerComponent2',
env: 'Server',
owner: null,
},
];
const App = ({initial}) => (
<>
{ServerPromise}
{ServerPromise}
{ServerPromise2}
{initial ? null : ServerPromise2}
</>
);

await actAsync(() => render(<App initial={true} />));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <ServerComponent> [Server]
<ClientComponent>
<ClientComponent>
▾ <ServerComponent2> [Server]
<ClientComponent>
`);

await actAsync(() => render(<App initial={false} />));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <ServerComponent> [Server]
<ClientComponent>
<ClientComponent>
▾ <ServerComponent2> [Server]
<ClientComponent>
<ClientComponent>
`);
});

// @reactVersion > 18.2
it('can reparent a child when the server components change', async () => {
function ClientComponent() {
return <div />;
}
const ServerPromise = Promise.resolve(<ClientComponent />);
ServerPromise._debugInfo = [
{
name: 'ServerAB',
env: 'Server',
owner: null,
},
];
const ServerPromise2 = Promise.resolve(<ClientComponent />);
ServerPromise2._debugInfo = [
{
name: 'ServerA',
env: 'Server',
owner: null,
},
{
name: 'ServerB',
env: 'Server',
owner: null,
},
];
const App = ({initial}) => (initial ? ServerPromise : ServerPromise2);

await actAsync(() => render(<App initial={true} />));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <ServerAB> [Server]
<ClientComponent>
`);

await actAsync(() => render(<App initial={false} />));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <ServerA> [Server]
▾ <ServerB> [Server]
<ClientComponent>
`);
});

// @reactVersion > 18.2
it('splits a server component parent when a different child appears between', async () => {
function ClientComponent() {
return <div />;
}
const ServerPromise = Promise.resolve(<ClientComponent />);
ServerPromise._debugInfo = [
{
name: 'ServerComponent',
env: 'Server',
owner: null,
},
];
const App = ({initial}) =>
initial ? (
<>
{ServerPromise}
{null}
{ServerPromise}
</>
) : (
<>
{ServerPromise}
<ClientComponent />
{ServerPromise}
</>
);

await actAsync(() => render(<App initial={true} />));
// Initially the Server Component only appears once because the children
// are consecutive.
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <ServerComponent> [Server]
<ClientComponent>
<ClientComponent>
`);

// Later the same instance gets split into two when it is no longer
// consecutive so we need two virtual instances to represent two parents.
await actAsync(() => render(<App initial={false} />));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <ServerComponent> [Server]
<ClientComponent>
<ClientComponent>
▾ <ServerComponent> [Server]
<ClientComponent>
`);
});

// @reactVersion > 18.2
it('can reorder keyed components', async () => {
function ClientComponent({text}) {
return <div>{text}</div>;
}
function getServerComponent(key) {
const ServerPromise = Promise.resolve(
<ClientComponent key={key} text={key} />,
);
ServerPromise._debugInfo = [
{
name: 'ServerComponent',
env: 'Server',
owner: null,
// TODO: Ideally the debug info should include the "key" too to
// preserve the virtual identity of the server component when
// reordered. Atm only the children of it gets reparented.
},
];
return ServerPromise;
}
const set1 = ['A', 'B', 'C'].map(getServerComponent);
const set2 = ['B', 'A', 'D'].map(getServerComponent);

const App = ({initial}) => (initial ? set1 : set2);

await actAsync(() => render(<App initial={true} />));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <ServerComponent> [Server]
<ClientComponent key="A">
▾ <ServerComponent> [Server]
<ClientComponent key="B">
▾ <ServerComponent> [Server]
<ClientComponent key="C">
`);

await actAsync(() => render(<App initial={false} />));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <ServerComponent> [Server]
<ClientComponent key="B">
▾ <ServerComponent> [Server]
<ClientComponent key="A">
▾ <ServerComponent> [Server]
<ClientComponent key="D">
`);
});
});
Loading
Loading