Files
Live_RC/tests/track_layouts_logic.test.js
larssand 8b63dd2372 fix(lap): keep best laps per track layout and count the current session for records
A layout switch during a running open practice kept every driver's
old-layout best lap, blocking personal-best calls and skewing the
practice leaderboard. Best laps now restart on a new layout and each
lap records its layout. The track record baseline now includes the
current session so the same record isn't called twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 21:14:19 +02:00

131 lines
6.5 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { normalizeTrackLayout, normalizeTrackLayouts, getTrackLayoutIdAt, getTrackLayoutLabel, nextTrackLayoutLabel, isPracticeSessionType, findRaceSessionBlockingLayoutChange, isTrackLayoutInUse } from "../src/track_layouts_logic.js";
import { getTrackRecordMs } from "../src/timing_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");
});