|
| 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