Files
Live_RC/tests/browser_event_schedule.js
larssand 05f0291ee9 Add permanent browser tests for the Settings tabs, Event Manager tabs, and Schedule panel
These three UIs (all shipped earlier this session) had only been verified with throwaway
scratch scripts during development. Adds tests/browser_settings_tabs.js,
browser_event_manager_tabs.js and browser_event_schedule.js, wired into the existing
test:browser suite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 16:45:24 +02:00

88 lines
6.1 KiB
JavaScript

import assert from "node:assert/strict";
// Regression coverage for the race day Schedule panel (Event Manager's "Schedule" tab).
// buildEventSchedule's cascading-time math and the getSessionSortWeight ordering bug it
// exposed are already covered at the unit level (race_timing_logic.test.js) - this file
// exercises the UI wiring on top of it: the form actually persists scheduleStartAt/
// scheduleGapMin onto event.raceConfig (previously silently dropped by normalizeEvent's
// field allowlist), the table renders sessions in running order (not seed order), a
// session that started late shows the Delayed badge, and Clear removes only the start
// time, not the configured gap.
export async function runEventScheduleSmoke({ evaluate, navigate, fill, savedState, waitFor, base }) {
const click = (id) => evaluate(`document.getElementById(${JSON.stringify(id)}).click()`);
const submit = (id) => evaluate(`document.getElementById(${JSON.stringify(id)}).requestSubmit()`);
const findEvent = async (id) => (await savedState()).events.find((event) => event.id === id);
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 classId = "esc_class";
const driverA = { id: "esc_driver_a", name: "ESC Driver A", classId, transponder: "7830001" };
const raceEvent = {
id: "esc_event", name: "ESC Event", classId, mode: "race",
raceConfig: { participantsConfigured: true, driverIds: [driverA.id], teams: [] },
};
// practice (sort weight 2) runs before qualification (weight 3) - buildEventSchedule
// orders by getSessionSortWeight, not seed/array order, so the table proves that too.
const qualifyingSession = { id: "esc_quali", eventId: raceEvent.id, name: "ESC Qualification", type: "qualification", durationMin: 5, status: "ready" };
const practiceSession = { id: "esc_practice", eventId: raceEvent.id, name: "ESC Practice", type: "practice", durationMin: 5, status: "ready" };
const seeded = await savedState();
await patchState({
drivers: [...seeded.drivers, driverA],
events: [...seeded.events, raceEvent],
sessions: [...seeded.sessions, qualifyingSession, practiceSession],
});
const openSchedule = async () => {
await navigate("race_setup", "raceWizardStepForm");
await click("event-manage-" + raceEvent.id);
await waitFor(async () => await evaluate(`Boolean(document.querySelector(".manage-tabs"))`), "manage tabs rendered");
await evaluate(`document.querySelector('[data-manage-tab="schedule"]').click()`);
await waitFor(async () => await evaluate(`Boolean(document.getElementById("scheduleForm"))`), "schedule panel visible");
};
await openSchedule();
assert.equal(await evaluate(`document.querySelector('[data-manage-panel="schedule"]')?.hidden`), false, "schedule panel is visible after clicking its tab");
assert.equal(await evaluate(`Boolean(document.querySelector('[data-manage-panel="schedule"] table'))`), false, "with no start time set yet, the not-set hint is shown instead of a table");
await fill("scheduleForm", { scheduleStartAt: "2026-09-11T10:00", scheduleGapMin: "5" });
await submit("scheduleForm");
await waitFor(async () => Number.isFinite((await findEvent(raceEvent.id)).raceConfig.scheduleStartAt), "schedule form saves scheduleStartAt onto the event");
const savedEvent = await findEvent(raceEvent.id);
assert.equal(savedEvent.raceConfig.scheduleGapMin, 5, "schedule form saves scheduleGapMin onto the event");
const startAt = savedEvent.raceConfig.scheduleStartAt;
console.log("PASS saving the schedule form persists scheduleStartAt/scheduleGapMin onto event.raceConfig");
await openSchedule();
const rowNames = await evaluate(`Array.from(document.querySelectorAll('[data-manage-panel="schedule"] table tbody tr td:first-child')).map((td) => td.textContent)`);
assert.deepEqual(rowNames, [practiceSession.name, qualifyingSession.name], "the schedule table lists sessions in running order (practice before qualification), not seed order");
const delayedBadgesBefore = await evaluate(`document.querySelectorAll('[data-manage-panel="schedule"] .pill-warn').length`);
assert.equal(delayedBadgesBefore, 0, "no session has started yet, so nothing is flagged delayed");
console.log("PASS the schedule table cascades planned times in running order with no delayed sessions yet");
// Start practice 2.5 minutes after its own planned time - past buildEventSchedule's
// 1-minute delayed threshold - and leave it running (no endedAt) so the table has to
// show a real actualStart with no actualEnd.
await patchState({
sessions: (await savedState()).sessions.map((session) =>
session.id === practiceSession.id ? { ...session, status: "running", startedAt: startAt + 150000 } : session
),
});
await openSchedule();
const actualCells = await evaluate(`Array.from(document.querySelectorAll('[data-manage-panel="schedule"] table tbody tr td:nth-child(4)')).map((td) => td.textContent)`);
assert.notEqual(actualCells[0], "-", "practice's actual start is shown once the session has started");
const delayedBadgesAfter = await evaluate(`document.querySelectorAll('[data-manage-panel="schedule"] .pill-warn').length`);
assert.equal(delayedBadgesAfter, 1, "the late-started practice session is flagged delayed");
console.log("PASS a session that started more than a minute late is flagged with the Delayed badge");
await evaluate(`document.getElementById("scheduleClear").click()`);
await waitFor(async () => (await findEvent(raceEvent.id)).raceConfig.scheduleStartAt === null, "Clear removes scheduleStartAt");
assert.equal((await findEvent(raceEvent.id)).raceConfig.scheduleGapMin, 5, "Clear leaves the configured gap untouched");
await openSchedule();
assert.equal(await evaluate(`Boolean(document.querySelector('[data-manage-panel="schedule"] table'))`), false, "with no start time set, the schedule table is replaced by the not-set hint");
console.log("PASS clearing the schedule removes the start time but keeps the configured gap");
}