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});