Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-dashboard/test/race.test.ts

Snapshot at 34e3175

Containing directory

19,964 bytes · typescriptView raw
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&#x27;t load this race.*Try again/,
349  );
350  assert.match(html(view("race-unknown")), /This race doesn&#x27;t exist anymore./);
351  assert.match(html(view("race-standard")), /This packet isn&#x27;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