import { describe, it, expect, beforeAll } from 'vitest'; import { existsSync, readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { compileAllInk } from './compile-ink.mjs'; /** * Phase 2 Plan 02-04 Task 1 sanity test for the build-time Ink compiler. * * Imports compile-ink.mjs (the CLI guard prevents the auto-run path from * firing under Vitest) and exercises compileAllInk() against the real * /content/dialogue tree exactly once via beforeAll. Subsequent test * cases inspect the resulting artefacts. * * W9 invariant: compileAllInk() wipes src/content/compiled-ink/ at start, * so we MUST call it from a single beforeAll. Calling it inside multiple * test cases — or concurrently with src/content/ink-loader.test.ts — * creates a filesystem race. The npm run ci chain runs `compile:ink` * BEFORE `test` so under CI both this file and ink-loader.test.ts see * a fully-populated compiled-ink/ directory at module-eval time. This * file's beforeAll is defensive belt-and-suspenders. * * Determinism guarantee: inklecate is deterministic from .ink content, * so same inputs ALWAYS produce the same JSON output. */ let compileResult = null; beforeAll(async () => { // wipe=false to avoid racing with src/content/ink-loader.test.ts when // Vitest runs test files in parallel. Production CLI invocation // (`npm run compile:ink`) keeps wipe=true to clear deleted .ink files. compileResult = await compileAllInk({ wipe: false }); }); describe('scripts/compile-ink.mjs', () => { it('exports compileAllInk', () => { expect(typeof compileAllInk).toBe('function'); }); it('compiles all .ink files in content/dialogue/ and emits .ink.json under src/content/compiled-ink/', () => { expect(compileResult).not.toBeNull(); // 3 Lura beats + 1 compost = 4 minimum. Phase 4+ will add more. expect(compileResult.compiled).toBeGreaterThanOrEqual(4); const expected = [ 'src/content/compiled-ink/season1/lura-arrival.ink.json', 'src/content/compiled-ink/season1/lura-mid.ink.json', 'src/content/compiled-ink/season1/lura-farewell.ink.json', 'src/content/compiled-ink/season1/compost-acknowledgements.ink.json', ]; for (const rel of expected) { expect(existsSync(resolve(process.cwd(), rel))).toBe(true); } }); it('produces valid JSON output (parses without error)', () => { const arrival = readFileSync( resolve(process.cwd(), 'src/content/compiled-ink/season1/lura-arrival.ink.json'), 'utf8', ); // inklecate emits a UTF-8 BOM header byte on some platforms; strip it // before JSON.parse just like the runtime loader will. const stripped = arrival.charCodeAt(0) === 0xfeff ? arrival.slice(1) : arrival; expect(() => JSON.parse(stripped)).not.toThrow(); const obj = JSON.parse(stripped); expect(obj).toBeTypeOf('object'); // inklecate v1.x stories carry an `inkVersion` property at the root. expect(obj.inkVersion).toBeTypeOf('number'); }); });