159 lines
5.8 KiB
Python
159 lines
5.8 KiB
Python
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('<section class="mobile-today-hud"'):html.index('</section>', html.index('<section class="mobile-today-hud"'))]
|
|
sheet = html[html.index('<dialog class="mobile-today-actions"'):html.index('</dialog>', html.index('<dialog class="mobile-today-actions"'))]
|
|
|
|
assert "data-mobile-today-open" in hud
|
|
assert "data-mobile-today-complete" in hud
|
|
assert "data-mobile-today-toggle" in hud
|
|
assert "data-mobile-today-more" in hud
|
|
for secondary in (
|
|
"data-work-session-previous",
|
|
"data-work-session-next",
|
|
"data-mobile-today-update",
|
|
"data-mobile-today-blocked",
|
|
"data-today-break-open",
|
|
"data-work-session-adjust-plan",
|
|
"data-mobile-today-end",
|
|
):
|
|
assert secondary not in hud
|
|
assert secondary in sheet
|
|
assert 'aria-modal="true"' in sheet
|
|
assert "body.mobile-today-active { padding-bottom:calc(var(--mobile-today-clearance" in css
|
|
assert "createMobileTodayCommandBar({" in dashboard
|
|
assert '<script src="static/mobile-today-command-bar.js"></script>' in html
|