Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-dashboard/test/race.test.ts
Snapshot at 34e3175
1import "./dom.ts";
2import assert from "node:assert/strict";
3import test from "node:test";
4import { createRequire } from "node:module";
5const { JSDOM } = createRequire(import.meta.url)("jsdom");
6import { createElement } from "react";
7import { renderToStaticMarkup } from "react-dom/server";
8import { TooltipProvider } from "@cloudflare/kumo";
9import { installDom } from "./dom.ts";
10import { raceFixtureState } from "./race-fixtures.ts";
11import { stateView } from "../src/model.ts";
12import { saddlecloth } from "../src/lane.tsx";
13import { RaceView } from "../src/race.tsx";
14import {
15 entrantResult,
16 ordinal,
17 photoFinish,
18 raceEntrants,
19 raceSplit,
20 raceStart,
21 rejectionDelta,
22} from "../src/race-model.ts";
23import { mountDashboard } from "../src/dashboard.ts";
24import { renderDashboard } from "../src/render.ts";
25import type { ProjectState, DashboardTransport } from "../src/contracts.ts";
26import type { ViewModel } from "../src/model.ts";
27import type { ViewActions } from "../src/view.tsx";
28import { getByRole } from "./roles.ts";
29const brand = { name: "Fixture app", tagline: "Fixture" };
30const actions: ViewActions = {
31 section: "work",
32 owner: true,
33 openReview() {},
34 openRace() {},
35 setSection() {},
36 async selectProject() {},
37 async action() {},
38};
39function view(scenario = "race-open"): ViewModel {
40 const state = stateView(raceFixtureState(scenario));
41 return {
42 brand,
43 project: scenario,
44 projects: [],
45 fixture: true,
46 state,
47 activity: state.recentActivity ?? [],
48 busy: false,
49 };
50}
51function html(value = view()) {
52 return renderToStaticMarkup(
53 createElement(
54 TooltipProvider,
55 null,
56 createElement(RaceView, { packet: "race-01", view: value, actions }),
57 ),
58 );
59}
60const until = async (check: () => boolean) => {
61 for (let i = 0; i < 100; i++) {
62 if (check()) return;
63 await new Promise((resolve) => setTimeout(resolve, 10));
64 }
65 assert.fail("Expected fixture UI did not appear");
66};
67
68test("race projection retains exact ledger reference, enrollment time and rejected attempt", () => {
69 const state = view("race-photo").state!;
70 assert.equal(state.races![0].ledgerEntryId, "ledger-race-1");
71 assert.equal(state.ledger[0].id, "ledger-race-1");
72 assert.equal(state.ledger[0].before, "a".repeat(40));
73 assert.equal(state.ledger[0].race, "race-01");
74 assert.deepEqual(state.races![0].places, [
75 { agent: "cedar", place: 1 },
76 { agent: "birch", place: 2 },
77 { agent: "amber", place: 3 },
78 ]);
79 assert.equal(state.reviews[0].receipt?.checks[0].tier, "quick");
80 assert.equal(state.reviews[0].receipt?.checks[0].runtime, "dynamic-worker");
81 assert.equal(state.races![0].entrants[2].promoteRejected?.found, "c".repeat(40));
82 assert.equal(state.agents[0].createdAt, "2026-10-13T14:00:00Z");
83});
84test("saddlecloth identity follows enrollment and repeats independently of claim and response order", () => {
85 const state = view().state!;
86 assert.equal(saddlecloth(state, "amber"), 1);
87 assert.equal(saddlecloth(state, "birch"), 2);
88 assert.equal(saddlecloth(state, "cedar"), 3);
89 state.agents.reverse();
90 state.agents.push({ name: "alder", createdAt: "2026-10-13T14:05:00Z" });
91 assert.equal(saddlecloth(state, "birch"), 2);
92 assert.equal(saddlecloth(state, "alder"), 4);
93 assert.equal(saddlecloth(state, "missing"), undefined);
94});
95test("same-time bootstrap enrollment tie-breaks alphabetically and lane order is stable", () => {
96 const state = view().state!;
97 state.agents = [
98 { name: "birch", createdAt: "2026-10-13T14:00:00Z" },
99 { name: "amber", createdAt: "2026-10-13T14:00:00Z" },
100 ];
101 assert.equal(saddlecloth(state, "amber"), 1);
102 assert.equal(saddlecloth(state, "birch"), 2);
103 const race = state.races![0];
104 race.entrants[1].claimedAt = race.entrants[2].claimedAt;
105 // Fork aliases must not override core's agent tie-break at an equal claim time.
106 race.entrants[1].repoName = "aaa-fork";
107 race.entrants[2].repoName = "zzz-fork";
108 const before = raceEntrants(race).map((entrant) => entrant.agent);
109 assert.deepEqual(before, ["birch", "cedar", "amber"]);
110 race.entrants.reverse();
111 assert.deepEqual(
112 raceEntrants(race).map((entrant) => entrant.agent),
113 before,
114 );
115});
116test("one recorded race clock includes late claims, seconds, hours and supplied milliseconds", () => {
117 assert.equal(raceSplit("2026-10-13T14:03:00Z", Date.parse("2026-10-13T14:02:00Z")), "01:00");
118 assert.equal(
119 raceSplit("2026-10-13T15:06:45.400Z", Date.parse("2026-10-13T14:02:00Z")),
120 "1:04:45.4",
121 );
122 assert.equal(raceSplit(null, 0), "");
123 assert.equal(raceSplit("invalid", 0), "");
124 assert.equal(rejectionDelta("2026-10-13T14:14:00.400Z", "2026-10-13T14:14:00Z"), "0.4 s after");
125 assert.equal(rejectionDelta("2026-10-13T14:14:13Z", "2026-10-13T14:14:00Z"), "13 s after");
126 assert.equal(rejectionDelta("2026-10-13T14:13:59Z", "2026-10-13T14:14:00Z"), "");
127 assert.deepEqual([1, 2, 3, 11, 12, 13, 21].map(ordinal), [
128 "1st",
129 "2nd",
130 "3rd",
131 "11th",
132 "12th",
133 "13th",
134 "21st",
135 ]);
136});
137test("semantic track keeps claim order at finish, checks after request, and real evidence", () => {
138 const value = view("race-finished"),
139 state = value.state!,
140 race = state.races![0];
141 assert.deepEqual(
142 raceEntrants(race).map((entry) => entry.agent),
143 ["birch", "cedar", "amber"],
144 );
145 race.entrants.find((entry) => entry.agent === "birch")!.claimedAt = "2026-10-13T14:01:59Z";
146 const dom = new JSDOM(html(value));
147 const track = dom.window.document.querySelector(".race-track")!;
148 assert.equal(track.querySelector("caption")!.textContent, "Race progress for race-01");
149 assert.deepEqual(
150 [...track.querySelectorAll("tbody tr")].map((row) => row.getAttribute("data-race-agent")),
151 ["birch", "cedar", "amber"],
152 );
153 assert.deepEqual(
154 [...track.querySelectorAll("thead th")].map((th) => th.textContent),
155 [
156 "Runner",
157 "Claimed",
158 "First push",
159 "Review requested",
160 "Checks passed",
161 "Approved",
162 "Promoted",
163 "Tokens",
164 "Result",
165 ],
166 );
167 assert.match(track.textContent!, /Checks failed, plus/);
168 assert.match(track.textContent!, /Promoted to main aaaaaaa → ccccccc/);
169 assert.match(track.textContent!, /Closed at the finish/);
170 assert.equal(track.querySelector(".lane-marker-review")!.textContent, "○");
171 assert.equal(track.querySelector(".lane-marker-approved")!.textContent, "◆");
172 assert.equal(track.querySelector(".race-saddlecloth .sr-only")!.textContent, "Agent number 2");
173 assert.doesNotMatch(html(value), /Photo finish|progressbar|% done/);
174 assert.equal(raceStart(race), Date.parse("2026-10-13T14:01:59Z"));
175});
176test("photo finish requires two recorded approvals before finish; rejected rows require data", () => {
177 const value = view("race-photo"),
178 race = value.state!.races![0];
179 assert.equal(photoFinish(race), 2);
180 assert.match(html(value), /2 entrants were approved/);
181 assert.match(
182 html(value),
183 /birch.*tried to promote.*expected.*aaaaaaa.*found.*ccccccc.*0.4 s after/,
184 );
185 assert.match(html(value), /Main moved/);
186 race.entrants.forEach((entry) => (entry.promoteRejected = null));
187 assert.doesNotMatch(html(value), /tried to promote|s after/);
188 race.entrants.find((entry) => entry.agent === "birch")!.approvedAt = "2026-10-13T14:15:00Z";
189 assert.equal(photoFinish(race), 1);
190 assert.doesNotMatch(html(value), /Photo finish/);
191});
192test("failed check details and later retry count come only from the entrant's own receipts", () => {
193 const value = view();
194 const review = value.state!.reviews.find((candidate) => candidate.agent === "birch")!;
195 const failed = structuredClone(review);
196 failed.id = "earlier-failed-review";
197 failed.requestedAt = "2026-10-13T14:06:00Z";
198 failed.receipt!.status = "failed";
199 failed.receipt!.checks = [{ name: "brand", tier: "full", outcome: "failed", durationMs: 13000 }];
200 value.state!.reviews.push(failed);
201 const dom = new JSDOM(html(value));
202 const hurdle = dom.window.document.querySelector(
203 '[data-race-agent="birch"] .race-hurdle-button',
204 )!;
205 assert.match(
206 hurdle.getAttribute("aria-label")!,
207 /brand · failed · 13 s; retried, passed at \+09:00\.0/,
208 );
209 assert.match(dom.window.document.querySelector(".race-results")!.textContent!, /1 of 2/);
210});
211test("missing ledger and activity never invent before/after or push counts", () => {
212 const value = view("race-photo");
213 // An unrelated first entry must not replace the exact ledger id binding.
214 value.state!.ledger.unshift({
215 ...value.state!.ledger[0],
216 id: "unrelated",
217 before: "e".repeat(40),
218 sha: "f".repeat(40),
219 });
220 assert.match(html(value), /aaaaaaa → ccccccc/);
221 assert.doesNotMatch(html(value), /eeeeeee → fffffff/);
222 value.state!.ledger[1].id = "different-entry";
223 value.activity = [];
224 assert.doesNotMatch(html(value), /aaaaaaa → ccccccc|<th[^>]*>Pushes/);
225});
226function resultsPushes(value: ViewModel) {
227 const dom = new JSDOM(html(value));
228 const compare = dom.window.document.querySelector(".race-compare")!;
229 const agents = [...compare.querySelectorAll("thead th")].slice(1).map((cell) => cell.textContent);
230 const pushRow = [...compare.querySelectorAll("tbody tr")].find((row) =>
231 row.querySelector("th")?.textContent === "Recorded pushes",
232 )!;
233 const counts = [...pushRow.querySelectorAll(".compare-value")].map((cell) => cell.textContent);
234 const results = [...dom.window.document.querySelectorAll(".race-results tbody tr")].map((row) => [
235 row.querySelector(".race-runner strong")!.textContent,
236 row.querySelector('[data-label="Pushes"]')?.textContent,
237 ]);
238 for (const [agent, count] of results) assert.equal(counts[agents.indexOf(agent)], count);
239 return results;
240}
241test("reused forks count only their race pushes, independently of loaded activity order", () => {
242 const value = view("race-reused-forks");
243 const race = value.state!.races![0];
244 assert.deepEqual(race.entrants.map((entry) =>
245 value.activity.filter((event) => event.repoName === entry.repoName).length,
246 ), [3, 20]);
247 assert.deepEqual(resultsPushes(value), [["cedar", "1"], ["birch", "1"]]);
248 value.activity.reverse();
249 assert.deepEqual(resultsPushes(value), [["cedar", "1"], ["birch", "1"]]);
250});
251test("race pushes include interval endpoints, exclude unrelated Git events, and honor close/finish/now", (t) => {
252 const value = view("race-reused-forks");
253 const race = value.state!.races![0];
254 const birch = race.entrants.find((entry) => entry.agent === "birch")!;
255 const event = value.activity.find((entry) => entry.repoName === birch.repoName)!;
256 const push = (at: string, fields = {}) => ({ ...event, recordedAt: at, ...fields });
257 birch.closedAt = "2026-10-13T14:04:00.000Z";
258 value.activity = [
259 push(birch.claimedAt), push(birch.closedAt),
260 // Provider event time never brings an out-of-window receive back into this race.
261 push("2026-10-13T14:01:59.999Z", { eventTimestamp: birch.claimedAt }),
262 push("2026-10-13T14:04:00.001Z", { eventTimestamp: birch.closedAt }),
263 push(birch.claimedAt, { ref: "refs/heads/other" }),
264 push(birch.claimedAt, { after: "0".repeat(40) }),
265 push(birch.claimedAt, { repoName: "unrelated-fork" }),
266 push("invalid"),
267 ];
268 assert.deepEqual(resultsPushes(value), [["cedar", "0"], ["birch", "2"]]);
269 birch.closedAt = null;
270 race.finishedAt = "2026-10-13T14:04:00.000Z";
271 assert.equal(resultsPushes(value)[1][1], "2");
272 race.finishedAt = null;
273 t.mock.method(Date, "now", () => Date.parse("2026-10-13T14:04:00.000Z"));
274 assert.equal(resultsPushes(value)[1][1], "2");
275});
276test("incomplete loaded race history hides the entire Pushes column and its caption", () => {
277 const value = view("race-reused-forks");
278 // Coverage includes cedar's later claim, but not birch's earlier claim.
279 const laterClaim = value.state!.races![0].entrants.find((entry) => entry.agent === "cedar")!.claimedAt;
280 value.activity = value.activity.filter((event) =>
281 Date.parse(event.recordedAt) >= Date.parse(laterClaim),
282 );
283 value.activity.unshift({ ...value.activity[0], recordedAt: laterClaim, repoName: value.state!.project.repoName });
284 assert.doesNotMatch(html(value), /<th[^>]*>Pushes|data-label="Pushes"|Pushes from loaded activity|Recorded pushes|Push counts cover/);
285 value.activity = [];
286 assert.doesNotMatch(html(value), /data-label="Pushes"|Pushes from loaded activity|Recorded pushes|Push counts cover/);
287});
288test("advisory approval skips hollow check marker with a connector and remains truthful", () => {
289 const value = view("race-advisory");
290 const dom = new JSDOM(html(value));
291 const lane = dom.window.document.querySelector('[data-race-agent="cedar"]')!;
292 assert.match(
293 lane.querySelector('[data-milestone="receiptPassedAt"]')!.textContent!,
294 /Checks failed/,
295 );
296 assert.match(
297 lane.querySelector('[data-milestone="approvedAt"]')!.getAttribute("style")!,
298 /--race-gap:2/,
299 );
300});
301test("receipt interruption fixtures use warning discs while failed checks keep the cross", () => {
302 for (const [scenario, label, kind, glyph] of [
303 ["race-timed-out", "Timed out", "interrupted", "!"],
304 ["race-couldnt-run", "Couldn't run", "interrupted", "!"],
305 ["race-finished", "Failed", "failed", "✗"],
306 ]) {
307 const dom = new JSDOM(html(view(scenario)));
308 try {
309 const cell = dom.window.document.querySelector(
310 '[data-race-agent="amber"] [data-milestone="receiptPassedAt"]',
311 )!;
312 assert.match(cell.textContent!, new RegExp(label));
313 assert.equal(cell.querySelector(`.lane-marker-${kind}`)!.textContent, glyph);
314 assert.equal(cell.querySelector(".lane-marker")!.getAttribute("aria-hidden"), "true");
315 assert.ok(cell.querySelector(".sr-only")!.textContent!.includes(label.toLowerCase()));
316 } finally { dom.window.close(); }
317 }
318});
319test("two-digit fixture keeps enrollment numbers throughout the race", () => {
320 const value = view("race-two-digit");
321 const dom = new JSDOM(html(value));
322 try {
323 for (const [agent, number] of [["amber", 10], ["birch", 11], ["cedar", 12]] as const) {
324 assert.equal(saddlecloth(value.state!, agent), number);
325 const cloth = dom.window.document.querySelector(`[data-race-agent="${agent}"] .race-saddlecloth`)!;
326 assert.equal(cloth.querySelector('[aria-hidden="true"]')!.textContent, String(number));
327 assert.equal(cloth.querySelector(".sr-only")!.textContent, `Agent number ${number}`);
328 const track = getByRole(dom.window.document.body, "table", { name: "Race progress for race-01" });
329 assert.ok(getByRole(track, "row", { name: new RegExp(`^Agent number ${number} ${agent}\\b`) }));
330 }
331 } finally { dom.window.close(); }
332});
333test("closed provenance never labels release or owner close as closed at finish", () => {
334 const entrant = view().state!.races![0].entrants[0];
335 entrant.closedAt = entrant.claimedAt;
336 entrant.closedBy = "release";
337 assert.equal(entrantResult(entrant, true), "Left the race");
338 entrant.closedBy = "owner";
339 assert.equal(entrantResult(entrant, false), "Closed by the owner");
340 entrant.closedBy = undefined;
341 assert.equal(entrantResult(entrant, true), "Closed");
342});
343test("empty, loading, error, unknown and standard pages contain no invented lanes", () => {
344 assert.match(html(view("race-empty")), /No entrants yet. Agents join by claiming race-01./);
345 assert.match(html({ ...view(), state: undefined, busy: true }), /Loading race…/);
346 assert.match(
347 html({ ...view(), state: undefined, error: "fixture error" }),
348 /Couldn't load this race.*Try again/,
349 );
350 assert.match(html(view("race-unknown")), /This race doesn't exist anymore./);
351 assert.match(html(view("race-standard")), /This packet isn't a race./);
352});
353test("Work and Review badges and Needs you race copy use packet mode", () => {
354 const content = renderDashboard(view());
355 assert.match(content, /3 entrants/);
356 assert.match(content, /Racing/);
357 assert.match(content, /First promote wins the race./);
358 assert.match(renderDashboard(view("race-photo")), /Won by cedar/);
359});
360test("race route opens from Work, supports back/opaque history and stays read-only for agents", async () => {
361 const dom = new JSDOM('<div id="dashboard"></div>', { url: "https://fixture.idiot.tax/#work" });
362 installDom(dom.window as unknown as Window & typeof globalThis);
363 const state: ProjectState = view().state!;
364 const writes: string[] = [];
365 const transport: DashboardTransport = {
366 async getState() {
367 return structuredClone(state);
368 },
369 async getActivity() {
370 return { activity: [], nextAfter: 0 };
371 },
372 async approve() {
373 writes.push("approve");
374 },
375 async promote() {
376 writes.push("promote");
377 return { sha: "fixture" };
378 },
379 };
380 const root = dom.window.document.getElementById("dashboard")! as HTMLElement;
381 const app = mountDashboard(root, {
382 brand,
383 transport,
384 initialProject: state.project.id,
385 owner: false,
386 });
387 try {
388 await until(() => !!root.querySelector(".packet-link"));
389 const historyLength = dom.window.history.length;
390 root.querySelector<HTMLButtonElement>(".packet-link")!.click();
391 await until(() => !!root.querySelector(".race-track"));
392 assert.equal(dom.window.history.length, historyLength + 1);
393 assert.equal(dom.window.location.hash, "#race/race-01");
394 await until(() => dom.window.document.activeElement?.id === "race-title");
395 assert.equal(root.querySelector("#activity-panel"), null);
396 assert.equal(root.querySelector('[data-action="promote"]'), null);
397 assert.equal(root.querySelector('[data-action="approve"]'), null);
398 assert.deepEqual(writes, []);
399 await app.refresh();
400 assert.ok(root.querySelector(".race-track"));
401 [...root.querySelectorAll<HTMLButtonElement>("button")]
402 .find((button) => button.textContent === "← Work")!
403 .click();
404 await until(() => !!root.querySelector("#view-work"));
405 assert.equal(dom.window.location.hash, "#work");
406 await until(() => dom.window.document.activeElement === root.querySelector(".packet-link"));
407 dom.window.history.pushState = () => {
408 throw new dom.window.DOMException("Opaque", "SecurityError");
409 };
410 root.querySelector<HTMLButtonElement>(".packet-link")!.click();
411 await until(() => !!root.querySelector(".race-track"));
412 assert.equal(dom.window.location.hash, "#race/race-01");
413 } finally {
414 app.dispose();
415 dom.window.close();
416 }
417});
418test("race Promote dialog states the other open entrants before owner confirmation", async () => {
419 const dom = new JSDOM('<div id="dashboard"></div>', { url: "https://fixture.idiot.tax/#review" });
420 installDom(dom.window as unknown as Window & typeof globalThis);
421 const state = view().state!;
422 const root = dom.window.document.getElementById("dashboard")! as HTMLElement;
423 const transport: DashboardTransport = {
424 async getState() {
425 return state;
426 },
427 async getActivity() {
428 return { activity: [], nextAfter: 0 };
429 },
430 async approve() {},
431 async promote() {
432 assert.fail("Opening a dialog must never write");
433 },
434 };
435 const app = mountDashboard(root, { brand, transport, initialProject: state.project.id });
436 try {
437 await until(() => !!root.querySelector('[data-action="promote"]'));
438 root.querySelector<HTMLButtonElement>('[data-action="promote"]')!.click();
439 await until(() => !!dom.window.document.querySelector(".promotion-dialog"));
440 assert.match(
441 dom.window.document.querySelector(".promotion-dialog")!.textContent!,
442 /Promoting this finishes the race. amber and birch will be closed and placed./,
443 );
444 } finally {
445 app.dispose();
446 dom.window.close();
447 }
448});
449
450
451test("a finished race without a recorded winner omits Won in", () => {
452 const value = view("race-finished");
453 value.state!.races![0].winner = null;
454 assert.doesNotMatch(html(value), /Won in/);
455 value.state!.races![0].winner = "cedar";
456 assert.match(html(value), /Won in/);
457});
458