Skip to content

Commit 125415a

Browse files
committed
test(rsc-mf): broaden rsc federation exposure matrix
1 parent 9ee6176 commit 125415a

10 files changed

Lines changed: 177 additions & 33 deletions

File tree

tests/integration/rsc-mf/host/src/server-component-root/App.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,38 @@
11
import 'server-only';
22
import { Suspense } from 'react';
3+
import { AsyncRemoteServerInfo } from 'rscRemote/AsyncRemoteServerInfo';
4+
import RemoteClientBadge from 'rscRemote/RemoteClientBadge';
35
import { RemoteNestedMixed } from 'rscRemote/RemoteNestedMixed';
6+
import RemoteServerDefault from 'rscRemote/RemoteServerDefault';
7+
import remoteMeta, { getRemoteMetaLabel } from 'rscRemote/remoteMeta';
48
import { getServerOnlyInfo } from 'rscRemote/remoteServerOnly';
9+
import getServerOnlyDefaultInfo from 'rscRemote/remoteServerOnlyDefault';
510
import styles from './App.module.less';
11+
import HostRemoteActionRunner from './HostRemoteActionRunner';
612

713
const App = () => {
814
const remoteServerOnlyInfo = getServerOnlyInfo();
15+
const remoteServerOnlyDefaultInfo = getServerOnlyDefaultInfo();
16+
const remoteMetaLabel = getRemoteMetaLabel();
917

1018
return (
1119
<div className={styles.root}>
1220
<h1>Host RSC Module Federation</h1>
1321
<p className="host-remote-server-only">{remoteServerOnlyInfo}</p>
22+
<p className="host-remote-server-only-default">
23+
{remoteServerOnlyDefaultInfo}
24+
</p>
25+
<p className="host-remote-meta-kind">{remoteMeta.kind}</p>
26+
<p className="host-remote-meta-label">{remoteMetaLabel}</p>
27+
<Suspense fallback={<div>Loading Remote Async Server Info...</div>}>
28+
<AsyncRemoteServerInfo />
29+
</Suspense>
30+
<RemoteServerDefault label="Remote Default Server Card" />
1431
<Suspense fallback={<div>Loading Remote RSC...</div>}>
1532
<RemoteNestedMixed label="Remote Federated Tree" />
1633
</Suspense>
34+
<RemoteClientBadge initialLabel="remote-client-badge-initial" />
35+
<HostRemoteActionRunner />
1736
</div>
1837
);
1938
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import { remoteActionEcho } from 'rscRemote/actions';
5+
import defaultRemoteAction from 'rscRemote/defaultAction';
6+
7+
export default function HostRemoteActionRunner() {
8+
const [defaultResult, setDefaultResult] = useState('');
9+
const [echoResult, setEchoResult] = useState('');
10+
const [isPending, setIsPending] = useState(false);
11+
12+
const runActions = async () => {
13+
setIsPending(true);
14+
try {
15+
const [defaultValue, echoValue] = await Promise.all([
16+
defaultRemoteAction('from-host-client'),
17+
remoteActionEcho('from-host-client'),
18+
]);
19+
setDefaultResult(defaultValue);
20+
setEchoResult(echoValue);
21+
} finally {
22+
setIsPending(false);
23+
}
24+
};
25+
26+
return (
27+
<div className="host-remote-action-runner">
28+
<button
29+
className="host-remote-run-actions"
30+
disabled={isPending}
31+
onClick={runActions}
32+
>
33+
{isPending ? 'Running...' : 'Run Host Remote Actions'}
34+
</button>
35+
<p className="host-remote-default-action-result">{defaultResult}</p>
36+
<p className="host-remote-echo-action-result">{echoResult}</p>
37+
</div>
38+
);
39+
}

tests/integration/rsc-mf/remote/module-federation.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@ export default createModuleFederationConfig({
88
filename: 'static/remoteEntry.js',
99
exposes: {
1010
'./RemoteClientCounter': './src/components/RemoteClientCounter.tsx',
11+
'./RemoteClientBadge': './src/components/RemoteClientBadge.tsx',
1112
'./RemoteServerCard': './src/components/RemoteServerCard.tsx',
13+
'./RemoteServerDefault': './src/components/RemoteServerDefault.tsx',
14+
'./AsyncRemoteServerInfo': './src/components/AsyncRemoteServerInfo.tsx',
1215
'./RemoteNestedMixed': './src/components/RemoteNestedMixed.tsx',
1316
'./remoteServerOnly': './src/components/serverOnly.ts',
17+
'./remoteServerOnlyDefault': './src/components/serverOnlyDefault.ts',
18+
'./remoteMeta': './src/components/remoteMeta.ts',
1419
'./actions': './src/components/actions.ts',
1520
'./nestedActions': './src/components/nestedActions.ts',
21+
'./defaultAction': './src/components/defaultAction.ts',
1622
},
1723
shared: {
1824
react: { singleton: true },
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export async function AsyncRemoteServerInfo() {
2+
const value = await Promise.resolve('remote-async-server-info-ok');
3+
4+
return <p className="remote-async-server-info">{value}</p>;
5+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use client';
2+
3+
import 'client-only';
4+
import { useState } from 'react';
5+
6+
export default function RemoteClientBadge({
7+
initialLabel,
8+
}: {
9+
initialLabel: string;
10+
}) {
11+
const [label, setLabel] = useState(initialLabel);
12+
13+
return (
14+
<div className="remote-client-badge">
15+
<p className="remote-client-badge-value">{label}</p>
16+
<button
17+
className="remote-client-badge-toggle"
18+
onClick={() => setLabel('remote-client-badge-toggled')}
19+
>
20+
Toggle Badge
21+
</button>
22+
</div>
23+
);
24+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { getServerOnlyInfo } from './serverOnly';
2+
3+
export default function RemoteServerDefault({ label }: { label: string }) {
4+
return (
5+
<section className="remote-server-default-card">
6+
<h2>{label}</h2>
7+
<p className="remote-server-default-server-only">{getServerOnlyInfo()}</p>
8+
</section>
9+
);
10+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use server';
2+
3+
export default async function defaultRemoteAction(value: string) {
4+
return `default-action:${value}`;
5+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const remoteFeatureFlags = ['rsc', 'mf', 'actions'] as const;
2+
3+
export function getRemoteMetaLabel() {
4+
return remoteFeatureFlags.join('|');
5+
}
6+
7+
const remoteMeta = {
8+
kind: 'remote-meta-default',
9+
version: 'v1',
10+
};
11+
12+
export default remoteMeta;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import 'server-only';
2+
3+
export default function getServerOnlyDefaultInfo() {
4+
return 'remote-server-only-default-ok';
5+
}

tests/integration/rsc-mf/tests/index.test.ts

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ const fixtureDir = path.resolve(__dirname, '../');
1616
const hostDir = path.resolve(fixtureDir, 'host');
1717
const remoteDir = path.resolve(fixtureDir, 'remote');
1818
const HOST_RSC_URL = '/server-component-root';
19-
const RSC_RUNTIME_BLOCKER_PATTERN =
20-
/Cannot find (render handler|server bundle) for RSC/;
2119

2220
type Mode = 'dev' | 'build';
2321

@@ -56,16 +54,14 @@ function createHostEnv(remotePort: number) {
5654
async function renderRemoteRscIntoHost({ hostPort, page }: TestContext) {
5755
const response = await fetch(`http://127.0.0.1:${hostPort}${HOST_RSC_URL}`);
5856
const html = await response.text();
59-
if (RSC_RUNTIME_BLOCKER_PATTERN.test(html)) {
60-
return {
61-
blocked: true,
62-
html,
63-
};
64-
}
65-
6657
expect(html).toContain('Host RSC Module Federation');
6758
expect(html).toContain('Remote Federated Tree');
6859
expect(html).toContain('remote-server-only-ok');
60+
expect(html).toContain('remote-server-only-default-ok');
61+
expect(html).toContain('remote-meta-default');
62+
expect(html).toContain('rsc|mf|actions');
63+
expect(html).toContain('remote-async-server-info-ok');
64+
expect(html).toContain('Remote Default Server Card');
6965

7066
await page.goto(`http://127.0.0.1:${hostPort}${HOST_RSC_URL}`, {
7167
waitUntil: ['networkidle0', 'domcontentloaded'],
@@ -75,11 +71,24 @@ async function renderRemoteRscIntoHost({ hostPort, page }: TestContext) {
7571
el => el.textContent?.trim(),
7672
);
7773
expect(hostRemoteServerOnly).toBe('remote-server-only-ok');
78-
79-
return {
80-
blocked: false,
81-
html,
82-
};
74+
const hostRemoteServerOnlyDefault = await page.$eval(
75+
'.host-remote-server-only-default',
76+
el => el.textContent?.trim(),
77+
);
78+
expect(hostRemoteServerOnlyDefault).toBe('remote-server-only-default-ok');
79+
const hostRemoteMetaKind = await page.$eval('.host-remote-meta-kind', el =>
80+
el.textContent?.trim(),
81+
);
82+
expect(hostRemoteMetaKind).toBe('remote-meta-default');
83+
const hostRemoteMetaLabel = await page.$eval('.host-remote-meta-label', el =>
84+
el.textContent?.trim(),
85+
);
86+
expect(hostRemoteMetaLabel).toBe('rsc|mf|actions');
87+
const hostRemoteAsyncServerInfo = await page.$eval(
88+
'.remote-async-server-info',
89+
el => el.textContent?.trim(),
90+
);
91+
expect(hostRemoteAsyncServerInfo).toBe('remote-async-server-info-ok');
8392
}
8493

8594
async function supportRemoteClientAndServerActions({
@@ -129,6 +138,31 @@ async function supportRemoteClientAndServerActions({
129138
remoteAction?.textContent?.trim() === 'remote-action:from-client'
130139
);
131140
});
141+
142+
let badgeValue = await page.$eval('.remote-client-badge-value', el =>
143+
el.textContent?.trim(),
144+
);
145+
expect(badgeValue).toBe('remote-client-badge-initial');
146+
await page.click('.remote-client-badge-toggle');
147+
badgeValue = await page.$eval('.remote-client-badge-value', el =>
148+
el.textContent?.trim(),
149+
);
150+
expect(badgeValue).toBe('remote-client-badge-toggled');
151+
152+
await page.click('.host-remote-run-actions');
153+
await page.waitForFunction(() => {
154+
const defaultActionResult = document.querySelector(
155+
'.host-remote-default-action-result',
156+
);
157+
const echoActionResult = document.querySelector(
158+
'.host-remote-echo-action-result',
159+
);
160+
return (
161+
defaultActionResult?.textContent?.trim() ===
162+
'default-action:from-host-client' &&
163+
echoActionResult?.textContent?.trim() === 'remote-action:from-host-client'
164+
);
165+
});
132166
}
133167

134168
function runTests({ mode }: TestConfig) {
@@ -140,8 +174,6 @@ function runTests({ mode }: TestConfig) {
140174
let page: Page;
141175
let browser: Browser;
142176
const runtimeErrors: string[] = [];
143-
let runtimeBlocked = false;
144-
let runtimeBlockerHtml = '';
145177

146178
if (skipForLowerNodeVersion()) {
147179
return;
@@ -206,24 +238,11 @@ function runTests({ mode }: TestConfig) {
206238
}
207239
});
208240

209-
it('should render remote RSC content in host app', async () => {
210-
const renderResult = await renderRemoteRscIntoHost({ hostPort, page });
211-
runtimeBlocked = renderResult.blocked;
212-
runtimeBlockerHtml = renderResult.html;
213-
214-
if (runtimeBlocked) {
215-
expect(runtimeBlockerHtml).toMatch(RSC_RUNTIME_BLOCKER_PATTERN);
216-
}
217-
});
241+
it('should render remote RSC content in host app', () =>
242+
renderRemoteRscIntoHost({ hostPort, page }));
218243

219-
it('should support remote use client and server actions', async () => {
220-
if (runtimeBlocked) {
221-
expect(runtimeBlockerHtml).toMatch(RSC_RUNTIME_BLOCKER_PATTERN);
222-
return;
223-
}
224-
225-
await supportRemoteClientAndServerActions({ hostPort, page });
226-
});
244+
it('should support remote use client and server actions', () =>
245+
supportRemoteClientAndServerActions({ hostPort, page }));
227246

228247
if (mode === 'build') {
229248
it('should have no browser runtime errors', () => {

0 commit comments

Comments
 (0)