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>
88 lines
6.1 KiB
JavaScript
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");
|
|
}
|