import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, it, expect } from 'vitest' import { PWA_SUPPORT_LEVELS, PwaMetaSchema, OfflineInputSchema, normalizeOffline, scanPagespecPwaDeclarations, validatePwaMetaV1, toPageMobileMetaLiteral, } from '../pwa-meta.js' import { renamePagespecRefs } from '../ba-menu-tree.js' describe('normalizeOffline', () => { it('maps the full input table', () => { expect(normalizeOffline(undefined)).toBe('none') expect(normalizeOffline(false)).toBe('none') expect(normalizeOffline(true)).toBe('read') expect(normalizeOffline('read')).toBe('read') expect(normalizeOffline('write')).toBe('write') }) }) describe('PwaMetaSchema', () => { it('accepts every support level with and without offline', () => { for (const support of PWA_SUPPORT_LEVELS) { expect(PwaMetaSchema.safeParse({ support }).success).toBe(true) } expect(PwaMetaSchema.safeParse({ support: 'adapted', offline: 'write' }).success).toBe(true) expect(PwaMetaSchema.safeParse({ support: 'adapted', offline: true }).success).toBe(true) }) it('rejects unknown support levels and offline values', () => { expect(PwaMetaSchema.safeParse({ support: 'mobile' }).success).toBe(false) expect(PwaMetaSchema.safeParse({ support: 'adapted', offline: 'sync' }).success).toBe(false) expect(OfflineInputSchema.safeParse('none').success).toBe(false) }) it('passes unknown future fields through (pipeline tolerance)', () => { const parsed = PwaMetaSchema.parse({ support: 'adapted', mobileComponent: 'X' }) expect((parsed as Record).mobileComponent).toBe('X') }) }) describe('validatePwaMetaV1', () => { it("rejects support 'full' (v2 — .mobile.tsx variants)", () => { expect(validatePwaMetaV1({ support: 'full' })).toMatch(/mobile\.tsx|'adapted'/) expect(validatePwaMetaV1({ support: 'full', offline: 'read' })).not.toBeNull() }) it('rejects offline on a desktop-only page', () => { expect(validatePwaMetaV1({ support: 'desktop-only', offline: 'read' })).toMatch(/desktop-only/) expect(validatePwaMetaV1({ support: 'desktop-only', offline: true })).not.toBeNull() }) it('accepts the generatable combinations', () => { expect(validatePwaMetaV1({ support: 'adapted' })).toBeNull() expect(validatePwaMetaV1({ support: 'adapted', offline: 'read' })).toBeNull() expect(validatePwaMetaV1({ support: 'adapted', offline: 'write' })).toBeNull() expect(validatePwaMetaV1({ support: 'desktop-only' })).toBeNull() expect(validatePwaMetaV1({ support: 'desktop-only', offline: false })).toBeNull() }) }) describe('toPageMobileMetaLiteral', () => { it('maps offline level onto the boolean offlineCapable', () => { expect(toPageMobileMetaLiteral({ support: 'adapted' })).toBe("{ support: 'adapted' }") expect(toPageMobileMetaLiteral({ support: 'adapted', offline: 'read' })).toBe( "{ support: 'adapted', offlineCapable: true }", ) // write is a data-layer fact — the registry only sees offlineCapable: true expect(toPageMobileMetaLiteral({ support: 'adapted', offline: 'write' })).toBe( "{ support: 'adapted', offlineCapable: true }", ) expect(toPageMobileMetaLiteral({ support: 'desktop-only' })).toBe("{ support: 'desktop-only' }") }) }) // --------------------------------------------------------------------------- // scanPagespecPwaDeclarations — the machine block is read through lib/pagespec-blocks (lot 15, 2026-09-24) // --------------------------------------------------------------------------- describe('scanPagespecPwaDeclarations — reads the FIRST machine block through lib/pagespec-blocks (lot 15)', () => { let root = '' afterEach(() => { if (root) rmSync(root, { recursive: true, force: true }) root = '' }) const pagespecsDir = (app: string, mod: string): string => { const dir = join(root, '.smartstack', 'ba', app, mod, 'pagespecs') mkdirSync(dir, { recursive: true }) return dir } const SPEC = (pwa?: unknown): Record => ({ screenCode: 'SCR-CRM-PIPELINE-CLIENTS-001', appCode: 'crm', module: 'pipeline', section: 'clients', entity: 'JourFerie', view: 'list', permission: 'pipeline.clients.read', ...(pwa === undefined ? {} : { pwa }), }) /** * A pagespec as a REAL SmartStack writer leaves it on disk: authored with the * section under its old code, then re-serialised by renamePagespecRefs * (lib/ba-menu-tree) — the fence and indent the derive CLIs use, not a * hand-typed mimic. `prose` goes between the title and the block. */ const written = (spec: Record, prose = ''): string => { const authored = `# JourFerie — list\n${prose}\n\`\`\`json\n${JSON.stringify({ ...spec, section: 'opportunites', permission: 'pipeline.opportunites.read' })}\n\`\`\`\n` const out = renamePagespecRefs(authored, { app: 'crm', mod: 'pipeline', oldSec: 'opportunites', newSec: 'clients', ownModule: true }) if (out.error !== undefined || out.count === 0) throw new Error(`renamePagespecRefs did not rewrite: ${JSON.stringify(out)}`) return out.content } it('SILENT — counts, across apps and modules, the pagespecs whose block declares `pwa`; no pwa / pwa null / no block / unparsable / not .md / `_` and dot folders / missing root → not counted', () => { root = mkdtempSync(join(tmpdir(), 'ss-pwa-meta-')) const crm = pagespecsDir('CRM', 'PIPELINE') writeFileSync(join(crm, 'JourFerie.list.md'), written(SPEC({ support: 'adapted', offline: 'read' }))) writeFileSync(join(crm, 'JourFerie.form.md'), written(SPEC())) writeFileSync(join(crm, 'Status.list.md'), written(SPEC(null))) writeFileSync(join(crm, 'Address.detail.md'), '# no block\n') writeFileSync(join(crm, 'Category.list.md'), '```json\n{ nope\n```\n') writeFileSync(join(crm, 'notes.txt'), written(SPEC({ support: 'adapted' }))) writeFileSync(join(pagespecsDir('ERP', 'STOCK'), 'Category.list.md'), written(SPEC({ support: 'desktop-only' }))) writeFileSync(join(pagespecsDir('_audit', 'X'), 'Category.list.md'), written(SPEC({ support: 'adapted' }))) writeFileSync(join(pagespecsDir('CRM', '.hidden'), 'Category.list.md'), written(SPEC({ support: 'adapted' }))) expect(scanPagespecPwaDeclarations(root)).toBe(2) expect(scanPagespecPwaDeclarations(join(root, 'nowhere'))).toBe(0) }) it('FIRES — a prose line ending with an inline ```json mention BEFORE the machine block: the declaration is counted (the unanchored copy read the phantom and counted nothing)', () => { root = mkdtempSync(join(tmpdir(), 'ss-pwa-meta-')) const prose = '\nThe permission shape, e.g. ```json\n{"permission": "pipeline.clients.read"}\n```\nprecedes the machine block:\n' writeFileSync(join(pagespecsDir('CRM', 'PIPELINE'), 'JourFerie.list.md'), written(SPEC({ support: 'adapted', offline: 'write' }), prose)) expect(scanPagespecPwaDeclarations(root)).toBe(1) }) it('SILENT — a CRLF file on disk is counted like an LF one', () => { root = mkdtempSync(join(tmpdir(), 'ss-pwa-meta-')) writeFileSync(join(pagespecsDir('CRM', 'PIPELINE'), 'JourFerie.list.md'), written(SPEC({ support: 'adapted' })).replace(/\n/g, '\r\n')) expect(scanPagespecPwaDeclarations(root)).toBe(1) }) })