1import { describe, expect, it, vi } from 'vitest'; 2import { nextTracker } from './nextTrack.mjs'; 3 4const song = (id) => ({ id: `jev/${id}`, title: id, description: `about ${id}`, code: `// ${id}` }); 5const songs = ['a', 'b', 'c', 'd'].map(song); 6 7function tracker({ answer = (list) => list.find((s) => s.id !== 'jev/a') ?? list[0], recent = [] } = {}) { 8 const picks = []; 9 const played = []; 10 const recorded = []; 11 const loadRecent = vi.fn(async () => recent); 12 const t = nextTracker({ 13 pick: async (list, current, heard, options) => { 14 picks.push({ current: current?.id ?? null, heard, options }); 15 const next = answer(list.filter((s) => s.id !== current?.id)); 16 if (!next) throw new Error('unreachable'); 17 return { song: next, p: 0.5 }; 18 }, 19 play: async (_editor, s) => played.push(s.id), 20 loadRecent, 21 record: (id) => recorded.push(id), 22 }); 23 return { t, picks, played, recorded, loadRecent }; 24} 25 26describe('nextTracker', () => { 27 it('signed out, Jev hears the titles this tab played, and nothing is recorded', async () => { 28 const { t, picks, played, recorded, loadRecent } = tracker(); 29 await t.next({ songs, current: songs[0], user: null }); 30 await t.next({ songs, current: songs[1], user: null }); 31 expect(picks.map((p) => p.heard)).toEqual([['a'], ['a', 'b']]); 32 expect(picks[1].options).toEqual({}); 33 expect(played).toEqual(['jev/b', 'jev/c']); 34 expect(recorded).toEqual([]); 35 expect(loadRecent).not.toHaveBeenCalled(); 36 }); 37 38 it('signed in, Jev hears the account’s history, oldest first, and each pick is recorded', async () => { 39 const { t, picks, recorded, loadRecent } = tracker({ recent: ['jev/c', 'jev/d'] }); 40 const user = { id: 'u1' }; 41 const { song } = await t.next({ songs, current: songs[0], user }); 42 expect(picks[0].heard).toEqual(['d', 'c']); 43 expect(picks[0].options).toEqual({ recent: ['jev/c', 'jev/d'] }); 44 expect(recorded).toEqual([song.id]); 45 await t.next({ songs, current: song, user }); 46 // loaded once per account; the pick it made is the newest now 47 expect(loadRecent).toHaveBeenCalledTimes(1); 48 expect(picks[1].options.recent[0]).toBe(song.id); 49 }); 50 51 it('with nothing of the site playing, asks with no current song', async () => { 52 const { t, picks } = tracker(); 53 await t.next({ songs, current: null, user: null }); 54 expect(picks[0]).toMatchObject({ current: null, heard: [] }); 55 }); 56 57 it('plays the next in the list when Jev cannot answer, and says why', async () => { 58 const { t, played } = tracker({ answer: () => null }); 59 const r = await t.next({ songs, current: songs[3], user: null }); 60 expect(played).toEqual(['jev/a']); 61 expect(r.error).toBe('unreachable'); 62 }); 63 64 it('ignores a press while a pick is under way', async () => { 65 const { t, played } = tracker(); 66 const [one, two] = await Promise.all([ 67 t.next({ songs, current: songs[0], user: null }), 68 t.next({ songs, current: songs[0], user: null }), 69 ]); 70 expect(two).toBeNull(); 71 expect(one.song.id).toBe('jev/b'); 72 expect(played).toEqual(['jev/b']); 73 }); 74});