fix live leaderbord details

This commit is contained in:
larssand
2026-08-28 21:12:43 +02:00
parent 3cf68cf34a
commit c6262aceab
15 changed files with 293 additions and 34 deletions

View File

@@ -59,7 +59,7 @@ function createHarness() {
test("split decoder records intermediate time without changing main lap state", () => {
const harness = createHarness();
harness.passing(17_000, "main");
assert.equal(harness.passing(17_000, "main"), true);
const result = harness.state.resultsBySession.session_1;
const entry = result.competitors.team_team_1;
assert.equal(entry.laps, 1);
@@ -150,3 +150,13 @@ test("connect decoder opens one websocket per enabled role", () => {
globalThis.WebSocket = OriginalWebSocket;
}
});
test("non-passing decoder messages do not persist race state", () => {
const harness = createHarness();
const changed = processDecoderMessageHelper({ msg: "STATUS", decoder: "online" }, {
state: harness.state,
getActiveSession: () => harness.session,
});
assert.equal(Boolean(changed), false);
assert.equal(harness.getSaveCount(), 0);
});

View File

@@ -5,6 +5,7 @@ import {
applyPersistedStateHelper,
hydrateFromBackendHelper,
syncStateToBackendHelper,
tickClockHelper,
} from "../src/runtime_services.js";
function withMockFetch(mock, callback) {
@@ -284,3 +285,29 @@ test("failed state sync leaves version and dirty state unchanged", async () => {
assert.equal(backend.available, false);
assert.equal(backend.lastError, "error.sync_failed:connection refused");
});
test("running timing clock updates without rebuilding the leaderboard details modal", () => {
let renders = 0;
let dynamicRefreshes = 0;
let headerRefreshes = 0;
const base = {
dom: { clock: { textContent: "" } },
currentLanguage: () => "sv",
getActiveSession: () => ({ status: "running" }),
getCurrentView: () => "timing",
getOverlayMode: () => false,
renderView: () => { renders += 1; },
refreshTimingDynamics: () => { dynamicRefreshes += 1; },
updateHeaderState: () => { headerRefreshes += 1; },
};
tickClockHelper({ ...base, handleSessionTimerTick: () => ({ changed: false }) });
assert.equal(dynamicRefreshes, 1);
assert.equal(renders, 0);
assert.equal(headerRefreshes, 0);
tickClockHelper({ ...base, handleSessionTimerTick: () => ({ changed: true }) });
assert.equal(dynamicRefreshes, 1);
assert.equal(renders, 1);
assert.equal(headerRefreshes, 1);
});

View File

@@ -74,6 +74,7 @@ test("SQLite API persists state, passings, and overlay session results", { timeo
PORT: String(port),
RC_TIMING_DATA_DIR: dataDir,
RC_TIMING_PID_FILE: "",
RC_ALLOWED_ORIGINS: "https://trusted.example",
},
stdio: ["ignore", "pipe", "pipe"],
});
@@ -87,6 +88,20 @@ test("SQLite API persists state, passings, and overlay session results", { timeo
assert.equal(health.ok, true);
assert.equal(health.dbPath, join(dataDir, "rc_timing.sqlite"));
const sameOriginResponse = await fetch(`${baseUrl}/api/health`, { headers: { Origin: baseUrl } });
assert.equal(sameOriginResponse.status, 200);
assert.equal(sameOriginResponse.headers.get("access-control-allow-origin"), baseUrl);
const allowedOriginResponse = await fetch(`${baseUrl}/api/health`, { headers: { Origin: "https://trusted.example" } });
assert.equal(allowedOriginResponse.status, 200);
assert.equal(allowedOriginResponse.headers.get("access-control-allow-origin"), "https://trusted.example");
const rejectedOriginResponse = await fetch(`${baseUrl}/api/ammc/start`, {
method: "POST",
headers: { Origin: "https://untrusted.example" },
});
assert.equal(rejectedOriginResponse.status, 403);
const soundManifestResponse = await fetch(`${baseUrl}/race-sounds/sound_manifest.json`);
const soundManifest = await soundManifestResponse.json();
assert.equal(soundManifestResponse.status, 200);