import json import subprocess from pathlib import Path MODULE = Path(__file__).parents[1] / "frontend" / "progressive-my-work.js" MY_WORK = Path(__file__).parents[1] / "frontend" / "my-work.js" DASHBOARD = Path(__file__).parents[1] / "frontend" / "dashboard.js" def test_progressive_my_work_renders_and_filters_assigned_work_before_full_workspace(): harness = f""" const fs=require('fs'); const vm=require('vm'); const buttons=[ {{dataset:{{workFilter:'all'}},attrs:{{}},addEventListener(n,cb){{this.cb=cb;}},setAttribute(n,v){{this.attrs[n]=v;}}}}, {{dataset:{{workFilter:'issue'}},attrs:{{}},addEventListener(n,cb){{this.cb=cb;}},setAttribute(n,v){{this.attrs[n]=v;}}}}, ]; const list={{innerHTML:''}}; const status={{textContent:''}}; const document={{querySelector:s=>s==='#my-work-list'?list:s==='#my-work-status'?status:null,querySelectorAll:()=>buttons}}; const context={{module:{{exports:{{}}}},exports:{{}},console,URL,document}}; vm.createContext(context); vm.runInContext(fs.readFileSync({json.dumps(str(MY_WORK))},'utf8'),context); context.buildMyWork=context.module.exports; context.module={{exports:{{}}}}; vm.runInContext(fs.readFileSync({json.dumps(str(MODULE))},'utf8'),context); const createProgressiveMyWork=context.module.exports; const flow=createProgressiveMyWork({{document,fetchSnapshot:async()=>({{ user:{{login:'timmy'}},issues:[{{number:7,title:'Fix mobile queue',repository:'stackchain/dashboard',assignees:['timmy'],url:'https://forge.example/issues/7'}}],pull_requests:[] }})}}); (async()=>{{ await flow.start(); const rendered=list.innerHTML; buttons[1].cb(); console.log(JSON.stringify({{status:status.textContent,rendered,pressed:buttons.map(b=>b.attrs['aria-pressed'])}})); }})().catch(e=>{{console.error(e);process.exit(1);}}); """ result = subprocess.run(["node", "-e", harness], check=True, capture_output=True, text=True) state = json.loads(result.stdout) assert state["status"] == "1 assigned work item ready." assert "Fix mobile queue" in state["rendered"] assert 'href="https://forge.example/issues/7"' in state["rendered"] assert state["pressed"] == ["false", "true"] def test_progressive_my_work_keeps_supported_queues_truthful_while_planning_loads(): harness = f""" const fs=require('fs'); const vm=require('vm'); const names=['all','today','agenda','attention','filed','authored','issue','pull','review','update','later','draft']; const counts=Object.fromEntries(names.map(name=>[name,{{textContent:'0'}}])); const buttons=names.map(name=>({{ dataset:{{workFilter:name}},attrs:{{}}, addEventListener(_name,cb){{this.cb=cb;}}, removeEventListener(){{}}, setAttribute(key,value){{this.attrs[key]=value;}}, }})); const list={{innerHTML:''}}; const status={{textContent:''}}; const document={{ querySelector(selector){{ if(selector==='#my-work-list')return list; if(selector==='#my-work-status')return status; const match=selector.match(/^\\[data-work-count="(.+)"\\]$/); return match?counts[match[1]]:null; }}, querySelectorAll:()=>buttons, }}; const context={{module:{{exports:{{}}}},exports:{{}},console,URL,document}}; vm.createContext(context); vm.runInContext(fs.readFileSync({json.dumps(str(MY_WORK))},'utf8'),context); context.buildMyWork=context.module.exports; context.module={{exports:{{}}}}; vm.runInContext(fs.readFileSync({json.dumps(str(MODULE))},'utf8'),context); const flow=context.module.exports({{document,fetchSnapshot:async()=>({{ user:{{login:'timmy'}}, issues:[{{number:7,title:'Filed from phone',repository:'stackchain/dashboard',assignees:[],work_reasons:['created_by_me'],url:'https://forge.example/issues/7'}}], pull_requests:[{{number:8,title:'Authored release',repository:'stackchain/dashboard',assignees:[],work_reasons:['authored_by_me'],url:'https://forge.example/pulls/8'}}], }})}}); (async()=>{{ await flow.start(); buttons[names.indexOf('filed')].cb(); const filed=list.innerHTML; buttons[names.indexOf('authored')].cb(); const authored=list.innerHTML; buttons[names.indexOf('today')].cb(); const today=list.innerHTML; console.log(JSON.stringify({{filed,authored,today,counts:Object.fromEntries(names.map(name=>[name,counts[name].textContent]))}})); }})().catch(error=>{{console.error(error);process.exit(1);}}); """ result = subprocess.run(["node", "-e", harness], check=True, capture_output=True, text=True) state = json.loads(result.stdout) assert "Filed from phone" in state["filed"] assert "Authored release" in state["authored"] assert "Today is still loading" in state["today"] assert "No work" not in state["today"] assert state["counts"] == { "all": "2", "today": "0", "agenda": "0", "attention": "0", "filed": "1", "authored": "1", "issue": "1", "pull": "1", "review": "0", "update": "0", "later": "0", "draft": "0", } def test_progressive_queue_selection_is_handed_to_the_hydrated_workspace(): harness = f""" const fs=require('fs'); const vm=require('vm'); const buttons=['all','filed'].map(name=>({{ dataset:{{workFilter:name}},attrs:{{}},removed:false, addEventListener(_name,cb){{this.cb=cb;}}, removeEventListener(_name,cb){{if(cb===this.cb)this.removed=true;}}, setAttribute(key,value){{this.attrs[key]=value;}}, }})); const document={{ querySelector:selector=>selector==='#my-work-list'?{{innerHTML:''}}:selector==='#my-work-status'?{{textContent:''}}:null, querySelectorAll:()=>buttons, }}; const context={{module:{{exports:{{}}}},exports:{{}},console,URL,document}}; vm.createContext(context); vm.runInContext(fs.readFileSync({json.dumps(str(MY_WORK))},'utf8'),context); context.buildMyWork=context.module.exports; context.module={{exports:{{}}}}; vm.runInContext(fs.readFileSync({json.dumps(str(MODULE))},'utf8'),context); const flow=context.module.exports({{document,fetchSnapshot:async()=>({{user:{{login:'timmy'}},issues:[],pull_requests:[]}})}}); (async()=>{{ await flow.start(); buttons[1].cb(); const before=flow.handoff(); flow.stop(); const after=flow.handoff(); console.log(JSON.stringify({{before,after,removed:buttons.map(button=>button.removed)}})); }})().catch(error=>{{console.error(error);process.exit(1);}}); """ result = subprocess.run(["node", "-e", harness], check=True, capture_output=True, text=True) state = json.loads(result.stdout) assert state == { "before": {"selectedFilter": "filed"}, "after": {"selectedFilter": "filed"}, "removed": [True, True], } dashboard = DASHBOARD.read_text() assert "const progressiveWorkHandoff = window.stackchainProgressiveMyWork?.handoff?.();" in dashboard assert "progressiveWorkHandoff?.selectedFilter" in dashboard