feat: explain Following changes before review (Closes #1322)
This commit is contained in:
1 parent
31700d5c71
commit
5921027e80
8 files changed
+174
-18
No files matched your search
@@ -41,7 +41,8 @@ def test_following_queue_is_phone_usable_at_narrow_viewport(viewport):
|
||||
json:async () => ({revision:1,items:[{
|
||||
repository:'stackchain/api', kind:'pull', number:42,
|
||||
title:'Changed pull request with a long mobile title', state:'open',
|
||||
updated_at:'2026-08-23T05:00:00Z', has_unseen_change:true
|
||||
updated_at:'2026-08-23T05:00:00Z', has_unseen_change:true,
|
||||
change_summary:'Closed since last review'
|
||||
}]})
|
||||
});
|
||||
globalThis.followingReleaseQueue = attachFollowing(() => {});
|
||||
@@ -50,7 +51,7 @@ def test_following_queue_is_phone_usable_at_narrow_viewport(viewport):
|
||||
|
||||
expect(page.locator("#following-sheet")).to_be_visible()
|
||||
expect(page.locator(".following-card")).to_be_visible()
|
||||
expect(page.locator(".following-card")).to_contain_text("New activity")
|
||||
expect(page.locator(".following-card")).to_contain_text("Closed since last review")
|
||||
expect(page.locator(".following-card")).to_contain_text("Pull request")
|
||||
expect(page.locator("#review-following")).to_have_text("Review new activity")
|
||||
assert page.locator("#review-following").bounding_box()["height"] >= 44
|
||||
|
||||
@@ -113,6 +113,69 @@ def test_following_preview_wires_keep_for_later_action_and_retryable_failure():
|
||||
assert "keepForLater:feature.keepForLater" in following
|
||||
|
||||
|
||||
def test_following_conversation_marks_messages_newer_than_last_review():
|
||||
preview_module = ROOT / "frontend" / "search-preview.js"
|
||||
script = f"""
|
||||
require({json.dumps(str(preview_module))});
|
||||
const nodes={{
|
||||
'#search-preview-comments':{{innerHTML:'',textContent:''}},
|
||||
'#search-preview-conversation-status':{{textContent:''}},
|
||||
'#retry-search-preview-conversation':{{hidden:true}},
|
||||
'#load-older-search-preview-comments':{{hidden:true,disabled:false}},
|
||||
}};
|
||||
const document={{querySelector:selector=>nodes[selector]}};
|
||||
global.renderSearchPreviewConversation({{
|
||||
status:'ready', reviewedAt:'2026-08-23T03:00:00Z', comments:[
|
||||
{{id:1,author:'Timmy',created_at:'2026-08-23T02:00:00Z',body:'Earlier'}},
|
||||
{{id:2,author:'Alex',created_at:'2026-08-23T04:00:00Z',body:'New decision'}},
|
||||
]
|
||||
}},document,String,String,String);
|
||||
process.stdout.write(JSON.stringify({{html:nodes['#search-preview-comments'].innerHTML,status:nodes['#search-preview-conversation-status'].textContent}}));
|
||||
"""
|
||||
|
||||
result = json.loads(subprocess.run(
|
||||
["node", "-e", script], text=True, capture_output=True, check=True
|
||||
).stdout)
|
||||
|
||||
assert result["html"].count("new-since-review") == 1
|
||||
assert "New since last review" in result["html"]
|
||||
assert result["status"] == "2 messages · 1 new since last review."
|
||||
|
||||
|
||||
def test_following_revision_is_acknowledged_only_after_conversation_context_loads():
|
||||
preview_module = ROOT / "frontend" / "search-preview.js"
|
||||
script = f"""
|
||||
const createSearchPreview=require({json.dumps(str(preview_module))});
|
||||
let unavailable=true;
|
||||
const opened=[];
|
||||
const preview=createSearchPreview({{
|
||||
fetchJson:async item=>({{...item,title:'Changed issue'}}),
|
||||
fetchConversation:async()=>{{if(unavailable)throw new Error('offline');return {{comments:[]}};}},
|
||||
onOpened:async item=>opened.push(item.updated_at),
|
||||
onState:()=>{{}},
|
||||
}});
|
||||
(async()=>{{
|
||||
await preview.open({{
|
||||
repository:'stackchain/api',kind:'issue',number:42,following:true,
|
||||
reviewed_at:'2026-08-23T03:00:00Z',updated_at:'2026-08-23T04:00:00Z'
|
||||
}});
|
||||
const afterFailure=[...opened];
|
||||
unavailable=false;
|
||||
await preview.retryConversation();
|
||||
process.stdout.write(JSON.stringify({{afterFailure,opened}}));
|
||||
}})().catch(error=>{{console.error(error);process.exit(1);}});
|
||||
"""
|
||||
|
||||
result = json.loads(subprocess.run(
|
||||
["node", "-e", script], text=True, capture_output=True, check=True
|
||||
).stdout)
|
||||
|
||||
assert result == {
|
||||
"afterFailure": [],
|
||||
"opened": ["2026-08-23T04:00:00Z"],
|
||||
}
|
||||
|
||||
|
||||
def test_following_typed_identity_prevents_issue_pull_collisions():
|
||||
script = f"""
|
||||
const createFollowing = require({json.dumps(str(MODULE))});
|
||||
|
||||
@@ -51,6 +51,34 @@ def test_refresh_marks_changed_items_unseen_and_sorts_them_first(tmp_path):
|
||||
assert snapshot["items"][0]["state"] == "closed"
|
||||
|
||||
|
||||
def test_refresh_explains_change_from_last_reviewed_snapshot(tmp_path):
|
||||
store = FollowingStore(tmp_path / "following.sqlite3", encryption_key=KEY)
|
||||
store.set_watching("timmy", ITEM, True)
|
||||
|
||||
closed = store.refresh("timmy", [{
|
||||
**ITEM,
|
||||
"title": "Make mobile review trustworthy",
|
||||
"state": "closed",
|
||||
"updated_at": "2026-08-23T04:00:00Z",
|
||||
}])
|
||||
|
||||
assert closed["items"][0]["reviewed_at"] == ITEM["updated_at"]
|
||||
assert closed["items"][0]["change_summary"] == "Closed since last review"
|
||||
|
||||
store.acknowledge(
|
||||
"timmy", ITEM["repository"], ITEM["number"], "2026-08-23T04:00:00Z"
|
||||
)
|
||||
title_change = store.refresh("timmy", [{
|
||||
**ITEM,
|
||||
"title": "Make mobile review understandable",
|
||||
"state": "closed",
|
||||
"updated_at": "2026-08-23T05:00:00Z",
|
||||
}])
|
||||
|
||||
assert title_change["items"][0]["reviewed_at"] == "2026-08-23T04:00:00Z"
|
||||
assert title_change["items"][0]["change_summary"] == "Title changed since last review"
|
||||
|
||||
|
||||
def test_acknowledgement_is_revision_conditional_and_later_change_is_unseen(tmp_path):
|
||||
store = FollowingStore(tmp_path / "following.sqlite3", encryption_key=KEY)
|
||||
store.set_watching("timmy", ITEM, True)
|
||||
|
||||
Reference in new issue
Block a user