import { readFile, writeFile } from "node:fs/promises"; import { test, expect } from "@playwright/test"; import { resolvePath } from "./helpers"; // This spec drives a flow that REWRITES a changelog file and can make a real // git commit. It used to point at the repo's own tracked editor/CHANGELOG.md, // which meant: a failed run left the working tree dirty, a successful run left // a stray "Release editor " commit on the current branch, and the run // failed outright whenever the tree was already dirty (the action refuses to // commit over unrelated changes). // // It now targets editor/test-changelog.md — disposable, gitignored, and pointed // at by EDITOR_CHANGELOG_FILE in the dev:test/start:test scripts, which is the // same file the /changelog page reads (common/lib/paths.ts editorChangelogFile). // "Commit changelog" is unchecked in every test below: none of them assert // anything about git, and leaving it on would try to `git add` an ignored file. const CHANGELOG_PATH = resolvePath("test-changelog.md"); const WITH_UNRELEASED = "# Changelog\n" + "\n" + "## [Unreleased]\n" + "- test bullet for cut-release spec\n" + "\n" + "## [9.9.9] - 2024-01-01\n" + "- old released bullet\n"; const WITHOUT_UNRELEASED = "# Changelog\n" + "\n" + "## [9.9.9] - 2024-01-01\n" + "- old released bullet\n"; function todayISO(): string { const now = new Date(); const y = now.getFullYear().toString().padStart(4, "0"); const m = (now.getMonth() + 1).toString().padStart(2, "0"); const d = now.getDate().toString().padStart(2, "0"); return `${y}-${m}-${d}`; } // Leave a valid changelog behind: /changelog reads this file unconditionally. test.afterEach(async () => { await writeFile(CHANGELOG_PATH, WITHOUT_UNRELEASED); }); // The form defaults "Commit changelog" to checked; these tests only care about // the file rewrite and the form state, so turn it off. async function uncheckCommit(page: import("@playwright/test").Page) { await page.getByRole("checkbox", { name: "Commit changelog" }).uncheck(); } test("cutting a release removes the Unreleased heading from the file", async ({ page, }) => { await writeFile(CHANGELOG_PATH, WITH_UNRELEASED); await page.goto("/changelog"); await page.locator("#version-editor").fill("9.9.10"); await uncheckCommit(page); await page.getByRole("button", { name: /^cut release$/i }).click(); await expect( page.getByRole("status").filter({ hasText: "Cut release 9.9.10." }), ).toBeVisible(); const after = await readFile(CHANGELOG_PATH, "utf8"); expect(after).toContain(`## [9.9.10] - ${todayISO()}`); expect(after).not.toMatch(/^##\s+\[Unreleased\]/im); expect(after).toContain("## [9.9.9] - 2024-01-01"); expect(after).toContain("- test bullet for cut-release spec"); }); test("form disables after a successful cut on reload", async ({ page }) => { await writeFile(CHANGELOG_PATH, WITH_UNRELEASED); await page.goto("/changelog"); await page.locator("#version-editor").fill("9.9.10"); await uncheckCommit(page); await page.getByRole("button", { name: /^cut release$/i }).click(); await expect( page.getByRole("status").filter({ hasText: "Cut release 9.9.10." }), ).toBeVisible(); await page.goto("/changelog"); await expect(page.locator("#version-editor")).toBeDisabled(); await expect( page.getByRole("button", { name: /^cut release$/i }), ).toBeDisabled(); await expect( page.getByText("Nothing pending under [Unreleased] yet."), ).toBeVisible(); }); test("form is disabled when the file has no Unreleased heading", async ({ page, }) => { await writeFile(CHANGELOG_PATH, WITHOUT_UNRELEASED); await page.goto("/changelog"); await expect(page.locator("#version-editor")).toBeDisabled(); await expect( page.getByRole("button", { name: /^cut release$/i }), ).toBeDisabled(); await expect( page.getByText("Nothing pending under [Unreleased] yet."), ).toBeVisible(); });