import { actionLabel, activeStatusById, applyFilterAndSort, applyGameTransferStatusSnapshot, canStreamInstall, countByFilter, deriveState, downloadProgressPercent, formatBytesPerSecond, formatDownloadBytes, formatDownloadEta, formatDownloadSpeed, formatDownloadSpeedShort, gameStatusLabel, mergeGameUpdate, primaryActionFor, stateChipLabel, } from "../src/lib/gameState.ts"; import { ActiveOperationKind, type Game, GameAvailability, type GameFilter, 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", ); }); const assertFilterMembership = ( current: Game, expected: Record, ) => { const counts = countByFilter([current]); for (const filter of ["all", "local", "installed"] as const) { assertEquals( applyFilterAndSort([current], filter, "az", "").length, expected[filter], `${current.install_status} visibility in ${filter}`, ); assertEquals(counts[filter], expected[filter], `${filter} count`); } }; Deno.test("offline local install stays visible through withdrawal and settlement", () => { const local = game({ downloaded: true, peer_count: 0 }); const active = mergeGameUpdate(local, local, InstallStatus.Installing); // The peer withdraws shareable local state while staging the installation. const withdrawn = mergeGameUpdate( game({ peer_count: 0 }), active, InstallStatus.Installing, ); for (const current of [local, active, withdrawn]) { assertFilterMembership(current, { all: 1, local: 1, installed: 0 }); } assertEquals( withdrawn.downloaded, false, "keep backend availability truthful", ); assertEquals(stateChipLabel(withdrawn), "Installing", "card status chip"); assertEquals(gameStatusLabel(withdrawn), "Installing…", "detail status"); assertEquals(actionLabel(withdrawn), "Installing…", "action label"); assertEquals( primaryActionFor(withdrawn), "busy", "disable duplicate installs", ); assertEquals( applyFilterAndSort([withdrawn], "all", "az", "unrelated").length, 0, "busy cards still obey search", ); const settled = mergeGameUpdate( game({ downloaded: true, installed: true, peer_count: 0 }), withdrawn, ); assertFilterMembership(settled, { all: 1, local: 1, installed: 1 }); assertEquals( actionLabel(settled), "Play", "successful install becomes playable", ); const failed = mergeGameUpdate(local, withdrawn); assertFilterMembership(failed, { all: 1, local: 1, installed: 0 }); assertEquals(actionLabel(failed), "Install", "failed install can be retried"); }); Deno.test("stream install remains local after its last source leaves", () => { const active = mergeGameUpdate(game(), game(), InstallStatus.Installing); const withdrawn = mergeGameUpdate( game({ peer_count: 0 }), active, InstallStatus.Installing, ); assertFilterMembership(withdrawn, { all: 1, local: 1, installed: 0 }); const settled = mergeGameUpdate(game({ peer_count: 0 }), withdrawn); assertFilterMembership(settled, { all: 0, local: 0, installed: 0 }); }); Deno.test("installed filter retains busy games until authoritative settlement", () => { for ( const operation of [ ActiveOperationKind.Downloading, ActiveOperationKind.Updating, ActiveOperationKind.Uninstalling, ] ) { const installed = game({ downloaded: true, installed: true, peer_count: 0, }); const activeStatus = activeStatusById([{ id: "game", operation }]).get( "game", ); const active = mergeGameUpdate(installed, installed, activeStatus); const withdrawn = mergeGameUpdate( game({ peer_count: 0 }), active, activeStatus, ); const repeated = mergeGameUpdate( game({ peer_count: 0 }), withdrawn, activeStatus, ); for (const current of [active, withdrawn, repeated]) { assertFilterMembership(current, { all: 1, local: 1, installed: 1 }); } assertEquals( repeated.installed, false, "filter pin must not imply playable state", ); assertEquals( primaryActionFor(repeated), "busy", "operation stays disabled", ); const settled = mergeGameUpdate( game({ downloaded: true, peer_count: 0 }), repeated, ); assertFilterMembership(settled, { all: 1, local: 1, installed: 0 }); assertEquals( settled.installed_before_operation, undefined, "clear prior membership", ); const nextInstall = mergeGameUpdate( settled, settled, InstallStatus.Installing, ); assertFilterMembership(nextInstall, { all: 1, local: 1, installed: 0 }); } }); Deno.test("local removal stays visible with its operation label until settled", () => { const local = game({ downloaded: true, peer_count: 0 }); const active = mergeGameUpdate(local, local, InstallStatus.Removing); const withdrawn = mergeGameUpdate( game({ peer_count: 0 }), active, InstallStatus.Removing, ); assertFilterMembership(withdrawn, { all: 1, local: 1, installed: 0 }); assertEquals(stateChipLabel(withdrawn), "Removing", "card status chip"); assertEquals(gameStatusLabel(withdrawn), "Removing…", "detail status"); assertFilterMembership( mergeGameUpdate(game({ peer_count: 0 }), withdrawn), { all: 0, local: 0, installed: 0 }, ); }); 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", ); });