import json import subprocess from html.parser import HTMLParser from pathlib import Path from urllib.parse import urljoin from tests.dashboard_bundle import dashboard_bundle_text FRONTEND = Path(__file__).parents[1] / "frontend" COMMANDS = FRONTEND / "commands.js" SEARCH_PREVIEW = FRONTEND / "search-preview.js" class ScriptSourceParser(HTMLParser): def __init__(self): super().__init__() self.sources = [] def handle_starttag(self, tag, attrs): if tag == "script": source = dict(attrs).get("src") if source: self.sources.append(source) def test_filtered_command_runs_the_matching_action(): script = f""" const filterCommands = require({json.dumps(str(COMMANDS))}); let action = ''; const commands = [ {{ name: 'Open whiteboard', run: () => {{ action = 'whiteboard'; }} }}, {{ name: 'Refresh now', run: () => {{ action = 'refresh'; }} }}, ]; const matches = filterCommands(commands, 'refresh'); if (matches.length !== 1) throw new Error(`expected one match, got ${{matches.length}}`); matches[0].run(); if (action !== 'refresh') throw new Error(`expected refresh, got ${{action}}`); """ subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) def test_command_script_resolves_inside_dashboard_subpath(): parser = ScriptSourceParser() parser.feed(dashboard_bundle_text()) command_source = next(source for source in parser.sources if source.endswith("commands.js")) assert urljoin( "https://forge.alexanderwhitestone.com/dashboard/", command_source ) == "https://forge.alexanderwhitestone.com/dashboard/static/commands.js" def test_remote_command_search_ignores_stale_responses(): script = f""" const filterCommands = require({json.dumps(str(COMMANDS))}); (async () => {{ const pending = new Map(); const states = []; const controller = filterCommands.createGlobalSearchController({{ delay: 0, search: query => new Promise(resolve => pending.set(query, resolve)), onState: state => states.push(state), }}); controller.setQuery('mobile'); await new Promise(resolve => setTimeout(resolve, 0)); controller.setQuery('release'); await new Promise(resolve => setTimeout(resolve, 0)); pending.get('release')([{{ title: 'Current result' }}]); await new Promise(resolve => setTimeout(resolve, 0)); pending.get('mobile')([{{ title: 'Stale result' }}]); await new Promise(resolve => setTimeout(resolve, 0)); const ready = states.filter(state => state.status === 'ready'); if (ready.length !== 1) throw new Error(`expected one ready state, got ${{ready.length}}`); if (ready[0].query !== 'release' || ready[0].items[0].title !== 'Current result') {{ throw new Error(`stale response replaced current state: ${{JSON.stringify(ready)}}`); }} }})().catch(error => {{ console.error(error); process.exit(1); }}); """ subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True, ) def test_remote_command_search_aborts_superseded_and_cleared_queries(): script = f""" const filterCommands = require({json.dumps(str(COMMANDS))}); (async () => {{ const signals = []; const states = []; const controller = filterCommands.createGlobalSearchController({{ delay: 0, search: (query, signal) => {{ signals.push(signal); return new Promise((resolve, reject) => signal.addEventListener('abort', () => {{ const error = new Error('aborted'); error.name = 'AbortError'; reject(error); }})); }}, onState: state => states.push(state), }}); controller.setQuery('mobile'); await new Promise(resolve => setTimeout(resolve, 0)); controller.setQuery('release'); if (!signals[0].aborted) throw new Error('superseded request was not aborted'); await new Promise(resolve => setTimeout(resolve, 0)); controller.setQuery(''); if (!signals[1].aborted) throw new Error('cleared query request was not aborted'); await new Promise(resolve => setTimeout(resolve, 0)); if (states.some(state => state.status === 'error')) throw new Error('abort rendered an error'); if (states.at(-1).status !== 'idle') throw new Error('clear did not restore idle state'); }})().catch(error => {{ console.error(error); process.exit(1); }}); """ subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) def test_remote_command_search_preserves_partial_result_status(): script = f""" const filterCommands = require({json.dumps(str(COMMANDS))}); (async () => {{ const states = []; const controller = filterCommands.createGlobalSearchController({{ delay: 0, search: () => Promise.resolve({{ items:[{{ title:'Useful result' }}], partial:true }}), onState: state => states.push(state), }}); controller.setQuery('mobile'); await new Promise(resolve => setTimeout(resolve, 0)); await new Promise(resolve => setTimeout(resolve, 0)); const ready = states.find(state => state.status === 'ready'); if (!ready || ready.items.length !== 1 || ready.partial !== true) {{ throw new Error('partial result metadata was lost: ' + JSON.stringify(states)); }} }})().catch(error => {{ console.error(error); process.exit(1); }}); """ subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) def test_command_selection_wraps_for_arrow_keys(): script = f""" const commands = require({json.dumps(str(COMMANDS))}); if (commands.nextSelection(-1, 'ArrowDown', 3) !== 0) throw new Error('should select first'); if (commands.nextSelection(2, 'ArrowDown', 3) !== 0) throw new Error('should wrap down'); if (commands.nextSelection(0, 'ArrowUp', 3) !== 2) throw new Error('should wrap up'); if (commands.nextSelection(1, 'Enter', 3) !== 1) throw new Error('other keys should not move'); """ subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) def test_palette_exposes_accessible_global_work_search_under_dashboard_subpath(): html = dashboard_bundle_text() assert 'role="combobox"' in html assert 'aria-controls="cmd-results"' in html assert 'role="listbox"' in html assert "fetch('api/v1/search?q='" in html assert "async function searchGlobalWork(query, signal)" in html assert "signal," in html assert "Some results are temporarily unavailable." in html assert urljoin( "https://forge.alexanderwhitestone.com/dashboard/", "api/v1/search?q=mobile", ) == "https://forge.alexanderwhitestone.com/dashboard/api/v1/search?q=mobile" def test_search_preview_ignores_stale_result_details(): script = f""" const createSearchPreview = require({json.dumps(str(SEARCH_PREVIEW))}); (async () => {{ const pending = new Map(); const states = []; const preview = createSearchPreview({{ fetchJson: item => new Promise(resolve => pending.set(item.number, resolve)), claim: () => Promise.resolve(), onState: state => states.push(state), }}); preview.open({{ repository:'stackchain/api', number:1, kind:'issue' }}); preview.open({{ repository:'stackchain/api', number:2, kind:'issue' }}); pending.get(2)({{ number:2, title:'Current' }}); await new Promise(resolve => setTimeout(resolve, 0)); pending.get(1)({{ number:1, title:'Stale' }}); await new Promise(resolve => setTimeout(resolve, 0)); const ready = states.filter(state => state.status === 'ready'); if (ready.length !== 1 || ready[0].detail.number !== 2) {{ throw new Error('stale detail replaced current preview: ' + JSON.stringify(ready)); }} }})().catch(error => {{ console.error(error); process.exit(1); }}); """ subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) def test_search_preview_claim_is_single_flight_and_reports_confirmation(): script = f""" const createSearchPreview = require({json.dumps(str(SEARCH_PREVIEW))}); (async () => {{ let claims = 0; let resolveClaim; const states = []; const preview = createSearchPreview({{ fetchJson: item => Promise.resolve(item), claim: () => {{ claims += 1; return new Promise(resolve => {{ resolveClaim = resolve; }}); }}, onState: state => states.push(state), }}); await preview.open({{ repository:'stackchain/api', number:42, kind:'issue' }}); const detail = {{ repository:'stackchain/api', number:42, claimable:true }}; const first = preview.claim(detail); const second = preview.claim(detail); if (claims !== 1 || first !== second) throw new Error('claim was not single-flight'); resolveClaim({{ assignees:['timmy'] }}); await first; if (!states.some(state => state.status === 'claimed')) throw new Error('claim confirmation missing'); }})().catch(error => {{ console.error(error); process.exit(1); }}); """ subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) def test_remote_search_selection_opens_native_preview_without_navigation(): html = dashboard_bundle_text() run_item = html.split("function runCommandItem(item)", 1)[1].split( "function renderCommands", 1 )[0] assert "searchPreview.open(item.result)" in run_item assert "window.location.assign" not in run_item remote_branch = run_item.split("} else {", 1)[1] assert "qs('#cmd-input').value = ''" not in remote_branch def test_assigned_issue_preview_hands_off_to_existing_my_work_sheet(): html = dashboard_bundle_text() assert "detail.claimable || (detail.assigned_to_me && detail.kind === 'issue')" in html assert "claimButton.textContent = detail.assigned_to_me ? 'Open in My Work' : 'Assign to me'" in html assert "openPreviewIssueInMyWork" in html