Skip to content

Commit 396e709

Browse files
committed
test(rsc-mf): add modern config behavior contract tests
1 parent b7dbdc6 commit 396e709

1 file changed

Lines changed: 301 additions & 0 deletions

File tree

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
import path from 'path';
2+
3+
const HOST_MODERN_CONFIG_MODULE = '../host/modern.config';
4+
const REMOTE_MODERN_CONFIG_MODULE = '../remote/modern.config';
5+
6+
const withEnv = <T>(
7+
env: Partial<Record<'PORT' | 'RSC_MF_REMOTE_PORT', string>>,
8+
run: () => T,
9+
): T => {
10+
const previousPort = process.env.PORT;
11+
const previousRemotePort = process.env.RSC_MF_REMOTE_PORT;
12+
13+
if (typeof env.PORT === 'undefined') {
14+
delete process.env.PORT;
15+
} else {
16+
process.env.PORT = env.PORT;
17+
}
18+
if (typeof env.RSC_MF_REMOTE_PORT === 'undefined') {
19+
delete process.env.RSC_MF_REMOTE_PORT;
20+
} else {
21+
process.env.RSC_MF_REMOTE_PORT = env.RSC_MF_REMOTE_PORT;
22+
}
23+
24+
try {
25+
return run();
26+
} finally {
27+
if (typeof previousPort === 'undefined') {
28+
delete process.env.PORT;
29+
} else {
30+
process.env.PORT = previousPort;
31+
}
32+
if (typeof previousRemotePort === 'undefined') {
33+
delete process.env.RSC_MF_REMOTE_PORT;
34+
} else {
35+
process.env.RSC_MF_REMOTE_PORT = previousRemotePort;
36+
}
37+
}
38+
};
39+
40+
const loadHostConfig = () =>
41+
withEnv({}, () => {
42+
jest.resetModules();
43+
jest.doMock('@modern-js/app-tools', () => ({
44+
appTools: () => ({ name: 'app-tools-mock' }),
45+
defineConfig: (config: unknown) => config,
46+
}));
47+
jest.doMock('@module-federation/modern-js-v3', () => ({
48+
moduleFederationPlugin: () => ({ name: 'mf-plugin-mock' }),
49+
}));
50+
51+
let config: any;
52+
jest.isolateModules(() => {
53+
config = require(HOST_MODERN_CONFIG_MODULE).default;
54+
});
55+
return config;
56+
});
57+
58+
const loadRemoteConfig = ({
59+
port,
60+
remotePort,
61+
}: {
62+
port?: string;
63+
remotePort?: string;
64+
}) =>
65+
withEnv(
66+
{
67+
PORT: port,
68+
RSC_MF_REMOTE_PORT: remotePort,
69+
},
70+
() => {
71+
jest.resetModules();
72+
jest.doMock('@modern-js/app-tools', () => ({
73+
appTools: () => ({ name: 'app-tools-mock' }),
74+
defineConfig: (config: unknown) => config,
75+
}));
76+
jest.doMock('@module-federation/modern-js-v3', () => ({
77+
moduleFederationPlugin: () => ({ name: 'mf-plugin-mock' }),
78+
}));
79+
80+
let config: any;
81+
jest.isolateModules(() => {
82+
config = require(REMOTE_MODERN_CONFIG_MODULE).default;
83+
});
84+
return config;
85+
},
86+
);
87+
88+
const createChainHarness = (target: string | string[]) => {
89+
const aliasMap = new Map<string, string>();
90+
const conditionNames: string[] = [];
91+
const moduleDirectories: string[] = [];
92+
const publicPathCalls: string[] = [];
93+
const splitChunksCalls: unknown[] = [];
94+
const targetCalls: string[] = [];
95+
const rules: Array<{ name: string; test?: RegExp; layer?: string }> = [];
96+
97+
const aliasApi = {
98+
set: (key: string, value: string) => {
99+
aliasMap.set(key, value);
100+
return aliasApi;
101+
},
102+
};
103+
const conditionNamesApi = {
104+
clear: () => {
105+
conditionNames.length = 0;
106+
return conditionNamesApi;
107+
},
108+
add: (value: string) => {
109+
conditionNames.push(value);
110+
return conditionNamesApi;
111+
},
112+
};
113+
const modulesApi = {
114+
clear: () => {
115+
moduleDirectories.length = 0;
116+
return modulesApi;
117+
},
118+
add: (value: string) => {
119+
moduleDirectories.push(value);
120+
return modulesApi;
121+
},
122+
};
123+
124+
const chain = {
125+
get: (key: string) => (key === 'target' ? target : undefined),
126+
target: (value: string) => {
127+
targetCalls.push(value);
128+
return chain;
129+
},
130+
resolve: {
131+
alias: aliasApi,
132+
conditionNames: conditionNamesApi,
133+
modules: modulesApi,
134+
},
135+
output: {
136+
publicPath: (value: string) => {
137+
publicPathCalls.push(value);
138+
return chain.output;
139+
},
140+
},
141+
optimization: {
142+
splitChunks: (value: unknown) => {
143+
splitChunksCalls.push(value);
144+
return chain.optimization;
145+
},
146+
},
147+
module: {
148+
rule: (name: string) => {
149+
const rule = { name } as {
150+
name: string;
151+
test?: RegExp;
152+
layer?: string;
153+
};
154+
rules.push(rule);
155+
return {
156+
test: (value: RegExp) => {
157+
rule.test = value;
158+
return {
159+
layer: (layerValue: string) => {
160+
rule.layer = layerValue;
161+
return chain.module;
162+
},
163+
};
164+
},
165+
};
166+
},
167+
},
168+
};
169+
170+
return {
171+
chain,
172+
aliasMap,
173+
conditionNames,
174+
moduleDirectories,
175+
publicPathCalls,
176+
splitChunksCalls,
177+
targetCalls,
178+
rules,
179+
};
180+
};
181+
182+
describe('rsc-mf modern config contracts', () => {
183+
it('keeps host modern server and source contracts', () => {
184+
const hostConfig = loadHostConfig();
185+
expect(hostConfig.server).toEqual(
186+
expect.objectContaining({
187+
rsc: true,
188+
port: 3007,
189+
}),
190+
);
191+
expect(hostConfig.source).toEqual(
192+
expect.objectContaining({
193+
enableAsyncEntry: false,
194+
}),
195+
);
196+
expect(hostConfig.plugins).toHaveLength(2);
197+
});
198+
199+
it('applies host async-node bundler behavior for node targets', () => {
200+
const hostConfig = loadHostConfig();
201+
const harness = createChainHarness('node');
202+
hostConfig.tools?.bundlerChain?.(harness.chain as any);
203+
204+
expect(harness.targetCalls).toContain('async-node');
205+
expect(harness.conditionNames).toEqual(['require', 'import', 'default']);
206+
expect(harness.aliasMap.get('server-only$')).toMatch(
207+
/server-only[\\/]empty\.js$/,
208+
);
209+
expect(harness.moduleDirectories).toEqual([
210+
path.resolve(__dirname, '../host/node_modules'),
211+
'node_modules',
212+
]);
213+
});
214+
215+
it('configures remote port-driven server and asset settings', () => {
216+
const remoteConfig = loadRemoteConfig({
217+
remotePort: '3991',
218+
});
219+
220+
expect(remoteConfig.server).toEqual(
221+
expect.objectContaining({
222+
rsc: true,
223+
ssr: false,
224+
port: 3991,
225+
}),
226+
);
227+
expect(remoteConfig.output).toEqual(
228+
expect.objectContaining({
229+
assetPrefix: 'http://127.0.0.1:3991',
230+
}),
231+
);
232+
expect(remoteConfig.source).toEqual(
233+
expect.objectContaining({
234+
enableAsyncEntry: false,
235+
}),
236+
);
237+
});
238+
239+
it('enables remote ssr mode when explicit PORT is set', () => {
240+
const remoteConfig = loadRemoteConfig({
241+
port: '4550',
242+
});
243+
expect(remoteConfig.server).toEqual(
244+
expect.objectContaining({
245+
ssr: true,
246+
port: 4550,
247+
}),
248+
);
249+
});
250+
251+
it('applies remote async-node + layer settings for node targets', () => {
252+
const remoteConfig = loadRemoteConfig({
253+
remotePort: '3777',
254+
});
255+
const harness = createChainHarness('node');
256+
remoteConfig.tools?.bundlerChain?.(harness.chain as any);
257+
258+
expect(harness.targetCalls).toContain('async-node');
259+
expect(harness.conditionNames).toEqual(['require', 'import', 'default']);
260+
expect(harness.aliasMap.get('server-only$')).toMatch(
261+
/server-only[\\/]empty\.js$/,
262+
);
263+
expect(harness.aliasMap.get('react/jsx-runtime$')).toMatch(
264+
/react[\\/]jsx-runtime\.react-server\.js$/,
265+
);
266+
expect(harness.aliasMap.get('react/jsx-dev-runtime$')).toMatch(
267+
/react[\\/]jsx-dev-runtime\.react-server\.js$/,
268+
);
269+
expect(
270+
harness.aliasMap.get('rsc-mf-react-server-dom-client-browser$'),
271+
).toContain('react-server-dom-rspack');
272+
expect(harness.publicPathCalls).toContain('http://127.0.0.1:3777/bundles/');
273+
expect(harness.rules).toEqual(
274+
expect.arrayContaining([
275+
expect.objectContaining({
276+
name: 'rsc-mf-remote-components-layer',
277+
layer: 'react-server-components',
278+
}),
279+
]),
280+
);
281+
expect(harness.moduleDirectories).toEqual([
282+
path.resolve(__dirname, '../remote/node_modules'),
283+
'node_modules',
284+
]);
285+
});
286+
287+
it('applies remote client split-chunk + publicPath settings for web targets', () => {
288+
const remoteConfig = loadRemoteConfig({
289+
remotePort: '3888',
290+
});
291+
const harness = createChainHarness('web');
292+
remoteConfig.tools?.bundlerChain?.(harness.chain as any);
293+
294+
expect(harness.targetCalls).toEqual([]);
295+
expect(harness.splitChunksCalls).toEqual([false]);
296+
expect(harness.publicPathCalls).toContain('http://127.0.0.1:3888/');
297+
expect(
298+
harness.aliasMap.get('rsc-mf-react-server-dom-client-browser$'),
299+
).toContain('react-server-dom-rspack');
300+
});
301+
});

0 commit comments

Comments
 (0)