// innboks-split.test.mjs // Step 4 (SC idempotens): deterministisk heading-split av (allerede konvertert) // markdown til konsepter. splitConcepts er en REN funksjon -- samme input gir // identisk konsept-sett + filnavn (kebab-slug). Header-tekst -> title; flat // dokument (ingen #/##) -> ett konsept; slug-kollisjon -> stabil numerisk // disambiguering; preamble foer foerste heading bevares (ingen datatap). // Direkte import (zero npm deps). Moenster: tests/frontmatter.test.mjs. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { splitConcepts } from '../lib/innboks-split.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const FIX = join(HERE, 'fixtures', 'inbox-converted'); const dokA = readFileSync(join(FIX, 'dok-a.md'), 'utf8'); const dokB = readFileSync(join(FIX, 'dok-b.md'), 'utf8'); test('splitConcepts: deterministisk -- samme input 2x gir identisk konsept-sett', () => { const a1 = splitConcepts(dokA, { sourceSlug: 'dok-a' }); const a2 = splitConcepts(dokA, { sourceSlug: 'dok-a' }); assert.deepEqual(a1, a2, 'ren funksjon: identisk output for identisk input'); }); test('splitConcepts: #/## heading -> ett konsept per seksjon, header-tekst -> title', () => { const concepts = splitConcepts(dokA, { sourceSlug: 'dok-a' }); assert.equal(concepts.length, 2, 'dok-a har en H1 + en H2 -> 2 konsepter'); assert.equal(concepts[0].title, 'Tildelingsbrev 2026'); assert.equal(concepts[0].slug, 'tildelingsbrev-2026'); assert.equal(concepts[0].level, 1); assert.equal(concepts[1].title, 'Oppfolging mot Virksomhetsplan 2026'); assert.equal(concepts[1].level, 2); // body baerer seksjons-innholdet (uten heading-linja). assert.match(concepts[0].body, /Tildelingsbrevet gir overordnede/); assert.doesNotMatch(concepts[0].body, /^#/, 'heading-linja er ikke i body'); }); test('splitConcepts: slug stripper klammer/parenteser (adversarial heading)', () => { const concepts = splitConcepts(dokB, { sourceSlug: 'dok-b' }); assert.equal(concepts.length, 2); assert.equal(concepts[1].title, 'Mal og rammer (2026) [utkast]'); assert.equal(concepts[1].slug, 'mal-og-rammer-2026-utkast', 'klammer/parenteser strippet fra slug'); }); test('splitConcepts: ### og dypere er IKKE split-punkt (forblir body)', () => { const md = '# Topp\n\nIntro.\n\n### Underseksjon\n\nDetalj.'; const concepts = splitConcepts(md, { sourceSlug: 'dyp' }); assert.equal(concepts.length, 1, 'kun H1 splitter; ### forblir i body'); assert.match(concepts[0].body, /### Underseksjon/, '### bevart som body-innhold'); }); test('splitConcepts: flat dokument (ingen #/##) -> ett konsept', () => { const flat = 'Bare en paragraf uten overskrift.\n\nEnda en linje.'; const concepts = splitConcepts(flat, { sourceSlug: 'notat' }); assert.equal(concepts.length, 1); assert.equal(concepts[0].slug, 'notat', 'flat-fallback slug fra sourceSlug'); assert.match(concepts[0].body, /Bare en paragraf/); }); test('splitConcepts: slug-kollisjon -> stabil numerisk disambiguering', () => { const md = '# Samme tittel\n\nA\n\n## Samme tittel\n\nB'; const concepts = splitConcepts(md, { sourceSlug: 'kollisjon' }); assert.equal(concepts.length, 2); assert.equal(concepts[0].slug, 'samme-tittel'); assert.equal(concepts[1].slug, 'samme-tittel-2', 'andre forekomst faar -2 suffiks'); }); test('splitConcepts: preamble foer foerste heading bevares som ledende konsept', () => { const md = 'Forord uten overskrift.\n\n# Ekte overskrift\n\nKropp.'; const concepts = splitConcepts(md, { sourceSlug: 'med-forord' }); assert.equal(concepts.length, 2, 'preamble + en heading -> 2 konsepter (ingen datatap)'); assert.equal(concepts[0].slug, 'med-forord'); assert.match(concepts[0].body, /Forord uten overskrift/); assert.equal(concepts[1].title, 'Ekte overskrift'); });