1import { afterEach, describe, expect, it, vi } from 'vitest'; 2import { MIN_CHOICES, notRecent, pickNext } from './picker.mjs'; 3 4const song = (id) => ({ id: `jev/${id}`, title: id, description: `about ${id}`, code: `// ${id}` }); 5const songs = ['a', 'b', 'c', 'd', 'e', 'f'].map(song); 6 7describe('notRecent: the radio’s choices for a listener with a history', () => { 8 it('leaves out the most recently played, newest first, keeping at least MIN_CHOICES', () => { 9 expect(MIN_CHOICES).toBe(3); 10 const ids = (list) => list.map((s) => s.title); 11 expect(ids(notRecent(songs, ['jev/b']))).toEqual(['a', 'c', 'd', 'e', 'f']); 12 expect(ids(notRecent(songs, ['jev/f', 'jev/e', 'jev/d', 'jev/c', 'jev/b']))).toEqual(['a', 'b', 'c']); 13 // repeats and songs no longer on the site do not use up the allowance 14 expect(ids(notRecent(songs, ['jev/gone', 'jev/f', 'jev/f', 'jev/e']))).toEqual(['a', 'b', 'c', 'd']); 15 expect(ids(notRecent(songs.slice(0, 2), ['jev/a']))).toEqual(['a', 'b']); 16 expect(ids(notRecent(songs, []))).toEqual(ids(songs)); 17 }); 18}); 19 20describe('pickNext', () => { 21 afterEach(() => vi.unstubAllGlobals()); 22 const asked = () => { 23 const fetch = vi.fn(async (_url, init) => { 24 const { questions } = JSON.parse(init.body); 25 const [first] = Object.keys(questions.song.criteria); 26 return new Response(JSON.stringify({ answers: { song: { choice: first } } })); 27 }); 28 vi.stubGlobal('fetch', fetch); 29 return () => JSON.parse(fetch.mock.calls[0][1].body); 30 }; 31 32 it('offers every other song when anonymous, as before', async () => { 33 const body = asked(); 34 await pickNext(songs, songs[0], ['a']); 35 expect(Object.keys(body().questions.song.criteria)).toEqual(songs.slice(1).map((s) => s.id)); 36 }); 37 38 it('does not offer what a signed-in listener heard most recently, on any device', async () => { 39 const body = asked(); 40 const { song: next } = await pickNext(songs, songs[0], ['b'], { recent: ['jev/b', 'jev/c'] }); 41 expect(Object.keys(body().questions.song.criteria)).toEqual(['jev/d', 'jev/e', 'jev/f']); 42 expect(next.id).toBe('jev/d'); 43 }); 44 45 it('with nothing of the site playing, offers every song and tells Jev only what was played', async () => { 46 const body = asked(); 47 const { song: next } = await pickNext(songs, null, ['a', 'b']); 48 expect(Object.keys(body().questions.song.criteria)).toEqual(songs.map((s) => s.id)); 49 expect(body().state).toEqual({ playedRecently: ['a', 'b'] }); 50 expect(next.id).toBe('jev/a'); 51 }); 52});