import { describe, expect, it } from 'vitest'; import { detectChange, estimateProgress, formatClock, nextDelay, sseEvent, toLive, type PlayerSample } from './now-playing'; const track = (id: string) => ({ spotifyId: id, name: `Song ${id}`, durationMs: 200_000, explicit: false, artists: [ { spotifyId: null, name: 'A' }, { spotifyId: null, name: 'B' } ], album: { spotifyId: null, name: 'Album', imageUrl: 'https://i/640', imageUrlSmall: 'https://i/300', releaseDate: null } }); const sample = (over: Partial = {}): PlayerSample => ({ trackId: 1, track: track('x'), isPlaying: true, progressMs: 10_000, sampledAt: 1_000_000, ...over }); describe('now playing', () => { it('builds the public payload', () => { expect(toLive(sample())).toEqual({ isPlaying: true, track: 'Song x', artists: 'A, B', album: 'Album', image: 'https://i/300', url: 'https://open.spotify.com/track/x', progressMs: 10_000, durationMs: 200_000, sampledAt: 1_000_000 }); expect(toLive(sample({ track: null, trackId: null }))).toMatchObject({ isPlaying: false, track: null }); }); it('detects track changes, play/pause and seeks, not normal progress', () => { const a = sample(); expect(detectChange(null, a).any).toBe(true); expect(detectChange(a, sample({ progressMs: 15_000, sampledAt: 1_005_000 })).any).toBe(false); expect(detectChange(a, sample({ progressMs: 90_000, sampledAt: 1_005_000 })).seek).toBe(true); expect(detectChange(a, sample({ isPlaying: false })).playing).toBe(true); expect(detectChange(a, sample({ track: track('y') })).track).toBe(true); }); it('estimates progress between events and clamps to the duration', () => { const l = toLive(sample()); expect(estimateProgress(l, 0, 5_000)).toBe(15_000); expect(estimateProgress(l, 0, 10_000_000)).toBe(200_000); expect(estimateProgress({ ...l, isPlaying: false }, 0, 5_000)).toBe(10_000); expect(formatClock(75_000)).toBe('1:15'); }); it('formats SSE events with the sample age, and backs off when idle', () => { const event = sseEvent(toLive(sample()), 1_002_000); expect(event.startsWith('event: now\ndata: ')).toBe(true); expect(event.endsWith('\n\n')).toBe(true); expect(JSON.parse(event.split('data: ')[1]).ageMs).toBe(2_000); expect(nextDelay(true, 9)).toBe(5_000); expect(nextDelay(false, 0)).toBe(10_000); expect(nextDelay(false, 50)).toBe(30_000); }); });