import { actionLabel, activeStatusById, applyFilterAndSort, applyGameTransferStatusSnapshot, canStreamInstall, countByFilter, deriveState, downloadProgressPercent, formatBytesPerSecond, formatDownloadBytes, formatDownloadEta, formatDownloadSpeed, formatDownloadSpeedShort, gameStatusLabel, mergeGameUpdate, stateChipLabel, } from "../src/lib/gameState.ts"; import { ActiveOperationKind, type Game, GameAvailability, GameTransferStatus, InstallStatus, } from "../src/lib/types.ts"; const assertEquals = (actual: T, expected: T, message: string) => { if (actual !== expected) { throw new Error(`${message}: expected ${expected}, got ${actual}`); } }; const game = (overrides: Partial = {}): Game => ({ id: "game", name: "Game", description: "", size: 0, downloaded: false, installed: false, availability: GameAvailability.LocalOnly, install_status: InstallStatus.NotInstalled, peer_count: 1, ...overrides, }); Deno.test("snapshot keeps installing visible until installed state settles", () => { const fromDownloading = game({ install_status: InstallStatus.Downloading, }); const installing = mergeGameUpdate( game({ downloaded: true }), fromDownloading, InstallStatus.Installing, ); const installedWhileActive = mergeGameUpdate( game({ downloaded: true, installed: true }), installing, InstallStatus.Installing, ); const settled = mergeGameUpdate( game({ downloaded: true, installed: true }), installedWhileActive, ); assertEquals( installing.install_status, InstallStatus.Installing, "active install snapshot should render Installing", ); assertEquals( installedWhileActive.install_status, InstallStatus.Installing, "installed local state should not override an active install snapshot", ); assertEquals( settled.install_status, InstallStatus.Installed, "cleared active snapshot with installed local state should render Installed", ); }); Deno.test("active operation snapshot is the source of busy status", () => { const statuses = activeStatusById([ { id: "game", operation: ActiveOperationKind.Downloading }, { id: "other", operation: ActiveOperationKind.Updating }, ]); assertEquals( statuses.get("game"), InstallStatus.Downloading, "download operation should render Downloading", ); assertEquals( statuses.get("other"), InstallStatus.Installing, "update operation should render Installing", ); }); Deno.test("download progress is preserved only while actively downloading", () => { const downloading = game({ install_status: InstallStatus.Downloading, download_progress: { attemptId: "1", downloaded_bytes: 50, total_bytes: 100, bytes_per_second: 12_500_000, active_peer_count: 2, }, }); const stillDownloading = mergeGameUpdate( game(), downloading, InstallStatus.Downloading, ); const settled = mergeGameUpdate(game({ downloaded: true }), stillDownloading); assertEquals( stillDownloading.download_progress?.downloaded_bytes, 50, "active download snapshot should keep progress", ); assertEquals( stillDownloading.download_progress?.active_peer_count, 2, "active download snapshot should keep live peer count", ); assertEquals( settled.download_progress, undefined, "settled snapshot should clear progress", ); }); Deno.test("downloading action label includes current speed", () => { const downloading = game({ install_status: InstallStatus.Downloading, download_progress: { attemptId: "1", downloaded_bytes: 50, total_bytes: 100, bytes_per_second: 12_500_000, active_peer_count: 2, }, }); assertEquals( formatBytesPerSecond(12_500_000), "12.5 MB/s", "speed formatter should use compact decimal units", ); assertEquals( actionLabel(downloading), "Downloading… 12.5 MB/s", "download label should include speed", ); }); Deno.test("downloading state is distinct and stays on the local filter", () => { const downloading = game({ id: "downloading", name: "Downloading", install_status: InstallStatus.Downloading, }); const local = game({ id: "local", name: "Local", downloaded: true, }); const remote = game({ id: "remote", name: "Remote", peer_count: 1, }); assertEquals( deriveState(downloading), "downloading", "download operation should render the dedicated downloading state", ); assertEquals( countByFilter([downloading, local, remote]).local, 2, "local filter count should include in-flight downloads", ); assertEquals( applyFilterAndSort([downloading, local, remote], "local", "status", "") .length, 2, "local filter should include in-flight downloads", ); }); Deno.test("sticky transfer exhaustion keeps a departed remote game visible until cleared", () => { const remoteOnly = game({ id: "remote-only", name: "Remote only", peer_count: 1, }); assertEquals( applyFilterAndSort([remoteOnly], "all", "az", "").length, 1, "a remote-only game should initially be visible", ); const peerDeparted = mergeGameUpdate( game({ id: "remote-only", name: "Remote only", peer_count: 0 }), remoteOnly, ); assertEquals( peerDeparted.install_status, InstallStatus.NotInstalled, "an empty active-operation snapshot should leave the game idle", ); assertEquals( peerDeparted.peer_count, 0, "the sole remote source should be absent from the next catalog snapshot", ); assertEquals( applyFilterAndSort([peerDeparted], "all", "az", "").length, 0, "an idle remote-only game should be invisible before exhaustion arrives", ); const [exhausted] = applyGameTransferStatusSnapshot([peerDeparted], { revision: 1, statuses: { "remote-only": GameTransferStatus.Exhausted }, openAttempts: {}, }); assertEquals( exhausted.transfer_status, GameTransferStatus.Exhausted, "the sticky snapshot should preserve the exact terminal status", ); assertEquals( applyFilterAndSort([exhausted], "all", "az", "").length, 1, "terminal exhaustion should keep the departed remote game visible", ); assertEquals( countByFilter([exhausted]).all, 1, "the All-filter count should match terminal exhaustion visibility", ); const [cleared] = applyGameTransferStatusSnapshot([exhausted], { revision: 2, statuses: {}, openAttempts: {}, }); assertEquals( cleared.transfer_status, undefined, "a full replacement that omits the game should clear exhaustion", ); assertEquals( applyFilterAndSort([cleared], "all", "az", "").length, 0, "clearing exhaustion should remove the otherwise-invisible game", ); }); Deno.test("transient transfer activity does not resurrect an invisible game", () => { const departed = game({ peer_count: 0 }); for ( const transferStatus of [ GameTransferStatus.Verifying, GameTransferStatus.Retrying, ] ) { assertEquals( applyFilterAndSort( [{ ...departed, transfer_status: transferStatus }], "all", "az", "", ).length, 0, `${transferStatus} should not make an otherwise-invisible game visible`, ); } }); Deno.test("download progress formatting matches the progress-bar layouts", () => { const downloading = game({ install_status: InstallStatus.Downloading, download_progress: { attemptId: "1", downloaded_bytes: 12 * 1024 * 1024 * 1024, total_bytes: 35 * 1024 * 1024 * 1024, bytes_per_second: 49_400_000, active_peer_count: 3, }, }); assertEquals( Math.round(downloadProgressPercent(downloading)), 34, "progress percent should come from backend byte counters", ); assertEquals( formatDownloadSpeed(49_400_000), "49.4 MB/s", "large bar speed format", ); assertEquals( formatDownloadSpeedShort(49_400_000), "49 MB/s", "card speed format", ); assertEquals( formatDownloadBytes(12 * 1024 * 1024 * 1024), "12 GB", "downloaded byte format should avoid noisy trailing decimals", ); assertEquals( formatDownloadEta(485), "8 min", "eta format should stay compact", ); }); Deno.test("stream install is available only for idle remote games", () => { assertEquals( canStreamInstall( game({ downloaded: false, installed: false, peer_count: 1 }), true, ), true, "catalog-supported remote-only idle games should allow streamed install", ); assertEquals( canStreamInstall( game({ downloaded: false, installed: false, peer_count: 1 }), false, ), false, "catalog-unsupported games should not expose streamed install", ); assertEquals( canStreamInstall( game({ downloaded: true, installed: false, peer_count: 1 }), true, ), false, "downloaded games should install from local archives", ); assertEquals( canStreamInstall( game({ downloaded: false, installed: true, peer_count: 1 }), true, ), false, "installed games should not expose streamed install", ); assertEquals( canStreamInstall( game({ downloaded: false, installed: false, peer_count: 0 }), true, ), false, "games without peers should not expose streamed install", ); assertEquals( canStreamInstall( game({ downloaded: false, installed: false, peer_count: 1, install_status: InstallStatus.Installing, }), true, ), false, "busy games should not expose streamed install", ); }); Deno.test("streamed local installs are labeled installed but not shareable", () => { const streamed = game({ downloaded: false, installed: true, install_status: InstallStatus.Installed, }); const downloadedInstall = game({ downloaded: true, installed: true, install_status: InstallStatus.Installed, }); assertEquals( deriveState(streamed), "installed", "streamed local installs should keep installed visual state", ); assertEquals( stateChipLabel(streamed), "Not shareable", "card chip should make the non-shareable state visible", ); assertEquals( gameStatusLabel(streamed), "Installed, not shareable", "detail status should spell out installed plus non-shareable", ); assertEquals( stateChipLabel(downloadedInstall), "Installed", "normal downloaded installs should keep the installed chip label", ); assertEquals( gameStatusLabel(downloadedInstall), "Installed", "normal downloaded installs should keep the installed detail label", ); });