stackchain-dashboard/tests/test_workspace_bootstrap.py
timmy 0cb7328aa1
All checks were successful
CI / lint (pull_request) Successful in 3m21s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 3m53s
CI / release-candidate (pull_request) Has been skipped
feat: plan tomorrow independently (Closes #1152)
2026-08-19 22:46:56 +00:00

154 lines
6.3 KiB
Python

import json
import subprocess
from pathlib import Path
BOOTSTRAP = Path(__file__).parents[1] / "frontend" / "workspace-bootstrap.js"
INDEX = Path(__file__).parents[1] / "frontend" / "index.html"
CSS = Path(__file__).parents[1] / "frontend" / "dashboard.css"
def run_bootstrap(scenario: str) -> dict:
harness = f"""
const loadWorkspace = require({json.dumps(str(BOOTSTRAP))});
(async()=>{{ {scenario} }})().catch(error=>{{console.error(error);process.exit(1);}});
"""
completed = subprocess.run(
["node", "-e", harness], check=True, capture_output=True, text=True
)
return json.loads(completed.stdout)
def test_workspace_bootstrap_loads_content_addressed_feature_before_startup():
result = run_bootstrap("""
const status={textContent:''};
const document={
querySelector(selector) {
if (selector === 'meta[name="stackchain-feature-today-timer"]') return {content:'feature-workspace-abc.js'};
if (selector === 'meta[name="stackchain-feature-planning"]') return {content:'feature-planning-def.js'};
if (selector === '#my-work-action-status') return status;
return null;
}
};
const requested=[];
const createLoader=options=>({load:async name=>{requested.push(name + ':' + options.urls[name]);}});
await loadWorkspace({document,createLoader});
console.log(JSON.stringify({requested,status:status.textContent}));
""")
assert result == {
"requested": [
"today-timer:feature-workspace-abc.js",
"planning:feature-planning-def.js",
],
"status": "",
}
def test_workspace_bootstrap_recovers_one_transient_failure_in_place():
result = run_bootstrap("""
const status={textContent:''}; const retry={hidden:true,disabled:false}; let attempts=0;
const document={querySelector(selector) {
if (selector.startsWith('meta[')) return {content:'feature-workspace-abc.js'};
if (selector === '#my-work-action-status') return status;
if (selector === '#retry-workspace') return retry;
return null;
}};
const createLoader=()=>({load:async()=>{attempts++; if (attempts === 1) throw new Error('brief outage');}});
const schedule=callback=>{callback();};
await loadWorkspace({document,createLoader,schedule});
console.log(JSON.stringify({attempts,status:status.textContent,retryHidden:retry.hidden}));
""")
assert result == {"attempts": 4, "status": "", "retryHidden": True}
def test_workspace_bootstrap_offers_single_flight_manual_retry_without_reloading():
result = run_bootstrap("""
const status={textContent:''}; const listeners={}; let reloads=0; let attempts=0;
const retry={hidden:true,disabled:false,addEventListener(name,callback){listeners[name]=callback;}};
const document={querySelector(selector) {
if (selector.startsWith('meta[')) return {content:'feature-workspace-abc.js'};
if (selector === '#my-work-action-status') return status;
if (selector === '#retry-workspace') return retry;
return null;
}};
const window={location:{reload(){reloads++;}},addEventListener(){},removeEventListener(){}};
const createLoader=()=>({load:async()=>{attempts++; if (attempts < 5) throw new Error('offline');}});
const loading=loadWorkspace({document,window,createLoader,schedule:callback=>callback()});
await new Promise(resolve=>setImmediate(resolve));
const offered={hidden:retry.hidden,disabled:retry.disabled,status:status.textContent};
const first=listeners.click(); const second=listeners.click();
await Promise.all([first,second,loading]);
console.log(JSON.stringify({attempts,reloads,offered,status:status.textContent,retryHidden:retry.hidden}));
""")
assert result == {
"attempts": 6,
"reloads": 0,
"offered": {
"hidden": False,
"disabled": False,
"status": "Workspace unavailable. Reconnect or retry.",
},
"status": "",
"retryHidden": True,
}
def test_workspace_retry_control_is_accessible_and_touch_safe():
index = INDEX.read_text()
css = CSS.read_text()
assert '<button class="retry-work-route" id="retry-workspace" type="button" hidden>Retry workspace</button>' in index
assert ".retry-work-route { min-height:44px;" in css
def test_workspace_bootstrap_reconnect_retries_in_place_without_reloading():
result = run_bootstrap("""
const status={textContent:''}; const listeners={}; let reloads=0; let attempts=0;
const retry={hidden:true,disabled:false,addEventListener(){},removeEventListener(){}};
const document={querySelector(selector) {
if (selector.startsWith('meta[')) return {content:'feature-workspace-abc.js'};
if (selector === '#my-work-action-status') return status;
if (selector === '#retry-workspace') return retry;
return null;
}};
const window={
location:{reload(){reloads++;}},
addEventListener(name,callback) { listeners[name]=callback; },
removeEventListener(name,callback) { if (listeners[name] === callback) delete listeners[name]; },
};
const createLoader=()=>({load:async()=>{attempts++; if (attempts < 5) throw new Error('offline');}});
const loading=loadWorkspace({document,window,createLoader,schedule:callback=>callback()});
await new Promise(resolve=>setImmediate(resolve));
const waiting=Boolean(listeners.online);
if (listeners.online) listeners.online();
await loading;
console.log(JSON.stringify({attempts,waiting,reloads,status:status.textContent}));
""")
assert result == {
"attempts": 6,
"waiting": True,
"reloads": 0,
"status": "",
}
def test_workspace_bootstrap_replays_online_event_after_dashboard_registers_handlers():
result = run_bootstrap("""
const status={textContent:''}; const listeners={};
const document={querySelector(selector) {
if (selector.startsWith('meta[')) return {content:'feature-workspace-abc.js'};
if (selector === '#my-work-action-status') return status;
return null;
}};
const window={
addEventListener(name,callback) { listeners[name]=callback; },
removeEventListener(name,callback) { if (listeners[name] === callback) delete listeners[name]; },
};
const createLoader=()=>({load:async()=>{listeners.online();}});
const lifecycle=await loadWorkspace({document,window,createLoader});
let reconnects=0;
lifecycle.replayOnline(()=>{reconnects++;});
lifecycle.replayOnline(()=>{reconnects++;});
console.log(JSON.stringify({reconnects,listening:Boolean(listeners.online)}));
""")
assert result == {"reconnects": 1, "listening": False}