114 lines
4.8 KiB
Python
114 lines
4.8 KiB
Python
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from tests.dashboard_bundle import dashboard
|
|
|
|
|
|
MOBILE_LAUNCH = Path(__file__).resolve().parents[1] / "frontend" / "mobile-launch.js"
|
|
APP_SHORTCUTS = Path(__file__).resolve().parents[1] / "frontend" / "mobile-app-shortcuts.js"
|
|
|
|
|
|
def test_installed_app_shortcut_accepts_only_bounded_actions_and_runs_once():
|
|
script = f"""
|
|
const shortcuts = require({json.dumps(str(APP_SHORTCUTS))});
|
|
const calls = [];
|
|
const controller = shortcuts.createController({{
|
|
search:'?launch=continue',
|
|
continueWork:() => calls.push('continue'),
|
|
newIssue:() => calls.push('new'),
|
|
agenda:() => calls.push('agenda'),
|
|
}});
|
|
Promise.resolve(controller.run()).then(() => controller.run()).then(() => {{
|
|
process.stdout.write(JSON.stringify({{
|
|
action:controller.action(), calls,
|
|
invalid:shortcuts.parse('?launch=delete-account'),
|
|
shared:shortcuts.parse('?launch=new&title=Shared'),
|
|
}}));
|
|
}});
|
|
"""
|
|
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
assert json.loads(result.stdout) == {
|
|
"action": "continue", "calls": ["continue"], "invalid": None, "shared": "new"
|
|
}
|
|
|
|
|
|
def test_mobile_launch_selects_priority_queue_without_overriding_saved_choice():
|
|
script = f"""
|
|
const mobileLaunch = require({json.dumps(str(MOBILE_LAUNCH))});
|
|
process.stdout.write(JSON.stringify({{
|
|
today: mobileLaunch.chooseFilter({{saved:null, today:2, attention:4, agenda:6}}),
|
|
attention: mobileLaunch.chooseFilter({{saved:null, today:0, attention:4, agenda:6}}),
|
|
agenda: mobileLaunch.chooseFilter({{saved:null, today:0, attention:0, agenda:6}}),
|
|
all: mobileLaunch.chooseFilter({{saved:null, today:0, attention:0, agenda:0}}),
|
|
saved: mobileLaunch.chooseFilter({{saved:'agenda', today:2, attention:4, agenda:6}}),
|
|
}}));
|
|
"""
|
|
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
assert json.loads(result.stdout) == {
|
|
"today": "today",
|
|
"attention": "attention",
|
|
"agenda": "agenda",
|
|
"all": "all",
|
|
"saved": "agenda",
|
|
}
|
|
|
|
|
|
def test_mobile_disclosure_closes_on_escape_and_restores_launcher_focus():
|
|
script = f"""
|
|
const mobileLaunch = require({json.dumps(str(MOBILE_LAUNCH))});
|
|
const listeners = {{}};
|
|
const disclosure = {{open:true, addEventListener(name, fn) {{ listeners[name] = fn; }}}};
|
|
const launcher = {{focuses:0, focus() {{ this.focuses += 1; }}}};
|
|
const controller = mobileLaunch.createDisclosure({{disclosure, launcher}});
|
|
controller.start();
|
|
let prevented = false;
|
|
listeners.keydown({{key:'Escape', preventDefault() {{ prevented = true; }}}});
|
|
process.stdout.write(JSON.stringify({{open:disclosure.open, focuses:launcher.focuses, prevented}}));
|
|
"""
|
|
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
assert json.loads(result.stdout) == {"open": False, "focuses": 1, "prevented": True}
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_mobile_launch_progressively_discloses_secondary_controls():
|
|
html = await dashboard()
|
|
|
|
assert 'class="app-brand"' in html
|
|
assert 'class="app-live-status"' in html
|
|
assert 'class="app-menu"' in html
|
|
assert 'id="app-menu-toggle"' in html
|
|
assert 'class="work-settings"' in html
|
|
assert 'id="work-settings-toggle"' in html
|
|
assert 'id="active-work-queue"' in html
|
|
assert '<script src="static/mobile-launch.js"></script>' in html
|
|
assert '<script src="static/mobile-app-shortcuts.js"></script>' in html
|
|
service_worker = (Path(__file__).resolve().parents[1] / "frontend" / "service-worker.js").read_text()
|
|
assert "BASE + 'static/mobile-launch.js'" in service_worker
|
|
assert "BASE + 'static/mobile-app-shortcuts.js'" in service_worker
|
|
bootstrap = (Path(__file__).resolve().parents[1] / "frontend" / "dashboard.js").read_text()
|
|
assert "mobileAppShortcuts.createController" in bootstrap
|
|
adopted = bootstrap.index(
|
|
"let adoptedProgressiveSnapshot = contextPoller.adopt(progressiveWorkHandoff?.liveSnapshot);"
|
|
)
|
|
fallback = bootstrap.index("if (!adoptedProgressiveSnapshot) await load();")
|
|
shortcuts = bootstrap.index("await appShortcut.run();")
|
|
assert adopted < fallback < shortcuts
|
|
assert "contextPoller.start();" not in bootstrap
|
|
assert "mobileLaunch.chooseFilter" in html
|
|
assert "agenda: counts.agenda" in html
|
|
assert "mobileLaunch.createDisclosure" in html
|
|
assert "@media (max-width: 600px)" in html
|
|
assert "header { min-height:56px; max-height:64px;" in html
|
|
assert ".work-settings:not([open]) > .work-settings-panel { display:none; }" in html
|
|
assert ".app-menu:not([open]) > .app-menu-panel { display:none; }" in html
|
|
assert ".my-work-actions { display:none; }" in html
|