feat: add global work search (#195)
This commit is contained in:
1 parent
de20406a45
commit
7651e5b090
8 files changed
+359
-17
No files matched your search
@@ -19,5 +19,6 @@ def test_api_requests_resolve_inside_dashboard_subpath():
|
||||
"https://forge.alexanderwhitestone.com/dashboard/api/v1/notifications/",
|
||||
"https://forge.alexanderwhitestone.com/dashboard/api/v1/notifications?page=",
|
||||
"https://forge.alexanderwhitestone.com/dashboard/api/v1/notifications/read",
|
||||
"https://forge.alexanderwhitestone.com/dashboard/api/v1/search?q=",
|
||||
"https://forge.alexanderwhitestone.com/dashboard/api/v1/work/",
|
||||
}
|
||||
@@ -46,3 +46,61 @@ def test_command_script_resolves_inside_dashboard_subpath():
|
||||
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_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 = (FRONTEND / "index.html").read_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 urljoin(
|
||||
"https://forge.alexanderwhitestone.com/dashboard/",
|
||||
"api/v1/search?q=mobile",
|
||||
) == "https://forge.alexanderwhitestone.com/dashboard/api/v1/search?q=mobile"
|
||||
@@ -0,0 +1,83 @@
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from src import gitea_proxy, main
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_global_search_endpoint_returns_bounded_normalized_results(monkeypatch):
|
||||
requested = []
|
||||
|
||||
async def search(query, limit):
|
||||
requested.append((query, limit))
|
||||
return [{
|
||||
"kind": "issue",
|
||||
"repository": "stackchain/api",
|
||||
"number": 42,
|
||||
"title": "Repair mobile queue",
|
||||
"state": "open",
|
||||
"url": "https://forge.example/stackchain/api/issues/42",
|
||||
}]
|
||||
|
||||
monkeypatch.setattr(main.gitea_proxy, "global_search", search, raising=False)
|
||||
transport = httpx.ASGITransport(app=main.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
response = await client.get("/api/v1/search?q=mobile&limit=7")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["cache-control"] == "no-store"
|
||||
assert requested == [("mobile", 7)]
|
||||
assert response.json() == {"query": "mobile", "items": [{
|
||||
"kind": "issue",
|
||||
"repository": "stackchain/api",
|
||||
"number": 42,
|
||||
"title": "Repair mobile queue",
|
||||
"state": "open",
|
||||
"url": "https://forge.example/stackchain/api/issues/42",
|
||||
}]}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_global_search_queries_issues_and_pulls_and_skips_unsafe_results():
|
||||
requests = []
|
||||
|
||||
async def handler(request):
|
||||
requests.append(dict(request.url.params))
|
||||
kind = request.url.params["type"]
|
||||
if kind == "issues":
|
||||
return httpx.Response(200, json=[{
|
||||
"id": 4, "number": 42, "title": "Repair queue", "state": "open",
|
||||
"repository": {"full_name": "stackchain/api"},
|
||||
"html_url": "https://forge.example/stackchain/api/issues/42",
|
||||
}, {
|
||||
"id": 4, "number": 42, "title": "Repair queue", "state": "open",
|
||||
"repository": {"full_name": "stackchain/api"},
|
||||
"html_url": "https://forge.example/stackchain/api/issues/42",
|
||||
}, {
|
||||
"id": 5, "number": 43, "title": "Unsafe", "state": "open",
|
||||
"repository": {"full_name": "stackchain/api"},
|
||||
"html_url": "javascript:alert(1)",
|
||||
}])
|
||||
return httpx.Response(200, json=[{
|
||||
"id": 8, "number": 9, "title": "Improve search", "state": "closed",
|
||||
"repository": {"full_name": "stackchain/web"},
|
||||
"html_url": "https://forge.example/stackchain/web/pulls/9",
|
||||
}])
|
||||
|
||||
gitea_proxy.start_client(transport=httpx.MockTransport(handler))
|
||||
try:
|
||||
results = await gitea_proxy.global_search("mobile queue", 7)
|
||||
finally:
|
||||
await gitea_proxy.stop_client()
|
||||
|
||||
assert {request["type"] for request in requests} == {"issues", "pulls"}
|
||||
assert all(request["q"] == "mobile queue" and request["limit"] == "7" for request in requests)
|
||||
assert results == [{
|
||||
"kind": "issue", "repository": "stackchain/api", "number": 42,
|
||||
"title": "Repair queue", "state": "open",
|
||||
"url": "https://forge.example/stackchain/api/issues/42",
|
||||
}, {
|
||||
"kind": "pull", "repository": "stackchain/web", "number": 9,
|
||||
"title": "Improve search", "state": "closed",
|
||||
"url": "https://forge.example/stackchain/web/pulls/9",
|
||||
}]
|
||||
Reference in new issue
Block a user