/**
* 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, resolve } from 'node:path';
import { tmpdir } from 'node:os';
import { execFileSync, execSync } from 'node:child_process';
import {
buildSearchQueries,
findElement,
findClosingLine,
detectCommentSyntax,
} from '../skill/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('accepts browser className whitespace when building class queries', () => {
const queries = buildSearchQueries(null, 'hero-title _heroTitle_1lpqp_2', 'h1', null);
assert.ok(queries.includes('
{
const queries = buildSearchQueries(null, null, null, 'Welcome to our app');
assert.deepEqual(queries, ['Welcome to our app']);
});
it('returns all query types when everything is provided', () => {
const queries = buildSearchQueries('main', 'container,wide', 'div', 'fallback');
assert.ok(queries.length >= 4);
assert.equal(queries[0], 'id="main"');
assert.ok(queries.includes('fallback'));
});
});
describe('findElement', () => {
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 = [
'',
];
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 = [
'',
'',
];
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 = [
'',
];
assert.equal(findClosingLine(lines, 0), 2);
});
it('handles nested tags of the same type', () => {
const lines = [
'',
];
assert.equal(findClosingLine(lines, 0), 4);
});
it('handles deeply nested structures', () => {
const lines = [
'',
];
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-'));
clearManualEditsBuffer();
});
afterEach(() => {
rmSync(tmp, { recursive: true, force: true });
clearManualEditsBuffer();
});
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 skill/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, '