feat: retire closed issues during Following review (Closes #1305)
This commit is contained in:
1 parent
fdf38a72ac
commit
c4bb7a9be1
20 files changed
+278
-46
No files matched your search
@@ -901,7 +901,7 @@ def test_mobile_search_preview_exposes_touch_safe_watch_action():
|
||||
assert ".search-preview-actions button" in css and "min-height:44px" in css
|
||||
|
||||
|
||||
def test_search_preview_watch_is_available_for_every_open_issue_but_not_pulls_or_closed_issues():
|
||||
def test_search_preview_watch_is_available_for_open_issues_and_closed_following_retirement():
|
||||
script = f"""
|
||||
require({json.dumps(str(SEARCH_PREVIEW))});
|
||||
(async () => {{
|
||||
@@ -925,7 +925,10 @@ for (const detail of [
|
||||
globalThis.renderSearchPreviewWatch(detail, {{status:'ready'}}, candidate);
|
||||
excluded.push(candidate.hidden);
|
||||
}}
|
||||
process.stdout.write(JSON.stringify({{loaded,watching:hydrated.watching,button,excluded}}));
|
||||
const closedFollowing = await options.load({{...assigned,state:'closed',following:true}});
|
||||
const retire = {{hidden:true,textContent:'',disabled:false}};
|
||||
globalThis.renderSearchPreviewWatch(closedFollowing, {{status:'ready'}}, retire);
|
||||
process.stdout.write(JSON.stringify({{loaded,watching:hydrated.watching,button,excluded,closedFollowing,retire}}));
|
||||
}})().catch(error=>{{console.error(error);process.exit(1);}});
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
@@ -942,6 +945,98 @@ process.stdout.write(JSON.stringify({{loaded,watching:hydrated.watching,button,e
|
||||
"disabled": False,
|
||||
}
|
||||
assert payload["excluded"] == [True, True]
|
||||
assert payload["closedFollowing"]["watching"] is True
|
||||
assert payload["retire"] == {
|
||||
"hidden": False,
|
||||
"textContent": "Stop watching & next",
|
||||
"disabled": False,
|
||||
}
|
||||
|
||||
|
||||
def test_closed_following_unwatch_is_single_flight_and_advances_only_after_confirmation():
|
||||
script = f"""
|
||||
const createSearchPreview = require({json.dumps(str(SEARCH_PREVIEW))});
|
||||
(async () => {{
|
||||
let finish;
|
||||
const states=[]; const retired=[]; const opened=[]; let calls=0;
|
||||
const next={{repository:'stackchain/web',number:9,kind:'issue',state:'open'}};
|
||||
const preview=createSearchPreview({{
|
||||
fetchJson:item => Promise.resolve(item), mutate:()=>Promise.resolve(),
|
||||
watch:() => {{ calls += 1; return new Promise(resolve => finish=resolve); }},
|
||||
afterUnwatch:item => {{ retired.push(item.number); return next; }},
|
||||
onNavigate:item => opened.push(item.number), onState:state => states.push(state),
|
||||
}});
|
||||
await preview.open({{repository:'stackchain/api',number:42,kind:'issue',state:'closed',following:true,watching:true}});
|
||||
const first=preview.setWatching(false); const second=preview.setWatching(false);
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
const before={{calls,retired:[...retired],opened:[...opened]}};
|
||||
finish({{watching:false,following_synced:true}});
|
||||
await Promise.all([first,second]);
|
||||
process.stdout.write(JSON.stringify({{before,calls,same:first===second,retired,opened,last:states.at(-1)}}));
|
||||
}})().catch(error=>{{console.error(error);process.exit(1);}});
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
payload = json.loads(result.stdout)
|
||||
assert payload["before"] == {"calls": 1, "retired": [], "opened": []}
|
||||
assert payload["same"] is True
|
||||
assert payload["retired"] == [42]
|
||||
assert payload["opened"] == [9]
|
||||
assert payload["last"]["detail"]["number"] == 9
|
||||
|
||||
|
||||
def test_search_subscription_preview_hydrates_detail_before_watch_state():
|
||||
script = f"""
|
||||
require({json.dumps(str(SEARCH_PREVIEW))});
|
||||
(async () => {{
|
||||
const calls=[];
|
||||
const options=globalThis.searchPreviewSubscriptionOptions(async path => {{
|
||||
calls.push(path);
|
||||
if (path.includes('/subscription')) return {{watching:true}};
|
||||
return {{repository:'stackchain/api',number:42,kind:'issue',state:'open',title:'Hydrated'}};
|
||||
}});
|
||||
const detail=await options.preview({{repository:'stackchain/api',number:42,kind:'issue'}});
|
||||
process.stdout.write(JSON.stringify({{calls,detail}}));
|
||||
}})().catch(error=>{{console.error(error);process.exit(1);}});
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {
|
||||
"calls": [
|
||||
"api/v1/repos/stackchain/api/issues/42/preview?kind=issue",
|
||||
"api/v1/repos/stackchain/api/issues/42/preview/subscription?kind=issue",
|
||||
],
|
||||
"detail": {
|
||||
"repository": "stackchain/api", "number": 42, "kind": "issue",
|
||||
"state": "open", "title": "Hydrated", "watching": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_closed_following_preview_preserves_origin_and_skips_status_lookup():
|
||||
script = f"""
|
||||
require({json.dumps(str(SEARCH_PREVIEW))});
|
||||
(async () => {{
|
||||
const calls=[];
|
||||
const options=globalThis.searchPreviewSubscriptionOptions(async path => {{
|
||||
calls.push(path);
|
||||
return {{repository:'stackchain/api',number:42,kind:'issue',state:'closed',title:'Finished'}};
|
||||
}});
|
||||
const detail=await options.preview({{repository:'stackchain/api',number:42,kind:'issue',following:true}});
|
||||
process.stdout.write(JSON.stringify({{calls,detail}}));
|
||||
}})().catch(error=>{{console.error(error);process.exit(1);}});
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
payload = json.loads(result.stdout)
|
||||
assert payload["calls"] == [
|
||||
"api/v1/repos/stackchain/api/issues/42/preview?kind=issue"
|
||||
]
|
||||
assert payload["detail"]["following"] is True
|
||||
assert payload["detail"]["watching"] is True
|
||||
|
||||
|
||||
def test_search_preview_shares_canonical_url_without_closing_the_preview():
|
||||
|
||||
@@ -303,4 +303,4 @@ async def test_unread_update_offers_reply_mark_read_and_next_independent_of_toda
|
||||
assert '.update-reply-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr));' in html
|
||||
assert '.update-reply-actions button { min-height:44px;' in html
|
||||
worker = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
|
||||
assert "stackchain-dashboard-shell-v133" in worker
|
||||
assert "stackchain-dashboard-shell-v134" in worker
|
||||
@@ -249,3 +249,49 @@ async def test_following_acknowledges_only_the_exact_loaded_revision(monkeypatch
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["items"][0]["has_unseen_change"] is False
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_closed_following_issue_can_be_unwatched_but_not_newly_watched(monkeypatch, tmp_path):
|
||||
store = FollowingStore(tmp_path / "following.sqlite3", encryption_key=b"g" * 32)
|
||||
closed = {
|
||||
"repository": "stackchain/api", "number": 42, "title": "Finished work",
|
||||
"state": "closed", "updated_at": "2026-08-23T04:00:00Z",
|
||||
"url": "https://forge.example/stackchain/api/issues/42",
|
||||
}
|
||||
store.set_watching("timmy", closed, True)
|
||||
mutations = []
|
||||
|
||||
async def preview(repository, kind, number):
|
||||
return {**closed, "kind": "issue"}
|
||||
|
||||
async def set_subscription(repository, number, watching):
|
||||
mutations.append((repository, number, watching))
|
||||
return {"watching": watching}
|
||||
|
||||
async def user():
|
||||
return {"login": "timmy"}
|
||||
|
||||
monkeypatch.setattr(main, "_following_store", lambda: store)
|
||||
monkeypatch.setattr(main.gitea_proxy, "work_preview", preview)
|
||||
monkeypatch.setattr(main.gitea_proxy, "set_issue_subscription", set_subscription)
|
||||
monkeypatch.setattr(main, "current_user", user)
|
||||
transport = httpx.ASGITransport(app=main.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
watch = await client.put(
|
||||
"/api/v1/repos/stackchain/api/issues/42/preview/subscription?kind=issue"
|
||||
)
|
||||
unwatch = await client.delete(
|
||||
"/api/v1/repos/stackchain/api/issues/42/preview/subscription?kind=issue"
|
||||
)
|
||||
|
||||
assert watch.status_code == 404
|
||||
assert unwatch.status_code == 200
|
||||
assert unwatch.json() == {
|
||||
"watching": False,
|
||||
"following_synced": True,
|
||||
"following_revision": 2,
|
||||
"following_count": 0,
|
||||
}
|
||||
assert mutations == [("stackchain/api", 42, False)]
|
||||
assert store.get("timmy")["items"] == []
|
||||
@@ -46,6 +46,7 @@ process.stdout.write(JSON.stringify(state));
|
||||
"url": "https://forge.example/issue/42",
|
||||
"has_unseen_change": False,
|
||||
"kind": "issue",
|
||||
"following": True,
|
||||
}]
|
||||
|
||||
|
||||
@@ -133,6 +134,7 @@ const feature = createFollowing({{
|
||||
assert result["started"] is True
|
||||
assert [item["number"] for item in result["session"]["items"]] == [42, 9]
|
||||
assert all(item["kind"] == "issue" for item in result["session"]["items"])
|
||||
assert all(item["following"] is True for item in result["session"]["items"])
|
||||
assert result["session"]["more"] is False
|
||||
assert result["state"] == {"opened": [42], "acknowledged": [42]}
|
||||
|
||||
@@ -196,6 +198,46 @@ const feature = createFollowing({{
|
||||
).stdout) == {"beforeFinish": [], "completed": ["following"]}
|
||||
|
||||
|
||||
def test_following_retirement_removes_closed_item_and_keeps_review_moving():
|
||||
script = f"""
|
||||
const createFollowing = require({json.dumps(str(MODULE))});
|
||||
const state = {{renders:[], counts:[], completed:[]}};
|
||||
const items=[
|
||||
{{repository:'stackchain/api',number:42,title:'Closed first',state:'closed',updated_at:'2026-08-23T06:00:00Z',has_unseen_change:true}},
|
||||
{{repository:'stackchain/web',number:9,title:'Closed second',state:'closed',updated_at:'2026-08-23T05:00:00Z',has_unseen_change:true}}
|
||||
];
|
||||
const feature = createFollowing({{
|
||||
fetchJson:async () => ({{revision:7,items}}),
|
||||
render:value => state.renders.push(value), onCount:value => state.counts.push(value),
|
||||
onOpen:async () => {{}}, onAcknowledge:async () => {{}},
|
||||
onReviewComplete:() => state.completed.push('following'),
|
||||
}});
|
||||
(async () => {{
|
||||
await feature.load();
|
||||
await feature.startReview();
|
||||
const first=feature.session().items[0];
|
||||
const next=feature.retire(first);
|
||||
await feature.previewLoaded(next);
|
||||
const final=feature.retire(next);
|
||||
const handoff=feature.finishReview();
|
||||
process.stdout.write(JSON.stringify({{
|
||||
next:next.number, final, handoff, items:feature.items(), session:feature.session(), state
|
||||
}}));
|
||||
}})().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["next"] == 9
|
||||
assert result["final"] is None
|
||||
assert result["handoff"] is True
|
||||
assert result["items"] == []
|
||||
assert result["session"] is None
|
||||
assert result["state"]["counts"][-1] == 0
|
||||
assert result["state"]["completed"] == ["following"]
|
||||
|
||||
|
||||
def test_following_review_never_clears_activity_newer_than_the_loaded_preview():
|
||||
script = f"""
|
||||
const createFollowing = require({json.dumps(str(MODULE))});
|
||||
@@ -250,11 +292,12 @@ def test_following_review_controls_are_wired_into_the_phone_preview_flow():
|
||||
assert "query('#review-following').addEventListener('click'" in following
|
||||
assert "getSession:() => followingQueue.session() || commandSearchState" in dashboard
|
||||
assert "onOpened:item => followingQueue.previewLoaded(item)" in dashboard
|
||||
assert "afterUnwatch:item => followingQueue.retire(item)" in dashboard
|
||||
assert "followingQueue.returnToFollowing()" in dashboard
|
||||
assert "'Back to Following'" in dashboard
|
||||
assert "if (searchPreviewReturnKind === 'following')" in dashboard
|
||||
assert "e.key === 'Escape' && searchPreviewReturnKind === 'following'" in dashboard
|
||||
assert "stackchain-dashboard-shell-v133" in service_worker
|
||||
assert "stackchain-dashboard-shell-v134" in service_worker
|
||||
|
||||
|
||||
def test_prepare_today_lazily_refreshes_and_directly_reviews_following():
|
||||
|
||||
@@ -435,5 +435,5 @@ async def test_dashboard_syncs_every_later_change_and_exposes_account_status():
|
||||
def test_later_sync_ships_atomically_in_the_offline_shell():
|
||||
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/later-sync.js'" in source
|
||||
@@ -256,4 +256,4 @@ def test_markdown_work_bodies_are_mobile_safe_block_containers():
|
||||
assert ".markdown-content { min-width:0; max-width:100%; overflow-wrap:anywhere;" in css
|
||||
assert ".markdown-content pre { max-width:100%; overflow-x:auto;" in css
|
||||
assert ".markdown-content a { min-height:44px;" in css
|
||||
assert "stackchain-dashboard-shell-v133" in worker
|
||||
assert "stackchain-dashboard-shell-v134" in worker
|
||||
@@ -45,7 +45,7 @@ def test_offline_shell_contains_every_local_dashboard_runtime_asset():
|
||||
shell_assets = set(re.findall(r"BASE \+ '([^']+)'", worker.split("async function sessionCsrf", 1)[0]))
|
||||
|
||||
assert local_assets <= shell_assets, f"Offline shell is missing: {sorted(local_assets - shell_assets)}"
|
||||
assert "stackchain-dashboard-shell-v133" in worker
|
||||
assert "stackchain-dashboard-shell-v134" in worker
|
||||
|
||||
|
||||
def test_all_conversation_composers_offer_accessible_mobile_mentions():
|
||||
|
||||
@@ -383,7 +383,7 @@ def test_mobile_dashboard_mounts_phone_safe_device_setup_flow():
|
||||
assert "controller.recoverPermission('deadline')" in dashboard
|
||||
assert "pushControllerReady.then(ensureDeviceSetup)" in dashboard
|
||||
assert "BASE + 'static/mobile-device-setup.js'" in worker
|
||||
assert "stackchain-dashboard-shell-v133" in worker
|
||||
assert "stackchain-dashboard-shell-v134" in worker
|
||||
assert ".device-setup-panel" in css
|
||||
assert ".device-readiness-card" in css
|
||||
assert "overflow-x:hidden" in css
|
||||
|
||||
@@ -243,5 +243,5 @@ async def test_mobile_home_progressively_discloses_secondary_panels_as_insights(
|
||||
def test_mobile_insights_rolls_into_the_offline_shell():
|
||||
worker = (CONTROLLER.parent / "service-worker.js").read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in worker
|
||||
assert "stackchain-dashboard-shell-v134" in worker
|
||||
assert "BASE + 'static/mobile-insights.js'" in worker
|
||||
@@ -414,7 +414,7 @@ async def test_dashboard_wires_thumb_safe_start_day_briefing_into_offline_mobile
|
||||
assert ".mobile-start-day-finish { min-height:44px;" in html
|
||||
assert "max-width:100%; overflow-wrap:anywhere;" in html
|
||||
assert "BASE + 'static/mobile-start-day.js'" in service_worker
|
||||
assert "stackchain-dashboard-shell-v133" in service_worker
|
||||
assert "stackchain-dashboard-shell-v134" in service_worker
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
|
||||
@@ -418,7 +418,7 @@ async def test_starting_saved_today_work_closes_a_concurrent_rollover_planner():
|
||||
def test_plan_today_controller_is_available_in_the_offline_shell():
|
||||
source = SERVICE_WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/plan-today.js'" in source
|
||||
assert "BASE + 'static/plan-today-readiness.js'" in source
|
||||
assert "BASE + 'static/plan-today-preview.js'" in source
|
||||
@@ -186,7 +186,7 @@ async function dispatchPush(payload) {{
|
||||
def test_week_unplan_undo_rolls_the_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/week-plan.js'" in source
|
||||
assert "BASE + 'static/dashboard.css'" in source
|
||||
|
||||
@@ -194,20 +194,20 @@ def test_week_unplan_undo_rolls_the_offline_shell():
|
||||
def test_private_today_action_mailbox_rolls_the_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
|
||||
|
||||
def test_per_day_week_conflict_ui_rolls_the_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/week-plan.js'" in source
|
||||
|
||||
|
||||
def test_resumable_today_session_ships_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/my-work.js'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
assert "BASE + 'static/dashboard.css'" in source
|
||||
@@ -216,7 +216,7 @@ def test_resumable_today_session_ships_in_a_new_offline_shell():
|
||||
def test_mobile_conversation_photo_bundles_roll_the_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
assert "BASE + 'static/authored-outbox.js'" in source
|
||||
assert "BASE + 'static/background-issue-sync.js'" in source
|
||||
@@ -225,7 +225,7 @@ def test_mobile_conversation_photo_bundles_roll_the_offline_shell():
|
||||
def test_photo_metadata_sanitizer_rolls_the_cached_optimizer_atomically():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/issue-evidence-review.js'" in source
|
||||
assert "BASE + 'static/issue-attachment.js'" in source
|
||||
|
||||
@@ -233,14 +233,14 @@ def test_photo_metadata_sanitizer_rolls_the_cached_optimizer_atomically():
|
||||
def test_ownership_exit_runtime_rolls_the_offline_shell_cache():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
|
||||
|
||||
def test_offline_review_next_ships_today_completion_atomically():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/today-completion.js'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
|
||||
@@ -248,7 +248,7 @@ def test_offline_review_next_ships_today_completion_atomically():
|
||||
def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/create-issue-sheet.js'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
|
||||
@@ -256,7 +256,7 @@ def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
|
||||
def test_inline_checklist_step_flow_rolls_the_offline_shell_atomically():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/issue-sheet.js'" in source
|
||||
assert "BASE + 'static/checklist-conflict.js'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
@@ -266,14 +266,14 @@ def test_inline_checklist_step_flow_rolls_the_offline_shell_atomically():
|
||||
def test_exact_later_picker_ships_atomically_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/later-picker.js'" in source
|
||||
|
||||
|
||||
def test_navigation_deadline_ships_in_a_new_shell_cache():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/dashboard.css'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
assert "BASE + 'static/install-app.js'" in source
|
||||
@@ -282,21 +282,21 @@ def test_navigation_deadline_ships_in_a_new_shell_cache():
|
||||
def test_today_convergence_ships_in_a_new_shell_cache():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/today-sync.js'" in source
|
||||
|
||||
|
||||
def test_mobile_search_viewport_ships_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/mobile-search-viewport.js'" in source
|
||||
|
||||
|
||||
def test_update_ownership_flow_ships_atomically_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/update-ownership.js'" in source
|
||||
|
||||
|
||||
@@ -1302,7 +1302,7 @@ def test_one_session_bound_csrf_proof_is_reused_for_a_background_drain():
|
||||
def test_queue_today_ships_atomically_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/queue-today.js'" in source
|
||||
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ async def test_today_blocker_opens_existing_preview_and_preserves_readiness_gate
|
||||
def test_readiness_runtime_is_available_in_offline_shell():
|
||||
service_worker = SERVICE_WORKER.read_text()
|
||||
|
||||
assert "const CACHE = 'stackchain-dashboard-shell-v133';" in service_worker
|
||||
assert "const CACHE = 'stackchain-dashboard-shell-v134';" in service_worker
|
||||
assert "BASE + 'static/today-readiness.js'" in service_worker
|
||||
|
||||
|
||||
|
||||
@@ -343,7 +343,7 @@ listeners['stackchain:first-task-complete']();
|
||||
def test_inflight_today_drain_ships_in_a_new_offline_shell():
|
||||
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v133" in source
|
||||
assert "stackchain-dashboard-shell-v134" in source
|
||||
assert "BASE + 'static/today-sync.js'" in source
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user