import json import subprocess from pathlib import Path import pytest from src.views import dashboard MY_WORK = Path(__file__).parents[1] / "frontend" / "my-work.js" REVIEW_SHEET = Path(__file__).parents[1] / "frontend" / "review-sheet.js" def test_my_work_queue_prioritizes_labels_then_reviews_and_keeps_repo_identity(): payload = { "user": {"login": "timmy"}, "issues": [ { "id": 1, "number": 7, "title": "Assigned issue", "state": "open", "repository": "stackchain/mobile", "labels": [], "assignees": ["timmy"], "updated_at": "2026-08-06T12:00:00Z", "url": "https://forge.example/mobile/issues/7", }, { "id": 2, "number": 7, "title": "Priority issue", "state": "open", "repository": "stackchain/api", "labels": ["P0"], "assignees": [], "updated_at": "2026-08-06T11:00:00Z", "url": "https://forge.example/api/issues/7", }, ], "pull_requests": [ { "id": 3, "number": 4, "title": "Review PR", "state": "open", "repository": "stackchain/web", "work_reasons": ["review_requested"], "updated_at": "2026-08-06T13:00:00Z", "url": "https://forge.example/web/pulls/4", } ], } script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); const queue = buildMyWork({json.dumps(payload)}); process.stdout.write(JSON.stringify(queue)); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) queue = json.loads(result.stdout) assert [item["title"] for item in queue] == [ "Priority issue", "Review PR", "Assigned issue", ] assert queue[0]["key"] == "stackchain/api#7" assert queue[0]["reason"] == "P0 priority" assert queue[1]["reason"] == "Needs your review" assert queue[1]["is_review"] is True assert queue[2]["reason"] == "Assigned to you" def test_my_work_reviews_filter_and_summary_are_actionable(): items = [ {"title": "Issue", "kind": "issue", "is_review": False, "is_assigned": True}, {"title": "Assigned PR", "kind": "pull", "is_review": False, "is_assigned": True}, {"title": "Review PR", "kind": "pull", "is_review": True, "is_assigned": False}, ] script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); const items = {json.dumps(items)}; process.stdout.write(JSON.stringify({{ reviews: buildMyWork.filterMyWork(items, 'review'), summary: buildMyWork.summarizeMyWork(items), }})); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) output = json.loads(result.stdout) assert [item["title"] for item in output["reviews"]] == ["Review PR"] assert output["summary"] == "1 review · 2 assigned" def test_my_work_filter_counts_distinguish_prs_from_review_requests(): items = [ {"kind": "issue", "is_review": False}, {"kind": "pull", "is_review": False}, {"kind": "pull", "is_review": True}, ] script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); process.stdout.write(JSON.stringify(buildMyWork.countMyWork({json.dumps(items)}))); """ result = subprocess.run( ["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} def test_unread_updates_enrich_matching_work_and_keep_unassigned_mentions_actionable(): payload = { "user": {"login": "timmy"}, "issues": [{ "id": 1, "number": 7, "title": "Assigned issue", "repository": "stackchain/api", "labels": [], "assignees": ["timmy"], "updated_at": "2026-08-06T10:00:00Z", "url": "https://forge.example/stackchain/api/issues/7", }], "pull_requests": [], "notifications": [ { "id": 42, "number": 7, "title": "Assigned issue", "repository": "stackchain/api", "subject_type": "Issue", "unread": True, "updated_at": "2026-08-06T12:00:00Z", "url": "https://forge.example/stackchain/api/issues/7#issuecomment-9", }, { "id": 43, "number": 8, "title": "Mention only", "repository": "stackchain/web", "subject_type": "Issue", "unread": True, "updated_at": "2026-08-06T13:00:00Z", "url": "https://forge.example/stackchain/web/issues/8#issuecomment-2", }, ], } script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); const queue = buildMyWork({json.dumps(payload)}); process.stdout.write(JSON.stringify({{ queue, updates: buildMyWork.filterMyWork(queue, 'update'), counts: buildMyWork.countMyWork(queue), summary: buildMyWork.summarizeMyWork(queue), }})); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) output = json.loads(result.stdout) assert len(output["queue"]) == 2 assert [item["key"] for item in output["updates"]] == ["stackchain/web#8", "stackchain/api#7"] 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["summary"] == "2 unread updates · 0 reviews · 1 assigned" def test_unread_update_correlation_distinguishes_issue_and_pull_with_same_number(): payload = { "user": {"login": "timmy"}, "issues": [{"number": 7, "title": "Issue seven", "repository": "stackchain/api", "url": "https://forge.example/issues/7"}], "pull_requests": [{"number": 7, "title": "Pull seven", "repository": "stackchain/api", "url": "https://forge.example/pulls/7"}], "notifications": [{ "id": 42, "number": 7, "title": "Issue seven", "repository": "stackchain/api", "subject_type": "Issue", "unread": True, "url": "https://forge.example/issues/7#comment-1", }], } script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); process.stdout.write(JSON.stringify(buildMyWork({json.dumps(payload)}))); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) queue = json.loads(result.stdout) issue = next(item for item in queue if item["kind"] == "issue") pull = next(item for item in queue if item["kind"] == "pull") assert issue["has_update"] is True assert issue["url"].endswith("#comment-1") assert pull["has_update"] is False def test_notification_identity_survives_merge_and_acknowledgement_preserves_assigned_work(): payload = { "user": {"login": "timmy"}, "issues": [{ "number": 7, "title": "Assigned issue", "repository": "stackchain/api", "assignees": ["timmy"], "url": "https://forge.example/issues/7", }], "notifications": [ {"id": 42, "number": 7, "title": "Assigned issue", "repository": "stackchain/api", "subject_type": "Issue", "unread": True, "url": "https://forge.example/issues/7#comment"}, {"id": 43, "number": 8, "title": "Mention", "repository": "stackchain/web", "subject_type": "Issue", "unread": True, "url": "https://forge.example/issues/8"}, ], } script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); const before = buildMyWork({json.dumps(payload)}); const afterMerged = buildMyWork.acknowledgeNotification(before, 42); const afterStandalone = buildMyWork.acknowledgeNotification(before, 43); process.stdout.write(JSON.stringify({{before, afterMerged, afterStandalone}})); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) output = json.loads(result.stdout) assigned = next(item for item in output["before"] if item["kind"] == "issue") assert assigned["notification_id"] == 42 assert assigned["has_update"] is True assert len(output["afterMerged"]) == 2 acknowledged = next(item for item in output["afterMerged"] if item["kind"] == "issue") assert acknowledged["has_update"] is False assert "notification_id" not in acknowledged assert [item["notification_id"] for item in output["afterStandalone"] if item["has_update"]] == [42] def test_notification_acknowledger_is_single_flight_and_rolls_back_on_failure(): script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); const original = [{{kind:'update', notification_id:42, has_update:true}}]; let calls = 0; let release; const states = []; const statuses = []; const controller = buildMyWork.createNotificationAcknowledger({{ markRead: () => {{ calls += 1; return new Promise((resolve, reject) => {{ release = reject; }}); }}, onItems: items => states.push(items), onStatus: status => statuses.push(status), }}); const first = controller.acknowledge(original, 42); const duplicate = controller.acknowledge(original, 42); release(new Error('offline')); Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{ calls, states, statuses, results }}))); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) output = json.loads(result.stdout) assert output["calls"] == 1 assert output["states"] == [[], [{"kind": "update", "notification_id": 42, "has_update": True}]] assert output["statuses"] == ["Marking update read…", "Could not mark update read. Retry."] assert output["results"] == [False, False] def test_bulk_notification_acknowledger_deduplicates_and_keeps_partial_failures_retryable(): script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); const original = [ {{kind:'issue', key:'repo#1', notification_id:42, has_update:true}}, {{kind:'update', key:'repo#2', notification_id:43, has_update:true}}, {{kind:'update', key:'repo#3', notification_id:44, has_update:true}}, ]; let calls = 0; let release; const states = []; const statuses = []; const controller = buildMyWork.createBulkNotificationAcknowledger({{ markRead: ids => {{ calls += 1; return new Promise(resolve => {{ release = () => resolve({{marked:[42,43], failed:[44]}}); }}); }}, onItems: items => states.push(items), onStatus: status => statuses.push(status), }}); const first = controller.acknowledge(original, [42, 43, 42, 44]); const duplicate = controller.acknowledge(original, [42, 43, 44]); release(); Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{ calls, states, statuses, results, retryIds: buildMyWork.notificationIds(states.at(-1)), }}))); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) output = json.loads(result.stdout) assert output["calls"] == 1 assert output["results"] == [ {"marked": [42, 43], "failed": [44]}, False, ] assert output["states"] == [[ {"kind": "issue", "key": "repo#1", "has_update": False}, {"kind": "update", "key": "repo#3", "notification_id": 44, "has_update": True}, ]] assert output["retryIds"] == [44] assert output["statuses"] == [ "Marking 3 updates read…", "2 marked read · 1 could not be updated — retry.", ] def test_notification_pager_is_single_flight_and_merges_unique_updates(): script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); let calls = 0; let release; const pages = []; const notifications = []; const statuses = []; const pager = buildMyWork.createNotificationPager({{ load: page => {{ calls += 1; return new Promise(resolve => {{ release = () => resolve({{ items: [{{id:50, title:'duplicate'}}, {{id:51, title:'older'}}], page, total: 75, has_more: false, }}); }}); }}, onNotifications: items => notifications.push(items), onPagination: page => pages.push(page), onStatus: status => statuses.push(status), }}); pager.reset({{page:1, total:75, has_more:true}}); const existing = [{{id:50, title:'newer'}}]; const first = pager.loadMore(existing); const duplicate = pager.loadMore(existing); release(); Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{ calls, pages, notifications, statuses, results }}))); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) output = json.loads(result.stdout) assert output["calls"] == 1 assert output["notifications"] == [[ {"id": 50, "title": "newer"}, {"id": 51, "title": "older"}, ]] assert output["pages"][-1] == {"page": 2, "total": 75, "has_more": False} assert output["statuses"] == ["Loading older updates…", "75 of 75 unread updates loaded."] assert output["results"] == [True, False] def test_notification_pager_keeps_loaded_updates_and_retries_the_same_page_after_failure(): script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); const states = []; const statuses = []; const requested = []; const pager = buildMyWork.createNotificationPager({{ load: async page => {{ requested.push(page); throw new Error('offline'); }}, onNotifications: items => states.push(items), onPagination: () => {{}}, onStatus: status => statuses.push(status), }}); pager.reset({{page:2, total:125, has_more:true}}); pager.loadMore([{{id:1}}]).then(result => pager.loadMore([{{id:1}}]).then(retry => process.stdout.write(JSON.stringify({{requested, states, statuses, result, retry}})) ) ); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) assert json.loads(result.stdout) == { "requested": [3, 3], "states": [], "statuses": [ "Loading older updates…", "Could not load older updates. Retry.", "Loading older updates…", "Could not load older updates. Retry.", ], "result": False, "retry": False, } @pytest.mark.anyio async def test_mobile_dashboard_puts_filterable_my_work_before_auxiliary_panels(): html = await dashboard() assert html.index('id="my-work"') < html.index('data-panel-key="context"') assert 'data-work-filter="all"' in html assert 'data-work-filter="issue"' in html assert 'data-work-filter="pull"' in html assert 'data-work-filter="review"' in html assert 'data-work-filter="update"' in html assert '.work-filter' in html and 'min-height: 44px' in html assert '.my-work-card' in html and 'min-height: 44px' in html assert '' in html assert "buildMyWork(data)" in html assert "markMyWorkStale()" in html assert "filterMyWork(lastMyWork, selectedWorkFilter)" in html @pytest.mark.anyio async def test_unread_cards_offer_accessible_mobile_mark_read_without_nested_actions(): html = await dashboard() assert '.mark-update-read' in html and 'min-height:44px' in html assert 'data-notification-id' in html assert 'Unread update' in html assert 'id="my-work-action-status"' in html and 'aria-live="assertive"' in html assert "createNotificationAcknowledger" in html assert "method: 'PATCH'" in html assert "api/v1/notifications/" in html assert ' {{ request = {{ url, accept: options.headers.Accept }}; return {{ title: 'Review API' }}; }} }}); controller.load({{ repository: 'stackchain/api', number: 7 }}).then(detail => process.stdout.write(JSON.stringify({{ request, title: detail.title }})) ); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) assert json.loads(result.stdout) == { "request": { "url": "api/v1/repos/stackchain/api/pulls/7/review", "accept": "application/json", }, "title": "Review API", } def test_review_diff_rows_escape_content_and_toggle_accessibly(): script = f""" const reviewSheet = require({json.dumps(str(REVIEW_SHEET))}); const escapeHtml = value => String(value) .replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>'); const html = reviewSheet.renderDiffFile({{ filename: 'src/.py', status: 'modified', additions: 1, deletions: 1, diff_available: true, diff_truncated: true, diff_lines: ['@@ -1 +1 @@', '-old ', '+new & safe'] }}, 2, escapeHtml); const button = {{ attrs: {{ 'aria-expanded': 'false' }}, getAttribute(k) {{ return this.attrs[k]; }}, setAttribute(k,v) {{ this.attrs[k]=v; }} }}; const panel = {{ hidden: true }}; reviewSheet.toggleDiff(button, panel); process.stdout.write(JSON.stringify({{ html, expanded: button.attrs['aria-expanded'], hidden: panel.hidden }})); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) output = json.loads(result.stdout) assert 'aria-expanded="false"' in output["html"] assert 'src/<api>.py' in output["html"] assert '-old <token>' in output["html"] assert '+new & safe' in output["html"] assert 'Preview truncated' in output["html"] assert 'class="review-mark"' in output["html"] assert 'data-review-filename="src/<api>.py"' in output["html"] assert 'Mark reviewed' in output["html"] assert output["expanded"] == "true" assert output["hidden"] is False def test_review_progress_is_explicit_and_restores_for_the_same_head_sha(): script = f""" const reviewSheet = require({json.dumps(str(REVIEW_SHEET))}); const values = new Map(); const storage = {{ getItem(key) {{ return values.has(key) ? values.get(key) : null; }}, setItem(key, value) {{ values.set(key, value); }} }}; const options = {{ storage, repository: 'stackchain/api', number: 7, headSha: 'abc123', files: [{{filename:'src/a.py'}}, {{filename:'src/b.py'}}, {{filename:'README.md'}}] }}; const first = reviewSheet.createProgress(options); const before = first.snapshot(); const marked = first.markReviewed('src/a.py'); const restored = reviewSheet.createProgress(options).snapshot(); process.stdout.write(JSON.stringify({{ before, marked, restored }})); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) output = json.loads(result.stdout) assert output["before"] == { "reviewed": [], "reviewedCount": 0, "total": 3, "nextFilename": "src/a.py" } assert output["marked"] == { "reviewed": ["src/a.py"], "reviewedCount": 1, "total": 3, "nextFilename": "src/b.py", } assert output["restored"] == output["marked"] def test_review_progress_resets_when_new_commits_change_the_head_sha(): script = f""" const reviewSheet = require({json.dumps(str(REVIEW_SHEET))}); const values = new Map(); const storage = {{ getItem(key) {{ return values.has(key) ? values.get(key) : null; }}, setItem(key, value) {{ values.set(key, value); }} }}; const base = {{ storage, repository: 'stackchain/api', number: 7, files: [{{filename:'src/a.py'}}] }}; reviewSheet.createProgress({{...base, headSha:'abc123'}}).markReviewed('src/a.py'); const changed = reviewSheet.createProgress({{...base, headSha:'def456'}}).snapshot(); process.stdout.write(JSON.stringify(changed)); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) assert json.loads(result.stdout) == { "reviewed": [], "reviewedCount": 0, "total": 1, "nextFilename": "src/a.py", "newHead": True, } def test_review_feedback_draft_restores_notes_summary_and_decision_for_same_head(): script = f""" const reviewSheet = require({json.dumps(str(REVIEW_SHEET))}); const values = new Map(); const storage = {{ getItem(key) {{ return values.has(key) ? values.get(key) : null; }}, setItem(key, value) {{ values.set(key, value); }} }}; const options = {{ storage, repository: 'stackchain/api', number: 7, headSha: 'abc123', files: [{{filename:'src/a.py'}}, {{filename:'src/b.py'}}] }}; const first = reviewSheet.createDraft(options); first.setNote('src/a.py', 'Handle the empty state.'); first.setSummary('One blocker remains.'); first.setDecision('request_changes'); const restored = reviewSheet.createDraft(options).snapshot(); process.stdout.write(JSON.stringify(restored)); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) assert json.loads(result.stdout) == { "notes": {"src/a.py": "Handle the empty state."}, "summary": "One blocker remains.", "decision": "request_changes", } def test_review_feedback_formats_non_empty_file_notes_in_changed_file_order(): script = f""" const reviewSheet = require({json.dumps(str(REVIEW_SHEET))}); const markdown = reviewSheet.formatFeedback({{ decision: 'request_changes', summary: 'One blocker remains.', notes: {{'src/b.py':'Second note', 'src/a.py':'First note', 'README.md':''}} }}, [{{filename:'src/a.py'}}, {{filename:'README.md'}}, {{filename:'src/b.py'}}]); process.stdout.write(markdown); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) assert result.stdout == ( "## Intended decision\nRequest changes\n\n" "## Summary\nOne blocker remains.\n\n" "## File notes\n### `src/a.py`\nFirst note\n\n" "### `src/b.py`\nSecond note" ) def test_review_handoff_reserves_window_before_copy_and_reports_blocked_popup(): script = f""" const reviewSheet = require({json.dumps(str(REVIEW_SHEET))}); const events = []; let finishCopy; const reserved = {{ location: {{ href: '' }}, close: () => events.push('close') }}; const success = reviewSheet.copyAndContinue({{ text: 'feedback', url: 'https://forge.example/pulls/7', copy: text => new Promise(resolve => {{ events.push('copy:' + text); finishCopy = resolve; }}), open: () => {{ events.push('reserve'); return reserved; }}, fallback: text => events.push('fallback:' + text), }}); const beforeCopySettles = events.slice(); finishCopy(); const blockedEvents = []; const blocked = reviewSheet.copyAndContinue({{ text: 'copied', url: 'https://forge.example/pulls/8', copy: async () => blockedEvents.push('copy'), open: () => {{ blockedEvents.push('reserve'); return null; }}, fallback: text => blockedEvents.push('fallback:' + text), }}); Promise.all([success, blocked]).then(results => process.stdout.write(JSON.stringify({{ beforeCopySettles, events, blockedEvents, destination: reserved.location.href, results }}))); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) assert json.loads(result.stdout) == { "beforeCopySettles": ["reserve", "copy:feedback"], "events": ["reserve", "copy:feedback"], "blockedEvents": ["reserve", "copy", "fallback:copied"], "destination": "https://forge.example/pulls/7", "results": [ {"copied": True, "opened": True}, {"copied": True, "opened": False}, ], } def test_review_handoff_closes_reserved_window_when_copy_fails(): script = f""" const reviewSheet = require({json.dumps(str(REVIEW_SHEET))}); const events = []; reviewSheet.copyAndContinue({{ text: 'keep me', url: 'https://forge.example/pulls/8', copy: async () => {{ throw new Error('denied'); }}, open: () => ({{ close: () => events.push('close') }}), fallback: text => events.push('fallback:' + text), }}).then(result => process.stdout.write(JSON.stringify({{ events, result }}))); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) assert json.loads(result.stdout) == { "events": ["close", "fallback:keep me"], "result": {"copied": False, "opened": False}, } @pytest.mark.anyio async def test_review_requests_open_an_accessible_mobile_detail_sheet(): html = await dashboard() assert 'id="review-sheet"' in html assert 'role="dialog"' in html and 'aria-modal="true"' in html assert 'id="review-sheet-status"' in html and 'aria-live="polite"' in html assert 'id="open-review-gitea"' in html and 'rel="noopener noreferrer"' in html assert '' in html assert '@media (max-width: 600px)' in html assert '.review-sheet-panel' in html and 'width:100%' in html assert '.review-action' in html and 'min-height:44px' in html assert '.review-file-toggle' in html and 'min-height:44px' in html assert '.review-mark' in html and 'min-height:44px' in html assert 'id="review-progress"' in html and 'aria-live="polite"' in html assert 'id="next-unreviewed-review"' in html assert '.review-progress-actions' in html and 'position:sticky' in html assert "createReviewController.createProgress" in html assert "progress.markReviewed" in html assert "scrollIntoView" in html assert '.review-diff' in html and 'overflow-x:auto' in html @pytest.mark.anyio async def test_review_sheet_loads_details_and_preserves_safe_gitea_handoff(): html = await dashboard() assert 'data-review-index' in html assert "reviewController.load(selectedReview)" in html assert "review-files" in html assert "review-history" in html assert "open-review-gitea" in html assert "reviewController.submit" not in html @pytest.mark.anyio async def test_mobile_review_sheet_captures_and_safely_hands_off_feedback(): html = await dashboard() review_script = REVIEW_SHEET.read_text() assert 'class="review-note"' in review_script assert 'id="review-decision"' in html assert 'id="review-summary"' in html assert 'id="copy-review-feedback"' in html assert 'id="review-copy-fallback"' in html assert 'id="review-handoff-link"' in html assert '.review-handoff-link' in html and 'min-height:44px' in html assert '.review-handoff-link[hidden]' in html and 'display:none' in html assert '.review-note' in html and 'min-height:88px' in html assert '.review-handoff' in html and 'position:sticky' in html assert "createReviewController.createDraft" in html assert "draft.setNote" in html assert "draft?.setSummary" in html assert "draft?.setDecision" in html assert "createReviewController.formatFeedback" in html assert "createReviewController.copyAndContinue" in html assert "navigator.clipboard.writeText" in html assert "window.open('about:blank', '_blank')" in html assert "handoffWindow.opener = null" in html @pytest.mark.anyio async def test_mobile_review_failure_offers_an_in_place_retry_for_the_same_item(): html = await dashboard() assert 'id="retry-review-load"' in html assert '.review-retry' in html and 'min-height:44px' in html assert "qs('#retry-review-load').hidden = false" in html assert "qs('#retry-review-load').hidden = true" in html assert "openReviewSheet(selectedReview, reviewTrigger)" in html assert "qs('#retry-review-load').focus()" in html