From a13c9a15f9bb8599c9320885ab27be99113fb1b7 Mon Sep 17 00:00:00 2001 From: larssand Date: Wed, 7 Oct 2026 21:20:03 +0200 Subject: [PATCH] feat(judging): keep penalties and race lap changes in the Judging view only Race sessions: Timing driver details become read-only with an Open in Judging button; penalties and lap changes happen only in Judging. Practice: no penalties anywhere, per-lap clean-up stays on Timing. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 9 +++++ ROADMAP.md | 18 +++++++++ package-lock.json | 4 +- package.json | 2 +- src/app.js | 18 +++++---- src/timing_views.js | 63 +++++++----------------------- tests/browser_corrections_scope.js | 62 +++++++++++++++++++++++++++++ tests/browser_smoke.js | 2 + 8 files changed, 120 insertions(+), 58 deletions(-) create mode 100644 tests/browser_corrections_scope.js diff --git a/CHANGELOG.md b/CHANGELOG.md index a75ad75..0df394a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,15 @@ The application version is maintained in `package.json` and displayed in the adm No changes recorded yet. +## 1.32.0 - 2026-10-07 + +### Changed + +- Penalties and lap changes now have one place each. + - **Races (qualifying, heats, finals):** penalties (±lap, ±seconds, reset) and lap changes are made only in the **Judging** view, where they are logged and undoable. In Timing, the driver details show the corrections and the lap list read-only, with an **Open in Judging** button that opens Judging with that driver selected. + - **Practice:** there are no penalties anywhere. Removing, restoring and approving laps stays one click away on Timing, and in Judging. +- The Guide texts were updated to match. + ## 1.31.1 - 2026-10-07 ### Fixed diff --git a/ROADMAP.md b/ROADMAP.md index 1b4154e..556e034 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -742,6 +742,24 @@ Verified 2026-10-07: - The browser test clicks the sidebar link from the Dashboard and checks that System & backup is active and the Updates section is visible. - 253 unit and 58 browser scenarios pass. +## Penalties and lap changes: Judging only, races only + +Decided by the user 2026-10-07: in qualifying, heats and finals, the judge should be the one who modifies laps, not anyone at the Timing page. Penalties make no sense outside races. + +Before this, Timing's driver details had the full set of penalty and last-lap buttons for every session type, and from 1.30.0 also Remove / Restore per lap. + +- [x] Timing → driver details: + - Race sessions show the corrections and the lap list read-only, plus "Open in Judging" (`openJudging` selects the driver and navigates). + - Practice sessions show no corrections block, and keep Remove / Restore / Approve per lap. + - The old click handlers were removed. +- [x] Judging: penalty buttons (±lap, ±seconds, reset) only for race sessions. Last-lap invalidate / restore and the per-lap list work for every session. +- [x] Guide texts `validation_5`-`7` (sv/en) updated. + +Verified 2026-10-07: +- 253 unit tests pass. +- `tests/browser_corrections_scope.js` checks a running heat: Timing has no penalty or lap-removal buttons and the lap list has no actions, "Open in Judging" lands on Judging with the penalty buttons and the driver. It then checks a running Open Practice: Timing has Remove per lap and no corrections block, and Judging has invalidate but no penalty or reset buttons. +- 60 browser scenarios pass. + ## Documentation maintenance - [x] Update the architecture's module/dependency inventory as modules change. diff --git a/package-lock.json b/package-lock.json index 1602499..13eca9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rc-racecore", - "version": "1.31.1", + "version": "1.32.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rc-racecore", - "version": "1.31.1", + "version": "1.32.0", "hasInstallScript": true, "dependencies": { "better-sqlite3": "^12.11.1", diff --git a/package.json b/package.json index f928147..ef6d013 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-racecore", - "version": "1.31.1", + "version": "1.32.0", "private": true, "description": "RC RaceCore live timing and race control with AMMC ingest and SQLite persistence", "engines": { diff --git a/src/app.js b/src/app.js index 7b630dd..9f71b25 100644 --- a/src/app.js +++ b/src/app.js @@ -465,7 +465,7 @@ const refreshTimingDynamics = () => { node.textContent = clock; }); }; -const renderTiming = () => renderTimingView({ state, dom, t, escapeHtml, formatLap, formatCountdown, formatElapsedClock, formatRaceClock, renderTable, uid, getActiveSession, ensureSessionResult, buildLeaderboard, getSessionTiming, getVisiblePassings, getEventName, getSessionTypeLabel, getStatusLabel, normalizeStartMode, getStartModeLabel, renderPositionGrid, renderSessionChannelAnalysis, connectDecoder, disconnectDecoder, applyCompetitorCorrection, invalidateCompetitorLastLap, restoreCompetitorLastInvalidLap, startOperationalSession, finishOperationalSession, cancelRaceStart, triggerRaceAudio, getStartLightState: () => raceAudioEngine.getStartLightState(), saveState, updateHeaderState, renderView, normalizeDriver, normalizeCar, processDecoderMessage, getSelectedLeaderboardKey: () => selectedLeaderboardKey, setSelectedLeaderboardKey: (value) => { selectedLeaderboardKey = value; }, getQuickAddDraft: () => quickAddDraft, setQuickAddDraft: (value) => { quickAddDraft = value; }, getPassingValidationLabel, isCountedPassing, getCompetitorPassings, getManualCorrectionSummary, formatTeamActiveMemberLabel, lastFinishAnnouncementSessionId: () => lastFinishAnnouncementSessionId, setLastFinishAnnouncementSessionId: (value) => { lastFinishAnnouncementSessionId = value; }, lastOverlayLeaderKeyBySession, lastOverlayTop3BySession, overlayEvents: () => overlayEvents, setOverlayEvents: (value) => { overlayEvents = value; }, sessionReplay, syncTrainingLedger, setCompetitorLapStatus }); +const renderTiming = () => renderTimingView({ state, dom, t, escapeHtml, formatLap, formatCountdown, formatElapsedClock, formatRaceClock, renderTable, uid, getActiveSession, ensureSessionResult, buildLeaderboard, getSessionTiming, getVisiblePassings, getEventName, getSessionTypeLabel, getStatusLabel, normalizeStartMode, getStartModeLabel, renderPositionGrid, renderSessionChannelAnalysis, connectDecoder, disconnectDecoder, applyCompetitorCorrection, invalidateCompetitorLastLap, restoreCompetitorLastInvalidLap, startOperationalSession, finishOperationalSession, cancelRaceStart, triggerRaceAudio, getStartLightState: () => raceAudioEngine.getStartLightState(), saveState, updateHeaderState, renderView, normalizeDriver, normalizeCar, processDecoderMessage, getSelectedLeaderboardKey: () => selectedLeaderboardKey, setSelectedLeaderboardKey: (value) => { selectedLeaderboardKey = value; }, getQuickAddDraft: () => quickAddDraft, setQuickAddDraft: (value) => { quickAddDraft = value; }, getPassingValidationLabel, isCountedPassing, getCompetitorPassings, getManualCorrectionSummary, formatTeamActiveMemberLabel, lastFinishAnnouncementSessionId: () => lastFinishAnnouncementSessionId, setLastFinishAnnouncementSessionId: (value) => { lastFinishAnnouncementSessionId = value; }, lastOverlayLeaderKeyBySession, lastOverlayTop3BySession, overlayEvents: () => overlayEvents, setOverlayEvents: (value) => { overlayEvents = value; }, sessionReplay, syncTrainingLedger, setCompetitorLapStatus, openJudging: (rowKey) => { selectedJudgeKey = rowKey; selectedLeaderboardKey = null; navigateToView("judging"); } }); const renderJudging = () => renderJudgingView({ state, dom, t, escapeHtml, formatLap, renderTable, getActiveSession, ensureSessionResult, buildLeaderboard, getSessionTypeLabel, getJudgeFilteredRows, getJudgeFilteredLog, getCompetitorPassings, isCountedPassing, getPassingValidationLabel, undoJudgingAdjustment, applyCompetitorCorrection, invalidateCompetitorLastLap, restoreCompetitorLastInvalidLap, renderView, getSelectedJudgeKey: () => selectedJudgeKey, setSelectedJudgeKey: (value) => { selectedJudgeKey = value; }, getJudgingCompetitorFilter: () => judgingCompetitorFilter, setJudgingCompetitorFilter: (value) => { judgingCompetitorFilter = value; }, getJudgingLogFilter: () => judgingLogFilter, setJudgingLogFilter: (value) => { judgingLogFilter = value; } , setCompetitorLapStatus }); const normalizeTrainingState = (training) => normalizeTrainingStateLogic(training, { uid }); const syncTrainingLedger = () => { @@ -1563,6 +1563,8 @@ const TRANSLATIONS = { "timing.stop": "Stoppa", "timing.reset": "Nollställ data", "timing.lap_remove": "Ta bort", + "timing.corrections_in_judging": "Straff och varvändringar görs av domaren i Domarvyn (loggas och kan ångras).", + "timing.open_in_judging": "Öppna i Domarvyn", "timing.lap_restore": "Återställ", "timing.lap_removed": "borttaget", "suspect.title": "Misstänkt korta varv ({count})", @@ -2035,9 +2037,9 @@ const TRANSLATIONS = { "guide.validation_2": "Ogiltiga kortvarv under min-gränsen räknas inte alls i leaderboard eller statistik.", "guide.validation_3": "Ogiltiga långvarv över max-gränsen räknas inte som varv, men de kan bryta lap-basen så nästa giltiga varv börjar om korrekt.", "guide.validation_4": "När ordinarie tid är slut kan sessionen gå in i Follow-up aktiv om du har satt Follow-up tid i raceformat eller sessionen.", - "guide.validation_5": "I Tidtagning -> Detaljer kan du ge +1/-1 varv och +1/+5/-1 sekunder som manuell korrigering. Det slår igenom direkt i leaderboarden.", - "guide.validation_6": "I samma detaljvy kan du också manuellt ogiltigförklara senaste räknade varvet om du behöver ta bort en felträff i efterhand.", - "guide.validation_7": "Menyn Domare samlar samma korrigeringar i en separat arbetsvy med leaderboard, lap history och domarlogg för pågående session.", + "guide.validation_5": "Straff (+1/-1 varv, +1/+5/-1 sekunder) finns bara i race (kval, heat, final) och görs av domaren i Domarvyn. Det slår igenom direkt i leaderboarden. Tidtagning -> Detaljer visar korrigeringarna och har knappen Öppna i Domarvyn.", + "guide.validation_6": "Under träning tar du bort, återställer eller godkänner valfritt varv direkt i Tidtagning -> Detaljer. Misstänkt korta varv (genvägar) flaggas med ⚠ och listas med Godkänn / Ta bort på Tidtagning och Race Day.", + "guide.validation_7": "Domarvyn har leaderboard, varvhistorik med Ta bort / Återställ per varv, straff (endast race) och domarlogg med ångra för pågående session.", "guide.validation_8": "Domarvyn kan filtrera på ogiltiga rader, korrigerade rader eller teamrace, exportera domarloggen och ångra flera senaste manuella åtgärder via undo-knappar.", "guide.qualifying_title": "Seedning, poängtabeller och tie-break", "guide.qualifying_1": "Practice och kval kan nu använda tre seedmetoder: bästa N varv som summa, bästa N varv som snitt eller bästa N konsekutiva varv.", @@ -3225,6 +3227,8 @@ const TRANSLATIONS = { "timing.stop": "Stop", "timing.reset": "Reset Data", "timing.lap_remove": "Remove", + "timing.corrections_in_judging": "Penalties and lap changes are made by the judge in the Judging view (logged and undoable).", + "timing.open_in_judging": "Open in Judging", "timing.lap_restore": "Restore", "timing.lap_removed": "removed", "suspect.title": "Suspiciously short laps ({count})", @@ -3697,9 +3701,9 @@ const TRANSLATIONS = { "guide.validation_2": "Invalid short laps under the minimum threshold do not count in the leaderboard or statistics.", "guide.validation_3": "Invalid long laps over the maximum threshold do not count as laps, but they can reset the lap base so the next valid lap starts correctly.", "guide.validation_4": "When the scheduled time ends, the session can enter Follow-up active if Follow-up time has been configured in race format or on the session.", - "guide.validation_5": "In Timing -> Details you can apply +1/-1 lap and +1/+5/-1 seconds as manual corrections. The leaderboard updates immediately.", - "guide.validation_6": "In the same detail view you can also manually invalidate the latest counted lap if you need to remove a false hit afterwards.", - "guide.validation_7": "The Judging menu collects the same corrections in a separate work view with leaderboard, lap history and a judging log for the current session.", + "guide.validation_5": "Penalties (+1/-1 lap, +1/+5/-1 seconds) exist only in races (qualifying, heats, finals) and are given by the judge in the Judging view. The leaderboard updates immediately. Timing -> Details shows the corrections and has an Open in Judging button.", + "guide.validation_6": "In practice, remove, restore or approve any lap right in Timing -> Details. Suspiciously short laps (shortcuts) are flagged with ⚠ and listed with Approve / Remove on Timing and Race Day.", + "guide.validation_7": "The Judging view has the leaderboard, lap history with Remove / Restore per lap, penalties (races only) and the judging log with undo for the current session.", "guide.validation_8": "The Judging view can filter invalid rows, corrected rows or team race rows, export the judging log and undo multiple recent manual actions via undo buttons.", "guide.qualifying_title": "Seeding, points tables and tie-break", "guide.qualifying_1": "Practice and qualifying can now use three seed methods: best N laps as total, best N laps as average or best N consecutive laps.", diff --git a/src/timing_views.js b/src/timing_views.js index f29764b..794a8aa 100644 --- a/src/timing_views.js +++ b/src/timing_views.js @@ -227,6 +227,10 @@ function renderQuickAddPanel(session, deps) { `; } +// Penalties belong to races only, and in races every correction is made by the judge in +// the Judging view (logged, undoable). Practice keeps quick lap clean-up on Timing. +const isPracticeType = (type) => ["open_practice", "free_practice", "practice"].includes(String(type || "")); + function renderLapActions(passing, rowKey, { t, escapeHtml }) { const button = (action, label, extra = "") => ``; if (passing.validLap === false) return passing.invalidReason === "manual_invalid" ? button("restore", "timing.lap_restore") : ""; @@ -276,21 +280,14 @@ function renderLeaderboardModal(session, row, deps) {

${t("table.pit")}: ${row.pitActive ? t("timing.pit_active") : `${Number(row.pitStops || 0)} / ${formatLap(row.totalPitMs)}`}

${t("table.own_delta")}: ${escapeHtml(row.lapDelta || "-")}

+ ${isPracticeType(session.type) ? "" : `

${t("timing.manual_corrections")}

${t("timing.lap_adjustment")}: ${Number(row.manualLapAdjustment || 0) || 0}

${t("timing.time_penalty")}: ${(Number(row.manualTimeAdjustmentMs || 0) || 0) > 0 ? "+" : (Number(row.manualTimeAdjustmentMs || 0) || 0) < 0 ? "-" : ""}${formatLap(Math.abs(Number(row.manualTimeAdjustmentMs || 0) || 0))}

-
- - - - - - - - -
-
+

${t("timing.corrections_in_judging")}

+ + `}

${t("timing.lap_history")}

${ @@ -306,7 +303,7 @@ function renderLeaderboardModal(session, row, deps) { ${counted ? lapNumber : "–"} ${formatRaceClock(Math.max(0, passing.timestamp - (session.startedAt || passing.timestamp)))} ${formatLap(passing.lapMs)}${passing.suspect && counted ? ` ⚠` : ""}${counted ? "" : ` ${escapeHtml(t(passing.invalidReason === "manual_invalid" ? "timing.lap_removed" : "timing.invalid_short"))}`} - ${renderLapActions(passing, row.key, { t, escapeHtml })} + ${isPracticeType(session.type) ? renderLapActions(passing, row.key, { t, escapeHtml }) : ""} `; } @@ -407,13 +404,13 @@ export function renderTimingView(deps) { state, dom, t, escapeHtml, formatLap, formatCountdown, formatElapsedClock, renderTable, uid, getActiveSession, ensureSessionResult, buildLeaderboard, getSessionTiming, getVisiblePassings, getEventName, getSessionTypeLabel, getStatusLabel, normalizeStartMode, getStartModeLabel, renderPositionGrid, - connectDecoder, disconnectDecoder, applyCompetitorCorrection, invalidateCompetitorLastLap, restoreCompetitorLastInvalidLap, + connectDecoder, disconnectDecoder, startOperationalSession, finishOperationalSession, cancelRaceStart, triggerRaceAudio, getStartLightState, saveState, updateHeaderState, renderView, normalizeDriver, normalizeCar, processDecoderMessage, getSelectedLeaderboardKey, setSelectedLeaderboardKey, getQuickAddDraft, setQuickAddDraft, getPassingValidationLabel, isCountedPassing, getCompetitorPassings, getManualCorrectionSummary, formatTeamActiveMemberLabel, formatRaceClock, renderSessionChannelAnalysis, setLastFinishAnnouncementSessionId, lastOverlayLeaderKeyBySession, lastOverlayTop3BySession, overlayEvents, setOverlayEvents, sessionReplay, - syncTrainingLedger = () => {}, setCompetitorLapStatus = () => false, + syncTrainingLedger = () => {}, setCompetitorLapStatus = () => false, openJudging = () => {}, } = deps; const helperDeps = { @@ -654,38 +651,7 @@ export function renderTimingView(deps) { if (active && selectedRow) { bindQuickAddActions(active, selectedRow.transponder, "leaderboardModal", helperDeps); - document.getElementById("corrLapPlus")?.addEventListener("click", () => { - applyCompetitorCorrection(active, selectedRow, { lapDelta: 1 }); - renderView(); - }); - document.getElementById("corrLapMinus")?.addEventListener("click", () => { - applyCompetitorCorrection(active, selectedRow, { lapDelta: -1 }); - renderView(); - }); - document.getElementById("corrSecPlus")?.addEventListener("click", () => { - applyCompetitorCorrection(active, selectedRow, { timeMsDelta: 1000 }); - renderView(); - }); - document.getElementById("corr5SecPlus")?.addEventListener("click", () => { - applyCompetitorCorrection(active, selectedRow, { timeMsDelta: 5000 }); - renderView(); - }); - document.getElementById("corrSecMinus")?.addEventListener("click", () => { - applyCompetitorCorrection(active, selectedRow, { timeMsDelta: -1000 }); - renderView(); - }); - document.getElementById("corrInvalidateLast")?.addEventListener("click", () => { - invalidateCompetitorLastLap(active, selectedRow); - renderView(); - }); - document.getElementById("corrRestoreInvalid")?.addEventListener("click", () => { - restoreCompetitorLastInvalidLap(active, selectedRow); - renderView(); - }); - document.getElementById("corrReset")?.addEventListener("click", () => { - applyCompetitorCorrection(active, selectedRow, { reset: true }); - renderView(); - }); + document.getElementById("corrOpenJudging")?.addEventListener("click", () => openJudging(selectedRow.key)); } if (active) { @@ -970,14 +936,15 @@ export function renderJudgingView(deps) {

${t("table.best_lap")}: ${formatLap(selectedRow.bestLapMs)}

${t("table.last_lap")}: ${formatLap(selectedRow.lastLapMs)}

+ ${isPracticeType(active.type) ? "" : ` - + `} - + ${isPracticeType(active.type) ? "" : ``}

${t("timing.lap_history")}

diff --git a/tests/browser_corrections_scope.js b/tests/browser_corrections_scope.js new file mode 100644 index 0000000..b625a84 --- /dev/null +++ b/tests/browser_corrections_scope.js @@ -0,0 +1,62 @@ +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: "", + }); + } +} diff --git a/tests/browser_smoke.js b/tests/browser_smoke.js index 6cc390a..c7d0390 100644 --- a/tests/browser_smoke.js +++ b/tests/browser_smoke.js @@ -13,6 +13,7 @@ import { runTrackLayoutsSmoke } from "./browser_track_layouts.js"; import { runPrologueSmoke } from "./browser_prologue.js"; import { runPracticeResetSmoke } from "./browser_practice_reset.js"; import { runSuspectLapsSmoke } from "./browser_suspect_laps.js"; +import { runCorrectionsScopeSmoke } from "./browser_corrections_scope.js"; import { runObsPopupSmoke } from "./browser_obs_popup.js"; import { runDecoderReconnectSmoke } from "./browser_decoder_reconnect.js"; import assert from "node:assert/strict"; @@ -156,6 +157,7 @@ try { await runPrologueSmoke({ evaluate, navigate, savedState, waitFor, base, dialogs }); await runPracticeResetSmoke({ evaluate, navigate, savedState, waitFor, base, dialogs }); await runSuspectLapsSmoke({ evaluate, navigate, savedState, waitFor, base, dialogs }); + await runCorrectionsScopeSmoke({ evaluate, navigate, savedState, waitFor, base }); await runRaceDaySmoke({ evaluate, navigate, fill, savedState, waitFor, pause, base, dialogs }); await runRaceDayStartSequencesSmoke({ evaluate, navigate, waitFor, pause, base, dialogs }); await runObsPopupSmoke({ evaluate, navigate, fill, savedState, waitFor, pause, base, debugPort, dialogs, realClick });