/** * Precache cap + runtime asset net (constat C of the PickEBike audit). * * The 2.5 MiB maximumFileSizeToCacheInBytes silently EXCLUDES any bigger * chunk from the precache (Workbox build warning only) — on an app whose * entry chunk exceeds it, offline boot was a blank screen with zero signal. * These tests pin the three legs of the fix: the cap is spec-tunable (default * unchanged), the emitted comment tells the truth (no more "load from the * network like today"), and sw.ts ships the CacheFirst same-origin asset net * with the /api/ + /hubs/ exclusions that keep tenant-keyed media off it. */ import { describe, it, expect } from 'vitest' import { vitePwaSnippet, patchViteConfig } from '../patchers.js' import { swSource } from '../generate.js' import { ScaffoldPwaInputSchema } from '../types.js' const MANIFEST = { name: 'Client App', shortName: 'Client', description: 'x', themeColor: '#112233', backgroundColor: '#ffffff', } as Parameters[0] describe('precache cap — spec-tunable, default pinned', () => { it('defaults to the socle pin (2.5 MiB)', () => { expect(vitePwaSnippet(MANIFEST)).toContain('maximumFileSizeToCacheInBytes: 2.5 * 1024 * 1024') }) it('emits a custom cap when precacheMaxMiB is provided', () => { const snippet = vitePwaSnippet(MANIFEST, ' ', 6) expect(snippet).toContain('maximumFileSizeToCacheInBytes: 6 * 1024 * 1024') }) it('the misleading "load from the network like today" comment is gone; DEV-PWA-012 pointer present', () => { const snippet = vitePwaSnippet(MANIFEST) expect(snippet).not.toContain('load from the network like today') expect(snippet).toContain('EXCLUDED from the precache') expect(snippet).toContain('DEV-PWA-012') expect(snippet).toContain('manualChunks') }) it('patchViteConfig propagates the cap into the patched config', () => { const src = "import react from '@vitejs/plugin-react'\nexport default {\n plugins: [\n react(),\n ],\n}\n" const r = patchViteConfig(src, MANIFEST, 4) expect(r.changed).toBe(true) expect(r.content).toContain('maximumFileSizeToCacheInBytes: 4 * 1024 * 1024') }) it('the zod schema defaults precacheMaxMiB to 2.5 and caps it under the iOS quota', () => { const parsed = ScaffoldPwaInputSchema.parse({ projectPath: '/p', appCode: 'client' }) expect(parsed.precacheMaxMiB).toBe(2.5) expect(() => ScaffoldPwaInputSchema.parse({ projectPath: '/p', appCode: 'client', precacheMaxMiB: 60 })).toThrow() }) }) describe('sw.ts — CacheFirst same-origin asset net', () => { const sw = swSource('Client App') it('registers the net with the exact guards (destination-based, /api/ + /hubs/ excluded)', () => { expect(sw).toContain("import { NetworkFirst, CacheFirst } from 'workbox-strategies';") expect(sw).toContain("const ASSETS_CACHE_NAME = 'smartstack-assets';") expect(sw).toContain("const ASSET_DESTINATIONS = ['script', 'style', 'font', 'image'];") expect(sw).toContain('ASSET_DESTINATIONS.includes(request.destination)') const net = sw.slice(sw.indexOf('Runtime safety net')) expect(net).toContain("!url.pathname.startsWith('/api/')") expect(net).toContain("!url.pathname.startsWith('/hubs/')") expect(net).toContain('purgeOnQuotaError: true') }) it('the net is registered AFTER the tenant-keyed API route (path guard documented)', () => { expect(sw.indexOf('Runtime safety net')).toBeGreaterThan(sw.indexOf('GET /api/* → NetworkFirst')) expect(sw).toContain('tenant-blind') }) })