oficina / meet Public

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

Plain git — no account needed to clone.

Download

Download this file

1// room-layout.test.mjs — Speaker view and the viewer's order (0.4.0),
2// gated without a browser.
3//
4// node --test test/browser/room-layout.test.mjs
5//
6// The same trick as room-glow.test.mjs: the functions are lifted out of
7// the shipped room.js and call-layout.js by name, so a retuned constant
8// or a renamed function fails here rather than passing against a copy.
9//
10// speakerStep who has the floor: the dominance delay, the minimum
11// hold, interjections, pauses, leavers
12// orderMerge arrivals on the end, leavers out, remembered keys back
13// orderMove a move among the people here
14// stageFor pin, then the newest screen, then the speaker
15// storedLayout Focus is Speaker
16import { test } from 'node:test';
17import assert from 'node:assert/strict';
18import { readFileSync } from 'node:fs';
19import { fileURLToPath } from 'node:url';
20
21const read = (rel) => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), 'utf8');
22const roomJS = read('../../internal/calls/static/room.js');
23const layoutJS = read('../../internal/calls/static/call-layout.js');
24const swift = read('../../native/apple/Sources/CallsKit/SpeakerView.swift');
25
26function lift(source, name) {
27 const start = source.indexOf('function ' + name + '(');
28 assert.notEqual(start, -1, name + ' is not there any more — this test is stale, not passing');
29 let depth = 0;
30 for (let i = source.indexOf('{', start); i < source.length; i++) {
31 if (source[i] === '{') depth += 1;
32 else if (source[i] === '}') {
33 depth -= 1;
34 if (depth === 0) return source.slice(start, i + 1);
35 }
36 }
37 assert.fail('unbalanced braces reading ' + name);
38}
39function liftConst(name) {
40 const m = roomJS.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 = ['SPEAKER_DOMINANCE', 'SPEAKER_HOLD'].map(liftConst).join('\n');
45// eslint-disable-next-line no-new-func
46const room = new Function(CONSTS + '\n' + lift(roomJS, 'speakerRecord') + '\n' + lift(roomJS, 'speakerStep') +
47 '; return { speakerRecord, speakerStep, SPEAKER_DOMINANCE, SPEAKER_HOLD };')();
48// eslint-disable-next-line no-new-func
49const layout = new Function(['storedLayout', 'orderMerge', 'orderShown', 'orderMove', 'stageFor']
50 .map((n) => lift(layoutJS, n)).join('\n') + '; return { storedLayout, orderMerge, orderShown, orderMove, stageFor };')();
51
52// Five polls a second, as pollLevels takes them, from `from` ms to `to`.
53function run(rec, from, to, speaking, present = ['ana', 'tom', 'li']) {
54 const here = new Set(present);
55 for (let t = from; t < to; t += 200) {
56 const voices = present.map((peer) => ({ peer, speaking: peer in speaking, level: speaking[peer] || 0.001 }));
57 room.speakerStep(rec, voices, here, t);
58 }
59}
60
61test('the thresholds are the native app’s', () => {
62 const dominance = Number(swift.match(/static let dominance: Double = ([0-9.]+)/)[1]);
63 const hold = Number(swift.match(/static let minimumHold: Double = ([0-9.]+)/)[1]);
64 assert.equal(room.SPEAKER_DOMINANCE, dominance * 1000);
65 assert.equal(room.SPEAKER_HOLD, hold * 1000);
66 assert.ok(room.SPEAKER_DOMINANCE >= 1200 && room.SPEAKER_DOMINANCE <= 1500);
67 assert.ok(room.SPEAKER_HOLD >= 3000);
68});
69
70test('the floor moves only after the dominance delay', () => {
71 const rec = room.speakerRecord();
72 run(rec, 0, 1300, { ana: 0.2 });
73 assert.equal(rec.current, '', '1.2 s is not yet a turn');
74 run(rec, 1400, 1500, { ana: 0.2 });
75 assert.equal(rec.current, 'ana');
76});
77
78test('the loudest speaker is the dominant one', () => {
79 const rec = room.speakerRecord();
80 run(rec, 0, 2000, { ana: 0.05, tom: 0.3 });
81 assert.equal(rec.current, 'tom');
82});
83
84test('an interjection does not take it, nor does a pause', () => {
85 const rec = room.speakerRecord();
86 run(rec, 0, 5000, { ana: 0.2 });
87 run(rec, 5000, 6000, { ana: 0.1, tom: 0.4 });
88 run(rec, 6000, 10000, { ana: 0.2 });
89 assert.equal(rec.current, 'ana');
90 run(rec, 10000, 40000, {});
91 assert.equal(rec.current, 'ana', 'silence changes nothing');
92});
93
94test('the minimum hold holds through a takeover', () => {
95 const rec = room.speakerRecord();
96 run(rec, 0, 1500, { ana: 0.2 });
97 assert.equal(rec.current, 'ana');
98 const took = rec.since;
99 run(rec, 1600, took + 2900, { tom: 0.3 });
100 assert.equal(rec.current, 'ana');
101 run(rec, took + 3000, took + 3100, { tom: 0.3 });
102 assert.equal(rec.current, 'tom');
103});
104
105test('two people alternating quickly never make it flap', () => {
106 const rec = room.speakerRecord();
107 let switches = 0;
108 for (let t = 0; t < 60000; t += 200) {
109 const who = Math.floor(t / 800) % 2 === 0 ? 'ana' : 'tom';
110 const other = who === 'ana' ? 'tom' : 'ana';
111 if (room.speakerStep(rec, [{ peer: who, speaking: true, level: 0.3 }, { peer: other, speaking: true, level: 0.1 }],
112 new Set(['ana', 'tom']), t)) switches += 1;
113 }
114 assert.equal(switches, 0);
115});
116
117test('a leaver loses the floor at once', () => {
118 const rec = room.speakerRecord();
119 run(rec, 0, 2000, { li: 0.2 });
120 assert.equal(rec.current, 'li');
121 assert.equal(room.speakerStep(rec, [], new Set(['ana', 'tom']), 2200), true);
122 assert.equal(rec.current, '');
123 run(rec, 2400, 3900, { ana: 0.2 }, ['ana', 'tom']);
124 assert.equal(rec.current, 'ana', 'and the next needs only the dominance delay');
125});
126
127test('storedLayout: Focus is Speaker', () => {
128 assert.equal(layout.storedLayout('focus'), 'speaker');
129 assert.equal(layout.storedLayout('speaker'), 'speaker');
130 assert.equal(layout.storedLayout('grid'), 'grid');
131 assert.equal(layout.storedLayout(null), 'speaker');
132 assert.equal(layout.storedLayout('mosaic'), 'speaker');
133});
134
135test('orderMerge and orderMove: arrivals on the end, leavers out, moves kept', () => {
136 let order = layout.orderMerge({ keys: [], present: [] }, ['self', 'name:Ana']);
137 order = layout.orderMerge(order, ['self', 'name:Ana', 'name:Tom']);
138 assert.deepEqual(layout.orderShown(order), ['self', 'name:Ana', 'name:Tom']);
139 order = layout.orderMove(order, 'name:Tom', 0);
140 assert.deepEqual(layout.orderShown(order), ['name:Tom', 'self', 'name:Ana']);
141 assert.equal(layout.orderMove(order, 'name:Tom', 0), null, 'already there');
142 order = layout.orderMerge(order, ['name:Li', 'self', 'name:Tom', 'name:Ana']);
143 assert.deepEqual(layout.orderShown(order), ['name:Tom', 'self', 'name:Ana', 'name:Li']);
144 order = layout.orderMerge(order, ['self', 'name:Tom', 'name:Li']);
145 assert.ok(!order.keys.includes('name:Ana'), 'a leaver is removed');
146 order = layout.orderMove(order, 'self', 99);
147 assert.deepEqual(layout.orderShown(order), ['name:Tom', 'name:Li', 'self']);
148});
149
150test('a kept order comes back as people arrive', () => {
151 let order = { keys: ['name:Li', 'name:Ana', 'self'], present: [] };
152 order = layout.orderMerge(order, ['self']);
153 assert.deepEqual(layout.orderShown(order), ['self']);
154 assert.deepEqual(order.keys, ['name:Li', 'name:Ana', 'self'], 'the absent are remembered, not removed');
155 order = layout.orderMerge(order, ['self', 'name:Ana', 'name:Li', 'name:Sam']);
156 assert.deepEqual(layout.orderShown(order), ['name:Li', 'name:Ana', 'self', 'name:Sam']);
157});
158
159test('stageFor: pin, then the newest screen, then the speaker, then the order', () => {
160 const others = ['tom', 'ana'];
161 assert.equal(layout.stageFor('', '', others, [], 'self'), 'tom');
162 assert.equal(layout.stageFor('', 'ana', others, [], 'self'), 'ana');
163 assert.equal(layout.stageFor('', 'ana', others, ['tom#share', 'ana#share'], 'self'), 'ana#share');
164 assert.equal(layout.stageFor('tom', 'ana', others, ['ana#share'], 'self'), 'tom', 'a pin beats a new screen');
165 assert.equal(layout.stageFor('gone', 'ana', others, [], 'self'), 'ana', 'a pin that left does not hold');
166 assert.equal(layout.stageFor('', '', [], [], 'self'), 'self', 'alone');
167 assert.equal(layout.stageFor('', 'self', others, [], 'self'), 'tom', 'never yourself unless pinned');
168});
169