import assert from "node:assert/strict"; // Where corrections live: penalties only exist in races and only in the Judging view; Timing's // driver details in a race are read-only with an "Open in Judging" button. Practice has no // penalties anywhere, but quick per-lap clean-up stays on Timing. export async function runCorrectionsScopeSmoke({ evaluate, navigate, savedState, waitFor, base }) { const patchState = async (patch) => { const current = await savedState(); Object.assign(current, patch); const response = await fetch(base + "/api/state", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(current) }); assert.equal(response.ok, true); }; const exists = (selector) => evaluate(`Boolean(document.querySelector(${JSON.stringify(selector)}))`); const seeded = await savedState(); const startedAt = Date.now() - 120000; const passings = [20000, 20500, 19800].map((lapMs, index) => ({ driverId: "cs_driver", driverName: "CS Driver", competitorKey: "driver_cs_driver", transponder: "8440001", lapMs, timestamp: startedAt + (index + 1) * 20000, validLap: true })); const competitors = { driver_cs_driver: { key: "driver_cs_driver", driverId: "cs_driver", driverName: "CS Driver", displayName: "CS Driver", transponder: "8440001", laps: 3, lastLapMs: 19800, bestLapMs: 19800, startTimestamp: startedAt, lastTimestamp: startedAt + 60000 } }; const event = { id: "cs_event", name: "CS Race", classId: "cs_class", mode: "race", raceConfig: { minLapMs: 3000, maxLapMs: 60000 } }; const heat = { id: "cs_heat", eventId: event.id, name: "CS Heat", type: "heat", durationMin: 30, status: "running", startedAt, driverIds: ["cs_driver"] }; const practice = { id: "cs_practice", eventId: event.id, name: "CS Practice", type: "open_practice", durationMin: 600, status: "ready" }; await patchState({ classes: [...seeded.classes, { id: "cs_class", name: "CS Class" }], drivers: [...seeded.drivers, { id: "cs_driver", name: "CS Driver", classId: "cs_class", transponder: "8440001" }], events: [...seeded.events, event], sessions: [...seeded.sessions.map((session) => (["running", "paused"].includes(session.status) ? { ...session, status: "finished", endedAt: Date.now() } : session)), heat, practice], resultsBySession: { ...seeded.resultsBySession, [heat.id]: { passings, competitors, adjustments: [] }, [practice.id]: { passings: passings.map((passing) => ({ ...passing })), competitors: JSON.parse(JSON.stringify(competitors)), adjustments: [] } }, activeSessionId: heat.id, }); try { // Race: Timing details are read-only and point to Judging. await navigate("timing", "resetSession"); await evaluate(`document.getElementById("leaderboard-detail-driver_cs_driver").click()`); await waitFor(async () => exists("#corrOpenJudging"), "Open in Judging in a race"); assert.equal(await exists("#corrLapPlus"), false, "no penalty buttons on Timing"); assert.equal(await exists("#corrInvalidateLast"), false, "no lap removal on Timing in a race"); assert.equal(await exists("#leaderboardModalOverlay [data-lap-action]"), false, "the lap list is read-only in a race"); await evaluate(`document.getElementById("corrOpenJudging").click()`); await waitFor(async () => exists("#judgeLapPlus"), "Judging opened with the penalty buttons"); assert.match(await evaluate(`document.body.innerText`), /CS Driver/, "the same driver is selected in Judging"); console.log("PASS in a race, penalties and lap changes are only in Judging; Timing links there"); // Practice: no penalties anywhere, lap clean-up on Timing. await patchState({ sessions: (await savedState()).sessions.map((session) => (session.id === heat.id ? { ...session, status: "finished", endedAt: Date.now() } : session.id === practice.id ? { ...session, status: "running", startedAt } : session)), activeSessionId: practice.id }); await navigate("timing", "resetSession"); await evaluate(`document.getElementById("leaderboard-detail-driver_cs_driver").click()`); await waitFor(async () => exists('#leaderboardModalOverlay [data-lap-action="remove"]'), "Remove per lap in practice"); assert.equal(await exists("#corrOpenJudging"), false, "no corrections block in practice"); await navigate("judging", "view"); await waitFor(async () => exists("#judgeInvalidate"), "Judging works in practice"); assert.equal(await exists("#judgeLapPlus"), false, "no penalties in practice"); assert.equal(await exists("#judgeReset"), false); console.log("PASS practice has no penalties anywhere but keeps per-lap clean-up"); } finally { const current = await savedState(); await patchState({ sessions: current.sessions.map((session) => ([heat.id, practice.id].includes(session.id) ? { ...session, status: "finished", endedAt: Date.now() } : session)), activeSessionId: "", }); } }