const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const read = f => fs.readFileSync(path.join(__dirname, '..', f), 'utf8'); // On a phone the assistant showed TWO hamburgers, stacked, and neither did // anything useful: the app's opened a sidebar the assistant had hidden, and the // assistant's opened a rail that duplicated it. test('a phone has exactly one menu button', () => { const assistant = read('public/components/assistant.html'); const index = read('public/index.html'); assert.doesNotMatch(assistant, /btn-assistant-mobile-menu/, 'the assistant no longer adds a drawer button of its own'); assert.equal((index.match(/id="btn-menu-toggle"/g) || []).length, 1, 'the app sidebar button is the only one'); }); test('the app sidebar stays reachable inside the assistant on a phone', () => { const css = read('public/css/styles.css'); // Hiding it unconditionally is what made the app hamburger open nothing. assert.match(css, /@media\(min-width:769px\)\{ body\.assistant-workspace \.sidebar\{ display:none; \} \}/, 'the sidebar is only replaced by the assistant rail on desktop'); const hideBlock = css.slice(css.indexOf('body.assistant-workspace .announcement-banner'), css.indexOf('body.assistant-workspace .app-body')); assert.doesNotMatch(hideBlock, /\.sidebar/, 'nothing hides it unconditionally'); }); test('the collapse control closes the sheet on a phone rather than collapsing a rail', () => { const app = read('public/js/app.js'); const handler = app.slice(app.indexOf("event.target.closest('[data-menu-toggle]')"), app.indexOf("localStorage.setItem('ped_sidebar_collapsed'")); assert.match(handler, /if \(window\.innerWidth <= 768\)/); assert.match(handler, /sidebar\.classList\.remove\('open'\)/, 'it closes the sheet'); }); test('sources are reachable on a phone instead of deleted', () => { const css = read('public/css/assistant.css'); const mobile = css.slice(css.indexOf('@media (max-width: 640px)')); // display:none left every citation pointing at nothing. assert.doesNotMatch(mobile.slice(0, 2000), /\.assistant-side \{ display:none; \}/); assert.match(mobile, /body\.assistant-sources-open \.assistant-side \{ transform:translateY\(0\); \}/, 'they slide up as a sheet'); const js = read('public/js/clinicalAssistant.js'); assert.match(js, /button\.hidden = !\(citationsOn && count > 0\);/, 'the opener appears only when an answer has citations'); assert.match(js, /event\.target\.closest\('\.assistant-cite'\)/, 'and tapping a citation opens it'); // A sheet with no way out is a trap. assert.match(js, /if \(event\.key !== 'Escape'\) return;[\s\S]{0,160}assistant-sources-open/); assert.match(js, /if \(!document\.body\.classList\.contains\('assistant-sources-open'\)\) return;/, 'tapping away closes it'); }); test('the mobile menu is sized for a thumb', () => { const css = read('public/css/styles.css'); const mobile = css.slice(css.indexOf('/* ── Mobile menu ─')); assert.match(mobile, /\.sidebar\{ width:min\(88vw,360px\)/, 'a sheet, not a narrow rail'); assert.match(mobile, /\.tab-btn\{ padding:12px 10px; font-size:14\.5px/, 'rows are tappable'); assert.match(mobile, /env\(safe-area-inset-bottom\)/, 'and clear of the home indicator'); });