feat: resume addressable mobile search sessions (Closes #781)
CI / lint (pull_request) Successful in 1m33s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-13 23:25:35 +00:00
1 parent 741c27eacf
commit bc0d47e8e7
4 files changed
+221 -26

No files matched your search

+85
View File
@@ -212,3 +212,88 @@ def test_dashboard_routes_mobile_task_overlays_through_browser_history():
assert "taskOverlayHistory.close()" in html
assert "saveIssueCaptureDraft();" in html
assert "history.replaceState(history.state || {}, '', cleanUrl)" in html
def test_search_history_serializes_bounded_query_and_stable_preview_identity():
script = f"""
const createTaskOverlayHistory = require({json.dumps(str(OVERLAY_HISTORY))});
const pushed = [];
const replaced = [];
const location = {{ pathname:'/dashboard/', search:'?keep=1', hash:'' }};
const history = {{
state: {{ page:'dashboard' }},
pushState(state, title, url) {{ this.state = state; pushed.push([state, url]); }},
replaceState(state, title, url) {{ this.state = state; replaced.push([state, url]); }},
back() {{}},
}};
const controller = createTaskOverlayHistory({{
history, location, eventTarget:{{addEventListener() {{}}}}, onChange() {{}},
}});
controller.open('search', {{ query:' release blocker ' }});
controller.update({{ query:'release candidate' }});
controller.open('search-preview', {{
query:'release candidate', preview:{{kind:'issue', repository:'stackchain/api', number:123,
title:'must not be serialized', body:'secret'}},
}});
process.stdout.write(JSON.stringify({{pushed, replaced, current:controller.currentState()}}));
"""
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
assert result.returncode == 0, result.stderr
payload = json.loads(result.stdout)
assert len(payload["pushed"]) == 2
assert len(payload["replaced"]) == 1
assert payload["pushed"][0][1] == "/dashboard/?keep=1&search=release+blocker"
assert payload["replaced"][0][1] == "/dashboard/?keep=1&search=release+candidate"
assert payload["pushed"][1][1] == (
"/dashboard/?keep=1&search=release+candidate&preview=issue%3Astackchain%2Fapi%3A123"
)
assert payload["current"] == {
"kind": "search-preview",
"query": "release candidate",
"preview": {"kind": "issue", "repository": "stackchain/api", "number": 123},
}
def test_search_history_restores_direct_url_and_rejects_malformed_or_oversized_state():
script = f"""
const createTaskOverlayHistory = require({json.dumps(str(OVERLAY_HISTORY))});
function restore(search) {{
const changes = [];
const location = {{ pathname:'/dashboard/', search, hash:'' }};
const history = {{ state:null, replaceState(state) {{ this.state = state; }}, back() {{}} }};
const controller = createTaskOverlayHistory({{
history, location, eventTarget:{{addEventListener() {{}}}},
onChange(kind, previous, detail) {{ changes.push([kind, previous, detail]); }},
}});
controller.start();
return {{ state:controller.currentState(), changes }};
}}
process.stdout.write(JSON.stringify({{
valid:restore('?search=release+blocker&preview=pull%3Astackchain%2Fapi%3A9'),
malformed:restore('?search=ok&preview=issue%3Ainvalid%3A0'),
oversized:restore('?search=' + 'x'.repeat(201)),
}}));
"""
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
assert result.returncode == 0, result.stderr
payload = json.loads(result.stdout)
expected = {
"kind": "search-preview",
"query": "release blocker",
"preview": {"kind": "pull", "repository": "stackchain/api", "number": 9},
}
assert payload["valid"]["state"] == expected
assert payload["valid"]["changes"] == [["search-preview", None, expected]]
assert payload["malformed"]["state"] == {"kind": "search", "query": "ok"}
assert payload["oversized"]["state"] == {"kind": None}
def test_dashboard_persists_search_query_and_restores_canonical_preview():
html = dashboard_bundle_text()
assert "taskOverlayHistory.update({ query:e.target.value })" in html
assert "taskOverlayHistory.open('search-preview', { query:qs('#cmd-input').value, preview:item.result })" in html
assert "detail?.query" in html
assert "searchPreview.open(detail.preview).catch" in html