import { test } from "node:test"; import assert from "node:assert/strict"; import { compareVersions, countUnreleasedBullets, cutRelease, dateISOProblem, getLatestRelease, hasUnreleasedHeading, suggestNextMinorVersion, suggestNextVersion, } from "./changelog"; // Run with: // pnpm --filter yt-dlp-transcript-common exec tsx --test lib/changelog.test.ts const SOURCE = [ "# Changelog", "", "## [Unreleased]", "- **One.** A bullet that runs long, on one line as this repo writes them.", "- **Two.**", " - a nested item is not a change of its own", "* **Three.**", "", "## [0.9.0] - 2026-09-26", "- released", "", "## [0.8.7] - 2026-08-12", "- older", ].join("\n"); test("suggestNextMinorVersion bumps the minor and zeroes the patch", () => { assert.equal(suggestNextMinorVersion("0.9.3"), "0.10.0"); assert.equal(suggestNextMinorVersion("1.2.0-rc.1"), "1.3.0"); assert.equal(suggestNextMinorVersion(null), "0.1.0"); assert.equal(suggestNextMinorVersion("garbage"), "0.1.0"); // The patch bump is unchanged. assert.equal(suggestNextVersion("0.9.0"), "0.9.1"); }); test("compareVersions is semver precedence: X.Y.Z numerically, a release above its prereleases", () => { assert.ok(compareVersions("0.10.0", "0.9.9") > 0); assert.ok(compareVersions("0.8.7", "0.9.0") < 0); assert.equal(compareVersions("0.9.0", "0.9.0"), 0); assert.ok(compareVersions("1.0.0", "1.0.0-rc.1") > 0); assert.ok(compareVersions("1.0.0-rc.1", "0.9.0") > 0); assert.ok(compareVersions("0.9.0-rc.1", "0.9.0") < 0); assert.ok(compareVersions("1.0.0-rc.2", "1.0.0-rc.10") < 0); assert.ok(compareVersions("1.0.0-alpha", "1.0.0-alpha.1") < 0); assert.ok(compareVersions("1.0.0-1", "1.0.0-alpha") < 0); assert.ok(compareVersions("1.0.0-beta", "1.0.0-alpha") > 0); assert.ok(compareVersions("0.1.0", null) > 0); assert.ok(compareVersions(null, "0.1.0") < 0); assert.equal(compareVersions(null, null), 0); }); test("dateISOProblem wants YYYY-MM-DD and a day on the calendar", () => { assert.equal(dateISOProblem("2026-09-26"), null); assert.equal(dateISOProblem("2024-02-29"), null); assert.equal(dateISOProblem("26/09/2026"), 'Date "26/09/2026" is not in YYYY-MM-DD form.'); assert.equal(dateISOProblem("2026-13-45"), 'Date "2026-13-45" is not a calendar date.'); assert.equal(dateISOProblem("2026-02-30"), 'Date "2026-02-30" is not a calendar date.'); assert.equal(dateISOProblem("2025-02-29"), 'Date "2025-02-29" is not a calendar date.'); assert.equal(dateISOProblem("2026-00-10"), 'Date "2026-00-10" is not a calendar date.'); }); test("cutRelease refuses an impossible date and a version that does not move forward", () => { assert.throws(() => cutRelease(SOURCE, "0.9.1", "2026-13-45"), /"2026-13-45" is not a calendar date/); assert.throws( () => cutRelease(SOURCE, "0.9.0", "2026-09-26"), /^CutReleaseError: Version 0\.9\.0 is not newer than the latest release, 0\.9\.0\.$/, ); assert.throws(() => cutRelease(SOURCE, "0.8.9", "2026-09-26"), /not newer than the latest release, 0\.9\.0/); assert.throws(() => cutRelease(SOURCE, "0.9.0-rc.1", "2026-09-26"), /not newer/); // A prerelease of a later version is forward; so is the first release of all. assert.match(cutRelease(SOURCE, "1.0.0-rc.1", "2026-09-26"), /^## \[1\.0\.0-rc\.1\] - 2026-09-26$/m); assert.match( cutRelease("# Changelog\n\n## [Unreleased]\n- first\n", "0.1.0", "2026-09-26"), /^## \[0\.1\.0\] - 2026-09-26$/m, ); }); test("getLatestRelease reads the newest released heading and its date", () => { assert.deepEqual(getLatestRelease(SOURCE), { version: "0.9.0", date: "2026-09-26" }); assert.deepEqual(getLatestRelease("## [1.0.0]\n- undated\n"), { version: "1.0.0", date: null, }); assert.equal(getLatestRelease("# Changelog\n\n## [Unreleased]\n- x\n"), null); }); test("countUnreleasedBullets counts the top-level bullets under [Unreleased] only", () => { assert.equal(countUnreleasedBullets(SOURCE), 3); assert.equal(countUnreleasedBullets("## [Unreleased]\n\n## [0.1.0] - 2026-01-01\n- x\n"), 0); assert.equal(countUnreleasedBullets("## [0.1.0] - 2026-01-01\n- x\n"), 0); assert.equal(hasUnreleasedHeading(SOURCE), true); assert.equal(hasUnreleasedHeading("## [0.1.0] - 2026-01-01\n- x\n"), false); });