jevstrudel.git / website / src / jev / lobby.test.mjs
1import { describe, expect, it } from 'vitest';
2import { cycleNow, groupTabs, progressOf, statusFor, worthSending } from './lobby.mjs';
3
4const song = { id: 'jev/a', title: 'A', code: 'song a', timing: { bars: 62 } };
5const walked = { id: 'jev/w', title: 'W', code: 'song w', timing: { jev: true } };
6const base = { songs: [song, walked], nowId: 'jev/a', code: 'song a', foreign: null, visible: true, booth: null };
7
8describe('statusFor: what this tab tells the lobby', () => {
9  it('a site song as written, playing: its id, playhead and length', () => {
10    expect(statusFor({ ...base, started: true, cycle: 12.34, cps: 0.5 })).toEqual({
11      t: 'status',
12      visible: true,
13      song: 'jev/a',
14      edited: false,
15      listener: null,
16      title: null,
17      playing: true,
18      cycle: 12.3,
19      cps: 0.5,
20      length: 64,
21      max: null,
22      section: null,
23    });
24  });
25
26  it('an edited song says so, and has no length', () => {
27    expect(statusFor({ ...base, code: 'mine now', started: false, cycle: null, cps: 0.5 })).toMatchObject({
28      song: 'jev/a',
29      edited: true,
30      playing: false,
31      cycle: null,
32      cps: null,
33      length: null,
34    });
35  });
36
37  it('a song Jev walks: its section by name, and the most it can last until Jev ends it', () => {
38    const booth = {
39      every: 8,
40      segments: 16,
41      last: null,
42      form: { name: 'section' },
43      decisions: [{ values: { section: { choice: 'intro' } } }, { values: { section: { choice: 'drop1' } } }],
44    };
45    const s = statusFor({ ...base, nowId: 'jev/w', code: 'song w', started: true, cycle: 9, cps: 0.5, booth });
46    expect(s).toMatchObject({ song: 'jev/w', length: null, max: 128, section: 'drop1' });
47    const ended = statusFor({ ...base, nowId: 'jev/w', code: 'song w', started: true, cycle: 9, cps: 0.5, booth: { ...booth, last: 5 } });
48    expect(ended.length).toBe(50);
49  });
50
51  it('a listener’s song in the sandbox: its id and title, never a site song', () => {
52    const foreign = { song: { id: 'listener:abc123', title: 'Theirs' } };
53    expect(statusFor({ ...base, foreign, started: true, cycle: 3, cps: 1 })).toMatchObject({
54      song: null,
55      listener: 'abc123',
56      title: 'Theirs',
57      playing: true,
58    });
59  });
60
61  it('the listener’s own code, or code from an AI or a link: nothing but whether it plays', () => {
62    expect(statusFor({ ...base, nowId: null, code: 'x', foreign: { ai: 'Claude' }, started: true, cycle: 1, cps: 1 })).toMatchObject({
63      song: null,
64      listener: null,
65      title: null,
66      playing: true,
67    });
68  });
69});
70
71describe('worthSending', () => {
72  const s = statusFor({ ...base, started: true, cycle: 10, cps: 0.5 });
73  it('sends a change, not the playhead moving as predicted', () => {
74    expect(worthSending(null, s, 0)).toBe(true);
75    expect(worthSending(s, { ...s, cycle: 11 }, 2000)).toBe(false);
76    expect(worthSending(s, { ...s, cycle: 14 }, 2000)).toBe(true);
77    expect(worthSending(s, { ...s, section: 'drop1' }, 1000)).toBe(true);
78    expect(worthSending(s, { ...s, visible: false }, 1000)).toBe(true);
79    expect(worthSending(s, { ...s, cycle: 25 }, 30_000)).toBe(true);
80  });
81});
82
83describe('reading other tabs', () => {
84  const tab = { id: 't1', group: 'g', name: null, playing: true, cycle: 10, cps: 0.5, at: 1000, length: 40 };
85  it('moves a playhead on at its tempo, in the room’s clock', () => {
86    expect(cycleNow(tab, 5000)).toBe(12);
87    expect(progressOf(tab, 5000)).toBeCloseTo(0.3);
88    expect(progressOf({ ...tab, length: null }, 5000)).toBeNull();
89    expect(cycleNow({ ...tab, playing: false, cycle: null }, 5000)).toBeNull();
90    expect(progressOf(tab, 1e9)).toBe(1);
91  });
92
93  it('groups an account’s tabs, most recently heard first', () => {
94    const groups = groupTabs([
95      { id: 'a', group: 'g1', name: 'Chris', at: 1 },
96      { id: 'b', group: 'x', name: null, at: 3 },
97      { id: 'c', group: 'g1', name: 'Chris', at: 2 },
98    ]);
99    expect(groups.map((g) => [g.name, g.tabs.map((t) => t.id)])).toEqual([
100      [null, ['b']],
101      ['Chris', ['c', 'a']],
102    ]);
103  });
104});