jevstrudel.git / website / src / jev / party.test.mjs
1// The listening party's arithmetic (party.mjs): the room's clock from
2// ping/pong samples, and the cycle a guest starts at.
3import { describe, expect, it } from 'vitest';
4import { clockOffset, codeHash, cycleNow, randomId } from './party.mjs';
5
6describe('clockOffset: the room’s clock, NTP-style', () => {
7  it('takes the fastest round trip, with the room’s reading at its midpoint', () => {
8    // the room is 5000 ms ahead; one round trip was slow and lopsided
9    const samples = [
10      { c: 0, r: 400, s: 5000 + 350 }, // 400 ms, mostly on the way back
11      { c: 1000, r: 1040, s: 5000 + 1020 }, // 40 ms, symmetric
12      { c: 2000, r: 2100, s: 5000 + 2010 },
13    ];
14    expect(clockOffset(samples)).toEqual({ offset: 5000, rtt: 40 });
15  });
16
17  it('is null with no samples', () => {
18    expect(clockOffset([])).toBeNull();
19  });
20});
21
22describe('cycleNow: where the host is', () => {
23  it('counts cycles since the host started, at the host’s cps', () => {
24    expect(cycleNow({ startedAt: 10_000, cps: 0.75 }, 10_000)).toBe(0);
25    expect(cycleNow({ startedAt: 10_000, cps: 0.75 }, 18_000)).toBe(6);
26    // a guest ready before the host's start
27    expect(cycleNow({ startedAt: 10_000, cps: 0.5 }, 9_000)).toBe(-0.5);
28  });
29});
30
31describe('ids and hashes', () => {
32  it('makes room ids of 128 random bits the room accepts', () => {
33    const ids = new Set(Array.from({ length: 100 }, () => randomId()));
34    expect(ids.size).toBe(100);
35    for (const id of ids) expect(id).toMatch(/^[A-Za-z0-9_-]{22}$/);
36    expect(randomId(24)).toMatch(/^[A-Za-z0-9_-]{32}$/);
37  });
38
39  it('tells an edited song from the published one', () => {
40    expect(codeHash('a')).toBe(codeHash('a'));
41    expect(codeHash('a')).not.toBe(codeHash('b'));
42    expect(codeHash('')).toMatch(/^[0-9a-f]{8}$/);
43  });
44});