oficina / meet Public

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

Plain git — no account needed to clone.

Download

Download this file

1// room-window.test.mjs — when the call window's controls show
2// (2026-09-28), gated without a browser.
3//
4// node --test test/browser/room-window.test.mjs
5//
6// The same trick as room-layout.test.mjs: the rule is lifted out of the
7// shipped call-window.js by name, and its constants are held equal to
8// the Mac's ControlsVisibility (CallChrome.swift), so the web and the
9// Mac cannot drift apart in silence. So is Grid's fit (call-layout.js's
10// gridColumns, the Mac's GridFit.columns) and where a dropped self view
11// settles (pipCorner, PiPLayout.corner).
12import { test } from 'node:test';
13import assert from 'node:assert/strict';
14import { readFileSync } from 'node:fs';
15import { fileURLToPath } from 'node:url';
16
17const read = (rel) => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), 'utf8');
18const windowJS = read('../../internal/calls/static/call-window.js');
19const roomJS = read('../../internal/calls/static/room.js');
20const layoutJS = read('../../internal/calls/static/call-layout.js');
21const swift = read('../../native/apple/Sources/CallsKit/CallChrome.swift');
22const css = read('../../internal/calls/static/calls.css');
23
24function lift(source, name) {
25 const start = source.indexOf('function ' + name + '(');
26 assert.notEqual(start, -1, name + ' is not there any more — this test is stale, not passing');
27 let depth = 0;
28 for (let i = source.indexOf('{', start); i < source.length; i++) {
29 if (source[i] === '{') depth += 1;
30 else if (source[i] === '}') {
31 depth -= 1;
32 if (depth === 0) return source.slice(start, i + 1);
33 }
34 }
35 assert.fail('unbalanced braces reading ' + name);
36}
37function jsConst(name) {
38 const m = windowJS.match(new RegExp('const ' + name + ' = ([0-9.]+);'));
39 assert.ok(m, name + ' is not a numeric const in call-window.js any more');
40 return Number(m[1]);
41}
42function swiftConst(name) {
43 const m = swift.match(new RegExp('static let ' + name + ': TimeInterval = ([0-9.]+)'));
44 assert.ok(m, name + ' is not in CallChrome.swift any more');
45 return Number(m[1]);
46}
47
48const CONSTS = ['CONTROLS_POINTER_DELAY', 'CONTROLS_TOUCH_DELAY', 'CONTROLS_EXIT_DELAY']
49 .map((n) => 'const ' + n + ' = ' + jsConst(n) + ';').join('\n');
50// eslint-disable-next-line no-new-func
51const m = new Function(CONSTS + '\n' + ['controlsDelay', 'controlsStart', 'controlsHeld', 'controlsStep']
52 .map((n) => lift(windowJS, n)).join('\n') + '; return { controlsStart, controlsStep, controlsHeld };')();
53// eslint-disable-next-line no-new-func
54const layout = new Function(['gridColumns', 'gridTileWidth', 'gridFit', 'pipCorner']
55 .map((n) => lift(layoutJS, n)).join('\n') + '; return { gridColumns, gridFit, pipCorner };')();
56
57const run = (s, events) => events.reduce((st, [t, ev]) => m.controlsStep(st, ev, t), s);
58
59test('the delays are the Mac\'s', () => {
60 assert.equal(jsConst('CONTROLS_POINTER_DELAY'), swiftConst('pointerDelay'));
61 assert.equal(jsConst('CONTROLS_TOUCH_DELAY'), swiftConst('touchDelay'));
62 assert.equal(jsConst('CONTROLS_EXIT_DELAY'), swiftConst('pointerExitDelay'));
63});
64
65test('the fades are the Mac\'s: 0.35 s going, 0.2 s coming back', () => {
66 const out = css.match(/--calls-fade-out: ([0-9.]+)s;/);
67 const back = css.match(/--calls-fade-in: ([0-9.]+)s;/);
68 assert.ok(out && back, 'calls.css has no fade durations');
69 assert.equal(Number(out[1]), swiftConst('fadeOutDuration'));
70 assert.equal(Number(back[1]), swiftConst('fadeInDuration'));
71});
72
73test('they start visible and go 3 s later', () => {
74 const s = m.controlsStart('pointer', 100);
75 assert.equal(s.visible, true);
76 assert.equal(s.hideAt, 103);
77 assert.equal(m.controlsStep(s, { type: 'tick' }, 102.9).visible, true);
78 assert.equal(m.controlsStep(s, { type: 'tick' }, 103).visible, false);
79});
80
81test('moving the pointer shows them and starts the wait again', () => {
82 const s = run(m.controlsStart('pointer', 0), [[4, { type: 'tick' }], [5, { type: 'move' }]]);
83 assert.equal(s.visible, true);
84 assert.equal(s.hideAt, 8);
85 assert.equal(s.pointerInside, true);
86 assert.equal(run(s, [[7, { type: 'move' }], [9, { type: 'tick' }]]).visible, true);
87 assert.equal(run(s, [[7, { type: 'move' }], [10, { type: 'tick' }]]).visible, false);
88});
89
90test('leaving the window hides them 1.5 s later, never later than they were due', () => {
91 let s = run(m.controlsStart('pointer', 0), [[0.5, { type: 'move' }], [1, { type: 'exit' }]]);
92 assert.equal(s.hideAt, 2.5);
93 assert.equal(s.pointerInside, false);
94 s = run(m.controlsStart('pointer', 0), [[0, { type: 'move' }], [2.5, { type: 'exit' }]]);
95 assert.equal(s.hideAt, 3, 'due at 3 already, so leaving at 2.5 does not put it off to 4');
96});
97
98test('a hold keeps them up; letting go starts the usual wait', () => {
99 let s = run(m.controlsStart('pointer', 0), [[1, { type: 'hold', hold: 'menuOpen', on: true }]]);
100 assert.equal(s.hideAt, null);
101 assert.equal(run(s, [[60, { type: 'tick' }]]).visible, true);
102 s = run(s, [[60, { type: 'hold', hold: 'menuOpen', on: false }]]);
103 assert.equal(s.hideAt, 63);
104 // A hold shows hidden controls.
105 s = run(m.controlsStart('pointer', 0), [[5, { type: 'tick' }]]);
106 assert.equal(s.visible, false);
107 assert.equal(run(s, [[6, { type: 'hold', hold: 'keyboardFocus', on: true }]]).visible, true);
108});
109
110test('leaving the window lets go of the pointer on the bar, but not of other holds', () => {
111 let s = run(m.controlsStart('pointer', 0), [[0, { type: 'move' }], [0, { type: 'hold', hold: 'pointerOverControls', on: true }], [1, { type: 'exit' }]]);
112 assert.equal(m.controlsHeld(s), false);
113 assert.equal(s.hideAt, 2.5);
114 s = run(m.controlsStart('pointer', 0), [[0, { type: 'hold', hold: 'menuOpen', on: true }], [1, { type: 'exit' }]]);
115 assert.equal(s.hideAt, null);
116});
117
118test('a tap toggles, unless they are held', () => {
119 let s = m.controlsStart('touch', 0);
120 s = m.controlsStep(s, { type: 'tap' }, 1);
121 assert.equal(s.visible, false);
122 s = m.controlsStep(s, { type: 'tap' }, 2);
123 assert.equal(s.visible, true);
124 assert.equal(s.hideAt, 5);
125 s = run(s, [[2, { type: 'hold', hold: 'menuOpen', on: true }], [3, { type: 'tap' }]]);
126 assert.equal(s.visible, true, 'a held tap only starts the wait again');
127});
128
129test('activity (Tab, a click) shows them', () => {
130 const s = run(m.controlsStart('pointer', 0), [[4, { type: 'tick' }], [5, { type: 'activity' }]]);
131 assert.equal(s.visible, true);
132 assert.equal(s.hideAt, 8);
133});
134
135test('Grid fits as the Mac\'s GridFit does', () => {
136 // Two 16:9 tiles in a laptop's window: one above the other is larger.
137 assert.equal(layout.gridColumns(2, 1264, 784, 16 / 9, 4), 1);
138 // In a wide, short one, side by side.
139 assert.equal(layout.gridColumns(2, 1600, 500, 16 / 9, 4), 2);
140 assert.equal(layout.gridColumns(4, 1400, 860, 16 / 9, 4), 2);
141 assert.equal(layout.gridColumns(1, 1400, 860, 16 / 9, 4), 1);
142 const small = layout.gridFit(8, 400, 300, 72);
143 assert.equal(small.tileW, 200, 'no tile under 200 wide: it scrolls instead');
144 const fit = layout.gridFit(4, 1400, 860, 72);
145 assert.ok(fit.tileW * fit.columns + (fit.columns - 1) * 4 <= 1392);
146 assert.ok(fit.tileH * fit.rows + (fit.rows - 1) * 4 <= 852);
147 assert.equal(fit.lastRow, 2);
148});
149
150test('a dropped self view settles where it was headed', () => {
151 assert.equal(layout.pipCorner({ x: 100, y: 100 }, null, 1000, 600), 'top-left');
152 assert.equal(layout.pipCorner({ x: 900, y: 500 }, null, 1000, 600), 'bottom-right');
153 assert.equal(layout.pipCorner({ x: 450, y: 500 }, { x: 700, y: 100 }, 1000, 600), 'top-right', 'a flick carries it');
154});
155
156test('every state the title pill carried is a notice now (2026-10-02)', () => {
157 // The pill said Connecting, Waiting to be let in, The room is full,
158 // Reconnecting and Call ended over the title. It is gone; the status
159 // notice (#status, room.js's statusLine) says each of them, and the
160 // call being over is stop()'s sentence with its way out.
161 // eslint-disable-next-line no-new-func
162 const statusLine = new Function(lift(roomJS, 'statusLine') + '; return statusLine;')();
163 assert.deepEqual(statusLine({ admitted: false }, false, 0, true), ['Connecting to the call.', false]);
164 assert.deepEqual(statusLine({ admitted: false, peer: 'p', joined: true }, false, 0, false),
165 ['Waiting for someone to let you in.', false]);
166 assert.deepEqual(statusLine({ admitted: false, peer: 'p', joined: true }, false, 0, true),
167 ['Connecting to the call.', false], 'a member is never waiting');
168 assert.deepEqual(statusLine({ full: true }, false, 0, true), ['The room is full.', false]);
169 assert.deepEqual(statusLine({ admitted: true }, true, 1, true), ['Reconnecting…', false]);
170 assert.deepEqual(statusLine({ admitted: true }, false, 0, true), ['You are the only one here.', true]);
171 assert.deepEqual(statusLine({ admitted: true }, false, 2, true), ['', false], 'in the meeting: nothing to say');
172 assert.ok(/This meeting has ended/.test(roomJS), 'the call being over is said');
173 assert.ok(!/callState|call-state/.test(windowJS), 'and no pill is drawn');
174});
175
176// 2026-10-03, Till on an iPhone: "kept cutting in and out". Safari there
177// stops the camera, the microphone and the sound with the tab in the
178// background or the screen locked, so the room says so, once a session.
179test('appleTouch: iPhone and iPad, in any browser there, and nothing else', () => {
180 const appleTouch = new Function(lift(windowJS, 'appleTouch') + '; return appleTouch;')();
181 const iphone = 'Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1';
182 const crios = 'Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/140.0 Mobile/15E148 Safari/604.1';
183 const mac = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15';
184 assert.equal(appleTouch({ platform: 'iPhone', userAgent: iphone, maxTouchPoints: 5 }), true);
185 assert.equal(appleTouch({ platform: 'iPhone', userAgent: crios, maxTouchPoints: 5 }), true, 'Chrome on iPhone is Safari underneath');
186 assert.equal(appleTouch({ platform: 'MacIntel', userAgent: mac, maxTouchPoints: 5 }), true, 'an iPad asking for the desktop site');
187 assert.equal(appleTouch({ platform: 'MacIntel', userAgent: mac, maxTouchPoints: 0 }), false, 'a Mac');
188 assert.equal(appleTouch({ platform: 'Linux armv8l', userAgent: 'Mozilla/5.0 (Linux; Android 15) Chrome/140 Mobile', maxTouchPoints: 5 }), false);
189 assert.equal(appleTouch(undefined), false);
190});
191