import { describe, it, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; import { cpSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync, symlinkSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { tmpdir } from 'node:os'; import { fileURLToPath } from 'node:url'; import { extractMatchingSourceCss, removeSelectorsFromSvelteSource, findSvelteComponentManifest, inlineSvelteComponentAccept, mergeCssIntoSvelteSource, reindentPreservingStructure, scaffoldSvelteComponentSession, } from '../skill/scripts/live/svelte-component.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const REPO_NODE_MODULES = join(__dirname, '..', 'node_modules'); const ROUTE_SOURCE = `
    {#each stages as stage, i}
  1. {stage.label}

    {stage.detail}

  2. {/each}
`; function write(root, rel, content) { const abs = join(root, rel); mkdirSync(dirname(abs), { recursive: true }); writeFileSync(abs, content); } describe('svelte component scaffold + accept pipeline', () => { let tmp; beforeEach(() => { tmp = realpathSync(mkdtempSync(join(tmpdir(), 'impeccable-svelte-accept-'))); // The scaffolder resolves the app's svelte compiler; link this repo's. mkdirSync(join(tmp, 'node_modules'), { recursive: true }); try { symlinkSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp, 'node_modules', 'svelte'), 'dir'); } catch { cpSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp, 'node_modules', 'svelte'), { recursive: true }); } write(tmp, 'package.json', JSON.stringify({ name: 'app', dependencies: { svelte: '^5' } })); write(tmp, 'src/routes/+page.svelte', ROUTE_SOURCE); }); afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); function scaffold(id = 'testacc1') { // The picked element spans the
    block: lines 10-17 (1-indexed). const originalLines = ROUTE_SOURCE.split('\n').slice(9, 17); assert.match(originalLines[0], /
      /); return scaffoldSvelteComponentSession({ id, count: 2, sourceFile: 'src/routes/+page.svelte', sourceStartLine: 10, sourceEndLine: 17, originalLines, cwd: tmp, }); } it('scaffolds a v2 contract with the each collection as one structured prop', () => { const session = scaffold(); assert.equal(session.fallback, undefined); assert.equal(session.manifest.contractVersion, 2); const collection = session.propContract.find((c) => c.kind === 'collection'); assert.equal(collection.prop, 'stages'); assert.equal(collection.item.rootTag, 'li'); const v1 = readFileSync(join(tmp, session.componentDir, 'v1.svelte'), 'utf-8'); assert.match(v1, /\{#each stages as stage, i\}/); assert.match(v1, /\{stage\.label\}/); assert.match(v1, /let \{ stages = \[\] \} = \$props\(\)/); // Stub CSS is seeded from the route's matching rules. assert.match(v1, /border-top: 1px solid #333/); }); it('falls back to source-preview for markup with component tags', () => { const res = scaffoldSvelteComponentSession({ id: 'fallb1', count: 3, sourceFile: 'src/routes/+page.svelte', sourceStartLine: 1, sourceEndLine: 1, originalLines: [''], cwd: tmp, }); assert.equal(res.fallback, 'source-preview'); assert.match(res.reason, /component tag/); }); it('accept merges CSS instead of appending: superseded rules are replaced, dead branches pruned', () => { const session = scaffold('acc2'); // Agent authors variant 1: arrows instead of divider borders, one param. write(tmp, join(session.componentDir, 'v1.svelte'), `
        {#each stages as stage, i}
      1. {stage.label}

        {stage.detail}

      2. {/each}
      `); write(tmp, join(session.componentDir, 'params.json'), JSON.stringify({ 1: [ { id: 'depth', kind: 'range', min: 0, max: 20, step: 1, default: 6, label: 'Depth' }, { id: 'density', kind: 'steps', default: 'airy', label: 'Density', options: [ { value: 'airy', label: 'Airy' }, { value: 'snug', label: 'Snug' }, ] }, ], })); const manifest = findSvelteComponentManifest('acc2', tmp); const result = inlineSvelteComponentAccept(manifest, 1, { depth: 10, density: 'snug' }, tmp); assert.equal(result.handled, true, result.error); const out = readFileSync(join(tmp, 'src/routes/+page.svelte'), 'utf-8'); // Loop restored with original expressions, one each block only. assert.equal(out.split('{#each stages as stage, i}').length - 1, 1); // Superseded divider border is GONE (replaced, not shadowed). assert.doesNotMatch(out, /border-top: 1px solid #333/); assert.match(out, /clip-path/); // Exactly one .pit-board rule. assert.equal(out.split('.pit-board {').length - 1, 1); // Range baked with paren-aware substitution. assert.match(out, /padding: calc\(10 \+ 2px\)/); // Steps: chosen branch folded into the .stage rule, other branch dropped, // no data-p attributes anywhere. assert.match(out, /margin: 4px/); assert.equal(out.split(/\.stage \{/).length - 1, 1); assert.doesNotMatch(out, /margin: 16px/); assert.doesNotMatch(out, /data-p-/); assert.doesNotMatch(out, /var\(--p-/); // The untouched .footer rule survives. assert.match(out, /\.footer \{ color: gray; \}/); // Self-check reports clean. assert.equal(result.verify.clean, true, JSON.stringify(result.verify.findings)); }); it('preserves the variant markup indentation structure', () => { const session = scaffold('acc3'); write(tmp, join(session.componentDir, 'v1.svelte'), `
        {#each stages as stage, i}
      1. {stage.label}
      2. {/each}
      `); const manifest = findSvelteComponentManifest('acc3', tmp); const result = inlineSvelteComponentAccept(manifest, 1, null, tmp); assert.equal(result.handled, true, result.error); const out = readFileSync(join(tmp, 'src/routes/+page.svelte'), 'utf-8'); const lines = out.split('\n'); const deepIdx = lines.findIndex((l) => l.includes('
      ')); const labelIdx = lines.findIndex((l) => l.includes('span class="label"')); const deepIndent = lines[deepIdx].match(/^\s*/)[0].length; const labelIndent = lines[labelIdx].match(/^\s*/)[0].length; // Nested structure survives: label sits deeper than its parent div. assert.equal(labelIndent > deepIndent, true, `expected nesting, got ${deepIndent} vs ${labelIndent}`); }); it('reindentPreservingStructure keeps relative depth', () => { const out = reindentPreservingStructure([' ', ' ', ' '], ' '); assert.deepEqual(out, [' ', ' ', ' ']); }); it('mergeCssIntoSvelteSource creates a style block when none exists', () => { const { text } = mergeCssIntoSvelteSource('
      hi
      ', '.x { color: red; }'); assert.match(text, / `; it('removes seeded rules the variant did not re-declare and orders new base rules before media blocks', () => { const tmp2 = realpathSync(mkdtempSync(join(tmpdir(), 'impeccable-pitch-mangle-'))); try { mkdirSync(join(tmp2, 'node_modules'), { recursive: true }); try { symlinkSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp2, 'node_modules', 'svelte'), 'dir'); } catch { cpSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp2, 'node_modules', 'svelte'), { recursive: true }); } write(tmp2, 'package.json', JSON.stringify({ name: 'app' })); write(tmp2, 'src/lib/Pitch.svelte', PITCH_SOURCE); // Picked element: the .decisions block (lines 10-17, 1-indexed). const lines = PITCH_SOURCE.split('\n'); const startLine = lines.findIndex((l) => l.includes('class="decisions"')) + 1; const endLine = lines.findIndex((l, i) => i >= startLine && l.trim() === '
      ' && lines[i + 1]?.includes('')) + 1; const originalLines = lines.slice(startLine - 1, endLine); const session = scaffoldSvelteComponentSession({ id: 'pitchm1', count: 1, sourceFile: 'src/lib/Pitch.svelte', sourceStartLine: startLine, sourceEndLine: endLine, originalLines, cwd: tmp2, }); assert.equal(session.fallback, undefined, session.reason); // Seeded selectors recorded for accept-time supersession. assert.equal(session.manifest.seededSelectors.includes('.decisions'), true); // The agent's variant: a NEW class, no re-declaration of .decisions. write(tmp2, join(session.componentDir, 'v1.svelte'), `
      {#each verdicts as verdict}

      {verdict.label}

      {verdict.detail}

      {/each}
      `); const manifest = findSvelteComponentManifest('pitchm1', tmp2); const result = inlineSvelteComponentAccept(manifest, 1, null, tmp2); assert.equal(result.handled, true, result.error); const out = readFileSync(join(tmp2, 'src/lib/Pitch.svelte'), 'utf-8'); // The superseded grid rules are GONE: they never applied in the // preview the user approved, and the root keeps the old class. assert.doesNotMatch(out, /grid-template-columns: repeat\(3, 1fr\)/); assert.doesNotMatch(out, /\.decisions > \.cell/); assert.equal(result.css.superseded.includes('.decisions'), true); // The untouched sibling rule survives. assert.match(out, /\.pitch \{ padding: 40px; \}/); // Source media block survives for the surviving class... assert.match(out, /\.pitch \{ padding: 16px; \}/); // ...and no longer carries the superseded selector. assert.doesNotMatch(out, /\.decisions \{ grid-template-columns: 1fr; \}/); // New base rules sit BEFORE the source's @media block (cascade order). const baseIdx = out.indexOf('.disposition-board {'); const mediaIdx = out.indexOf('@media (max-width: 700px)'); assert.equal(baseIdx > -1 && mediaIdx > -1 && baseIdx < mediaIdx, true, `expected base rules before media, got base@${baseIdx} media@${mediaIdx}`); assert.equal(result.verify.clean, true, JSON.stringify(result.verify.findings)); } finally { rmSync(tmp2, { recursive: true, force: true }); } }); it('keeps seeded rules the variant re-declares', () => { const { text, removed } = removeSelectorsFromSvelteSource('
      x
      \n', new Set(['.b'])); assert.match(text, /\.a \{ color: red; \}/); assert.doesNotMatch(text, /color: blue/); assert.deepEqual(removed, ['.b']); }); });