Skip to content

Commit 325ea24

Browse files
committed
test(e2e): assert module pages load their declared bundle in one request
Covers the #287 contract end to end: for each module route the shell must declare an assembly for that page's module, and the browser must fetch exactly that bundle, once, with a 200 — a second request would mean the client fell back to probing after a 404. Expectations are derived from the shell's own Inertia component name and assembly map rather than a hardcoded route table, so the test checks the contract instead of restating today's wiring. Also asserts Tailwind utilities used by module pages survive compilation, with an unused control class to prove the check discriminates (#288). Verified the spec fails when the map is emptied (5/5 route cases) and passes once restored.
1 parent 90e7ab2 commit 325ea24

1 file changed

Lines changed: 85 additions & 0 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { expect, test } from '../../fixtures/base';
2+
3+
// One route per module that renders its own React pages, so each case exercises a
4+
// different RCL assembly.
5+
const MODULE_ROUTES = ['/', '/files', '/settings/me', '/admin', '/branding/manage'] as const;
6+
7+
/**
8+
* The server declares module -> RCL assembly in the page shell, and the client resolves a
9+
* page bundle from it instead of guessing "SimpleModule.<Module>" and eating a 404 when the
10+
* guess is wrong (#287). These assertions are deliberately derived from the shell itself
11+
* rather than a hardcoded route->assembly table, so they check the actual contract: whatever
12+
* the server declared for this page's module is what the browser requested, once, and it
13+
* served.
14+
*/
15+
test.describe('Module page bundles', () => {
16+
for (const route of MODULE_ROUTES) {
17+
test(`${route} loads its declared bundle in a single request`, async ({ page }) => {
18+
const bundles: { status: number; url: string }[] = [];
19+
const failures: string[] = [];
20+
21+
page.on('response', (res) => {
22+
if (res.url().includes('.pages.js')) {
23+
bundles.push({ status: res.status(), url: res.url() });
24+
}
25+
if (res.status() >= 400) {
26+
failures.push(`${res.status()} ${res.url()}`);
27+
}
28+
});
29+
30+
await page.goto(route);
31+
await expect.poll(() => bundles.length, { timeout: 10_000 }).toBeGreaterThan(0);
32+
33+
const { module, declaredAssembly } = await page.evaluate(() => {
34+
const map = JSON.parse(
35+
document.querySelector('script[data-module-assemblies]')?.textContent ?? '{}',
36+
);
37+
const pageData = JSON.parse(
38+
document.querySelector('script[data-page="app"]')?.textContent ?? '{}',
39+
);
40+
const name: string = pageData.component ?? '';
41+
const mod = name.split('/')[0];
42+
return { module: mod, declaredAssembly: map[mod] as string | undefined };
43+
});
44+
45+
expect(module, 'shell should name the Inertia component').not.toBe('');
46+
expect(declaredAssembly, `no assembly declared for module "${module}"`).toBeTruthy();
47+
48+
// Exactly one bundle request: a second would mean the first candidate 404'd and the
49+
// client fell back to probing, which is the regression this map removes.
50+
expect(bundles).toHaveLength(1);
51+
expect(bundles[0].url).toContain(
52+
`/_content/${declaredAssembly}/${declaredAssembly}.pages.js`,
53+
);
54+
expect(bundles[0].status).toBe(200);
55+
expect(failures, 'no request should fail').toEqual([]);
56+
});
57+
}
58+
59+
test('compiled stylesheet carries the utility classes module pages use', async ({ page }) => {
60+
await page.goto('/');
61+
62+
const styles = await page.evaluate(() => {
63+
const probe = (cls: string, prop: string) => {
64+
const el = document.createElement('div');
65+
el.className = cls;
66+
document.body.appendChild(el);
67+
const value = getComputedStyle(el).getPropertyValue(prop);
68+
el.remove();
69+
return value;
70+
};
71+
return {
72+
flex: probe('flex', 'display'),
73+
padding: probe('p-4', 'padding'),
74+
// No module uses this one, so it must stay unstyled — otherwise the assertions
75+
// above would pass even against a stylesheet containing every possible utility.
76+
control: probe('bg-lime-700', 'background-color'),
77+
};
78+
});
79+
80+
// If Tailwind's input set misses a source root, these silently vanish (#288).
81+
expect(styles.flex).toBe('flex');
82+
expect(styles.padding).toBe('16px');
83+
expect(styles.control).toBe('rgba(0, 0, 0, 0)');
84+
});
85+
});

0 commit comments

Comments
 (0)