oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1// room-connecting.test.mjs — "<name> is connecting and can't hear you yet"
2// (spec §15.24, 2026-10-01), gated without a browser.
3//
4// node --test test/browser/room-connecting.test.mjs
5//
6// The functions are lifted out of the shipped room.js by name, with the
7// constants they read, as room-reconnect.test.mjs does:
8//
9// connectingStep the watch: the grace, clearing on `hearing`, the
10// fallback for a client that does not say, the cap, and
11// a rejoin starting it again
12// hearsYou a peer's report on us: true, false, or does not say
13// peerNotice what a tile says, and what replaces what
14// judgeHeard this tab's own half: when a path counts as heard
15import { test } from 'node:test';
16import assert from 'node:assert/strict';
17import { readFileSync } from 'node:fs';
18import { fileURLToPath } from 'node:url';
19
20const src = readFileSync(fileURLToPath(new URL('../../internal/calls/static/room.js', import.meta.url)), 'utf8');
21
22function lift(name) {
23 const start = src.indexOf('function ' + name + '(');
24 assert.notEqual(start, -1, name + ' is not in room.js any more — this test is stale, not passing');
25 let depth = 0;
26 for (let i = src.indexOf('{', start); i < src.length; i++) {
27 if (src[i] === '{') depth += 1;
28 else if (src[i] === '}') {
29 depth -= 1;
30 if (depth === 0) return src.slice(start, i + 1);
31 }
32 }
33 assert.fail('unbalanced braces reading ' + name);
34}
35function liftConst(name) {
36 const m = src.match(new RegExp('const ' + name + ' = ([0-9.]+);'));
37 assert.ok(m, name + ' is not a numeric const in room.js any more');
38 return 'const ' + name + ' = ' + m[1] + ';';
39}
40const CONSTS = ['CONNECTING_GRACE', 'CONNECTING_CAP'].map(liftConst).join('\n');
41const names = ['connectingRecord', 'connectingStep', 'hearsYou', 'peerNotice', 'remoteNotice', 'connectingWords',
42 'canHearWords', 'judgeHeard', 'hearingMoved'];
43// eslint-disable-next-line no-new-func
44const fns = new Function(CONSTS + '\n' + names.map(lift).join('\n') + '; return { ' + names.join(', ') +
45 ', GRACE: CONNECTING_GRACE, CAP: CONNECTING_CAP };')();
46const { connectingRecord, connectingStep, hearsYou, peerNotice, judgeHeard, hearingMoved, GRACE, CAP } = fns;
47
48const ME = 'm'.repeat(32);
49const SAM = 's'.repeat(32);
50const waiting = { epoch: 1, hearsMe: false, reported: true, connected: false, acked: false };
51
52// Five looks a second from `from` to `to` seconds; when Sam's words
53// showed, and when they cleared with "can hear you now".
54function run(rec, from, to, sam) {
55 const shown = [];
56 const cleared = [];
57 for (let t = from * 10; t < to * 10; t += 2) {
58 const s = Math.round(t) / 10;
59 const p = sam(s);
60 const people = p ? new Map([[SAM, p]]) : new Map();
61 if (connectingStep(rec, s * 1000, people).includes(SAM)) cleared.push(s);
62 const r = rec.peers.get(SAM);
63 if (r && r.shown) shown.push(s);
64 }
65 return { shown, cleared };
66}
67
68test('the timings are the ones the spec names', () => {
69 assert.equal(GRACE, 1000);
70 assert.equal(CAP, 20000);
71});
72
73test('an ordinary join, heard within the grace, says nothing at all', () => {
74 const rec = connectingRecord();
75 const seen = run(rec, 0, 10, (t) => ({ ...waiting, connected: t >= 0.4, hearsMe: t >= 0.8 }));
76 assert.deepEqual(seen.shown, []);
77 assert.deepEqual(seen.cleared, [], 'nothing was said, so "can hear you now" is not said either');
78 assert.equal(rec.peers.get(SAM).done, true);
79});
80
81test('a slow join says so after the grace, and clears the moment they hear us', () => {
82 const rec = connectingRecord();
83 const seen = run(rec, 0, 12, (t) => ({ ...waiting, connected: t >= 5, hearsMe: t >= 6 }));
84 assert.equal(seen.shown[0], 1);
85 assert.equal(seen.shown.at(-1), 5.8);
86 assert.deepEqual(seen.cleared, [6]);
87});
88
89test('once cleared it stays cleared, whatever their list does after', () => {
90 const rec = connectingRecord();
91 const seen = run(rec, 0, 30, (t) => ({ ...waiting, connected: true, hearsMe: t >= 3 && t < 10 }));
92 assert.deepEqual(seen.cleared, [3]);
93 assert.equal(seen.shown.at(-1), 2.8, '§15.23 and §15.20 speak for a problem after that');
94});
95
96test('it is given up at the cap, silently', () => {
97 const rec = connectingRecord();
98 const seen = run(rec, 0, 40, () => waiting);
99 assert.equal(seen.shown[0], 1);
100 assert.equal(seen.shown.at(-1), 19.8);
101 assert.deepEqual(seen.cleared, [], 'a cap is not their hearing us');
102 assert.equal(rec.peers.get(SAM).done, true);
103});
104
105test('a client that does not say who it hears: its first report and a connection clear it', () => {
106 const rec = connectingRecord();
107 const seen = run(rec, 0, 10, (t) => ({ epoch: 1, hearsMe: null, reported: t >= 4, connected: t >= 2, acked: false }));
108 assert.deepEqual(seen.cleared, [4]);
109});
110
111test('or a connection and receiver reports showing our audio reaching them', () => {
112 const rec = connectingRecord();
113 const seen = run(rec, 0, 10, (t) => ({ epoch: 1, hearsMe: null, reported: false, connected: t >= 2, acked: t >= 3 }));
114 assert.deepEqual(seen.cleared, [3]);
115 const rec2 = connectingRecord();
116 const unconnected = run(rec2, 0, 10, () => ({ epoch: 1, hearsMe: null, reported: true, connected: false, acked: true }));
117 assert.deepEqual(unconnected.cleared, [], 'never without a connection');
118});
119
120test('a client that says who it hears is believed over our own evidence', () => {
121 const rec = connectingRecord();
122 const seen = run(rec, 0, 10, () => ({ epoch: 1, hearsMe: false, reported: true, connected: true, acked: true }));
123 assert.deepEqual(seen.cleared, []);
124 assert.equal(seen.shown.length > 0, true);
125});
126
127test('a rejoin starts it again, and so does a new connection to the same person', () => {
128 const rec = connectingRecord();
129 const seen = run(rec, 0, 20, (t) => {
130 if (t >= 5 && t < 6) return null; // gone from the roster
131 if (t < 5) return { ...waiting, connected: true, hearsMe: t >= 2 };
132 return { ...waiting, connected: t >= 9, hearsMe: t >= 9 };
133 });
134 assert.deepEqual(seen.cleared, [2, 9]);
135 assert.equal(seen.shown.includes(7), true);
136 assert.equal(seen.shown.includes(6.8), false, 'the grace again from the moment they are back');
137
138 const rec2 = connectingRecord();
139 const rebuilt = run(rec2, 0, 20, (t) => ({ ...waiting, epoch: t < 8 ? 1 : 2, connected: true, hearsMe: t < 1 || t >= 12 }));
140 assert.deepEqual(rebuilt.cleared, [12]);
141 assert.equal(rebuilt.shown[0], 9);
142});
143
144test('hearsYou: listed, not listed, or not saying', () => {
145 assert.equal(hearsYou({ muted: false, cameraOff: false, hearing: [ME] }, ME), true);
146 assert.equal(hearsYou({ muted: false, cameraOff: false, hearing: [] }, ME), false);
147 assert.equal(hearsYou({ muted: false, cameraOff: false }, ME), null);
148 assert.equal(hearsYou(null, ME), null);
149});
150
151test('peerNotice: a more specific report replaces "connecting", and "Reconnecting" waits until after', () => {
152 const plain = { muted: false, cameraOff: false, hearing: [] };
153 assert.deepEqual(peerNotice('Sam', plain, ME, false, true, false, false), { kind: '', text: '' }, 'inside the grace');
154 assert.deepEqual(peerNotice('Sam', plain, ME, false, true, true, false),
155 { kind: 'connecting', text: 'Sam is connecting and can’t hear you yet' });
156 assert.deepEqual(peerNotice('Sam', plain, ME, true, true, true, false).kind, 'connecting',
157 'our own mute does not matter: it is about their hearing us when we speak');
158 assert.deepEqual(peerNotice('Sam', plain, ME, false, true, true, true).kind, 'connecting',
159 'a connection that has never been up is not reconnecting');
160 assert.deepEqual(peerNotice('Sam', { ...plain, audioBlocked: true }, ME, false, true, false, false),
161 { kind: 'report', text: 'Sam’s browser is blocking sound — they need to click to hear you.' },
162 'even inside the grace: it is their own report');
163 assert.deepEqual(peerNotice('Sam', { ...plain, noSoundFrom: [ME] }, ME, false, true, true, false),
164 { kind: 'report', text: 'Sam isn’t getting any sound from you.' });
165 assert.deepEqual(peerNotice('Sam', { ...plain, noSoundFrom: [ME] }, ME, true, true, true, false).kind, 'connecting',
166 'no sound from us while we are muted is expected, so it does not replace anything');
167 assert.deepEqual(peerNotice('Sam', plain, ME, false, false, false, true),
168 { kind: 'reconnecting', text: 'Reconnecting to Sam…' });
169 assert.deepEqual(peerNotice('Sam', { ...plain, audioBlocked: true }, ME, false, false, false, false).kind, 'report');
170 assert.deepEqual(peerNotice('Sam', plain, ME, false, false, false, false), { kind: '', text: '' });
171});
172
173test('judgeHeard: arriving and decoded, or a muted sender on a live connection', () => {
174 const base = { connected: true, bad: false, hasReceiver: true, receiverLive: true, packets: 0, samples: 0, senderMuted: false };
175 assert.equal(judgeHeard(false, base), false, 'nothing has arrived');
176 assert.equal(judgeHeard(false, { ...base, packets: 10 }), false, 'packets, but nothing decoded');
177 assert.equal(judgeHeard(false, { ...base, packets: 10, samples: 480 }), true);
178 assert.equal(judgeHeard(false, { ...base, connected: false, packets: 10, samples: 480 }), false, 'not before the connection is up');
179 assert.equal(judgeHeard(false, { ...base, senderMuted: true }), true, 'a muted sender must not look unheard');
180 assert.equal(judgeHeard(false, { ...base, senderMuted: true, hasReceiver: false, receiverLive: false }), true,
181 'nor one with no microphone at all');
182 assert.equal(judgeHeard(false, { ...base, senderMuted: true, receiverLive: false }), false, 'but not on a dead receiver');
183 assert.equal(judgeHeard(true, base), true, 'once heard, kept while the connection lasts');
184 assert.equal(judgeHeard(true, { ...base, connected: false }), true, 'a blip is not losing them');
185 assert.equal(judgeHeard(true, { ...base, bad: true }), false, 'a failed connection is');
186});
187
188test('hearingMoved: a gain, or a loss of someone still here, and never a departure alone', () => {
189 const here = new Set(['a', 'b']);
190 assert.equal(hearingMoved('', 'a', here), true);
191 assert.equal(hearingMoved('a', 'a,b', here), true);
192 assert.equal(hearingMoved('a,b', 'a', here), true, 'b is still here and no longer heard');
193 assert.equal(hearingMoved('a,c', 'a', here), false, 'c has left: not worth a report from everyone');
194 assert.equal(hearingMoved('a', 'a', here), false);
195 assert.equal(hearingMoved('', '', here), false);
196});
197