import { describe, it, expect } from 'vitest'; import { parse } from '../src/parser.js'; import { computeCheapSet } from '../src/model.js'; import { renderTreeHtml } from '../src/render.js'; /* Deterministischer i18n-Stub: gibt den Key zurück (bzw. interpoliert mixedWarn), damit die Snapshots sprachunabhängig und stabil sind. */ const t = (key, vars) => key === 'mixedWarn' ? `mixedWarn(line=${vars.line}, label=${vars.label})` : key; const SPEC_EXAMPLE = `%% Projektstruktur – Stand Sprint 14 [~] Website-Relaunch (XL) https://wiki.example.de/relaunch - [x] Konzeption (M) - [x] Zielgruppenanalyse (S) - [x] Sitemap (XS) - [~] Umsetzung (XL) - [/] Frontend (S) https://git.example.de/frontend @anna - [ ] Backend (L) @ben @carla - [ ] CMS-Anbindung (M) | [ ] WordPress | [?] Headless CMS | [-] Eigenentwicklung %% Aufwand zu hoch - [?] Hosting (M) | Cloud | On-Premise`; /* Rendert die (wie in app.js) vorab gefilterten Wurzeln. */ function renderExample({showDiscarded = false, cheapPath = false} = {}){ let {roots} = parse(SPEC_EXAMPLE); if(!showDiscarded){ roots = roots.filter(r => !r.status || r.status.key !== 'verworfen'); } const cheapSet = cheapPath ? computeCheapSet(roots) : new Set(); return renderTreeHtml(roots, {t, showDiscarded, cheapPath, cheapSet}); } const count = (html, needle) => html.split(needle).length - 1; describe('renderTreeHtml — kanonisches Beispiel', () => { it('Grundzustand (Pfad aus, verworfene aus): Struktur-Snapshot', () => { const {html, warnings} = renderExample(); expect(count(html, 'class="node')).toBe(13); // Eigenentwicklung ausgeblendet expect(count(html, 'cheap-leaf')).toBe(0); expect(count(html, 'size implicit')).toBe(0); expect(count(html, 'ghost-node')).toBe(1); // Backend (L) ist ein M+-Blatt expect(count(html, '