feat(layouts): switch track layout mid-practice and view stats per layout
A club can run one Open Practice all season and re-lay the track now and then. Previously a session's layout was frozen at start and every training lap inherited it, so a season-long practice stayed on its first layout forever, and changing layout was hidden under Settings -> Appearance. - Each practice lap now counts toward the layout active when it was driven; a running practice session moves onto a new layout when one is added - "New layout" strip on Training stats, Race Day and the Dashboard, offered only around practice; a running race session blocks layout changes everywhere - "Stats for" selector (current / older layout / before layouts / all layouts) on Training stats and in the driver's mobile profile (?layout= on the public API) - A layout that has practice laps on it can no longer be deleted - Fix: the buildDriverTrainingStats wrapper in app.js dropped extra arguments Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
getBestTrainingDurationResult,
|
||||
getDriverTrainingLaps,
|
||||
getDriverTrainingLapsForCurrentLayout,
|
||||
getDriverTrainingLapsForLayout,
|
||||
getNextTrainingMilestone,
|
||||
getClubTrainingGoalProgress,
|
||||
getTrainingGoalProgress,
|
||||
@@ -908,3 +909,49 @@ test("stint analysis hides race pace 10 for short stints and flags slow laps", (
|
||||
assert.equal(shortStint.consistencyPct, null);
|
||||
assert.equal(shortStint.insufficient, true);
|
||||
});
|
||||
|
||||
test("one Open Practice run across a layout switch: each lap lands on the layout active when it was driven", () => {
|
||||
const switchAt = 10_000;
|
||||
const state = {
|
||||
trackLayouts: [{ id: "layout_1", label: "Layout 1", startedAt: 1 }, { id: "layout_2", label: "Layout 2", startedAt: switchAt }],
|
||||
// The session itself was stamped with layout_1 when it started - that must not decide the laps.
|
||||
sessions: [{ id: "season", name: "Open Practice", eventId: "event_1", type: "open_practice", trackLayoutId: "layout_1" }],
|
||||
events: [{ id: "event_1", name: "Season practice" }],
|
||||
resultsBySession: {
|
||||
season: {
|
||||
passings: [
|
||||
{ driverId: "driver_1", competitorKey: "driver_1", lapMs: 12_000, timestamp: 5_000, validLap: true },
|
||||
{ driverId: "driver_1", competitorKey: "driver_1", lapMs: 11_800, timestamp: 8_000, validLap: true },
|
||||
{ driverId: "driver_1", competitorKey: "driver_1", lapMs: 15_000, timestamp: 20_000, validLap: true },
|
||||
{ driverId: "driver_1", competitorKey: "driver_1", lapMs: 14_600, timestamp: 35_000, validLap: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const training = syncTrainingLedger({}, state, { uid: createUid() });
|
||||
assert.deepEqual(training.ledger.map((lap) => lap.trackLayoutId), ["layout_1", "layout_1", "layout_2", "layout_2"]);
|
||||
|
||||
const withLedger = { ...state, training };
|
||||
assert.equal(buildDriverTrainingStats(withLedger, "driver_1").bestLapMs, 14_600, "default = current layout (layout_2)");
|
||||
assert.equal(buildDriverTrainingStats(withLedger, "driver_1", "", { layout: "layout_1" }).bestLapMs, 11_800, "an older layout can be picked");
|
||||
const overall = buildDriverTrainingStats(withLedger, "driver_1", "", { layout: "all" });
|
||||
assert.equal(overall.bestLapMs, 11_800, "all layouts = overall best");
|
||||
assert.equal(overall.totalLaps, 4);
|
||||
assert.equal(buildDriverTrainingStats(withLedger, "driver_1", "", { layout: "layout_1" }).totalLaps, 4, "lifetime totals never depend on the layout choice");
|
||||
assert.equal(buildTrainingStintAnalysis(withLedger, "driver_1", "", { layout: "layout_2" }).rows.reduce((sum, row) => sum + row.lapCount, 0), 2, "stint analysis follows the layout choice");
|
||||
});
|
||||
|
||||
test("layout choice \"none\" picks the laps driven before any layout existed", () => {
|
||||
const state = {
|
||||
trackLayouts: [{ id: "layout_1", label: "Layout 1", startedAt: 50_000 }],
|
||||
training: {
|
||||
ledger: [
|
||||
{ driverId: "driver_1", sessionId: "s0", timestamp: 1000, lapMs: 13_000, trackLayoutId: "" },
|
||||
{ driverId: "driver_1", sessionId: "s1", timestamp: 60_000, lapMs: 16_000, trackLayoutId: "layout_1" },
|
||||
],
|
||||
},
|
||||
};
|
||||
assert.deepEqual(getDriverTrainingLapsForLayout(state, "driver_1", "", "none").map((lap) => lap.lapMs), [13_000]);
|
||||
assert.deepEqual(getDriverTrainingLapsForLayout(state, "driver_1", "", "current").map((lap) => lap.lapMs), [16_000]);
|
||||
assert.equal(getDriverTrainingLapsForLayout(state, "driver_1", "", "all").length, 2);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user