stackchain-dashboard/tests/test_today_lock_screen.py
timmy a7f4f9714f
All checks were successful
CI / lint (pull_request) Successful in 2m55s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Successful in 2m21s
CI / release-candidate (pull_request) Has been skipped
feat: control Today from mobile lock screen (Closes #1026)
2026-08-17 14:47:07 +00:00

118 lines
5.0 KiB
Python

import json
import subprocess
from pathlib import Path
SOURCE = Path(__file__).parents[1] / "frontend" / "today-lock-screen.js"
ROOT = SOURCE.parents[1]
def run_scenario(scenario: str) -> dict:
script = SOURCE.read_text() + r"""
const values = new Map();
const messages = [];
const listeners = {};
const control = {checked:false, disabled:false, addEventListener:(name, fn)=>listeners[name]=fn};
const status = {textContent:''};
const serviceWorker = {
controller:{postMessage:message=>messages.push(message)},
ready:Promise.resolve({active:{postMessage:message=>messages.push(message)}}),
addEventListener:(name, fn)=>listeners['sw-' + name]=fn,
};
const storage = {getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)};
let permission = 'default';
const NotificationRef = {get permission(){return permission;},requestPermission:async()=>{permission='granted';return permission;}};
const actions = [];
const locationRef = {href:'https://forge.example/dashboard/#/my-work/today'};
const historyRef = {replaceState:(_a,_b,url)=>{locationRef.href=new URL(url, locationRef.href).href;}};
(async()=>{
const lockScreen = createTodayLockScreen({storage,getLogin:()=> 'Timmy',serviceWorker,NotificationRef,control,status,locationRef,historyRef,onAction:action=>actions.push(action)});
%SCENARIO%
})().catch(error=>{console.error(error);process.exit(1)});
""".replace("%SCENARIO%", scenario)
completed = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
return json.loads(completed.stdout)
def test_operator_opt_in_syncs_one_privacy_safe_session_notification():
result = run_scenario(r"""
await lockScreen.enable();
await lockScreen.sync({identity:'issue:secret/repo:42:',running:true,elapsed_ms:125000}, true);
process.stdout.write(JSON.stringify({checked:control.checked,status:status.textContent,messages,stored:[...values.entries()]}));
""")
assert result["checked"] is True
assert result["status"] == "Lock-screen Today controls are on."
assert result["stored"] == [["stackchain.today-lock-screen.v1.timmy", "1"]]
assert result["messages"][-1] == {
"type": "stackchain-today-lock-screen",
"active": True,
"running": True,
}
assert "secret" not in json.dumps(result)
def test_disabling_or_ending_session_removes_notification():
result = run_scenario(r"""
await lockScreen.enable();
await lockScreen.sync({identity:'issue:r:42:',running:false,elapsed_ms:0}, false);
await lockScreen.disable();
process.stdout.write(JSON.stringify({checked:control.checked,messages,stored:[...values.entries()]}));
""")
assert result["checked"] is False
assert result["stored"] == []
assert result["messages"][-2:] == [
{"type": "stackchain-today-lock-screen", "active": False, "running": False},
{"type": "stackchain-today-lock-screen", "active": False, "running": False},
]
def test_valid_notification_action_is_consumed_once_and_removed_from_url():
result = run_scenario(r"""
values.set('stackchain.today-lock-screen.v1.timmy','1');
locationRef.href='https://forge.example/dashboard/?today_timer_action=pause#/my-work/today';
const consumed = lockScreen.consumeLaunchAction();
const second = lockScreen.consumeLaunchAction();
listeners['sw-message']({data:{type:'stackchain-today-timer-action',action:'resume'}});
process.stdout.write(JSON.stringify({consumed,second,actions,href:locationRef.href}));
""")
assert result == {
"consumed": True,
"second": False,
"actions": ["pause", "resume"],
"href": "https://forge.example/dashboard/#/my-work/today",
}
def test_permission_denial_is_truthful_and_does_not_persist_opt_in():
result = run_scenario(r"""
NotificationRef.requestPermission=async()=>{permission='denied';return permission;};
const enabled = await lockScreen.enable();
process.stdout.write(JSON.stringify({enabled,checked:control.checked,status:status.textContent,stored:[...values.entries()]}));
""")
assert result == {
"enabled": False,
"checked": False,
"status": "Lock-screen controls are blocked in browser settings.",
"stored": [],
}
def test_lock_screen_flow_is_wired_into_the_packaged_today_journey():
index = (ROOT / "frontend" / "index.html").read_text()
dashboard = (ROOT / "frontend" / "dashboard.js").read_text()
bundle = (ROOT / "src" / "frontend_bundle.py").read_text()
assert 'id="today-lock-screen"' in index
assert 'id="today-lock-screen-status"' in index
assert '<script src="static/today-lock-screen.js"></script>' in index
assert index.index('static/today-lock-screen.js') < index.index('static/dashboard.js')
assert 'createTodayLockScreen({' in dashboard
assert "action === 'pause' ? timer.pause() : timer.resume()" in dashboard
assert "todayLockScreen.sync(snapshot, workSession.checkpointed())" in dashboard
assert "todayLockScreen.consumeLaunchAction()" in dashboard
assert '"static/today-lock-screen.js"' in bundle