import { applyDownloadProgress, applyGameTransferStatusSnapshot, downloadProgressAriaLabel, downloadProgressTransferLabel, gameTransferStatusPresentation, INITIAL_GAME_TRANSFER_STATUS_SNAPSHOT, newestGameTransferStatusSnapshot, } from "../src/lib/gameState.ts"; import { type DownloadProgressPayload, type Game, GameAvailability, GameTransferStatus, type GameTransferStatusSnapshot, InstallStatus, } from "../src/lib/types.ts"; const assertEquals = (actual: T, expected: T, message: string) => { if (JSON.stringify(actual) !== JSON.stringify(expected)) { throw new Error( `${message}: expected ${JSON.stringify(expected)}, got ${ JSON.stringify(actual) }`, ); } }; const game = (id: string): Game => ({ id, name: id, description: "", size: 0, downloaded: false, installed: false, availability: GameAvailability.LocalOnly, install_status: InstallStatus.Downloading, peer_count: 1, download_progress: { attemptId: `${id}-attempt`, downloaded_bytes: 10, total_bytes: 100, bytes_per_second: 5, active_peer_count: 1, }, }); const snapshot = ( revision: number, statuses: GameTransferStatusSnapshot["statuses"], openAttempts: GameTransferStatusSnapshot["openAttempts"] = {}, ): GameTransferStatusSnapshot => ({ revision, statuses, openAttempts }); const progress = ( id: string, attemptId: string, downloadedBytes: number, ): DownloadProgressPayload => ({ id, attemptId, downloaded_bytes: downloadedBytes, total_bytes: 100, bytes_per_second: 5, active_peer_count: 1, }); Deno.test("newer transfer event beats stale and equal GamesList snapshots", () => { const event = snapshot(2, { alpha: GameTransferStatus.Retrying }); assertEquals( newestGameTransferStatusSnapshot(event, snapshot(1, {})), event, "a delayed older GamesList snapshot must not replace the listener event", ); assertEquals( newestGameTransferStatusSnapshot( event, snapshot(2, { alpha: GameTransferStatus.Exhausted }), ), event, "an equal revision replay must not change the accepted full snapshot", ); }); Deno.test("higher revision full replacement clears omitted game statuses", () => { const games = applyGameTransferStatusSnapshot( [game("alpha"), game("bravo")], snapshot(4, { alpha: GameTransferStatus.Verifying, bravo: GameTransferStatus.Exhausted, }, { alpha: "alpha-attempt", bravo: "bravo-attempt" }), ); const replacement = applyGameTransferStatusSnapshot( games, snapshot( 5, { bravo: GameTransferStatus.Retrying }, { alpha: "alpha-attempt", bravo: "bravo-attempt" }, ), ); assertEquals( replacement.map((item) => item.transfer_status), [undefined, GameTransferStatus.Retrying], "omission must mean None while the listed game retains its exact status", ); assertEquals( replacement.map((item) => item.download_progress?.downloaded_bytes), [10, 10], "typed status replacement must not mutate direct byte progress", ); }); Deno.test("progress is fenced by the newest exact open attempt", () => { const attemptA = "9007199254740992"; const attemptB = "9007199254740993"; const withoutProgress = [{ ...game("alpha"), download_progress: undefined }]; const openA = snapshot(1, {}, { alpha: attemptA }); const openB = snapshot(2, {}, { alpha: attemptB }); const terminalB = snapshot(3, {}, {}); const progressA = applyDownloadProgress( withoutProgress, openA, progress("alpha", attemptA, 10), ); assertEquals( progressA[0].download_progress?.attemptId, attemptA, "A progress should be accepted while A is the open attempt", ); const beganB = applyGameTransferStatusSnapshot(progressA, openB); assertEquals( beganB[0].download_progress, undefined, "a newer B Begin snapshot must clear already-rendered A progress", ); const delayedA = applyDownloadProgress( beganB, openB, progress("alpha", attemptA, 20), ); assertEquals( delayedA[0].download_progress, undefined, "delayed A progress must be rejected after B becomes authoritative", ); const progressB = applyDownloadProgress( delayedA, openB, progress("alpha", attemptB, 30), ); assertEquals( progressB[0].download_progress?.downloaded_bytes, 30, "progress for the exact current B attempt must be accepted", ); const finishedB = applyGameTransferStatusSnapshot(progressB, terminalB); assertEquals( finishedB[0].download_progress, undefined, "terminal replacement must clear B progress with the open attempt", ); const lateB = applyDownloadProgress( finishedB, terminalB, progress("alpha", attemptB, 40), ); assertEquals( lateB[0].download_progress, undefined, "late B progress must remain rejected after terminal settlement", ); }); Deno.test("transfer status lookup uses only own game-id properties", () => { const [projected] = applyGameTransferStatusSnapshot( [game("toString")], snapshot(3, {}), ); assertEquals( projected.transfer_status, undefined, "prototype properties must not become catalog game statuses", ); assertEquals( newestGameTransferStatusSnapshot( INITIAL_GAME_TRANSFER_STATUS_SNAPSHOT, snapshot(1, {}), ).revision, 1, "the backend revision-one bootstrap must replace the frontend initial state", ); }); Deno.test("transfer notices use the exact copy and severity contract", () => { assertEquals( gameTransferStatusPresentation(GameTransferStatus.Verifying), { message: "Verifying downloaded chunks", level: "info" }, "verification copy", ); assertEquals( gameTransferStatusPresentation(GameTransferStatus.Retrying), { message: "A source sent invalid data; retrying another nearby peer", level: "warning", }, "retry copy", ); assertEquals( gameTransferStatusPresentation(GameTransferStatus.Exhausted), { message: "No nearby peer could provide the verified catalog version", level: "error", }, "exhaustion copy", ); assertEquals( gameTransferStatusPresentation(undefined), undefined, "None must remain invisible", ); }); Deno.test("download progress promotes transient verification and retry copy", () => { const verifying = { ...game("Alpha"), transfer_status: GameTransferStatus.Verifying, }; const retrying = { ...game("Bravo"), transfer_status: GameTransferStatus.Retrying, }; const exhausted = { ...game("Charlie"), transfer_status: GameTransferStatus.Exhausted, }; assertEquals( downloadProgressTransferLabel(verifying), "Verifying downloaded chunks", "verification should replace the primary progress copy", ); assertEquals( downloadProgressAriaLabel(verifying), "Verifying downloaded chunks: Alpha", "verification should replace the progressbar accessible name", ); assertEquals( downloadProgressTransferLabel(retrying), "A source sent invalid data; retrying another nearby peer", "retry should replace the primary progress copy", ); assertEquals( downloadProgressAriaLabel(retrying), "A source sent invalid data; retrying another nearby peer: Bravo", "retry should replace the progressbar accessible name", ); assertEquals( downloadProgressTransferLabel(exhausted), undefined, "terminal exhaustion should remain on the card/modal status surface", ); });