feat(training): base training stats on practice, keep race laps separate
Training stats count Open/Free practice by default; laps from qualifying, heats and finals are kept and shown under Based on: Races or All, marked with race, session and series. Races are listed per race session and never split into stints. Same picker in the mobile profile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
60
tests/browser_training_source.js
Normal file
60
tests/browser_training_source.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
// Training stats "Based on": practice by default; "Races" replaces the stint table with one
|
||||
// row per race session, marked 🏁 with race, session and series - race laps never form stints.
|
||||
export async function runTrainingSourceSmoke({ evaluate, navigate, savedState, waitFor, base }) {
|
||||
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 seeded = await savedState();
|
||||
const t0 = Date.now() - 3600000;
|
||||
const lap = (sessionId, offset, lapMs) => ({ driverId: "ts_driver", competitorKey: "driver_ts_driver", sessionId, lapMs, timestamp: t0 + offset, validLap: true });
|
||||
await patchState({
|
||||
classes: [...seeded.classes, { id: "ts_class", name: "TS 2WD" }],
|
||||
drivers: [...seeded.drivers, { id: "ts_driver", profileId: "ts_profile", name: "TS Driver", classId: "ts_class", transponder: "8770001" }],
|
||||
events: [...seeded.events, { id: "ts_practice_event", name: "TS Practice", classId: "ts_class", mode: "race", raceConfig: { practiceOnly: true } }, { id: "ts_race_event", name: "TS Winter #3 TS 2WD", classId: "ts_class", mode: "race", date: "2026-11-29" }],
|
||||
sessions: [...seeded.sessions, { id: "ts_op", eventId: "ts_practice_event", name: "TS Open Practice", type: "open_practice", status: "finished" }, { id: "ts_q2", eventId: "ts_race_event", name: "Kval 2", type: "qualification", status: "finished" }],
|
||||
series: [...(seeded.series || []), { id: "ts_series", name: "TS Winter Cup", eventIds: ["ts_race_event"], classIds: ["ts_class"] }],
|
||||
resultsBySession: {
|
||||
...seeded.resultsBySession,
|
||||
ts_op: { passings: [lap("ts_op", 20000, 20000), lap("ts_op", 40000, 20000), lap("ts_op", 59500, 19500), lap("ts_op", 79000, 19500)], competitors: {} },
|
||||
ts_q2: { passings: [lap("ts_q2", 900000, 18000), lap("ts_q2", 918000, 18200), lap("ts_q2", 936400, 18400)], competitors: {} },
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await navigate("training", "view");
|
||||
// The overview has its own driver picker; the driver page has another one.
|
||||
await waitFor(async () => Boolean(await evaluate(`[...document.querySelectorAll("#training-overview-driver option, #training-driver option")].some((option) => option.value === "ts_profile")`)), "driver listed");
|
||||
await evaluate(`(() => { const select = document.getElementById("training-overview-driver") || document.getElementById("training-driver"); select.value = "ts_profile"; select.dispatchEvent(new Event("change")); })()`);
|
||||
await waitFor(async () => (await evaluate(`document.getElementById("training-driver")?.value`)) === "ts_profile" && Boolean(await evaluate(`document.getElementById("training-source")`)), "training stats for the driver");
|
||||
assert.equal(await evaluate(`document.getElementById("training-source").value`), "practice", "practice is the default");
|
||||
assert.equal(await evaluate(`Boolean(document.querySelector('[data-sort-id="training-stints-featured"]'))`), true, "stints for practice");
|
||||
assert.equal(await evaluate(`document.querySelectorAll(".training-race-origin").length`), 0, "no race laps mixed into practice");
|
||||
|
||||
await evaluate(`(() => { const select = document.getElementById("training-source"); select.value = "race"; select.dispatchEvent(new Event("change")); })()`);
|
||||
await waitFor(async () => Boolean(await evaluate(`document.querySelector('[data-sort-id="training-race-sessions"]')`)), "race sessions table");
|
||||
assert.equal(await evaluate(`Boolean(document.querySelector('[data-sort-id="training-stints-featured"]'))`), false, "no stints for races");
|
||||
const row = await evaluate(`[...document.querySelectorAll('[data-sort-id="training-race-sessions"] tbody tr')].map((tr) => [...tr.children].map((td) => td.textContent.trim()))[0]`);
|
||||
assert.equal(row[1], "🏁 TS Winter #3 TS 2WD · Kval 2 · TS Winter Cup", "race, session and series");
|
||||
assert.equal(row[2], "3");
|
||||
console.log("PASS training stats: practice by default; races shown per race session with 🏁 race · session · series, never as stints");
|
||||
} finally {
|
||||
const current = await savedState();
|
||||
const results = { ...current.resultsBySession };
|
||||
delete results.ts_op;
|
||||
delete results.ts_q2;
|
||||
await patchState({
|
||||
resultsBySession: results,
|
||||
sessions: current.sessions.filter((session) => !session.id.startsWith("ts_")),
|
||||
events: current.events.filter((event) => !event.id.startsWith("ts_")),
|
||||
series: (current.series || []).filter((series) => series.id !== "ts_series"),
|
||||
drivers: current.drivers.filter((driver) => driver.id !== "ts_driver"),
|
||||
classes: current.classes.filter((item) => item.id !== "ts_class"),
|
||||
training: { ...current.training, ledger: (current.training?.ledger || []).filter((entry) => !String(entry.sessionId).startsWith("ts_")) },
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user