/** * Tests for the live-wrap CLI helper. * Run with: node --test tests/live-wrap.test.mjs */ import { describe, it, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, writeFileSync, readFileSync, rmSync, mkdirSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { execSync } from 'node:child_process'; import { buildSearchQueries, findElement, findClosingLine, detectCommentSyntax, } from '../source/skills/impeccable/scripts/live-wrap.mjs'; // --------------------------------------------------------------------------- // Unit tests: pure functions // --------------------------------------------------------------------------- describe('detectCommentSyntax', () => { it('returns HTML comments for .html files', () => { const result = detectCommentSyntax('index.html'); assert.equal(result.open, ''); }); it('returns JSX comments for .jsx files', () => { const result = detectCommentSyntax('App.jsx'); assert.equal(result.open, '{/*'); assert.equal(result.close, '*/}'); }); it('returns JSX comments for .tsx files', () => { const result = detectCommentSyntax('component.tsx'); assert.equal(result.open, '{/*'); assert.equal(result.close, '*/}'); }); it('returns HTML comments for .vue files', () => { const result = detectCommentSyntax('App.vue'); assert.equal(result.open, ''); }); it('returns HTML comments for .svelte files', () => { const result = detectCommentSyntax('Page.svelte'); assert.equal(result.open, ''); }); }); describe('buildSearchQueries', () => { it('prioritizes ID over classes', () => { const queries = buildSearchQueries('hero', 'hero-section,dark', 'section', null); assert.equal(queries[0], 'id="hero"'); }); it('includes full class match for multi-class elements', () => { const queries = buildSearchQueries(null, 'hero-section,dark-theme', 'div', null); assert.ok(queries.some(q => q === 'class="hero-section dark-theme"')); }); it('includes the most distinctive single class (longest)', () => { const queries = buildSearchQueries(null, 'btn,hero-combined-left', null, null); assert.ok(queries.some(q => q === 'hero-combined-left')); }); it('includes tag+class combo', () => { const queries = buildSearchQueries(null, 'hero-section', 'section', null); assert.ok(queries.some(q => q === '
{ it('finds an element by class name', () => { const lines = [ '', '', '
', '

Hello

', '
', '', '', ]; const result = findElement(lines, 'hero'); assert.ok(result); assert.equal(result.startLine, 2); assert.equal(result.endLine, 4); }); it('finds an element by ID', () => { const lines = [ '
', '

Content

', '
', ]; const result = findElement(lines, 'id="features"'); assert.ok(result); assert.equal(result.startLine, 0); assert.equal(result.endLine, 2); }); it('returns null when element is not found', () => { const lines = ['
hello
']; const result = findElement(lines, 'nonexistent'); assert.equal(result, null); }); it('skips comments containing the query', () => { const lines = [ '', '
', '

Content

', '
', ]; const result = findElement(lines, 'hero'); assert.ok(result); assert.equal(result.startLine, 1); // skips the comment on line 0 }); it('skips lines that contain data-impeccable-variant', () => { const lines = [ '
Old
', '
Real
', ]; const result = findElement(lines, 'hero'); assert.ok(result); assert.equal(result.startLine, 1); }); }); describe('findClosingLine', () => { it('finds the closing tag on the same line', () => { const lines = ['

Hello

']; assert.equal(findClosingLine(lines, 0), 0); }); it('finds the closing tag across multiple lines', () => { const lines = [ '
', '

Hello

', '
', ]; assert.equal(findClosingLine(lines, 0), 2); }); it('handles nested tags of the same type', () => { const lines = [ '
', '
', '

Content

', '
', '
', ]; assert.equal(findClosingLine(lines, 0), 4); }); it('handles deeply nested structures', () => { const lines = [ '
', '
', '
', ' text', '
', '
', '
', ]; assert.equal(findClosingLine(lines, 0), 6); }); it('handles self-closing tags', () => { const lines = [ '
', ' ', '
', '
', ]; assert.equal(findClosingLine(lines, 0), 3); }); }); // --------------------------------------------------------------------------- // Integration tests: full wrap CLI on fixture files // --------------------------------------------------------------------------- describe('wrapCli integration', () => { let tmp; beforeEach(() => { tmp = mkdtempSync(join(tmpdir(), 'impeccable-wrap-test-')); }); afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); it('wraps an HTML element by class name', () => { const html = `

Hello World

Welcome to our site.

`; writeFileSync(join(tmp, 'index.html'), html); const result = JSON.parse(execSync( `node source/skills/impeccable/scripts/live-wrap.mjs --id test123 --count 3 --classes "hero-section" --file "${join(tmp, 'index.html')}"`, { cwd: process.cwd(), encoding: 'utf-8' } )); // The file path is relative to cwd, so it may be a relative path to the tmp dir assert.ok(result.file.endsWith('index.html')); assert.ok(result.insertLine > 0); assert.equal(result.commentSyntax.open, '