import json import subprocess from pathlib import Path SOURCE = Path(__file__).parents[1] / "frontend" / "mobile-today-command-bar.js" FRONTEND = SOURCE.parent def run_node(body: str) -> dict: script = f"const createCommandBar = require({json.dumps(str(SOURCE))});\n" + body completed = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) return json.loads(completed.stdout) def test_more_sheet_opens_modally_and_restores_focus_after_close(): result = run_node(r""" function element() { const listeners = {}; return { listeners, open:false, hidden:false, attributes:{}, focusCount:0, addEventListener:(type, listener) => { (listeners[type] ||= []).push(listener); }, dispatch(type, event={}) { (listeners[type] || []).forEach(listener => listener({preventDefault(){}, target:this, ...event})); }, setAttribute(name, value) { this.attributes[name] = value; }, showModal() { this.open = true; }, close() { this.open = false; this.dispatch('close'); }, focus() { this.focusCount += 1; }, }; } const more = element(); const sheet = element(); const close = element(); const firstAction = element(); createCommandBar({more, sheet, close, firstAction}); more.dispatch('click'); const opened = {open:sheet.open, expanded:more.attributes['aria-expanded'], firstFocused:firstAction.focusCount}; close.dispatch('click'); process.stdout.write(JSON.stringify({opened, closed:!sheet.open, restored:more.focusCount})); """) assert result == { "opened": {"open": True, "expanded": "true", "firstFocused": 1}, "closed": True, "restored": 1, } def test_end_session_action_reuses_existing_session_control_and_closes_sheet(): result = run_node(r""" function element() { const listeners = {}; return { open:false, clicks:0, attributes:{}, addEventListener:(type, listener) => { (listeners[type] ||= []).push(listener); }, dispatch(type, event={}) { (listeners[type] || []).forEach(listener => listener({preventDefault(){}, target:this, ...event})); }, setAttribute(name, value) { this.attributes[name] = value; }, showModal() { this.open = true; }, close() { this.open = false; this.dispatch('close'); }, click() { this.clicks += 1; this.dispatch('click'); }, focus() {}, }; } const more = element(); const sheet = element(); const endAction = element(); const existingEndControl = element(); createCommandBar({more, sheet, endAction, existingEndControl}); more.dispatch('click'); endAction.dispatch('click'); process.stdout.write(JSON.stringify({open:sheet.open, endClicks:existingEndControl.clicks})); """) assert result == {"open": False, "endClicks": 1} def test_secondary_action_closes_command_sheet_before_existing_handler_runs(): result = run_node(r""" function element() { const listeners = {}; return { open:false, attributes:{}, observedOpen:null, addEventListener:(type, listener) => { (listeners[type] ||= []).push(listener); }, dispatch(type) { (listeners[type] || []).forEach(listener => listener({preventDefault(){}, target:this})); }, setAttribute(name, value) { this.attributes[name] = value; }, showModal() { this.open = true; }, close() { this.open = false; this.dispatch('close'); }, focus() {}, }; } const more = element(); const sheet = element(); const update = element(); createCommandBar({more, sheet, actionButtons:[update]}); update.addEventListener('click', () => { update.observedOpen = sheet.open; }); more.dispatch('click'); update.dispatch('click'); process.stdout.write(JSON.stringify({open:sheet.open, handlerSawOpen:update.observedOpen})); """) assert result == {"open": False, "handlerSawOpen": False} def test_rendered_hud_and_dock_height_define_scroll_clearance(): result = run_node(r""" const listeners = {}; const element = () => ({ open:false, attributes:{}, addEventListener:(type, listener) => { (listeners[type] ||= []).push(listener); }, setAttribute(name, value) { this.attributes[name] = value; }, focus() {}, showModal() { this.open = true; }, close() { this.open = false; }, }); const values = {}; let resize; const ResizeObserverImpl = class { constructor(callback) { resize = callback; } observe() {} disconnect() {} }; const more = element(); const sheet = element(); createCommandBar({ more, sheet, hud:{offsetHeight:96}, dock:{offsetHeight:56}, style:{setProperty:(name, value) => { values[name] = value; }}, ResizeObserverImpl, }); resize(); process.stdout.write(JSON.stringify(values)); """) assert result == {"--mobile-today-clearance": "168px"} def test_mobile_command_bar_keeps_primary_actions_visible_and_secondary_actions_modal(): html = (FRONTEND / "index.html").read_text() css = (FRONTEND / "dashboard.css").read_text() dashboard = (FRONTEND / "dashboard.js").read_text() hud = html[html.index('
', html.index('
', html.index('' in html