feat: batch-plan mobile work queues (Closes #661)
This commit is contained in:
1 parent
f04a547e19
commit
80a4096118
9 files changed
+250
-13
No files matched your search
@@ -25,6 +25,63 @@ PICK_WORK = Path(__file__).parents[1] / "frontend" / "pick-work.js"
|
||||
WORK_ROUTE = Path(__file__).parents[1] / "frontend" / "work-route.js"
|
||||
UPDATE_OWNERSHIP = Path(__file__).parents[1] / "frontend" / "update-ownership.js"
|
||||
CARD_PLANNING = Path(__file__).parents[1] / "frontend" / "card-planning.js"
|
||||
TODAY_WORK = Path(__file__).parents[1] / "frontend" / "today-work.js"
|
||||
WORK_SELECTION = Path(__file__).parents[1] / "frontend" / "work-selection.js"
|
||||
|
||||
|
||||
def test_today_batch_admission_is_ordered_deduplicated_and_atomic():
|
||||
script = f"""
|
||||
const createTodayWork = require({json.dumps(str(TODAY_WORK))});
|
||||
function storage(fail = false) {{
|
||||
const values = new Map();
|
||||
return {{
|
||||
getItem:key => values.get(key) || null,
|
||||
setItem(key, value) {{ if (fail) throw new Error('quota'); values.set(key, value); }},
|
||||
removeItem:key => values.delete(key),
|
||||
}};
|
||||
}}
|
||||
const first = {{kind:'issue',repository:'o/r',number:1}};
|
||||
const second = {{kind:'pull',repository:'o/r',number:2}};
|
||||
const third = {{kind:'issue',repository:'o/r',number:3}};
|
||||
const today = createTodayWork({{storage:storage(),getLogin:()=> 'timmy',limit:3}});
|
||||
const admitted = today.addMany([first, first, second]);
|
||||
const existing = today.addMany([second, third]);
|
||||
const full = today.addMany([{{kind:'issue',repository:'o/r',number:4}}]);
|
||||
const broken = createTodayWork({{storage:storage(true),getLogin:()=> 'timmy'}});
|
||||
process.stdout.write(JSON.stringify({{
|
||||
admitted, existing, full, ids:today.read(), broken:broken.addMany([first, second]), brokenIds:broken.read(),
|
||||
}}));
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {
|
||||
"admitted": {"status": "added", "ids": ["issue:o/r:1:", "pull:o/r:2:"]},
|
||||
"existing": {"status": "added", "ids": ["issue:o/r:3:"]},
|
||||
"full": {"status": "full", "ids": []},
|
||||
"ids": ["issue:o/r:1:", "pull:o/r:2:", "issue:o/r:3:"],
|
||||
"broken": {"status": "unavailable", "ids": []},
|
||||
"brokenIds": [],
|
||||
}
|
||||
|
||||
|
||||
def test_work_selection_tracks_cross_kind_items_and_retains_failures():
|
||||
script = f"""
|
||||
const create = require({json.dumps(str(WORK_SELECTION))});
|
||||
const selection = create({{limit:3}});
|
||||
const issue = {{kind:'issue',repository:'o/r',number:1,title:'One'}};
|
||||
const pull = {{kind:'pull',repository:'o/r',number:2,title:'Two'}};
|
||||
selection.start(); selection.select(issue); selection.select(pull);
|
||||
const before = selection.snapshot();
|
||||
selection.retain([pull]);
|
||||
process.stdout.write(JSON.stringify({{before, after:selection.snapshot(), issueId:selection.identity(issue)}}));
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {
|
||||
"before": {"active": True, "count": 2, "ids": ["issue:o/r:1:", "pull:o/r:2:"]},
|
||||
"after": {"active": True, "count": 1, "ids": ["pull:o/r:2:"]},
|
||||
"issueId": "issue:o/r:1:",
|
||||
}
|
||||
|
||||
|
||||
def test_card_planning_disclosures_keep_one_open_and_escape_restores_focus():
|
||||
@@ -5649,6 +5706,25 @@ async def test_selected_updates_can_be_deferred_together_without_marking_them_re
|
||||
assert '.my-work-bulk-actions button { min-height:44px; width:100%; }' in html
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mobile_work_queues_batch_plan_cross_kind_selection():
|
||||
html = await dashboard()
|
||||
service_worker = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
|
||||
|
||||
assert '<script src="static/work-selection.js"></script>' in html
|
||||
assert 'id="select-work"' in html
|
||||
assert 'id="batch-add-today"' in html
|
||||
assert 'createWorkSelection({ limit: 50' in html
|
||||
assert 'todayWork.addMany(selectedItems)' in html
|
||||
assert 'laterWork.deferMany(selectedItems, until)' in html
|
||||
assert 'data-select-work-id=' in html
|
||||
assert 'Select work ' in html
|
||||
assert "workSelectionState.count + ' selected'" in html
|
||||
assert '.work-selector { min-height:44px;' in html
|
||||
assert '.my-work-bulk { bottom:calc(56px + env(safe-area-inset-bottom)); }' in html
|
||||
assert "BASE + 'static/work-selection.js'" in service_worker
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_updates_view_discloses_incomplete_inbox_and_loads_more_on_mobile():
|
||||
html = await dashboard()
|
||||
|
||||
@@ -697,6 +697,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell():
|
||||
"/dashboard/static/my-work.js",
|
||||
"/dashboard/static/notification-undo.js",
|
||||
"/dashboard/static/card-planning.js",
|
||||
"/dashboard/static/work-selection.js",
|
||||
"/dashboard/static/today-work.js",
|
||||
"/dashboard/static/today-timer.js",
|
||||
"/dashboard/static/today-recap.js",
|
||||
|
||||
Reference in new issue
Block a user