import { expect, it } from 'vitest'; import { render } from 'svelte/server'; import LibraryVersions from '../../src/lib/ui/components/LibraryVersions.svelte'; import GameFileHosts from '../../src/lib/ui/components/GameFileHosts.svelte'; import type { HostLibraryCache } from '../../src/lib/game-files.js'; import type { Host } from '../../src/lib/hosts.js'; it('shows the promoted version once and keeps a newer failed version distinct', () => { const current = { id: 1, version: 1 }; const promoted = render(LibraryVersions, { props: { current, latest: current } }).body; expect(promoted.match(/v1/g)).toHaveLength(1); expect(promoted.match(/Promoted/g)).toHaveLength(1); const newer = render(LibraryVersions, { props: { current, latest: { id: 2, version: 2, state: 'test-failed' } } }).body; expect(newer).toContain('Test failed'); expect(newer).toContain('v2'); expect(newer).toContain('v1'); expect(newer).toContain('Promoted'); }); it('disambiguates two builds with the same release tag only when needed', () => { const current = { id: 1, version: 1, release_version: 'v1.1.0' }; expect(render(LibraryVersions, { props: { current, latest: current } }).body).not.toContain('upload'); const body = render(LibraryVersions, { props: { current, latest: { ...current, id: 2, version: 2, state: 'untested' } } }).body; expect(body).toContain('v1.1.0 (upload 1)'); expect(body).toContain('v1.1.0 (upload 2)'); expect(body).toContain('Needs testing'); }); it('does not present an active beta as passed or an empty artifact as promoted', () => { expect(render(LibraryVersions, { props: { current: null, latest: null } }).body).toContain('No versions'); const body = render(LibraryVersions, { props: { current: null, latest: { id: 1, version: 1, state: 'tested' }, betaId: 12 } }).body; expect(body).toContain('Beta active'); expect(body).not.toContain('Test passed'); expect(body).toContain('Never promoted'); }); const host = (id: string, connected = true) => ({ id, name: id, connected, enrolled: true, credentialActive: true, mode: 'active', readiness: 'ready' } as Host); const cache = (id: string, status: HostLibraryCache['status'], error: string | null = null): HostLibraryCache => ({ hostId: id, libraryVersionId: 'upload', status, error, bytesDownloaded: 100, bytesTotal: 100, manifestSha256: null, updatedAt: '2026-10-06T12:00:00Z', operationId: null }); it('surfaces failures, transfers and offline hosts before collapsed healthy copies', () => { const body = render(GameFileHosts, { props: { latest: true, hosts: [host('healthy'), host('failed'), host('transfer'), host('offline', false)], caches: [cache('healthy', 'ready'), cache('failed', 'failed', 'Checksum mismatch'), cache('transfer', 'verifying'), cache('offline', 'ready')] } }).body; const ready = body.indexOf('Ready hosts (1)'); expect(body.indexOf('Checksum mismatch')).toBeLessThan(ready); expect(body.indexOf('Host verification progress')).toBeLessThan(ready); expect(body.indexOf('Offline')).toBeLessThan(ready); expect(body.indexOf('href="/hosts/healthy"')).toBeGreaterThan(ready); expect(body).toMatch(/]*>]*>Ready hosts \(1\)/); expect(body).not.toMatch(/]*open/); expect(body).toContain('View host setup'); }); it('explains pending automatic downloads and keeps copy errors visible even for ready status', () => { const body = render(GameFileHosts, { props: { latest: true, hosts: [host('waiting', false), host('inconsistent')], caches: [cache('waiting', 'missing'), cache('inconsistent', 'ready', 'Host report needs review')] } }).body; expect(body).toContain('Automatic setup resumes when the host reconnects.'); expect(body).toContain('Host report needs review'); expect(body).not.toContain('Ready hosts'); });