voyage/tests/helpers/changelog-section.mjs
Kjell Tore Guttormsen b79495847d
test(changelog): find the 6.4.0 section by its title, before and after the release commit
Two docs tests located this delivery's CHANGELOG section by the literal
heading `## Unreleased` (one also required it to be the first section).
The release commit renames it to `## v6.4.0 — <date> — <title>`
(docs/release-runbook.md § 1.4), so all 16 content pins failed on the
release commit while the content was the same.

One shared helper, tests/helpers/changelog-section.mjs, now finds the
section by its title under either heading, wherever it stands (a later
delivery's section above it does not move the pins), with a trailing \r or
spaces tolerated, and only when it stands exactly once. Every content
assertion is unchanged; their messages no longer say "Unreleased". New
tests pin the accepted headings and seven near misses (version, date,
title, the literal dot in 1.9), and the helper on a later section, a \r,
a duplicate and a missing section.

Measured: 46/46 pass against main's CHANGELOG and against the release
commit's CHANGELOG; before the change, 16 failed on the release commit.
Three mutants of the helper (a looser date, no escaping, no trim) are
each failed by one test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:13:32 +02:00

30 lines
1.5 KiB
JavaScript

// The CHANGELOG section of the plan_version 1.9 / converging critic loop
// delivery, found by its title. Before the release its heading is
// `## Unreleased — <title>`; the release commit makes it
// `## vX.Y.Z — <date> — <title>` (docs/release-runbook.md § 1.4). The section
// is found wherever it stands, so a later delivery's section above it does not
// move the pins, and it must stand exactly once.
import assert from 'node:assert/strict';
export const SECTION_TITLE = 'plan_version 1.9, the trial-4 findings, session-clean tests, the converging critic loop';
const escaped = SECTION_TITLE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
export const SECTION_HEADING = new RegExp(`^## (?:Unreleased|v\\d+\\.\\d+\\.\\d+ — \\d{4}-\\d{2}-\\d{2}) — ${escaped}$`);
// A heading line as written, without a trailing `\r` or trailing spaces.
const bare = line => line.replace(/\s+$/, '');
// { heading, body } of the delivery's section in CHANGELOG text, up to the
// next `## ` heading. Fails unless exactly one heading matches.
export function deliverySection(text) {
const lines = text.split('\n');
const at = [];
lines.forEach((l, i) => { if (SECTION_HEADING.test(bare(l))) at.push(i); });
assert.equal(at.length, 1, `CHANGELOG.md has the "${SECTION_TITLE}" section exactly once (found ${at.length})`);
const start = at[0];
let end = lines.length;
for (let i = start + 1; i < lines.length; i++) {
if (/^## /.test(lines[i])) { end = i; break; }
}
return { heading: bare(lines[start]), body: lines.slice(start + 1, end).join('\n') };
}