1import assert from 'node:assert/strict';2import { readFile } from 'node:fs/promises';3import test from 'node:test';45const html = await readFile(new URL('./index.html', import.meta.url), 'utf8');6const css = await readFile(new URL('./style.css', import.meta.url), 'utf8');78test('session controls live in the bottom bar instead of over the stream', () => {9 const mainEnd = html.indexOf('</main>');10 const bottomBarStart = html.indexOf('<div class="bottombar">');11 const bottomBarEnd = html.indexOf('<div class="panel-overlay"');12 const bottomBar = html.slice(bottomBarStart, bottomBarEnd);13 const controlStart = html.indexOf('<div class="control-bar" id="control-bar">');14 const disconnectButton = html.indexOf('id="action-disconnect"');15 const controlButton = html.indexOf('id="control-toggle"');16 const audioButton = html.indexOf('id="audio-toggle"');1718 assert.ok(mainEnd >= 0 && mainEnd < bottomBarStart);19 assert.ok(disconnectButton > bottomBarStart && disconnectButton < bottomBarEnd);20 assert.ok(controlStart > bottomBarStart && controlStart < bottomBarEnd);21 assert.ok(controlButton > controlStart && controlButton < bottomBarEnd);22 assert.ok(audioButton > controlStart && audioButton < bottomBarEnd);23 assert.doesNotMatch(bottomBar, /id="stream-/);24 assert.doesNotMatch(css, /\.control-bar\s*\{[^}]*position:\s*absolute/s);25 assert.match(css, /\.control-bar\.visible \+ \.bottombar-end \.sep\s*\{\s*display:\s*none;/);26});2728test('stream diagnostics remain exclusive to the Info panel', () => {29 const panelStart = html.indexOf('<aside class="panel" id="panel"');30 const panelEnd = html.indexOf('</aside>', panelStart);31 const panel = html.slice(panelStart, panelEnd);32 const streamIds = [...html.matchAll(/id="(stream-[^"]+)"/g)].map((match) => match[1]);3334 assert.doesNotMatch(html, /id="stats"/);35 assert.ok(panelStart >= 0 && panelEnd > panelStart);36 assert.ok(streamIds.length > 0);37 for (const id of streamIds) assert.match(panel, new RegExp(`id="${id}"`));38 assert.match(html, /id="audio-toggle"[^>]*aria-label="Audio unavailable\. Not connected"/);39 assert.match(html, /type="button"[^>]*id="audio-toggle"/);40 assert.match(html, /id="audio-toggle"[^>]*>[\s\n]*vol off[\s\n]*<\/button>/);41 assert.doesNotMatch(html, /🔊|🔇|🔈/);42 assert.match(css, /#audio-toggle\s*\{[^}]*min-width:\s*80px;[^}]*min-height:\s*36px;/s);43});4445test('multi-viewer roster stays in the Info drawer and outside the stream surface', () => {46 const mainStart = html.indexOf('<main class="main">');47 const mainEnd = html.indexOf('</main>', mainStart);48 const panelStart = html.indexOf('<aside class="panel" id="panel"');49 const panelEnd = html.indexOf('</aside>', panelStart);50 const roster = html.indexOf('id="roster-section"');5152 assert.ok(roster > panelStart && roster < panelEnd);53 assert.ok(roster < mainStart || roster > mainEnd);54 assert.match(css, /\.roster-section\.hidden\s*\{\s*display:\s*none;/);55 assert.match(css, /@media \(max-width: 480px\)[\s\S]*\.viewer-status/);56});