Archilyzer · Source

archilyzer

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

commit 4fbd9ed6ef40ded8de52f9ec93ec7c2553a70ce4
parent 7e34cc17dedda9f9843e3489bebaefaf325327ca
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sun, 20 Sep 2026 17:45:48 -0400

e2e: assert the walk where the fixture holds still

The whole-cut walk test moves onto walk-fixture, which can state what the walk
skips and why: w01 to w04 by link and by key, over one clip with nothing to
play and one already judged; both of those still open by URL and say which
they are; ready 2 of 3 on the project page and in the bench header; and y
advancing over the judged one.

The two bench-fixture tests that walk now say what state they need out loud.
c04 is the only other clip there the walk can visit, so whether it carries a
verdict decided where they landed -- inherited from whichever test ran first.

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

Diffstat:
Mumtool/e2e/clip-bench.spec.ts | 130++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 112 insertions(+), 18 deletions(-)

diff --git a/umtool/e2e/clip-bench.spec.ts b/umtool/e2e/clip-bench.spec.ts @@ -36,11 +36,16 @@ const FIXTURE = path.join(UMTOOL, ".e2e-song"); // index and decision specs assert what report-fixture's windows are -- sharing // one project made the suite pass or fail on which spec file ran first. const PROJECT = "reports/bench-fixture"; +// The WALK's fixture, and read-only: see the block below, and the comment on +// walk-fixture in make-fixture.mjs. +const WALK = "reports/walk-fixture"; const MANIFEST = path.join(FIXTURE, "reports", "bench-fixture", "video.manifest.json"); const bench = (clip: string) => `/browse/${PROJECT}/clip/${clip}`; +const walkBench = (clip: string) => `/browse/${WALK}/clip/${clip}`; -const readClip = (id: string) => { - const m = JSON.parse(readFileSync(MANIFEST, "utf8")) as { +const readClipIn = (project: string, id: string) => { + const file = path.join(FIXTURE, ...project.split("/"), "video.manifest.json"); + const m = JSON.parse(readFileSync(file, "utf8")) as { timeline: { id: string; start: number; @@ -56,6 +61,7 @@ const readClip = (id: string) => { }; return m.timeline.find((e) => e.id === id)!; }; +const readClip = (id: string) => readClipIn(PROJECT, id); /** * Type into an attribution field and let it save the way a blur does. @@ -100,8 +106,12 @@ const keyboardLive = async (page: import("@playwright/test").Page) => { await box.blur(); }; -const token = async (request: { get: (u: string) => Promise<{ json: () => Promise<unknown> }> }, clip: string) => { - const r = await request.get(`/api/report/clip?project=${encodeURIComponent(PROJECT)}&clip=${clip}`); +const token = async ( + request: { get: (u: string) => Promise<{ json: () => Promise<unknown> }> }, + clip: string, + project = PROJECT, +) => { + const r = await request.get(`/api/report/clip?project=${encodeURIComponent(project)}&clip=${clip}`); return (await r.json()) as { token: string }; }; @@ -337,23 +347,92 @@ test("a date that is not a real day is refused, and the bench says so", async ({ await expect(page.locator("[data-attrib-field=date]")).toHaveValue("2025-02-31"); }); -test("prev and next walk the cut, by link and by key", async ({ page }) => { - await page.goto(bench("c02")); +// --------------------------------------------------------------------------- +// The walk, and the two things it skips. +// +// Reviewing a cut is watching every clip in order and answering one question +// about each, so the walk goes to the clips that still NEED an answer and can +// be WATCHED today. A clip with nothing cached is a dead end -- there is +// nothing to judge and the only move is to press `n` again -- and one already +// answered is the round trip the walk exists to remove. +// +// These run against reports/walk-fixture, which is read-only for exactly this +// reason: every other test in this file writes verdicts into bench-fixture, so +// "ready 2 of 3" would be true there only until one of them ran. Its four +// clips are w01 (fetched, unjudged), w02 (nothing holds 9-12), w03 (fetched, +// already confirmed) and w04 (fetched, unjudged). +// --------------------------------------------------------------------------- + +test("the walk visits only the clips that need judgement and are fetched", async ({ page }) => { + await page.goto(walkBench("w01")); + // w02 has nothing to play and w03 has already been answered, so the one move + // forward from w01 is w04 -- named in the link, not just arrived at. + await expect(page.locator("[data-clip-nav=next]")).toHaveText(/w04/); await page.locator("[data-clip-nav=next]").click(); - await expect(page.locator("[data-bench=c03]")).toBeVisible(); + await expect(page.locator("[data-bench=w04]")).toBeVisible(); - // The same move from the keyboard. Reviewing a cut is watching every clip in - // order, and the project page in between is a round trip to re-find your place. + // The same move from the keyboard, backwards over the same two clips. + await keyboardLive(page); await page.locator("body").press("p"); - await expect(page.locator("[data-bench=c02]")).toBeVisible(); + await expect(page.locator("[data-bench=w01]")).toBeVisible(); - // The ends say so rather than offering a link into nothing. - await page.goto(bench("c01")); + // The ends of the WALK, which are not the ends of the cut. await expect(page.locator("[data-clip-nav=prev]")).toHaveCount(0); - await page.goto(bench("c04")); + await page.goto(walkBench("w04")); await expect(page.locator("[data-clip-nav=next]")).toHaveCount(0); }); +test("a clip off the walk still opens by URL, and says why it is off it", async ({ page }) => { + // Nothing fetched: it renders, it says so, and its links point at the + // neighbours on either side rather than at nothing. + await page.goto(walkBench("w02")); + await expect(page.locator("[data-bench=w02]")).toBeVisible(); + await expect(page.locator("[data-clip-unfetched]")).toBeVisible(); + await expect(page.getByText("nothing fetched for this clip yet")).toBeVisible(); + await expect(page.locator("[data-clip-nav=prev]")).toHaveText(/w01/); + await expect(page.locator("[data-clip-nav=next]")).toHaveText(/w04/); + + // Already judged: it opens too, with what the walk said about it, and it is + // fetched -- so no pill. + await page.goto(walkBench("w03")); + await expect(page.locator("[data-bench=w03]")).toHaveAttribute("data-verdict", "confirmed"); + await expect(page.locator("[data-clip-unfetched]")).toHaveCount(0); +}); + +test("`ready N of M` counts the fetched clips that still need judgement", async ({ page }) => { + await page.goto(`/browse/${WALK}`); + await expect(page.locator("[data-ready-count]")).toHaveText("ready 2 of 3 needing judgement"); + // The pill is the same question the walk asks, per row. + await expect(page.locator("[data-entry=w02]")).toHaveAttribute("data-fetched", "0"); + await expect(page.locator("[data-entry=w02]")).toContainText("not fetched yet"); + await expect(page.locator("[data-entry=w01]")).toHaveAttribute("data-fetched", "1"); + // And the walk starts where the walk actually goes. + await expect(page.locator("[data-walk-start=w01]")).toHaveAttribute( + "href", + `/browse/${WALK}/clip/w01`, + ); + + await page.goto(walkBench("w01")); + await expect(page.locator("[data-ready-count]")).toHaveText("ready 2 of 3 needing judgement"); +}); + +test("`y` walks on OVER a clip that has already been judged", async ({ page, request }) => { + await page.goto(walkBench("w01")); + await keyboardLive(page); + await page.locator("body").press("y"); + // Not w02 (nothing to play) and not w03 (answered in the fixture): w04. + await expect(page.locator("[data-bench=w04]")).toBeVisible(); + await expect.poll(() => readClipIn(WALK, "w01").verdict).toBe("confirmed"); + + // Put it back. This is the ONE test that writes to walk-fixture, and the + // counter above is only stable because it does. + const { token: t } = await token(request, "w01", WALK); + await request.put("/api/report/window", { + data: { project: WALK, clip: "w01", verdict: "", token: t }, + }); + await expect.poll(() => readClipIn(WALK, "w01").verdict).toBeUndefined(); +}); + test("the segment route serves a built clip with ranges, and 404s when there is none", async ({ page, request, @@ -509,13 +588,22 @@ test("the bench says what the clip is supposed to be", async ({ page }) => { }); test("`y` confirms the clip and walks on; the manifest says so", async ({ page, request }) => { + // c04 is the only OTHER clip here the walk can visit -- c02 and c03 have + // nothing cached that holds them -- so the state it is in decides where this + // lands. Said out loud rather than inherited from whichever test ran before. + const { token: t0 } = await token(request, "c04"); + await request.put("/api/report/window", { + data: { project: PROJECT, clip: "c04", verdict: "", token: t0 }, + }); + await page.goto(bench("c01")); await keyboardLive(page); await page.locator("body").press("y"); - // Confirming ADVANCES: in the yes case the next clip is what you want, and - // a walk of sixty clips is one finger. - await expect(page.locator("[data-bench=c02]")).toBeVisible(); + // Confirming ADVANCES: in the yes case the next clip you can judge is what + // you want, and a walk of sixty clips is one finger. Over c02 and c03, which + // are not fetched, to c04. + await expect(page.locator("[data-bench=c04]")).toBeVisible(); await expect.poll(() => readClip("c01").verdict).toBe("confirmed"); await page.goto(bench("c01")); @@ -698,8 +786,14 @@ test("the playback speed is this browser's, and it survives a reload", async ({ .toBe(1.5); }); -test("auto-audition plays the clip you walk onto", async ({ page }) => { - // c03 -> c04, because c04 is the neighbour with cached material. +test("auto-audition plays the clip you walk onto", async ({ page, request }) => { + // c03 -> c04: c03 itself has nothing cached (it is reached here by URL, which + // still works), and c04 is the next clip the walk can visit. Its verdict is + // set explicitly, because an answered clip is not one the walk goes to. + const { token: t } = await token(request, "c04"); + await request.put("/api/report/window", { + data: { project: PROJECT, clip: "c04", verdict: "", token: t }, + }); await page.goto(bench("c03")); await page.locator("[data-auto-audition=off]").click(); await expect(page.locator("[data-auto-audition=on]")).toBeVisible();