// room-space.test.mjs — Space toggles mute in the room, gated without a // browser. // // node --test test/browser/room-space.test.mjs // // Same trick as room-mute.test.mjs: the functions are lifted out of the // shipped room.js by name, so a rename fails here loudly instead of // passing against a copy. // // spaceKeyAction 'toggle', 'swallow' for a repeat, or '' when the key // is not ours // spaceBelongsTo whether Space already means something to the // focused element (typing, activation, opening) import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; const roomJS = fileURLToPath(new URL('../../internal/calls/static/room.js', import.meta.url)); const src = readFileSync(roomJS, 'utf8'); function lift(source, name) { const start = source.indexOf('function ' + name + '('); assert.notEqual(start, -1, name + ' is not in room.js any more — this test is stale, not passing'); let depth = 0; for (let i = source.indexOf('{', start); i < source.length; i++) { if (source[i] === '{') depth += 1; else if (source[i] === '}') { depth -= 1; if (depth === 0) return source.slice(start, i + 1); } } assert.fail('unbalanced braces reading ' + name + ' out of room.js'); } // eslint-disable-next-line no-new-func const spaceKeyAction = new Function( lift(src, 'spaceBelongsTo') + '\n' + lift(src, 'spaceKeyAction') + '; return spaceKeyAction;', )(); // A stand-in for a focused element: a tag, attributes, and whether it is // editable, which is all the rules read. function el(tagName, attrs = {}, isContentEditable = false) { return { tagName, isContentEditable, hasAttribute: (name) => Object.prototype.hasOwnProperty.call(attrs, name), getAttribute: (name) => (Object.prototype.hasOwnProperty.call(attrs, name) ? attrs[name] : null), }; } const space = (extra = {}) => ({ key: ' ', code: 'Space', repeat: false, ...extra }); const body = el('BODY'); test('Space on the page toggles mute', () => { assert.equal(spaceKeyAction(space(), body), 'toggle'); assert.equal(spaceKeyAction(space(), el('DIV')), 'toggle'); assert.equal(spaceKeyAction(space(), el('VIDEO')), 'toggle', 'a tile is not a control'); assert.equal(spaceKeyAction(space(), null), 'toggle'); // Either half of the key's identity is enough: some layouts report a // different key for the physical Space bar, and some synthetic events // carry no code. assert.equal(spaceKeyAction({ key: ' ', repeat: false }, body), 'toggle'); assert.equal(spaceKeyAction({ key: 'Unidentified', code: 'Space', repeat: false }, body), 'toggle'); }); test('a held Space is handled once, and its repeats are swallowed', () => { assert.equal(spaceKeyAction(space({ repeat: true }), body), 'swallow'); }); test('other keys and modified Space are not ours', () => { assert.equal(spaceKeyAction({ key: 'm', code: 'KeyM', repeat: false }, body), ''); assert.equal(spaceKeyAction({ key: 'Enter', code: 'Enter', repeat: false }, body), ''); for (const mod of ['ctrlKey', 'metaKey', 'altKey', 'shiftKey']) { assert.equal(spaceKeyAction(space({ [mod]: true }), body), '', mod + '+Space is a shortcut of its own'); } assert.equal(spaceKeyAction(space({ isComposing: true }), body), '', 'an input method composing text owns it'); assert.equal(spaceKeyAction(undefined, body), ''); }); test('Space is typing in every text field, including the chat composer', () => { for (const target of [ el('INPUT', { type: 'text' }), el('INPUT'), el('TEXTAREA', { id: 'chat-text' }), el('textarea'), el('DIV', {}, true), // contenteditable, or inside one el('DIV', { role: 'textbox' }), el('INPUT', { type: 'search' }), ]) { assert.equal(spaceKeyAction(space(), target), '', JSON.stringify(target.tagName)); assert.equal(spaceKeyAction(space({ repeat: true }), target), '', 'repeats in a field are typing too'); } }); test('Space is activation on buttons, links and other controls', () => { for (const target of [ el('BUTTON', { id: 'mute' }), el('BUTTON', { id: 'leave' }), el('INPUT', { type: 'checkbox' }), el('INPUT', { type: 'button' }), el('SELECT'), el('SUMMARY'), el('A', { href: '/m/abc' }), el('VIDEO', { controls: '' }), el('DIV', { role: 'button' }), el('SPAN', { role: ' Switch ' }), el('LI', { role: 'menuitem' }), el('DIV', { role: 'slider' }), ]) { assert.equal(spaceKeyAction(space(), target), '', target.tagName + ' ' + JSON.stringify(target.getAttribute('role'))); } // An anchor with no href is not a link and does nothing with Space. assert.equal(spaceKeyAction(space(), el('A')), 'toggle'); // A role that is not a control is not claimed. assert.equal(spaceKeyAction(space(), el('DIV', { role: 'region' })), 'toggle'); });