/** * scaffold-routes — mobile/offline metadata emission (PWA v1). * * Locks the contract: `pwaByView[view]` > `entity.pwa` > `spec.defaultPwa`, * mapped onto the socle's PageMobileMeta as a 3rd register() argument, with * `offline` collapsing to the boolean `offlineCapable`. And the load-bearing * regression: a spec with NO pwa fields emits byte-identical legacy output. */ import { describe, it, expect } from 'vitest' import { generate } from '../generate.js' import { validate } from '../validate.js' import type { ScaffoldRoutesInput } from '../types.js' function fixture(overrides: Partial = {}): ScaffoldRoutesInput { return { module: 'hrm', appCode: 'client', entities: [ { name: 'Employee', section: 'employees', views: ['list', 'detail', 'create', 'edit'], }, ], projectPath: '/test', ...overrides, } as ScaffoldRoutesInput } function registryOf(spec: ScaffoldRoutesInput): string { return generate(spec).find((f) => f.path.includes('Registry'))!.content } describe('scaffold-routes — pwa metadata emission', () => { it('emits nothing (2-arg, byte-identical) when no pwa field is present', () => { const legacy = registryOf(fixture()) expect(legacy).not.toContain('mobile:') expect(legacy).toContain("PageRegistry.register('client.hrm.employees', EmployeesListPage);") }) it('entity-level pwa applies to every view, offline → offlineCapable: true', () => { const content = registryOf( fixture({ entities: [ { name: 'Employee', section: 'employees', views: ['list', 'detail'], pwa: { support: 'adapted', offline: 'write' }, }, ], } as Partial), ) const occurrences = content.match(/mobile: \{ support: 'adapted', offlineCapable: true \}/g) expect(occurrences).toHaveLength(2) }) it('pwaByView wins over entity pwa; entity pwa wins over defaultPwa', () => { const content = registryOf( fixture({ defaultPwa: { support: 'adapted' }, entities: [ { name: 'Employee', section: 'employees', views: ['list', 'create'], pwa: { support: 'adapted', offline: 'read' }, pwaByView: { create: { support: 'desktop-only' } }, }, // No entity-level pwa → the fleet default applies. { name: 'Department', section: 'departments', views: ['list'] }, ], } as Partial), ) // list ← entity pwa (offline read) expect(content).toMatch( /register\('client\.hrm\.employees', EmployeesListPage, \{\n mobile: \{ support: 'adapted', offlineCapable: true \},\n\}\)/, ) // create ← per-view override (explicit desktop-only, no offlineCapable) expect(content).toMatch( /register\('client\.hrm\.employees\.create', EmployeeFormPage, \{\n mobile: \{ support: 'desktop-only' \},\n\}\)/, ) // departments ← fleet default expect(content).toMatch( /register\('client\.hrm\.departments', DepartmentsListPage, \{\n mobile: \{ support: 'adapted' \},\n\}\)/, ) }) it('offline: true (back-compat boolean) also maps to offlineCapable: true', () => { const content = registryOf( fixture({ entities: [ { name: 'Employee', section: 'employees', views: ['list'], pwa: { support: 'adapted', offline: true }, }, ], } as Partial), ) expect(content).toContain("mobile: { support: 'adapted', offlineCapable: true }") }) }) describe('scaffold-routes — pwa validation gates', () => { it("rejects support 'full' (v2) at every level", () => { const base = fixture() for (const raw of [ { ...base, defaultPwa: { support: 'full' } }, { ...base, entities: [{ ...base.entities[0], pwa: { support: 'full' } }] }, { ...base, entities: [{ ...base.entities[0], pwaByView: { list: { support: 'full' } } }] }, ]) { const res = validate(raw) expect(res.valid).toBe(false) expect(res.errors.join('\n')).toMatch(/'full'|mobile\.tsx/) } }) it('rejects a pwaByView key naming a view the entity does not emit', () => { const base = fixture() const res = validate({ ...base, entities: [ { ...base.entities[0], views: ['list'], pwaByView: { kanban: { support: 'adapted' } } }, ], }) expect(res.valid).toBe(false) expect(res.errors.join('\n')).toMatch(/kanban.*does not emit|does not emit.*kanban/) }) it('rejects offline on a desktop-only meta', () => { const base = fixture() const res = validate({ ...base, entities: [ { ...base.entities[0], pwa: { support: 'desktop-only', offline: 'read' } }, ], }) expect(res.valid).toBe(false) expect(res.errors.join('\n')).toMatch(/desktop-only/) }) it('accepts a legacy spec untouched (no pwa fields)', () => { const res = validate(fixture()) expect(res.valid).toBe(true) expect(res.errors).toEqual([]) }) })