test/player-state.test.ts (2621 bytes)
1 import fc from 'fast-check'; 2 import { describe, expect, it } from 'vitest'; 3 import { isPlayerError, parseState, type PlayerState } from '../src/player-state'; 4 5 function state(line: string): PlayerState { 6 const parsed = parseState(line); 7 if (isPlayerError(parsed)) throw new Error(`error ${parsed.error} for ${line}`); 8 return parsed; 9 } 10 11 describe('parseState', () => { 12 it('reads a playing report', () => { 13 expect(state('playing=1;position=96153;speed=1.5;package=de.ph1b.audiobook')).toEqual({ 14 playing: true, 15 positionMs: 96153, 16 speed: 1.5, 17 package: 'de.ph1b.audiobook', 18 }); 19 }); 20 21 it('reads a paused report', () => { 22 const s = state('playing=0;position=0;speed=1.0;package=p'); 23 expect(s.playing).toBe(false); 24 expect(s.positionMs).toBe(0); 25 }); 26 27 it('gives no position for a player without one', () => { 28 const s = state('playing=1;position=-1;speed=1.0;package=p'); 29 expect(s.playing).toBe(true); 30 expect(s.positionMs).toBeNull(); 31 }); 32 33 it('returns the error name of a problem line', () => { 34 expect(parseState('error=no_player')).toEqual({ error: 'no_player' }); 35 expect(parseState('error=no_notification_access')).toEqual({ 36 error: 'no_notification_access', 37 }); 38 expect(parseState('error=no_overlay')).toEqual({ error: 'no_overlay' }); 39 }); 40 41 it('rejects an empty or broken line', () => { 42 expect(parseState(null)).toEqual({ error: 'empty' }); 43 expect(parseState(undefined)).toEqual({ error: 'empty' }); 44 expect(parseState('')).toEqual({ error: 'empty' }); 45 expect(parseState('hello')).toEqual({ error: 'malformed' }); 46 expect(parseState('playing=1;position=abc')).toEqual({ error: 'malformed' }); 47 expect(parseState('playing=1;position=')).toEqual({ error: 'malformed' }); 48 }); 49 50 it('falls back to speed 1 for a missing or zero speed', () => { 51 expect(state('playing=1;position=5').speed).toBe(1); 52 expect(state('playing=1;position=5;speed=0').speed).toBe(1); 53 expect(state('playing=1;position=5;speed=-2').speed).toBe(1); 54 expect(state('playing=1;position=5').package).toBe(''); 55 }); 56 57 it('round trips any state line', () => { 58 const pkg = fc.stringMatching(/^[a-z][a-z0-9_.]{0,30}$/); 59 const speed = fc.double({ min: 0.25, max: 4, noNaN: true, noDefaultInfinity: true }); 60 fc.assert( 61 fc.property(fc.boolean(), fc.nat({ max: 10 ** 9 }), speed, pkg, (playing, ms, sp, p) => { 62 const line = `playing=${playing ? 1 : 0};position=${ms};speed=${sp};package=${p}`; 63 expect(state(line)).toEqual({ playing, positionMs: ms, speed: sp, package: p }); 64 }), 65 ); 66 }); 67 });