feat: pause Today during mobile Search (Closes #1078)
This commit is contained in:
1 parent
ff34416c3c
commit
d4910b4398
7 files changed
+209
-4
No files matched your search
@@ -120,6 +120,30 @@ def test_release_artifact_plans_hands_off_and_opens_next_mobile_issue(tmp_path:
|
||||
assert resumed == {"running": True, "saved": True}
|
||||
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
|
||||
|
||||
page.locator('[data-mobile-task="search"]').click()
|
||||
search_pause = page.locator('#cmd-palette [data-search-today-interruption]')
|
||||
expect(search_pause).to_be_visible()
|
||||
expect(search_pause).to_contain_text("Today paused · Ship mobile capture")
|
||||
return_button = search_pause.locator('[data-return-from-search]')
|
||||
bounds = return_button.bounding_box()
|
||||
assert bounds and bounds["height"] >= 44
|
||||
assert page.evaluate("""() => {
|
||||
const key = Object.keys(localStorage).find(value => value.startsWith('stackchain.today-timer.v1.'));
|
||||
const timer = JSON.parse(localStorage.getItem(key));
|
||||
return timer.entries[timer.active_identity].running === false &&
|
||||
timer.search_interruption?.identity === timer.active_identity;
|
||||
}""")
|
||||
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
|
||||
return_button.click()
|
||||
expect(page.locator("#cmd-palette")).not_to_have_class("open")
|
||||
expect(page.locator("#issue-sheet")).to_have_class("issue-sheet open")
|
||||
assert page.evaluate("""() => {
|
||||
const key = Object.keys(localStorage).find(value => value.startsWith('stackchain.today-timer.v1.'));
|
||||
const timer = JSON.parse(localStorage.getItem(key));
|
||||
return timer.entries[timer.active_identity].running === true && !timer.search_interruption;
|
||||
}""")
|
||||
page.locator("#close-issue-sheet").click()
|
||||
|
||||
page.locator("[data-mobile-today-more]").click()
|
||||
expect(page.locator("#mobile-today-actions")).to_be_visible()
|
||||
blocked = page.locator("[data-mobile-today-blocked]")
|
||||
|
||||
@@ -951,6 +951,17 @@ def test_search_preview_offers_assign_and_start_for_eligible_issues():
|
||||
assert "grid-template-columns:repeat(2,minmax(0,1fr))" in css
|
||||
|
||||
|
||||
def test_mobile_search_pauses_today_and_offers_a_lossless_return():
|
||||
html = dashboard_bundle_text()
|
||||
css = (FRONTEND / "dashboard.css").read_text()
|
||||
|
||||
assert html.count('<aside class="search-today-interruption" data-search-today-interruption') == 2
|
||||
assert html.count('<button data-return-from-search type="button">') == 2
|
||||
assert "taskOverlayHistory.leave();" in html
|
||||
assert ".search-today-interruption" in css
|
||||
assert ".search-today-interruption button { min-height:44px;" in css
|
||||
|
||||
|
||||
def test_search_preview_queues_eligible_issue_and_continues_preserved_search():
|
||||
html = dashboard_bundle_text()
|
||||
css = (FRONTEND / "dashboard.css").read_text()
|
||||
|
||||
@@ -173,3 +173,57 @@ process.stdout.write(JSON.stringify({opened, shown, finished, closed, transferre
|
||||
"transferred": {"identity": "issue:r:1:", "resumed": False},
|
||||
"final": {"identity": "issue:r:1:", "elapsed_ms": 0, "running": False},
|
||||
}
|
||||
|
||||
|
||||
def test_search_leaves_manually_paused_work_alone_and_cannot_restore_replaced_work():
|
||||
script = SOURCE.read_text() + r"""
|
||||
const values = new Map();
|
||||
const storage = {getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value)};
|
||||
let now = 1000;
|
||||
const timer = createTodayTimer({storage, getLogin:()=> 'timmy', now:()=>now});
|
||||
timer.adopt('issue:r:1:', 3000, false);
|
||||
const pausedSearch = timer.beginSearch();
|
||||
timer.activate('issue:r:1:');
|
||||
const runningSearch = timer.beginSearch();
|
||||
timer.activate('issue:r:2:');
|
||||
const replacedReturn = timer.returnFromSearch();
|
||||
process.stdout.write(JSON.stringify({pausedSearch, runningSearch, replacedReturn, snapshot:timer.snapshot()}));
|
||||
"""
|
||||
completed = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
|
||||
assert completed.returncode == 0, completed.stderr
|
||||
assert json.loads(completed.stdout) == {
|
||||
"pausedSearch": None,
|
||||
"runningSearch": {"identity": "issue:r:1:", "resume": True},
|
||||
"replacedReturn": None,
|
||||
"snapshot": {"identity": "issue:r:2:", "elapsed_ms": 0, "running": True},
|
||||
}
|
||||
|
||||
|
||||
def test_search_pauses_running_today_time_and_restores_exactly_once():
|
||||
script = SOURCE.read_text() + r"""
|
||||
const values = new Map();
|
||||
const storage = {getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value)};
|
||||
let now = 1000;
|
||||
const timer = createTodayTimer({storage, getLogin:()=> 'timmy', now:()=>now});
|
||||
timer.activate('issue:r:42:');
|
||||
now = 6000;
|
||||
const interruption = timer.beginSearch();
|
||||
now = 26000;
|
||||
const paused = timer.snapshot();
|
||||
const returned = timer.returnFromSearch();
|
||||
now = 28000;
|
||||
const restored = timer.snapshot();
|
||||
const repeated = timer.returnFromSearch();
|
||||
process.stdout.write(JSON.stringify({interruption, paused, returned, restored, repeated}));
|
||||
"""
|
||||
completed = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
|
||||
assert completed.returncode == 0, completed.stderr
|
||||
assert json.loads(completed.stdout) == {
|
||||
"interruption": {"identity": "issue:r:42:", "resume": True},
|
||||
"paused": {"identity": "issue:r:42:", "elapsed_ms": 5000, "running": False},
|
||||
"returned": {"identity": "issue:r:42:", "resumed": True},
|
||||
"restored": {"identity": "issue:r:42:", "elapsed_ms": 7000, "running": True},
|
||||
"repeated": None,
|
||||
}
|
||||
Reference in new issue
Block a user