feat: add mobile attention queue (#244)
This commit is contained in:
1 parent
e0bc7271fa
commit
51ac802d77
7 files changed
+102
-13
No files matched your search
@@ -70,6 +70,31 @@ process.stdout.write(JSON.stringify({{
|
||||
}
|
||||
|
||||
|
||||
def test_mobile_task_dock_exposes_and_hides_deduplicated_attention_count():
|
||||
script = f"""
|
||||
const createDock = require({json.dumps(str(DOCK))});
|
||||
const work = {{ attributes: {{}}, setAttribute(name, value) {{ this.attributes[name] = value; }} }};
|
||||
const badge = {{ textContent:'', hidden:true }};
|
||||
const dock = createDock({{ nav:{{}}, buttons:{{work}}, attentionBadge:badge }});
|
||||
dock.updateAttention(3);
|
||||
const pending = {{ count:badge.textContent, hidden:badge.hidden, label:work.attributes['aria-label'] }};
|
||||
dock.updateAttention(0);
|
||||
process.stdout.write(JSON.stringify({{
|
||||
pending,
|
||||
empty:{{ count:badge.textContent, hidden:badge.hidden, label:work.attributes['aria-label'] }},
|
||||
}}));
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], capture_output=True, text=True
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {
|
||||
"pending": {"count": "3", "hidden": False, "label": "Work, 3 items need attention"},
|
||||
"empty": {"count": "0", "hidden": True, "label": "Work"},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_dashboard_renders_and_wires_phone_safe_task_dock():
|
||||
html = await dashboard()
|
||||
@@ -79,15 +104,20 @@ async def test_dashboard_renders_and_wires_phone_safe_task_dock():
|
||||
for task in ("work", "find", "new", "search", "drafts"):
|
||||
assert f'data-mobile-task="{task}"' in html
|
||||
assert 'id="mobile-draft-count"' in html
|
||||
assert 'data-work-filter="attention"' in html
|
||||
assert 'id="mobile-attention-count"' in html
|
||||
assert '.mobile-task-dock { display:none;' in html
|
||||
assert 'grid-template-columns:repeat(5,minmax(0,1fr))' in html
|
||||
assert 'padding-bottom:env(safe-area-inset-bottom)' in html
|
||||
assert '.mobile-task-action { min-width:0; min-height:44px;' in html
|
||||
assert '<script src="static/mobile-task-dock.js"></script>' in html
|
||||
assert "createMobileTaskDock({" in html
|
||||
assert "work: () => qs('#my-work').scrollIntoView" in html
|
||||
assert "work: openMobileWork" in html
|
||||
assert "counts.attention ? 'attention' : 'all'" in html
|
||||
assert "qs('[data-work-filter=\"' + filter + '\"]').click()" in html
|
||||
assert "find: () => qs('#find-work').click()" in html
|
||||
assert "new: () => qs('#new-issue').click()" in html
|
||||
assert "search: () => qs('#open-palette').click()" in html
|
||||
assert "drafts: () => qs('[data-work-filter=\"draft\"]').click()" in html
|
||||
assert "draftCount.textContent = sourceDraftCount.textContent" in html
|
||||
assert "mobileTaskDock.updateAttention(counts.attention)" in html
|
||||
+34
-3
@@ -651,6 +651,32 @@ process.stdout.write(JSON.stringify({{
|
||||
assert output["summary"] == "1 review · 2 assigned"
|
||||
|
||||
|
||||
def test_attention_filter_unions_updates_and_reviews_without_double_counting():
|
||||
items = [
|
||||
{"title": "Unread issue", "kind": "issue", "has_update": True, "is_review": False},
|
||||
{"title": "Requested review", "kind": "pull", "has_update": False, "is_review": True},
|
||||
{"title": "Updated review", "kind": "pull", "has_update": True, "is_review": True},
|
||||
{"title": "Ordinary assignment", "kind": "issue", "has_update": False, "is_review": False},
|
||||
]
|
||||
script = f"""
|
||||
const buildMyWork = require({json.dumps(str(MY_WORK))});
|
||||
const items = {json.dumps(items)};
|
||||
process.stdout.write(JSON.stringify({{
|
||||
attention: buildMyWork.filterMyWork(items, 'attention').map(item => item.title),
|
||||
count: buildMyWork.countMyWork(items).attention,
|
||||
}}));
|
||||
"""
|
||||
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
|
||||
assert json.loads(result.stdout) == {
|
||||
"attention": ["Unread issue", "Requested review", "Updated review"],
|
||||
"count": 3,
|
||||
}
|
||||
|
||||
|
||||
def test_milestone_lane_composes_with_type_filter_and_updates_confirmed_snapshot():
|
||||
payload = {
|
||||
"issues": [
|
||||
@@ -842,7 +868,9 @@ process.stdout.write(JSON.stringify(buildMyWork.countMyWork({json.dumps(items)})
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
|
||||
assert json.loads(result.stdout) == {"all": 3, "issue": 1, "pull": 1, "review": 1, "update": 0}
|
||||
assert json.loads(result.stdout) == {
|
||||
"all": 3, "attention": 1, "issue": 1, "pull": 1, "review": 1, "update": 0
|
||||
}
|
||||
|
||||
|
||||
def test_work_pager_is_single_flight_and_unions_pull_responsibilities():
|
||||
@@ -1458,7 +1486,9 @@ process.stdout.write(JSON.stringify({{
|
||||
assert output["updates"][0]["kind"] == "update"
|
||||
assert output["updates"][1]["kind"] == "issue"
|
||||
assert output["updates"][1]["url"].endswith("#issuecomment-9")
|
||||
assert output["counts"] == {"all": 2, "issue": 1, "pull": 0, "review": 0, "update": 2}
|
||||
assert output["counts"] == {
|
||||
"all": 2, "attention": 2, "issue": 1, "pull": 0, "review": 0, "update": 2
|
||||
}
|
||||
assert output["summary"] == "2 unread updates · 0 reviews · 1 assigned"
|
||||
|
||||
|
||||
@@ -2641,11 +2671,12 @@ async def test_mobile_filters_wrap_show_counts_and_persist_for_the_session():
|
||||
|
||||
assert '.work-filters { display:flex; gap:8px; flex-wrap:wrap; }' in html
|
||||
assert 'data-work-count="all"' in html
|
||||
assert 'data-work-count="attention"' in html
|
||||
assert 'data-work-count="issue"' in html
|
||||
assert 'data-work-count="pull"' in html
|
||||
assert 'data-work-count="review"' in html
|
||||
assert 'data-work-count="update"' in html
|
||||
assert "['all', 'issue', 'pull', 'review', 'update', 'draft'].includes(savedFilter)" in html
|
||||
assert "['all', 'attention', 'issue', 'pull', 'review', 'update', 'draft'].includes(savedFilter)" in html
|
||||
assert 'data-work-count="draft"' in html
|
||||
assert 'sessionStorage.getItem(WORK_FILTER_KEY)' in html
|
||||
assert 'sessionStorage.setItem(WORK_FILTER_KEY, selectedWorkFilter)' in html
|
||||
|
||||
@@ -63,10 +63,10 @@ async function dispatch(name, request) {{
|
||||
return json.loads(completed.stdout)
|
||||
|
||||
|
||||
def test_cross_tab_outbox_coordination_ships_in_a_new_shell_cache():
|
||||
def test_mobile_attention_queue_ships_in_a_new_shell_cache():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v10" in source
|
||||
assert "stackchain-dashboard-shell-v11" in source
|
||||
|
||||
|
||||
def test_install_precaches_complete_subpath_scoped_app_shell():
|
||||
|
||||
Reference in new issue
Block a user