feat: start work from unread updates (Closes #505)
This commit is contained in:
1 parent
b26b57d616
commit
2717f41957
6 files changed
+237
-14
No files matched your search
@@ -176,19 +176,137 @@ controller.open({{repository:'stackchain/api', issue:{{number:7, claimable:true}
|
||||
}
|
||||
|
||||
|
||||
def test_unread_issue_update_claims_and_starts_once_without_marking_it_read():
|
||||
script = f"""
|
||||
const createUpdateOwnership = require({json.dumps(str(UPDATE_OWNERSHIP))});
|
||||
let claims = 0;
|
||||
let starts = 0;
|
||||
let finishClaim;
|
||||
const reconciled = [];
|
||||
const states = [];
|
||||
const controller = createUpdateOwnership({{
|
||||
available: () => true,
|
||||
claim: () => {{ claims += 1; return new Promise(resolve => {{ finishClaim = resolve; }}); }},
|
||||
addToday: () => 'added',
|
||||
start: item => {{ starts += 1; return item.notification_id === 42 ? 'started' : 'broken'; }},
|
||||
recover: () => {{ throw new Error('recovery should not run'); }},
|
||||
onClaimed: item => reconciled.push(item),
|
||||
onStartState: state => states.push(state),
|
||||
onState: () => {{}},
|
||||
}});
|
||||
controller.open({{
|
||||
repository:'stackchain/api', title:'Retry deploy',
|
||||
issue:{{number:7, assignees:[], claimable:true}},
|
||||
}}, {{notification_id:42, updated_at:'2026-08-08T12:00:00Z'}});
|
||||
const first = controller.start();
|
||||
const second = controller.start();
|
||||
if (first !== second || claims !== 1) throw new Error('start was not single-flight');
|
||||
finishClaim({{number:7, title:'Retry deploy', assignees:['timmy'], state:'open'}});
|
||||
(async () => {{
|
||||
const result = await first;
|
||||
process.stdout.write(JSON.stringify({{result, claims, starts, reconciled, states}}));
|
||||
}})().catch(error => {{ console.error(error); process.exit(1); }});
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
output = json.loads(result.stdout)
|
||||
assert output["result"] == "started"
|
||||
assert output["claims"] == 1
|
||||
assert output["starts"] == 1
|
||||
assert len(output["reconciled"]) == 1
|
||||
assert output["reconciled"][0]["notification_id"] == 42
|
||||
assert output["reconciled"][0]["has_update"] is True
|
||||
assert output["states"][-1] == {
|
||||
"action": "hidden",
|
||||
"busy": False,
|
||||
"message": "Assigned, added to Today, and ready to work. The update is still unread.",
|
||||
}
|
||||
|
||||
|
||||
def test_unread_update_start_checks_today_capacity_before_claiming():
|
||||
script = f"""
|
||||
const createUpdateOwnership = require({json.dumps(str(UPDATE_OWNERSHIP))});
|
||||
let claims = 0;
|
||||
const states = [];
|
||||
const controller = createUpdateOwnership({{
|
||||
available: () => false,
|
||||
claim: () => {{ claims += 1; return Promise.resolve({{number:7}}); }},
|
||||
addToday: () => 'added', start: () => 'started',
|
||||
onStartState: state => states.push(state), onState: () => {{}},
|
||||
}});
|
||||
controller.open({{repository:'stackchain/api', issue:{{number:7, claimable:true}}}}, {{notification_id:42}});
|
||||
(async () => {{
|
||||
const result = await controller.start();
|
||||
process.stdout.write(JSON.stringify({{result, claims, states}}));
|
||||
}})().catch(error => {{ console.error(error); process.exit(1); }});
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
output = json.loads(result.stdout)
|
||||
assert output["result"] == "full"
|
||||
assert output["claims"] == 0
|
||||
assert output["states"][-1] == {
|
||||
"action": "start",
|
||||
"busy": False,
|
||||
"message": "Today is limited to 5 items. Remove one before taking ownership.",
|
||||
}
|
||||
|
||||
|
||||
def test_unread_update_opens_owned_issue_when_today_start_throws():
|
||||
script = f"""
|
||||
const createUpdateOwnership = require({json.dumps(str(UPDATE_OWNERSHIP))});
|
||||
let recovered = null;
|
||||
const states = [];
|
||||
const controller = createUpdateOwnership({{
|
||||
available: () => true,
|
||||
claim: () => Promise.resolve({{number:7, title:'Retry deploy'}}),
|
||||
addToday: () => 'added',
|
||||
start: () => {{ throw new Error('storage failed'); }},
|
||||
recover: item => {{ recovered = item; }},
|
||||
onStartState: state => states.push(state), onState: () => {{}},
|
||||
}});
|
||||
controller.open({{repository:'stackchain/api', issue:{{number:7, claimable:true}}}}, {{notification_id:42}});
|
||||
(async () => {{
|
||||
const result = await controller.start();
|
||||
process.stdout.write(JSON.stringify({{result, recovered, states}}));
|
||||
}})().catch(error => {{ console.error(error); process.exit(1); }});
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
output = json.loads(result.stdout)
|
||||
assert output["result"] == "recovery"
|
||||
assert output["recovered"]["number"] == 7
|
||||
assert output["states"][-1] == {
|
||||
"action": "hidden",
|
||||
"busy": False,
|
||||
"message": "Assigned to you, but Today could not start. The update is still unread; the owned issue is open so you can recover.",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mobile_update_sheet_wires_phone_safe_ownership_to_my_work_and_today():
|
||||
html = await dashboard()
|
||||
|
||||
assert '<script src="static/update-ownership.js"></script>' in html
|
||||
assert 'id="update-ownership-action"' in html
|
||||
assert 'id="update-ownership-start"' in html
|
||||
assert '>Take ownership & start</button>' in html
|
||||
assert 'hidden aria-describedby="update-sheet-status"' in html
|
||||
assert 'const updateOwnership = createUpdateOwnership({' in html
|
||||
assert 'available: () => createAndStart.available()' in html
|
||||
assert 'const outcome = createAndStart.complete(item)' in html
|
||||
assert "openRoutedWork(item, qs('#update-ownership-start'))" in html
|
||||
assert 'updateOwnership.open(detail, selectedUpdate)' in html
|
||||
assert "qs('#update-ownership-action').addEventListener('click'" in html
|
||||
assert "qs('#update-ownership-start').addEventListener('click', () => updateOwnership.start())" in html
|
||||
assert "lastContextSnapshot.issues" in html
|
||||
assert "todayWork.add(item)" in html
|
||||
assert '.update-sheet-actions button, .update-sheet-actions a { min-height:44px;' in html
|
||||
assert '.update-ownership-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr));' in html
|
||||
assert '.update-sheet-panel { width:100%; border-left:0; padding:14px; overflow-x:hidden;' in html
|
||||
|
||||
|
||||
def test_work_routes_round_trip_all_sheet_kinds_and_reject_unsafe_fragments():
|
||||
|
||||
Reference in new issue
Block a user