import test from "node:test"; import assert from "node:assert/strict"; import { normalizeTrackLayout, normalizeTrackLayouts, getTrackLayoutIdAt, getTrackLayoutLabel, nextTrackLayoutLabel, isPracticeSessionType, findRaceSessionBlockingLayoutChange, isTrackLayoutInUse, getLayoutLapWindowOverride } from "../src/track_layouts_logic.js"; import { getTrackRecordMs, getSessionLapWindow } from "../src/timing_logic.js"; import { buildRaceDayPreflight } from "../src/race_day_logic.js"; test("normalizeTrackLayout fills in an id via uid() when missing and coerces startedAt", () => { const withoutId = normalizeTrackLayout({ label: "Layout 1", startedAt: 1000 }, { uid: (prefix) => `${prefix}_generated` }); assert.equal(withoutId.id, "layout_generated"); assert.equal(withoutId.startedAt, 1000); const malformed = normalizeTrackLayout({ id: "x", label: " Layout 2 ", startedAt: "not-a-number" }); assert.equal(malformed.label, "Layout 2"); assert.ok(Number.isFinite(malformed.startedAt), "falls back to Date.now() rather than NaN"); }); test("normalizeTrackLayouts sorts chronologically and drops entries with no id", () => { const layouts = normalizeTrackLayouts([ { id: "b", label: "Later", startedAt: 2000 }, { id: "a", label: "Earlier", startedAt: 1000 }, { label: "" }, ], { uid: () => "" }); assert.deepEqual(layouts.map((layout) => layout.id), ["a", "b"]); }); test("getTrackLayoutIdAt returns the latest layout that has already started, treating layouts as contiguous", () => { const layouts = [ { id: "layout_1", label: "Layout 1", startedAt: 1000 }, { id: "layout_2", label: "Layout 2", startedAt: 2000 }, { id: "layout_3", label: "Layout 3", startedAt: 3000 }, ]; assert.equal(getTrackLayoutIdAt(layouts, 500), "", "before any layout existed"); assert.equal(getTrackLayoutIdAt(layouts, 1000), "layout_1", "exactly at a layout's start"); assert.equal(getTrackLayoutIdAt(layouts, 2500), "layout_2", "between two layouts belongs to the earlier one"); assert.equal(getTrackLayoutIdAt(layouts, 9999), "layout_3", "after the last change, still the latest layout"); }); test("getTrackLayoutIdAt and getTrackLayoutLabel are inert with no layouts configured", () => { assert.equal(getTrackLayoutIdAt([], Date.now()), ""); assert.equal(getTrackLayoutLabel([], ""), ""); assert.equal(getTrackLayoutLabel([{ id: "layout_1", label: "Layout 1" }], ""), "", "an empty id never matches a real layout"); }); test("getTrackLayoutLabel resolves a known id and nextTrackLayoutLabel numbers sequentially", () => { const layouts = [{ id: "layout_1", label: "Layout 1", startedAt: 1000 }]; assert.equal(getTrackLayoutLabel(layouts, "layout_1"), "Layout 1"); assert.equal(getTrackLayoutLabel(layouts, "missing"), ""); assert.equal(nextTrackLayoutLabel([]), "Layout 1"); assert.equal(nextTrackLayoutLabel(layouts), "Layout 2"); }); function createTrackRecordState({ sessions, results }) { return { events: [{ id: "event_1", classId: "class_1" }], sessions, resultsBySession: results, }; } test("getTrackRecordMs finds the fastest lap among other sessions in the same class when no layout is set", () => { const state = createTrackRecordState({ sessions: [ { id: "session_1", eventId: "event_1" }, { id: "session_2", eventId: "event_1" }, ], results: { session_2: { competitors: { a: { bestLapMs: 12345 } } }, }, }); assert.equal(getTrackRecordMs(state.sessions[0], { state }), 12345); }); test("getTrackRecordMs only compares sessions run on the same track layout once layouts are in use", () => { const state = createTrackRecordState({ sessions: [ { id: "current", eventId: "event_1", trackLayoutId: "layout_2" }, { id: "same_layout", eventId: "event_1", trackLayoutId: "layout_2" }, { id: "old_layout", eventId: "event_1", trackLayoutId: "layout_1" }, ], results: { same_layout: { competitors: { a: { bestLapMs: 15000 } } }, old_layout: { competitors: { a: { bestLapMs: 9000 } } }, }, }); // The 9000ms lap belongs to a different, earlier layout - it must not count as the record // for a lap set on the current layout, even though it is numerically faster. assert.equal(getTrackRecordMs(state.sessions[0], { state }), 15000); }); test("getTrackRecordMs includes the current session, so a record beaten earlier in it is the new bar", () => { const state = createTrackRecordState({ sessions: [ { id: "current", eventId: "event_1", trackLayoutId: "layout_1" }, { id: "older", eventId: "event_1", trackLayoutId: "layout_1" }, ], results: { older: { competitors: { a: { bestLapMs: 16000 } } }, current: { competitors: { b: { bestLapMs: 15000 } } }, }, }); // Driver B already set 15.0 here; a 15.5 must not be called a track record again. assert.equal(getTrackRecordMs(state.sessions[0], { state }), 15000); }); test("getTrackRecordMs does not conflate a layout-tagged session with an untagged legacy one", () => { const state = createTrackRecordState({ sessions: [ { id: "current", eventId: "event_1", trackLayoutId: "layout_1" }, { id: "legacy", eventId: "event_1" }, ], results: { legacy: { competitors: { a: { bestLapMs: 5000 } } }, }, }); assert.equal(getTrackRecordMs(state.sessions[0], { state }), null, "the legacy session (no trackLayoutId) is a different bucket from layout_1"); }); test("layouts change only around practice: a running or paused race session blocks it", () => { assert.equal(isPracticeSessionType("open_practice"), true); assert.equal(isPracticeSessionType("free_practice"), true); assert.equal(isPracticeSessionType("qualification"), false); assert.equal(findRaceSessionBlockingLayoutChange([{ type: "open_practice", status: "running" }, { type: "final", status: "ready" }]), null, "running practice and a not-yet-started final don't block"); assert.equal(findRaceSessionBlockingLayoutChange([{ name: "Q1", type: "qualification", status: "paused" }])?.name, "Q1"); assert.equal(findRaceSessionBlockingLayoutChange([{ name: "Heat", type: "heat", status: "running" }])?.name, "Heat"); }); test("a layout is in use when a session started on it or a practice lap was driven on it", () => { assert.equal(isTrackLayoutInUse("layout_2", { sessions: [{ trackLayoutId: "layout_1" }], ledger: [] }), false); assert.equal(isTrackLayoutInUse("layout_2", { sessions: [{ trackLayoutId: "layout_2" }] }), true); assert.equal(isTrackLayoutInUse("layout_2", { sessions: [{ trackLayoutId: "layout_1" }], ledger: [{ trackLayoutId: "layout_2" }] }), true, "a mid-session switch leaves laps on layout_2 even though no session started on it"); }); test("practice uses its layout's min/max lap time where set; races always keep the event's", () => { const state = { events: [{ id: "event_1", mode: "race", raceConfig: { minLapMs: 12000, maxLapMs: 60000 } }], trackLayouts: [ { id: "layout_1", label: "Layout 1", startedAt: 1000 }, { id: "layout_2", label: "Layout 2 (short)", startedAt: 2000, minLapMs: 8000, maxLapMs: 40000 }, { id: "layout_3", label: "Layout 3 (min only)", startedAt: 3000, minLapMs: 15000 }, ], }; const practice = (trackLayoutId) => ({ eventId: "event_1", type: "open_practice", trackLayoutId }); assert.deepEqual(getSessionLapWindow(practice("layout_1"), { state }), { minLapMs: 12000, maxLapMs: 60000 }, "no layout values -> the event's"); assert.deepEqual(getSessionLapWindow(practice("layout_2"), { state }), { minLapMs: 8000, maxLapMs: 40000 }); assert.deepEqual(getSessionLapWindow(practice("layout_3"), { state }), { minLapMs: 15000, maxLapMs: 60000 }, "only min set -> the event's max stays"); assert.deepEqual(getSessionLapWindow({ eventId: "event_1", type: "heat", trackLayoutId: "layout_2" }, { state }), { minLapMs: 12000, maxLapMs: 60000 }, "a heat ignores the layout"); assert.deepEqual(getLayoutLapWindowOverride(state.trackLayouts, { type: "free_practice", trackLayoutId: "layout_2" }), { minLapMs: 8000, maxLapMs: 40000 }); assert.deepEqual(normalizeTrackLayout({ id: "x", minLapMs: -5, maxLapMs: "40000" }), { ...normalizeTrackLayout({ id: "x" }), minLapMs: 0, maxLapMs: 40000, startedAt: normalizeTrackLayout({ id: "x", minLapMs: -5, maxLapMs: "40000" }).startedAt }); }); test("Race Day preflight shows the layout's lap window for practice", () => { const state = { events: [{ id: "event_1", mode: "race", raceConfig: { minLapMs: 12000, maxLapMs: 60000, practiceOnly: true } }], trackLayouts: [{ id: "layout_2", startedAt: 2000, minLapMs: 8000, maxLapMs: 40000 }], sessions: [], drivers: [], classes: [], resultsBySession: {}, settings: {}, decoder: {}, }; const session = { id: "op", eventId: "event_1", type: "open_practice", trackLayoutId: "layout_2", status: "ready", mode: "race" }; const lapWindow = buildRaceDayPreflight(state, session, { backendAvailable: true }).checks.find((check) => check.id === "lap_window"); assert.equal(lapWindow.minLapMs, 8000); assert.equal(lapWindow.maxLapMs, 40000); });