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 '' 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