Files
Live_RC/tests/track_layouts_logic.test.js
larssand b5707f92d1 feat(series): run each round from the series page
Step 3 becomes a rounds table: pick drivers inline (copy from the
previous round, add a late entry), set qualifying rounds/finals/max
cars per heat per round, see the status, and create qualifying and
finals from there. Qualifying heats are now split evenly so nobody
drives alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:55:36 +02:00

163 lines
8.9 KiB
JavaScript

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 });
// Fixed startedAt: without one each call takes Date.now(), which made this flaky.
assert.deepEqual(normalizeTrackLayout({ id: "x", startedAt: 1000, minLapMs: -5, maxLapMs: "40000" }), { id: "x", label: "", startedAt: 1000, minLapMs: 0, maxLapMs: 40000 });
});
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);
});