okr/tests/innboks-split.test.mjs
Kjell Tore Guttormsen 482effbad1 fix(okr): B2 ingestion-kode-hygiene + bump 1.7.1 (patch-lane)
8 hygiene-fikser fra review 2026-07-16 par.4 kode-lista, TDD roed-groenn
(suite 138 -> 149):
- innboks-split: slugify translittererer ae/oe (datatap-fiks)
- innboks-frontmatter: beskrivende feil ved manglende sourceMtime
- okf-index: --okf-version bumper eksisterende rot, flagg-tolerant CLI
  (exit 2 ved manglende verdi), sanitizeEntry strip C1/zero-width/bidi/
  Unicode-tag
- write-org-profile: circuit-breaker MERGER i stedet for aa overskrive
  full config (M4); test beviser at eksisterende config overlever
- compose-org-profile: intern ----linje trunkerer ikke blokken
- coaching-hook: at-risk teller status-markerte tabellrader (M1/m1)
- inject-okr-context: topic-guard treffer boeyningsformer (maalene)
- frontmatter: BOM/CRLF-toleranse (falsk mangler-type-fiks)

Versjonsflater bumpet til 1.7.1 (package/plugin/lock/CLAUDE/README/
SKILL x2/package-shape-test) + CHANGELOG 1.7.1-seksjon (B1+B2).
Release-tag + katalog-ref venter paa [G-B] operatoer-go.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 04:02:10 +02:00

84 lines
4.3 KiB
JavaScript

// 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: slug translittererer ae/oe -- norske bokstaver dropper ikke (B2)', () => {
// "Økonomi og ærlighet" -- foer B2 ga slugify 'konomi-og-rlighet' (datatap).
const md = '# Økonomi og ærlighet\n\nInnhold.';
const concepts = splitConcepts(md, { sourceSlug: 'norsk' });
assert.equal(concepts[0].slug, 'oekonomi-og-aerlighet', 'OE->oe, ae->ae (translitterert, ikke strippet)');
});
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');
});