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>
144 lines
6 KiB
JavaScript
144 lines
6 KiB
JavaScript
// 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('BOM + CRLF: frontmatter parses (falsk "mangler type"-fiks, B2)', () => {
|
|
// Windows-produsert fil: UTF-8 BOM foran forste fence + CRLF-linjeskift.
|
|
const content = '\uFEFF---\r\ntype: Notat\r\ntitle: "X"\r\n---\r\n# body\r\n';
|
|
const { raw, get } = parseFrontmatter(content);
|
|
assert.notEqual(raw, null, 'BOM/CRLF skal ikke gi raw null');
|
|
assert.equal(get('type'), 'Notat');
|
|
assert.equal(get('title'), 'X');
|
|
});
|
|
|
|
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');
|
|
});
|