jevstrudel.git / website / src / jev / picker.test.mjs
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});