// frontmatter.test.mjs // Tester den delte frontmatter-modulen (SC2): parseFrontmatter/get reproduserer // hookenes flate first-match-parser MEN (1) strip trailing " #..." KUN fra // USITERTE verdier, (2) anker key-match til linjestart (ingen midt-i-linje // falske treff), og (3) tolererer fler-linje OKF-list-verdier (`tags:`) uten // krasj. writeFrontmatter round-tripper og siterer `#`. Importerer modulen // direkte (zero npm deps). Moenster: tests/coaching-hook.test.mjs. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { parseFrontmatter, writeFrontmatter } from '../lib/frontmatter.mjs'; test('basic: sitert skalar leses', () => { const { get } = parseFrontmatter('---\nnavn: "Org"\n---\n'); assert.equal(get('navn'), 'Org'); }); test('basic: usitert skalar leses', () => { const { get } = parseFrontmatter('---\nnavn: Org\n---\n'); assert.equal(get('navn'), 'Org'); }); test('usitert verdi: trailing "# kommentar" strippes (comment-leak-fix)', () => { const { get } = parseFrontmatter( '---\nokr_frikoblet_fra_loenn: true # frikoblet fra lonn\n---\n', ); assert.equal(get('okr_frikoblet_fra_loenn'), 'true', 'usitert: # strippes'); }); test('sitert verdi: intern "#" bevares (space-hash i quotes)', () => { const { get } = parseFrontmatter('---\nkortform: "A #B"\n---\n'); assert.equal(get('kortform'), 'A #B', 'sitert: # er en del av verdien, ikke kommentar'); }); test('sitert verdi med kommentar UTENFOR quotes: kommentar ignoreres', () => { const { get } = parseFrontmatter('---\nkortform: "A #B" # en note\n---\n'); assert.equal(get('kortform'), 'A #B'); }); test('first-match: forste forekomst vinner', () => { const { get } = parseFrontmatter('---\nnavn: "Forste"\nnavn: "Andre"\n---\n'); assert.equal(get('navn'), 'Forste'); }); test('nestet: innrykket key resolver (load-bearing inject:69)', () => { const { get } = parseFrontmatter( '---\norganisasjon:\n navn: "NestetOrg"\n type: "offentlig"\n---\n', ); assert.equal(get('navn'), 'NestetOrg', 'innrykket navn under organisasjon: skal resolve'); }); test('linjeanker: key matcher IKKE som substring av annen key', () => { const { get } = parseFrontmatter('---\netternavn: "Hansen"\n---\n'); assert.equal(get('navn'), null, 'navn skal ikke matche etternavn: (anker linjestart)'); }); test('OKF type: Title-Case enkeltlinje', () => { const { get } = parseFrontmatter('---\ntype: Organisasjonsprofil\n---\n'); assert.equal(get('type'), 'Organisasjonsprofil'); }); test('OKF resource: URL med ":" bevares', () => { const { get } = parseFrontmatter('---\nresource: https://example.com/path\n---\n'); assert.equal(get('resource'), 'https://example.com/path'); }); test('OKF timestamp: sitert ISO-8601 med ":" og "+" bevares', () => { const { get } = parseFrontmatter("---\ntimestamp: '2026-05-28T22:49:59+00:00'\n---\n"); assert.equal(get('timestamp'), '2026-05-28T22:49:59+00:00'); }); test('OKF fler-linje tags-liste: krasjer ikke + folgende skalar resolver', () => { const content = '---\n' + 'type: BigQuery Dataset\n' + 'tags:\n' + '- ecommerce\n' + '- web analytics\n' + "timestamp: '2026-05-28T22:49:59+00:00'\n" + '---\n# body\n'; const { get } = parseFrontmatter(content); assert.doesNotThrow(() => get('tags'), 'get paa list-key skal ikke krasje'); assert.equal(get('type'), 'BigQuery Dataset'); assert.equal( get('timestamp'), '2026-05-28T22:49:59+00:00', 'skalar etter list-blokk skal fortsatt resolve', ); }); test('ingen frontmatter: raw null, get returnerer null', () => { const { raw, get } = parseFrontmatter('ingen frontmatter her\n'); assert.equal(raw, null); assert.equal(get('navn'), null); }); test('writeFrontmatter: round-trip via parseFrontmatter', () => { const block = writeFrontmatter({ navn: 'Org', type: 'Organisasjonsprofil' }); const { get } = parseFrontmatter(block); assert.equal(get('navn'), 'Org'); assert.equal(get('type'), 'Organisasjonsprofil'); }); test('writeFrontmatter: verdi med "#" siteres og round-tripper', () => { const block = writeFrontmatter({ kortform: 'A #B' }); assert.match(block, /kortform: "A #B"/, 'verdi med # skal siteres'); const { get } = parseFrontmatter(block); assert.equal(get('kortform'), 'A #B', 'sitert # round-tripper'); }); test('writeFrontmatter: array-verdi (tags) -> OKF multi-linje liste (Step 3)', () => { const block = writeFrontmatter({ type: 'OKR', tags: ['a', 'b'], kilde: 'innboks' }); assert.match(block, /tags:\n - a\n - b/, 'array -> innrykket OKF-liste'); const { get } = parseFrontmatter(block); assert.equal(get('type'), 'OKR', 'skalar FOR list-blokk resolver'); assert.equal(get('kilde'), 'innboks', 'skalar ETTER innrykket list-blokk resolver fortsatt'); // Lese-siden uendret (Step 3 scope = kun skrive-siden): get() paa en list-key // krasjer ikke (eksisterende tolerance-kontrakt, frontmatter.mjs:14-16). Den // returnerer foerste list-element fordi parser-\s* spiser newline -- IKKE // null; konsumentene leser aldri tag-VERDIER, kun at nabo-skalarer resolver. assert.doesNotThrow(() => get('tags'), 'get paa list-key krasjer ikke'); }); test('writeFrontmatter: skalar uendret av array-gren (additiv)', () => { const block = writeFrontmatter({ navn: 'Org', type: 'Notat' }); assert.match(block, /navn: Org/); assert.match(block, /type: Notat/); assert.doesNotMatch(block, /^\s*-\s/m, 'ingen list-syntaks for skalarer'); }); test('writeFrontmatter: tom array -> kun key-linje, ingen items', () => { const block = writeFrontmatter({ tags: [] }); assert.match(block, /tags:\n/, 'tom array gir key-linje'); assert.doesNotMatch(block, /^\s*-\s/m, 'ingen item-linjer for tom array'); });