oficina / meet Public

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

Plain git — no account needed to clone.

Download

Download this file

1// room-screen.test.mjs — room.js's screen watchdogs (spec §15.21,
2// 2026-09-28), gated without a browser.
3//
4// node --test test/browser/room-screen.test.mjs
5//
6// Lifted out of the shipped room.js by name, as room-hearing.test.mjs
7// does, with the constants they read.
8//
9// judgeScreen the receiver's verdict on one screen: stalled after
10// four seconds without a new frame, for three minutes at
11// most; any change in the count is movement
12// reattachDue the receiver's own mending, once per stall (§15.22)
13// escalateShare the sharer's ladder when keyframes do not answer
14// recoveringWords, noticeWords the sharer's notice
15// readingMoved what a stalled screen's statistics say
16// mediaFrom, mediaKey, mediaPayload the report's screenStalled list
17import { test } from 'node:test';
18import assert from 'node:assert/strict';
19import { readFileSync } from 'node:fs';
20import { fileURLToPath } from 'node:url';
21
22const roomJS = fileURLToPath(new URL('../../internal/calls/static/room.js', import.meta.url));
23const src = readFileSync(roomJS, 'utf8');
24
25function lift(source, name) {
26 const start = source.indexOf('function ' + name + '(');
27 assert.notEqual(start, -1, name + ' is not in room.js any more — this test is stale, not passing');
28 let depth = 0;
29 for (let i = source.indexOf('{', start); i < source.length; i++) {
30 if (source[i] === '{') depth += 1;
31 else if (source[i] === '}') {
32 depth -= 1;
33 if (depth === 0) return source.slice(start, i + 1);
34 }
35 }
36 assert.fail('unbalanced braces reading ' + name + ' out of room.js');
37}
38
39function liftConst(name) {
40 const m = src.match(new RegExp('const ' + name + ' = ([0-9.]+);'));
41 assert.ok(m, name + ' is not a numeric const in room.js any more');
42 return 'const ' + name + ' = ' + m[1] + ';';
43}
44const CONSTS = [
45 'SCREEN_STALL_AFTER', 'SCREEN_STALL_GIVE_UP', 'SCREEN_REATTACH_AFTER', 'SCREEN_REBUILD_AFTER',
46 'SCREEN_RESHARE_AFTER', 'SCREEN_GIVE_UP_AFTER', 'SCREEN_REBUILD_EVERY',
47].map(liftConst).join('\n');
48const build = (name) =>
49 // eslint-disable-next-line no-new-func
50 new Function(CONSTS + '\n' + lift(src, name) + '; return ' + name + ';')();
51
52const judgeScreen = build('judgeScreen');
53const recoveringWords = build('recoveringWords');
54const reattachDue = build('reattachDue');
55const escalateShare = build('escalateShare');
56const readingMoved = new Function(lift(src, 'readingSummary') + '\n' + lift(src, 'readingMoved') + '; return readingMoved;')();
57// eslint-disable-next-line no-new-func
58const noticeWords = new Function(lift(src, 'recoveringWords') + '\n' + lift(src, 'noticeWords') + '; return noticeWords;')();
59const mediaFrom = build('mediaFrom');
60const mediaPayload = build('mediaPayload');
61// eslint-disable-next-line no-new-func
62const mediaKey = new Function(lift(src, 'mediaKey') + '; return mediaKey;')();
63
64const ID = '0123456789abcdef0123456789abcdef';
65
66test('a screen is stalled four seconds after its last frame, and for three minutes at most', () => {
67 const seen = { decoded: 0, movedAt: 0 };
68 const stalledAt = [];
69 for (let t = 0; t <= 200; t++) {
70 const decoded = t < 10 ? t * 10 : 90;
71 if (judgeScreen(seen, decoded, t * 1000, false)) stalledAt.push(t);
72 }
73 assert.equal(stalledAt[0], 13);
74 assert.equal(stalledAt[stalledAt.length - 1], 188);
75});
76
77test('a rebuilt screen whose count starts again is moving, not stalled', () => {
78 const seen = { decoded: 0, movedAt: 0 };
79 for (let t = 0; t < 10; t++) judgeScreen(seen, 900 + t * 30, t * 1000, false);
80 for (let t = 10; t < 16; t++) judgeScreen(seen, 1170, t * 1000, false);
81 assert.equal(judgeScreen(seen, 1170, 16000, false), true);
82 for (let t = 17; t < 40; t++) {
83 assert.equal(judgeScreen(seen, (t - 17) * 30, t * 1000, false), false, 't=' + t);
84 }
85});
86
87test('a stalled picture is handed to its tile again once, four seconds into the stall', () => {
88 const seen = { decoded: 0, movedAt: 0 };
89 const due = [];
90 for (let t = 0; t < 60; t++) {
91 const decoded = t < 10 ? t * 10 : 90;
92 judgeScreen(seen, decoded, t * 1000, false);
93 if (reattachDue(seen, t * 1000)) due.push(t);
94 }
95 assert.deepEqual(due, [17], 'last moved at 9, stalled from 13, handed back at 17, once');
96 judgeScreen(seen, 91, 60000, false);
97 const again = [];
98 for (let t = 61; t < 80; t++) {
99 judgeScreen(seen, 91, t * 1000, false);
100 if (reattachDue(seen, t * 1000)) again.push(t);
101 }
102 assert.deepEqual(again, [68], 'a new stall is a new try');
103});
104
105// The ladder, driven once a second the way answerScreenStalls drives it.
106function ladder(from, to, reported, opts = {}) {
107 const rec = opts.rec || { stage: '', at: 0, lastRebuild: 0 };
108 let resharedAt = opts.resharedAt || 0;
109 const out = [];
110 for (let t = from; t < to; t++) {
111 const now = t * 1000;
112 if (!reported(t)) {
113 rec.stage = '';
114 continue;
115 }
116 const owed = opts.owed ? opts.owed(t) : true;
117 const free = !resharedAt || now - resharedAt >= 300000;
118 const action = escalateShare(rec, now, owed, free);
119 if (action === 'reshare') resharedAt = now;
120 if (action) out.push([t, action]);
121 }
122 return { out, rec, resharedAt };
123}
124
125test('a report keyframes do not answer is rebuilt, then shared again, then given up', () => {
126 const { out, rec } = ladder(0, 45, (t) => t >= 3);
127 assert.deepEqual(out, [[9, 'rebuild'], [19, 'reshare'], [29, 'failed']]);
128 assert.equal(rec.stage, 'failed');
129});
130
131test('a rebuild that works ends the ladder', () => {
132 const { out, rec } = ladder(0, 60, (t) => t >= 3 && t < 12);
133 assert.deepEqual(out, [[9, 'rebuild']]);
134 assert.equal(rec.stage, '');
135});
136
137test('rebuilds and shares again are rationed', () => {
138 const first = ladder(0, 20, (t) => t >= 3 && t < 12);
139 const second = ladder(20, 50, (t) => t >= 25 && t < 34, { rec: first.rec, resharedAt: first.resharedAt });
140 assert.deepEqual(second.out, [[31, 'reshare']], 'no second rebuild inside a minute');
141 const third = ladder(80, 120, (t) => t >= 85, { rec: second.rec, resharedAt: second.resharedAt });
142 assert.deepEqual(third.out, [[91, 'rebuild'], [101, 'failed']], 'and no second share again inside five');
143});
144
145test('nothing sent is nothing to escalate', () => {
146 const { out, rec } = ladder(0, 60, (t) => t >= 3, { owed: () => false });
147 assert.deepEqual(out, []);
148 assert.equal(rec.stage, 'keyframes');
149});
150
151test('the notice moves on in words, the app\'s own', () => {
152 assert.equal(noticeWords([], [], []), '');
153 assert.equal(noticeWords(['Samuel'], [], []), 'Samuel’s view of your screen stalled — recovering');
154 assert.equal(noticeWords([], ['Samuel'], []), 'Restarting your share for Samuel…');
155 assert.equal(noticeWords([], [], ['Samuel']), 'Couldn’t restore Samuel’s view — try Stop and Share again');
156 assert.equal(noticeWords(['Tony'], [], ['Samuel']),
157 'Couldn’t restore Samuel’s view — try Stop and Share again. Tony’s view of your screen stalled — recovering');
158 assert.equal(noticeWords([], ['A', 'B', 'C'], []), 'Restarting your share for 3 people…');
159});
160
161test('a stalled screen\'s statistics name what stopped', () => {
162 const base = { mid: '3', packets: 1000, lost: 0, received: 300, decoded: 300, dropped: 0, keyframes: 2, pli: 0, fir: 0,
163 nack: 0, freezes: 0, width: 1920, height: 1080, decoder: 'ExternalDecoder', muted: false };
164 const wedged = Object.assign({}, base, { packets: 1400, received: 420, dropped: 110 });
165 assert.match(readingMoved(wedged, base), /^packets \+400, frames received \+120, decoded \+0, dropped \+110.* — frames arriving, none decoded \(the decoder\)$/);
166 assert.match(readingMoved(base, base), /no packets arriving/);
167 assert.match(readingMoved(Object.assign({}, base, { mid: '5' }), base), /no earlier reading$/);
168});
169
170test('a moving screen is never stalled, and a frozen test window is', () => {
171 const seen = { decoded: 0, movedAt: 0 };
172 for (let t = 0; t < 20; t++) assert.equal(judgeScreen(seen, t, t * 1000, false), false);
173 const frozen = [];
174 for (let t = 20; t < 30; t++) if (judgeScreen(seen, t, t * 1000, true)) frozen.push(t);
175 assert.equal(frozen[0], 23, 'the gate\'s frozen window counts as no new frames');
176 assert.equal(judgeScreen(seen, 40, 30000, false), false, 'and frames moving again clears it');
177});
178
179test('the sharer\'s notice names whose view stalled', () => {
180 assert.equal(recoveringWords([]), '');
181 assert.equal(recoveringWords(['Vicky']), 'Vicky’s view of your screen stalled — recovering');
182 assert.equal(recoveringWords(['Tony', 'Vicky']), 'Tony’s and Vicky’s views of your screen stalled — recovering');
183 assert.equal(recoveringWords(['A', 'B', 'C']), '3 people’s views of your screen stalled — recovering');
184});
185
186test('screenStalled travels only with bad news, and never to a server that refused it', () => {
187 const mine = { muted: false, cameraOff: false };
188 assert.deepEqual(mediaPayload(mine, false, null, false, []), { muted: false, cameraOff: false });
189 assert.deepEqual(mediaPayload(mine, false, null, false, [ID]), { muted: false, cameraOff: false, screenStalled: [ID] });
190 assert.deepEqual(mediaPayload(mine, false, null, true, [ID]), { muted: false, cameraOff: false });
191 // The four-argument calls from before still mean what they meant.
192 assert.deepEqual(mediaPayload(mine, true, [ID], false), { muted: false, cameraOff: false, audioBlocked: true, noSoundFrom: [ID] });
193});
194
195test('a report\'s screenStalled is read forgivingly and changes the key', () => {
196 const m = mediaFrom({ muted: false, cameraOff: false, screenStalled: [ID, 'nope', ID, 7] });
197 assert.deepEqual(m.screenStalled, [ID]);
198 assert.equal(mediaFrom({ muted: false, cameraOff: false, screenStalled: [] }).screenStalled, undefined);
199 assert.notEqual(mediaKey(m), mediaKey({ muted: false, cameraOff: false }));
200 assert.notEqual(mediaKey({ muted: false, cameraOff: false, noSoundFrom: [ID] }), mediaKey(m),
201 'the same peer silent is not the same report as the same peer stalled');
202});
203