feat: open progressive My Work in app (Closes #1413)
This commit is contained in:
1 parent
7960f14b5b
commit
94728d81ff
5 files changed
+185
-7
No files matched your search
@@ -6,6 +6,8 @@ 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"
|
||||
INDEX = Path(__file__).parents[1] / "frontend" / "index.html"
|
||||
CSS = Path(__file__).parents[1] / "frontend" / "dashboard.css"
|
||||
|
||||
|
||||
def test_progressive_my_work_renders_and_filters_assigned_work_before_full_workspace():
|
||||
@@ -35,7 +37,8 @@ const flow=createProgressiveMyWork({{document,fetchSnapshot:async()=>({{
|
||||
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 'data-progressive-work-index="0"' in state["rendered"]
|
||||
assert "href=" not in state["rendered"]
|
||||
assert state["pressed"] == ["false", "true"]
|
||||
|
||||
|
||||
@@ -292,7 +295,7 @@ const flow=context.module.exports({{
|
||||
"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>',
|
||||
"html": '<article class="my-work-card progressive-my-work-card"><button class="my-work-card-main" type="button" data-progressive-work-index="0"><strong>Recovered assignment</strong><span class="small">stackchain/dashboard#9 · Assigned to you</span></button></article>',
|
||||
}
|
||||
|
||||
|
||||
@@ -363,5 +366,92 @@ const flow=context.module.exports({{
|
||||
"hasSignal": True,
|
||||
}
|
||||
assert state["latestTitle"] == "New assignment"
|
||||
assert state["removed"] == ["online", "visibilitychange"]
|
||||
assert state["removed"] == ["keydown", "online", "visibilitychange"]
|
||||
assert state["status"] == "1 assigned work item ready."
|
||||
|
||||
|
||||
def test_progressive_card_opens_safe_in_app_detail_and_hands_it_off_once():
|
||||
harness = f"""
|
||||
const fs=require('fs'); const vm=require('vm');
|
||||
function element(extra={{}}) {{
|
||||
return Object.assign({{
|
||||
hidden:false,textContent:'',attrs:{{}},listeners:{{}},focusCount:0,
|
||||
addEventListener(name,cb){{this.listeners[name]=cb;}},
|
||||
removeEventListener(name,cb){{if(this.listeners[name]===cb)delete this.listeners[name];}},
|
||||
setAttribute(name,value){{this.attrs[name]=String(value);}},
|
||||
removeAttribute(name){{delete this.attrs[name];}},
|
||||
focus(){{this.focusCount += 1;}},
|
||||
}},extra);
|
||||
}}
|
||||
const list=element({{innerHTML:''}}); const status=element();
|
||||
const sheet=element({{hidden:true}}); const title=element(); const meta=element(); const reason=element();
|
||||
const close=element(); const gitea=element({{hidden:true,href:''}});
|
||||
const elements={{
|
||||
'#my-work-list':list,'#my-work-status':status,'#progressive-work-detail':sheet,
|
||||
'#progressive-work-detail-title':title,'#progressive-work-detail-meta':meta,
|
||||
'#progressive-work-detail-reason':reason,'#close-progressive-work-detail':close,
|
||||
'#open-progressive-work-gitea':gitea,
|
||||
}};
|
||||
const document={{querySelector:s=>elements[s]||null,querySelectorAll:()=>[]}};
|
||||
const lifecycleTarget=element();
|
||||
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,lifecycleTarget,fetchSnapshot:async()=>({{
|
||||
user:{{login:'timmy'}},issues:[{{number:7,title:'<img src=x onerror=alert(1)>',repository:'stackchain/dashboard',assignees:['timmy'],url:'https://forge.example/issues/7'}}],pull_requests:[]
|
||||
}})}});
|
||||
(async()=>{{
|
||||
await flow.start();
|
||||
const trigger=element({{dataset:{{progressiveWorkIndex:'0'}},closest:()=>trigger}});
|
||||
list.listeners.click({{target:trigger,preventDefault(){{}}}});
|
||||
const opened={{sheetHidden:sheet.hidden,title:title.textContent,meta:meta.textContent,reason:reason.textContent,giteaHidden:gitea.hidden,giteaHref:gitea.href,html:list.innerHTML}};
|
||||
lifecycleTarget.listeners.keydown({{key:'Escape',preventDefault(){{}}}});
|
||||
list.listeners.click({{target:trigger,preventDefault(){{}}}});
|
||||
const first=flow.handoff(); const second=flow.handoff();
|
||||
console.log(JSON.stringify({{opened,closed:sheet.hidden,focusCount:trigger.focusCount,first,second}}));
|
||||
}})().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["opened"] == {
|
||||
"sheetHidden": False,
|
||||
"title": "<img src=x onerror=alert(1)>",
|
||||
"meta": "stackchain/dashboard#7 · Issue",
|
||||
"reason": "Assigned to you",
|
||||
"giteaHidden": False,
|
||||
"giteaHref": "https://forge.example/issues/7",
|
||||
"html": '<article class="my-work-card progressive-my-work-card"><button class="my-work-card-main" type="button" data-progressive-work-index="0"><strong><img src=x onerror=alert(1)></strong><span class="small">stackchain/dashboard#7 · Assigned to you</span></button></article>',
|
||||
}
|
||||
assert state["closed"] is False
|
||||
assert state["focusCount"] == 1
|
||||
assert state["first"]["openWork"] == {
|
||||
"kind": "issue",
|
||||
"key": "stackchain/dashboard#7",
|
||||
"number": 7,
|
||||
"repository": "stackchain/dashboard",
|
||||
}
|
||||
assert "openWork" not in state["second"]
|
||||
|
||||
|
||||
def test_progressive_detail_is_mobile_safe_and_adopted_by_hydrated_workspace():
|
||||
html = INDEX.read_text()
|
||||
css = CSS.read_text()
|
||||
dashboard = DASHBOARD.read_text()
|
||||
|
||||
assert 'id="progressive-work-detail" role="dialog" aria-modal="true"' in html
|
||||
assert 'aria-labelledby="progressive-work-detail-title" hidden' in html
|
||||
assert 'id="close-progressive-work-detail"' in html
|
||||
assert 'id="open-progressive-work-gitea"' in html
|
||||
assert ".progressive-work-detail[hidden] { display:none; }" in css
|
||||
assert "max-height:100dvh" in css
|
||||
assert "overflow-x:hidden" in css
|
||||
assert "env(safe-area-inset-top)" in css
|
||||
assert "env(safe-area-inset-bottom)" in css
|
||||
assert ".progressive-work-detail-panel button, .progressive-work-detail-panel .button-link" in css
|
||||
assert "min-height:44px" in css
|
||||
assert "@media(max-width:320px)" in css
|
||||
assert "progressiveWorkHandoff?.openWork" in dashboard
|
||||
assert "openRoutedWork(progressiveItem, null)" in dashboard
|
||||
assert "qs('#progressive-work-detail').hidden = true" in dashboard
|
||||
Reference in new issue
Block a user