/** * Tests for live-inject.mjs — script-tag insert/remove round-trip. * Run with: node --test tests/live-inject.test.mjs */ import { describe, it, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; import { existsSync, mkdirSync, mkdtempSync, writeFileSync, readFileSync, realpathSync, rmSync } from 'node:fs'; import { dirname, join, relative, resolve } from 'node:path'; import { tmpdir } from 'node:os'; import { fileURLToPath } from 'node:url'; import { execFileSync } from 'node:child_process'; import { livePathGlobToRegex } from '../skill/scripts/lib/live-path-globs.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const INJECT = resolve(__dirname, '..', 'skill/scripts/live-inject.mjs'); describe('live path globs', () => { it('matches recursive segments, including zero segments', () => { const anywhere = livePathGlobToRegex('**/index.html'); assert.equal(anywhere.test('index.html'), true); assert.equal(anywhere.test('public/index.html'), true); assert.equal(anywhere.test('apps/web/public/index.html'), true); const underPublic = livePathGlobToRegex('public/**/*.html'); assert.equal(underPublic.test('public/index.html'), true); assert.equal(underPublic.test('public/docs/index.html'), true); assert.equal(underPublic.test('src/index.html'), false); }); it('keeps single-star and question-mark matches inside one segment', () => { const pattern = livePathGlobToRegex('pages/*/item?.html'); assert.equal(pattern.test('pages/docs/item1.html'), true); assert.equal(pattern.test('pages/docs/deep/item1.html'), false); assert.equal(pattern.test('pages/docs/item12.html'), false); }); it('treats regular-expression punctuation as literal path text', () => { const pattern = livePathGlobToRegex('pages/[draft]/item+.html'); assert.equal(pattern.test('pages/[draft]/item+.html'), true); assert.equal(pattern.test('pages/d/itemm.html'), false); }); }); function runInject(cwd, configPath, args) { try { const out = execFileSync('node', [INJECT, ...args], { cwd, encoding: 'utf-8', env: { ...process.env, IMPECCABLE_LIVE_CONFIG: configPath }, stdio: ['ignore', 'pipe', 'pipe'], }); return JSON.parse(out.trim()); } catch (err) { const body = err.stdout?.toString().trim() || err.stderr?.toString().trim() || ''; return JSON.parse(body || '{}'); } } function runInjectDefault(cwd, args) { try { const out = execFileSync('node', [INJECT, ...args], { cwd, encoding: 'utf-8', env: { ...process.env, IMPECCABLE_LIVE_CONFIG: '' }, stdio: ['ignore', 'pipe', 'pipe'], }); return JSON.parse(out.trim()); } catch (err) { const body = err.stdout?.toString().trim() || err.stderr?.toString().trim() || ''; return JSON.parse(body || '{}'); } } describe('live-inject — insert/remove round-trip preserves file bytes', () => { let tmp; beforeEach(() => { tmp = mkdtempSync(join(tmpdir(), 'impeccable-inject-test-')); }); afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); it('reports .impeccable/live/config.json as the default missing config path', () => { const result = runInjectDefault(tmp, ['--check']); assert.equal(result.ok, false); assert.equal(result.error, 'config_missing'); assert.equal(result.path, join(realpathSync(tmp), '.impeccable', 'live', 'config.json')); }); it('uses .impeccable/live/config.json without an environment override', () => { const original = `
Content
`; writeFileSync(join(tmp, 'index.html'), original); const configDir = join(tmp, '.impeccable', 'live'); mkdirSync(configDir, { recursive: true }); writeFileSync(join(configDir, 'config.json'), JSON.stringify({ files: ['index.html'], insertBefore: '', commentSyntax: 'html', })); const inserted = runInjectDefault(tmp, ['--port', '8400']); assert.equal(inserted.ok, true); assert.match(readFileSync(join(tmp, 'index.html'), 'utf-8'), /localhost:8400\/live\.js/); const removed = runInjectDefault(tmp, ['--remove']); assert.equal(removed.ok, true); assert.equal(readFileSync(join(tmp, 'index.html'), 'utf-8'), original); }); it('round-trips an HTML file without mangling indentation', () => { const original = `