feat: keep progressive My Work live (Closes #1411)
This commit is contained in:
1 parent
e7e8c24d05
commit
f9b29d8d14
2 files changed
+186
-17
No files matched your search
@@ -243,4 +243,125 @@ const flow=context.module.exports({{document,fetchSnapshot:async()=>({{
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", harness], check=True, capture_output=True, text=True)
|
||||
|
||||
assert json.loads(result.stdout) == {"before": "", "after": "timmy", "stopped": "timmy"}
|
||||
assert json.loads(result.stdout) == {"before": "", "after": "timmy", "stopped": "timmy"}
|
||||
|
||||
|
||||
def test_progressive_my_work_retries_a_failed_snapshot_without_reloading():
|
||||
harness = f"""
|
||||
const fs=require('fs'); const vm=require('vm');
|
||||
const list={{innerHTML:''}}; const status={{textContent:''}};
|
||||
const document={{
|
||||
querySelector:selector=>selector==='#my-work-list'?list:selector==='#my-work-status'?status:null,
|
||||
querySelectorAll:()=>[],
|
||||
}};
|
||||
let timer=null; let attempts=0;
|
||||
const context={{module:{{exports:{{}}}},exports:{{}},console,URL,URLSearchParams,AbortController,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(Path(__file__).parents[1] / 'frontend' / 'context-poller.js'))},'utf8'),context);
|
||||
context.createContextPoller=context.module.exports; context.module={{exports:{{}}}};
|
||||
vm.runInContext(fs.readFileSync({json.dumps(str(MODULE))},'utf8'),context);
|
||||
const flow=context.module.exports({{
|
||||
document,
|
||||
fetchSnapshot:async()=>{{
|
||||
attempts += 1;
|
||||
if(attempts===1) throw new Error('temporary outage');
|
||||
return {{context:{{user:{{login:'timmy'}},issues:[{{
|
||||
number:9,title:'Recovered assignment',repository:'stackchain/dashboard',
|
||||
assignees:['timmy'],url:'https://forge.example/issues/9',
|
||||
}}],pull_requests:[]}},events:[],notifications:[]}};
|
||||
}},
|
||||
pollerOptions:{{
|
||||
setTimer:callback=>{{timer=callback; return 1;}}, clearTimer:()=>{{timer=null;}},
|
||||
setDeadlineTimer:()=>2, clearDeadlineTimer:()=>{{}}, intervalMs:5,
|
||||
}},
|
||||
}});
|
||||
(async()=>{{
|
||||
const first=await flow.start();
|
||||
const reconnecting=status.textContent;
|
||||
if(!timer) throw new Error('retry was not scheduled');
|
||||
timer(); await Promise.resolve(); await Promise.resolve(); await new Promise(resolve=>setImmediate(resolve));
|
||||
console.log(JSON.stringify({{first,reconnecting,attempts,status:status.textContent,html:list.innerHTML}}));
|
||||
}})().catch(error=>{{console.error(error);process.exit(1);}});
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", harness], check=True, capture_output=True, text=True)
|
||||
|
||||
assert json.loads(result.stdout) == {
|
||||
"first": False,
|
||||
"reconnecting": "Assigned work is reconnecting…",
|
||||
"attempts": 2,
|
||||
"status": "1 assigned work item ready.",
|
||||
"html": '<article class="my-work-card progressive-my-work-card"><a class="my-work-card-main" href="https://forge.example/issues/9"><strong>Recovered assignment</strong><span class="small">stackchain/dashboard#9 · Assigned to you</span></a></article>',
|
||||
}
|
||||
|
||||
|
||||
def test_progressive_my_work_refreshes_with_revisions_and_coalesces_lifecycle_recovery():
|
||||
harness = f"""
|
||||
const fs=require('fs'); const vm=require('vm');
|
||||
const list={{innerHTML:''}}; const status={{textContent:''}};
|
||||
const document={{
|
||||
hidden:false,
|
||||
querySelector:selector=>selector==='#my-work-list'?list:selector==='#my-work-status'?status:null,
|
||||
querySelectorAll:()=>[],
|
||||
}};
|
||||
const handlers={{}}; const removed=[];
|
||||
const lifecycleTarget={{
|
||||
addEventListener:(name,callback)=>{{handlers[name]=callback;}},
|
||||
removeEventListener:(name,callback)=>{{if(handlers[name]===callback){{removed.push(name);delete handlers[name];}}}},
|
||||
}};
|
||||
let timer=null; let calls=[]; let resolveRefresh;
|
||||
const initial={{
|
||||
context:{{user:{{login:'timmy'}},issues:[],pull_requests:[]}},events:[],notifications:[],
|
||||
revisions:{{context:'0123456789abcdef.1',events:'fedcba9876543210.2',notifications:'0011223344556677.3'}},
|
||||
freshness:{{fresh_for_seconds:8,sections:{{context:{{degraded:false,age_seconds:2}},events:{{degraded:false,age_seconds:2}},notifications:{{degraded:false,age_seconds:2}}}}}},
|
||||
}};
|
||||
const updated={{...initial,context:{{user:{{login:'timmy'}},issues:[{{number:10,title:'New assignment',repository:'stackchain/dashboard',assignees:['timmy']}}],pull_requests:[]}}}};
|
||||
const context={{module:{{exports:{{}}}},exports:{{}},console,URL,URLSearchParams,AbortController,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(Path(__file__).parents[1] / 'frontend' / 'context-poller.js'))},'utf8'),context);
|
||||
context.createContextPoller=context.module.exports; context.module={{exports:{{}}}};
|
||||
vm.runInContext(fs.readFileSync({json.dumps(str(MODULE))},'utf8'),context);
|
||||
const flow=context.module.exports({{
|
||||
document,lifecycleTarget,
|
||||
fetchSnapshot:(revisions,options)=>{{
|
||||
calls.push({{revisions,hasSignal:Boolean(options.signal)}});
|
||||
if(calls.length===1)return Promise.resolve(initial);
|
||||
return new Promise(resolve=>{{resolveRefresh=()=>resolve(updated);}});
|
||||
}},
|
||||
pollerOptions:{{
|
||||
setTimer:callback=>{{timer=callback;return 1;}},clearTimer:()=>{{timer=null;}},
|
||||
setDeadlineTimer:()=>2,clearDeadlineTimer:()=>{{}},intervalMs:5,
|
||||
}},
|
||||
}});
|
||||
(async()=>{{
|
||||
await flow.start(); const freshnessTimer=timer; freshnessTimer();
|
||||
handlers.online(); handlers.visibilitychange();
|
||||
await Promise.resolve();
|
||||
const callsWhilePending=calls.length;
|
||||
resolveRefresh(); await Promise.resolve(); await new Promise(resolve=>setImmediate(resolve));
|
||||
const handoff=flow.handoff(); flow.stop();
|
||||
console.log(JSON.stringify({{
|
||||
callsWhilePending,calls,latestTitle:handoff.liveSnapshot.context.issues[0].title,
|
||||
removed:removed.sort(),status:status.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 state["callsWhilePending"] == 2
|
||||
assert state["calls"][0] == {"revisions": {}, "hasSignal": True}
|
||||
assert state["calls"][1] == {
|
||||
"revisions": {
|
||||
"context": "0123456789abcdef.1",
|
||||
"events": "fedcba9876543210.2",
|
||||
"notifications": "0011223344556677.3",
|
||||
},
|
||||
"hasSignal": True,
|
||||
}
|
||||
assert state["latestTitle"] == "New assignment"
|
||||
assert state["removed"] == ["online", "visibilitychange"]
|
||||
assert state["status"] == "1 assigned work item ready."
|
||||
Reference in new issue
Block a user