Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 4f868150e753f77773c2494d6dc1ed8b3b791a9d
parent bd0565c54944b3ff6c5c42220176b9cf8cecb67a
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Wed, 30 Sep 2026 09:07:52 -0400

homepage: /source/ gains a History block — the commit count and head from the manifest, links to the log, the refs and the Atom feed — shown only when the manifest has a history and its log page is here; the e2e reads a fixture publish (E2E_SOURCE_PUBLIC_DIR, never in a production build) to show the page with and without one, and source.spec walks the real pages: the site's line, the theme by stored choice and by the OS, a commit, the Files index into the raw tree, the feed untouched

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
M.gitignore | 2++
MENVIRONMENT.md | 1+
Mcommon/lib/envVars.ts | 1+
Mhomepage/app/lib/source.test.ts | 40++++++++++++++++++++++++++++++++++++++--
Mhomepage/app/lib/source.ts | 51+++++++++++++++++++++++++++++++++++++++++++--------
Mhomepage/app/source/page.tsx | 41+++++++++++++++++++++++++++++++++++++++--
Ahomepage/e2e/fixture-source.ts | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahomepage/e2e/source-history.spec.ts | 45+++++++++++++++++++++++++++++++++++++++++++++
Mhomepage/e2e/source.spec.ts | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mhomepage/playwright.config.ts | 13+++++++++++++
10 files changed, 316 insertions(+), 12 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -145,6 +145,8 @@ yarn-error.log* /homepage/e2e/.e2e-settings.json* # the e2e's empty sites directory (no homepage.json; homepage/playwright.config.ts) /homepage/e2e/.e2e-sites/ +# the e2e's fixture publish for /source/ (homepage/e2e/fixture-source.ts) +/homepage/e2e/.e2e-source/ # site config /settings.json diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md @@ -190,5 +190,6 @@ Read only by a test harness, a fake binary or a test-mode branch. Never set one | `E2E_RETRIES` | `0` | Retries per shard (`--retries N` wins); 0 keeps a sharded run comparable to a serial one. | scripts/run-sharded-e2e.mjs | | `E2E_IMAGE` | `yt-dlp-transcript-browser-e2e` | The sharded e2e run's image tag. | scripts/run-sharded-e2e.mjs | | `E2E_SKIP_BUILD` | off | `1` reuses the sharded e2e image instead of rebuilding it (`--no-build`). | scripts/run-sharded-e2e.mjs | +| `E2E_SOURCE_PUBLIC_DIR` | unset (the page reads `homepage/public`) | The fixture publish the homepage's e2e dev server reads the `/source/` page from while it holds a manifest; set by `homepage/playwright.config.ts`, written and removed by `homepage/e2e/source-history.spec.ts`, ignored by a production build. | homepage/app/lib/source.ts | | `E2E_EXPECT_SOURCE` | off (both states pass) | `1` makes the homepage suite's `/source/` specs fail on the empty state; a gate that ran `archilyzer source publish` first sets it. | homepage/e2e/source.spec.ts | | `E2E_HOMEPAGE_SUMMARY_FILE` | `homepage/public/homepage-summary.json` | The synthetic summary the homepage's e2e dev server reads; set by `homepage/playwright.config.ts`, ignored by a production build. | homepage/app/lib/summary.ts | diff --git a/common/lib/envVars.ts b/common/lib/envVars.ts @@ -189,6 +189,7 @@ const DECLARED: EnvVarDecl[] = [ { name: "E2E_RETRIES", audience: "test", default: "`0`", readBy: "scripts/run-sharded-e2e.mjs", doc: "Retries per shard (`--retries N` wins); 0 keeps a sharded run comparable to a serial one." }, { name: "E2E_IMAGE", audience: "test", default: "`yt-dlp-transcript-browser-e2e`", readBy: "scripts/run-sharded-e2e.mjs", doc: "The sharded e2e run's image tag." }, { name: "E2E_SKIP_BUILD", audience: "test", default: "off", readBy: "scripts/run-sharded-e2e.mjs", doc: "`1` reuses the sharded e2e image instead of rebuilding it (`--no-build`)." }, + { name: "E2E_SOURCE_PUBLIC_DIR", audience: "test", default: "unset (the page reads `homepage/public`)", readBy: "homepage/app/lib/source.ts", doc: "The fixture publish the homepage's e2e dev server reads the `/source/` page from while it holds a manifest; set by `homepage/playwright.config.ts`, written and removed by `homepage/e2e/source-history.spec.ts`, ignored by a production build." }, { name: "E2E_EXPECT_SOURCE", audience: "test", default: "off (both states pass)", readBy: "homepage/e2e/source.spec.ts", doc: "`1` makes the homepage suite's `/source/` specs fail on the empty state; a gate that ran `archilyzer source publish` first sets it." }, { name: "E2E_HOMEPAGE_SUMMARY_FILE", audience: "test", default: "`homepage/public/homepage-summary.json`", readBy: "homepage/app/lib/summary.ts", doc: "The synthetic summary the homepage's e2e dev server reads; set by `homepage/playwright.config.ts`, ignored by a production build." }, ]; diff --git a/homepage/app/lib/source.test.ts b/homepage/app/lib/source.test.ts @@ -3,7 +3,7 @@ import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import { loadSourceManifest } from "./source"; +import { loadSourceManifest, sourcePublicDir } from "./source"; // Run with: // pnpm --filter homepage test @@ -34,7 +34,7 @@ const manifest = { }; let n = 0; -function pub(opts: { manifest?: unknown; refs?: boolean; tarball?: boolean }): string { +function pub(opts: { manifest?: unknown; refs?: boolean; tarball?: boolean; log?: boolean }): string { const p = path.join(TMP, `p${n++}`); mkdirSync(path.join(p, "source", "archilyzer.git", "info"), { recursive: true }); mkdirSync(path.join(p, "downloads"), { recursive: true }); @@ -46,6 +46,10 @@ function pub(opts: { manifest?: unknown; refs?: boolean; tarball?: boolean }): s } if (opts.refs !== false) writeFileSync(path.join(p, "source", "archilyzer.git", "info", "refs"), "x\trefs/heads/main\n"); if (opts.tarball !== false) writeFileSync(path.join(p, "downloads", "archilyzer-source.tar.gz"), "t"); + if (opts.log) { + mkdirSync(path.join(p, "source", "git"), { recursive: true }); + writeFileSync(path.join(p, "source", "git", "log.html"), "<html></html>"); + } return p; } @@ -69,3 +73,35 @@ test("malformed, wrong-version or orphaned manifests are the empty state, never assert.equal(loadSourceManifest(dir), null, what); } }); + +// Release 15 slice SG: the history block is believed only beside its log page. +const history = { + href: "/source/git/log.html", + commits: 3, + head: "2".repeat(40), + files: 9, + bytes: 1234, + sha256: "4".repeat(64), + tool: "stagit (sha256 0123456789ab)", +}; + +test("a history block beside its log page is believed; without the page the manifest comes back without it", () => { + assert.deepEqual(loadSourceManifest(pub({ manifest: { ...manifest, history }, log: true }))?.history, history); + const orphan = loadSourceManifest(pub({ manifest: { ...manifest, history } })); + assert.equal(orphan?.mirrorHead, "2".repeat(40), "the rest of the manifest stands"); + assert.ok(orphan && !("history" in orphan), "no History links for pages that are not here"); + assert.equal(loadSourceManifest(pub({ manifest, log: true }))?.history, undefined, "no block, no History"); + // A malformed block is a manifest nobody believes (parseSourceManifest). + assert.equal(loadSourceManifest(pub({ manifest: { ...manifest, history: { ...history, commits: "3" } }, log: true })), null); +}); + +test("the e2e's fixture publish is read only outside a production build, and only when it holds a manifest", () => { + const fixture = pub({ manifest, log: true }); + const empty = path.join(TMP, "empty-fixture"); + mkdirSync(empty, { recursive: true }); + const PUBLIC = "/checkout/homepage/public"; + assert.equal(sourcePublicDir({ NODE_ENV: "development", E2E_SOURCE_PUBLIC_DIR: fixture }, PUBLIC), fixture); + assert.equal(sourcePublicDir({ NODE_ENV: "production", E2E_SOURCE_PUBLIC_DIR: fixture }, PUBLIC), PUBLIC); + assert.equal(sourcePublicDir({ NODE_ENV: "development", E2E_SOURCE_PUBLIC_DIR: empty }, PUBLIC), PUBLIC, "no manifest: public/"); + assert.equal(sourcePublicDir({ NODE_ENV: "development" }, PUBLIC), PUBLIC); +}); diff --git a/homepage/app/lib/source.ts b/homepage/app/lib/source.ts @@ -1,6 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { + HISTORY_DIR, MIRROR_DIR, TARBALL_HREF, parseSourceManifest, @@ -15,27 +16,61 @@ import { // // Believed only when the files it describes are here too (the snapshot.ts // rule): a manifest beside a missing mirror or tarball would advertise a -// clone that fails and a download that 404s. A malformed or wrong-version -// manifest is null too (parseSourceManifest checks every number the page -// reads), so a bad file is the empty state, never a crash in `next build`. -// `pub` is the test's seam. +// clone that fails and a download that 404s. The same for the history block +// (release 15 slice SG): without its log page beside it, the manifest is +// returned WITHOUT it, and the page shows no History links. A malformed or +// wrong-version manifest is null too (parseSourceManifest checks every number +// the page reads), so a bad file is the empty state, never a crash in `next +// build`. `pub` is the test's seam. // // The default is a DIRECTORY join on `process.cwd()`, which Turbopack would // trace as every file under `public/` (the source mirror among them, and it // grows with every publish): the opt-out keeps it a plain run-time path // (plans/FACTS.md, "A path joined from `process.cwd()` …"). export function loadSourceManifest( - pub: string = path.join(/* turbopackIgnore: true */ process.cwd(), "public"), + pub: string = sourcePublicDir( + process.env, + path.join(/* turbopackIgnore: true */ process.cwd(), "public"), + ), ): SourceManifest | null { try { const manifest = parseSourceManifest( - JSON.parse(fs.readFileSync(path.join(pub, "source", "manifest.json"), "utf8")), + JSON.parse(fs.readFileSync(path.join(/* turbopackIgnore: true */ pub, "source", "manifest.json"), "utf8")), ); if (!manifest) return null; - if (!fs.statSync(path.join(pub, "source", MIRROR_DIR, "info", "refs")).isFile()) return null; - if (!fs.statSync(path.join(pub, TARBALL_HREF.replace(/^\//, ""))).isFile()) return null; + if (!fs.statSync(path.join(/* turbopackIgnore: true */ pub, "source", MIRROR_DIR, "info", "refs")).isFile()) return null; + if (!fs.statSync(path.join(/* turbopackIgnore: true */ pub, TARBALL_HREF.replace(/^\//, ""))).isFile()) return null; + if (manifest.history && !isFile(path.join(/* turbopackIgnore: true */ pub, "source", HISTORY_DIR, "log.html"))) { + const without: SourceManifest = { ...manifest }; + delete without.history; + return without; + } return manifest; } catch { return null; } } + +function isFile(p: string): boolean { + try { + return fs.statSync(p).isFile(); + } catch { + return false; + } +} + +// The directory loadSourceManifest reads: `public/`, or the one +// E2E_SOURCE_PUBLIC_DIR names when it holds a manifest — but ONLY outside a +// production build. The homepage e2e's `next dev` points it at a fixture +// publish its spec writes and removes (e2e/fixture-source.ts), so the page's +// states with and without a history are exercised whatever this checkout has +// published; with the fixture gone, the page reads `public/` again. `next +// build` runs with NODE_ENV=production and ignores it (the summary.ts rule). +export function sourcePublicDir( + env: Readonly<Record<string, string | undefined>>, + publicDir: string, +): string { + const fixture = env.NODE_ENV !== "production" ? env.E2E_SOURCE_PUBLIC_DIR : undefined; + if (fixture && isFile(path.join(/* turbopackIgnore: true */ fixture, "source", "manifest.json"))) return fixture; + return publicDir; +} diff --git a/homepage/app/source/page.tsx b/homepage/app/source/page.tsx @@ -1,6 +1,12 @@ import Link from "next/link"; import type { Metadata } from "next"; -import { CLONE_URL, TREE_HREF } from "yt-dlp-transcript-common/lib/sourceManifest"; +import { + CLONE_URL, + HISTORY_ATOM_HREF, + HISTORY_LOG_HREF, + HISTORY_REFS_HREF, + TREE_HREF, +} from "yt-dlp-transcript-common/lib/sourceManifest"; import { PageShell, PageHeading } from "../components/PageShell"; import { Fact } from "../components/Fact"; import { loadSourceManifest } from "../lib/source"; @@ -19,7 +25,8 @@ const linkClass = // and the tarball are build artefacts, so this page has two states and the // manifest decides which. A manifest is believed only when the files it // describes are here too (lib/source.ts): no clone command or link for a file -// that isn't there. +// that isn't there. The History block likewise: only when the manifest has a +// history (stagit rendered it) and its log page is here. export default function SourcePage() { const manifest = loadSourceManifest(); const date = manifest @@ -79,6 +86,36 @@ export default function SourcePage() { </Fact> </div> + {manifest.history ? ( + <div data-testid="source-history" className="doc-measure flex flex-col gap-2"> + <p className="label-machine">History</p> + <p className="text-sm leading-[1.7] text-[var(--muted-foreground)]"> + Every commit of main with its diff, as static pages:{" "} + <span data-testid="source-history-commits" className="tabular"> + {manifest.history.commits.toLocaleString("en-US")} + </span>{" "} + commit{manifest.history.commits === 1 ? "" : "s"}, the newest{" "} + <span data-testid="source-history-head" className="tabular break-all"> + {manifest.history.head} + </span> + . + </p> + <p className="text-sm text-[var(--muted-foreground)]"> + <a data-testid="source-history-log" href={HISTORY_LOG_HREF} className={linkClass}> + Log + </a> + {" · "} + <a data-testid="source-history-refs" href={HISTORY_REFS_HREF} className={linkClass}> + Refs + </a> + {" · "} + <a data-testid="source-history-atom" href={HISTORY_ATOM_HREF} className={linkClass}> + Atom feed + </a> + </p> + </div> + ) : null} + <div className="doc-measure flex flex-col gap-3"> <p> <a data-testid="source-tree-link" href={TREE_HREF} className={`text-sm ${linkClass}`}> diff --git a/homepage/e2e/fixture-source.ts b/homepage/e2e/fixture-source.ts @@ -0,0 +1,67 @@ +import fs from "node:fs"; +import path from "node:path"; + +// A FIXTURE PUBLISH for the /source/ page's History block (release 15 slice +// SG). The page reads the directory E2E_SOURCE_PUBLIC_DIR names instead of +// public/ while it holds a manifest (app/lib/source.ts sourcePublicDir, +// honoured only outside a production build), so a spec can show the page with +// a history and without one, whatever this checkout has published. The spec +// writes it, and removes it after each test: with it gone, the page — and +// every other spec — reads public/ again. playwright.config.ts clears it +// before the server starts, in case a killed run left one. +// +// Only the files the loader requires: the manifest, the mirror's info/refs, +// the tarball, and — with a history — the log page. Nothing here is served: +// the dev server serves public/, so the History links are checked by their +// hrefs, and the real pages by source.spec.ts. + +export const FIXTURE_SOURCE_NAME = ".e2e-source"; + +export const FIXTURE_HISTORY = { + href: "/source/git/log.html", + commits: 1234, + head: "c".repeat(40), + files: 1240, + bytes: 1_000_000, + sha256: "d".repeat(64), + tool: "stagit (sha256 0123456789ab)", +}; + +export const FIXTURE_MIRROR_HEAD = "c".repeat(40); + +export function writeFixtureSource(dir: string, o: { history: boolean }): void { + clearFixtureSource(dir); + fs.mkdirSync(path.join(dir, "source", "archilyzer.git", "info"), { recursive: true }); + fs.mkdirSync(path.join(dir, "downloads"), { recursive: true }); + fs.writeFileSync(path.join(dir, "source", "archilyzer.git", "info", "refs"), `${FIXTURE_MIRROR_HEAD}\trefs/heads/main\n`); + fs.writeFileSync(path.join(dir, "downloads", "archilyzer-source.tar.gz"), "fixture"); + if (o.history) { + fs.mkdirSync(path.join(dir, "source", "git"), { recursive: true }); + fs.writeFileSync(path.join(dir, "source", "git", "log.html"), "<html></html>\n"); + } + const manifest = { + version: 1, + generatedAt: "2026-09-30T12:00:00.000Z", + branch: "main", + sourceCommit: "b".repeat(40), + mirrorHead: FIXTURE_MIRROR_HEAD, + subject: "a fixture publish", + files: 10, + bytes: 100, + mirror: { files: 9, bytes: 80, packs: 1 }, + tree: { files: 5, dirs: 2, bytes: 20 }, + tarball: { href: "/downloads/archilyzer-source.tar.gz", bytes: 7, sha256: "e".repeat(64) }, + cloneUrl: "https://archilyzer.pages.dev/source/archilyzer.git", + treeHref: "/source/tree/", + audit: { objects: 3, commits: 1, gitleaks: "skipped" }, + tools: { git: "2.55.0", filterRepo: "fixture" }, + ...(o.history ? { history: FIXTURE_HISTORY } : {}), + }; + // Written last, as the publish writes it: the page reads the fixture only + // once a manifest is there. + fs.writeFileSync(path.join(dir, "source", "manifest.json"), JSON.stringify(manifest, null, 2)); +} + +export function clearFixtureSource(dir: string): void { + fs.rmSync(dir, { recursive: true, force: true }); +} diff --git a/homepage/e2e/source-history.spec.ts b/homepage/e2e/source-history.spec.ts @@ -0,0 +1,45 @@ +import path from "node:path"; +import { test, expect } from "@playwright/test"; +import { + FIXTURE_HISTORY, + FIXTURE_SOURCE_NAME, + clearFixtureSource, + writeFixtureSource, +} from "./fixture-source"; + +// The /source/ page's History block (release 15 slice SG), in both states, +// from a FIXTURE publish (e2e/fixture-source.ts): the dev server reads the +// page's manifest from E2E_SOURCE_PUBLIC_DIR while it holds one +// (playwright.config.ts, app/lib/source.ts). Each test writes it and removes +// it, so every other spec reads public/. The history PAGES themselves — what +// stagit rendered and the publish rewrote — are source.spec.ts's, over the +// checkout's real publish. +const FIXTURE = path.resolve(process.cwd(), "e2e", FIXTURE_SOURCE_NAME); + +test.describe.configure({ mode: "serial" }); +test.afterEach(() => clearFixtureSource(FIXTURE)); + +test("with a history in the manifest: the History block names the commits and the head, and links the log, the refs and the feed", async ({ page }) => { + writeFixtureSource(FIXTURE, { history: true }); + await page.goto("/source/"); + await expect(page.getByTestId("source-mirror-head")).toHaveText(FIXTURE_HISTORY.head); + const block = page.getByTestId("source-history"); + await expect(block).toBeVisible(); + await expect(block).toContainText("Every commit of main with its diff, as static pages"); + await expect(page.getByTestId("source-history-commits")).toHaveText("1,234"); + await expect(page.getByTestId("source-history-head")).toHaveText(FIXTURE_HISTORY.head); + await expect(page.getByTestId("source-history-log")).toHaveAttribute("href", "/source/git/log.html"); + await expect(page.getByTestId("source-history-refs")).toHaveAttribute("href", "/source/git/refs.html"); + await expect(page.getByTestId("source-history-atom")).toHaveAttribute("href", "/source/git/atom.xml"); + await expect(page.getByTestId("source-history-log")).toHaveText("Log"); + await expect(page.getByTestId("source-history-atom")).toHaveText("Atom feed"); +}); + +test("without a history in the manifest: no History block, and the rest of the page as before", async ({ page }) => { + writeFixtureSource(FIXTURE, { history: false }); + await page.goto("/source/"); + await expect(page.getByTestId("source-clone")).toBeVisible(); + await expect(page.getByTestId("source-tree-link")).toBeVisible(); + await expect(page.getByTestId("source-history")).toHaveCount(0); + await expect(page.getByRole("link", { name: "Atom feed" })).toHaveCount(0); +}); diff --git a/homepage/e2e/source.spec.ts b/homepage/e2e/source.spec.ts @@ -106,6 +106,73 @@ test("the raw tree: an index per directory, encoded hrefs that resolve, brackets expect(font.status()).toBe(200); }); +// The history pages (release 15 slice SG): stagit's rendering, rewritten by +// the publish. Run when the checkout's publish has one (a machine with +// stagit); without, the page must show no History block. +test("the history pages: the log, a commit, the Files index into the raw tree; the site's line, its theme by stored choice and by the OS; the feed untouched", async ({ page, browser, baseURL }) => { + if (!(await published(page))) { + await expect(page.getByTestId("source-history")).toHaveCount(0); + return; + } + const manifest = await (await page.request.get("/source/manifest.json")).json(); + if (!manifest.history) { + await expect(page.getByTestId("source-history")).toHaveCount(0); + return; + } + await expect(page.getByTestId("source-history-head")).toHaveText(manifest.history.head); + const shown = (await page.getByTestId("source-history-commits").textContent())!.replace(/\D/g, ""); + expect(shown).toBe(String(manifest.history.commits)); + + // The log, with the one line the publish adds, linking back here, and the + // homepage's base (dark) for a reader who has stored none. + await page.getByTestId("source-history-log").click(); + await expect(page).toHaveURL(/\/source\/git\/log\.html$/); + const back = page.getByRole("link", { name: "Archilyzer · Source" }); + await expect(back).toHaveAttribute("href", "/source/"); + await expect(page.locator("html")).toHaveAttribute("data-base", "dark"); + await expect(page.locator("html")).toHaveAttribute("data-theme-ready", "1"); + const bg = () => page.evaluate(() => getComputedStyle(document.body).backgroundColor); + expect(await bg()).toBe("rgb(12, 10, 8)"); + // The stored choice is the homepage's (one origin, one key). + await page.evaluate(() => localStorage.setItem("ytdlp-tb:base", "light")); + await page.reload(); + await expect(page.locator("html")).toHaveAttribute("data-base", "light"); + expect(await bg()).toBe("rgb(243, 246, 247)"); + + // The newest commit's page resolves from the log, and its diff's file links + // go to the raw tree (no per-file pages are published). + const first = page.locator("#log a[href^='commit/']").first(); + await expect(first).toHaveAttribute("href", `commit/${manifest.history.head}.html`); + const commit = await page.request.get(`/source/git/commit/${manifest.history.head}.html`); + expect(commit.status()).toBe(200); + const commitHtml = await commit.text(); + expect(commitHtml).toContain('<a href="/source/">Archilyzer &middot; Source</a>'); + expect(commitHtml).not.toMatch(/href="(?:\.\.\/)*file\//); + // Files is an index into the tree: its first link is a raw file, served. + await page.goto("/source/git/files.html"); + const href = await page.locator("#files a").first().getAttribute("href"); + expect(href).toMatch(/^\.\.\/tree\//); + const target = new URL(href!, `${baseURL}/source/git/files.html`).pathname; + expect((await page.request.get(target)).status()).toBe(200); + expect((await page.request.get("/source/git/refs.html")).status()).toBe(200); + // The feeds are not pages: nothing is injected into them. + const atom = await (await page.request.get("/source/git/atom.xml")).text(); + expect(atom).toContain("<feed"); + expect(atom).toContain(`https://archilyzer.pages.dev/source/git/commit/${manifest.history.head}.html`); + expect(atom).not.toContain("archilyzer-source"); + expect(atom).not.toContain("<script"); + + // Without the script (no JS), the OS decides: light, and dark. + for (const [scheme, rgb] of [["light", "rgb(243, 246, 247)"], ["dark", "rgb(12, 10, 8)"]] as const) { + const ctx = await browser.newContext({ javaScriptEnabled: false, colorScheme: scheme }); + const p = await ctx.newPage(); + await p.goto(`${baseURL}/source/git/log.html`); + expect(await p.locator("html").getAttribute("data-base"), "the script did not run").toBeNull(); + expect(await p.evaluate(() => getComputedStyle(document.body).backgroundColor), `no JS, a ${scheme} OS`).toBe(rgb); + await ctx.close(); + } +}); + test("the Downloads page points at the mirror for the history", async ({ page }) => { await page.goto("/downloads/"); await expect(page.getByRole("link", { name: "read-only git mirror" })).toHaveAttribute("href", "/source/"); diff --git a/homepage/playwright.config.ts b/homepage/playwright.config.ts @@ -11,6 +11,7 @@ import { FIXTURE_SOCIAL_TRIO, writeFixtureSettings, } from "./e2e/fixture-social"; +import { FIXTURE_SOURCE_NAME, clearFixtureSource } from "./e2e/fixture-source"; // Homepage e2e. Runs against `next dev` (default mode) so it reflects uncommitted // source. Kill any stale dev server on the port between runs. @@ -38,6 +39,12 @@ import { // `archilyzer source publish`): e2e/source.spec.ts // FAILS on the /source/ page's empty state instead // of accepting it +// E2E_SOURCE_PUBLIC_DIR set below on the dev server: the directory the +// /source/ page reads the publish from WHILE it +// holds a manifest (app/lib/source.ts, ignored by +// a production build). e2e/source-history.spec.ts +// writes a fixture publish there and removes it; +// empty, the page reads public/ const PORT = portFor("HOMEPAGE_E2E_PORT"); const baseURL = `http://localhost:${PORT}`; @@ -56,6 +63,11 @@ writeFixtureSettings(FIXTURE_SETTINGS, FIXTURE_SOCIAL_TRIO); const FIXTURE_SITES_DIR = path.resolve(process.cwd(), "e2e", ".e2e-sites"); fs.mkdirSync(FIXTURE_SITES_DIR, { recursive: true }); +// The fixture publish (e2e/fixture-source.ts): none until a spec writes one, +// so a killed run's leftover never stands in for public/. +const FIXTURE_SOURCE_DIR = path.resolve(process.cwd(), "e2e", FIXTURE_SOURCE_NAME); +clearFixtureSource(FIXTURE_SOURCE_DIR); + export default defineConfig({ testDir: "./e2e", timeout: 30_000, @@ -73,6 +85,7 @@ export default defineConfig({ E2E_HOMEPAGE_SUMMARY_FILE: FIXTURE_SUMMARY, SETTINGS_FILE: FIXTURE_SETTINGS, SITES_DIR: FIXTURE_SITES_DIR, + E2E_SOURCE_PUBLIC_DIR: FIXTURE_SOURCE_DIR, }, }, use: {