import { actionLabel, activeStatusById, formatBytesPerSecond, mergeGameUpdate, } from '../src/lib/gameState.ts'; import { ActiveOperationKind, GameAvailability, InstallStatus, type Game, } 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: { downloaded_bytes: 50, total_bytes: 100, bytes_per_second: 12_500_000, }, }); 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( 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: { downloaded_bytes: 50, total_bytes: 100, bytes_per_second: 12_500_000, }, }); 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', ); });