Public: anyone can read this; the crew can change it.

j/k, enter, esc and ? on returns, with test/keys-<page>.test.ts #10803

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/10795-j-k-enter-esc-and-on-returns-with-test-k → main 2 commits fork

  • Merged. mason/10795-j-k-enter-esc-and-on-returns-with-test-k was merged into main as 909b629.

Review

Sign in to review this change.
2 files changed +52 −0
src/web/pages/returns.ts +1
@@ -138,6 +138,7 @@ export function returnsPage(ctx: Ctx): Response {
138138 const slug = repo ? `${repo.ownerName}/${repo.name}` : "";
139139 return row({
140140 href: `/${slug}/issues/${c.number}`,
141 nav: true,
141142 title: html`#${c.number} ${c.title}`,
142143 meta: html`${slug} · ${runs(c)} ·
143144 ${c.outcome === "landed"
test/keys-returns.test.ts added +51
@@ -0,0 +1,51 @@
1 /** `/board/returns` joins the shell's j/k traversal (#10795): the costliest issues are data-nav rows; the figure rows are not links and stay out. */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtemp, rm } from "node:fs/promises";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { createRepo } from "../src/actions/repos.ts";
8 import { openIssue } from "../src/actions/issues.ts";
9 import { createRun } from "../src/core/runs.ts";
10 import { forgeHttp, type ForgeHttp } from "../src/http/server.ts";
11 import { createSession } from "../src/core/sessions.ts";
12 import { seedAdmin } from "./helpers/admin.ts";
13 import { quietLogs } from "./helpers/globals.ts";
14 import { expectListKeys, mainOf, navHrefs } from "./helpers/shell-page.ts";
15
16 quietLogs();
17 let dataDir = "";
18 let app: App;
19 let http: ForgeHttp;
20 let cookie = "";
21
22 beforeAll(async () => {
23 dataDir = await mkdtemp(join(tmpdir(), "forge-keys-returns-"));
24 app = createApp({ dataDir, port: 0 });
25 http = forgeHttp(app);
26 const admin = await seedAdmin(app, { username: "alice", email: "[email protected]", password: "correct-horse-battery" });
27 if (!admin.ok) throw new Error(admin.error);
28 cookie = `forge_session=${createSession(app.db, admin.value.id).token}`;
29 const made = await createRepo(app, admin.value, { name: "mill", visibility: "public", description: "" });
30 if (!made.ok) throw new Error("repo");
31 for (const title of ["First work", "Second work"]) {
32 const opened = openIssue(app, admin.value, made.value, { title });
33 if (!opened.ok) throw new Error("issue");
34 const id = createRun(app.db, { repoId: made.value.id, kind: "agent", issueId: opened.value.id, branch: "", actorId: admin.value.id, reason: "assigned" }).id;
35 app.db.query("UPDATE run SET state='ok', cost_usd=1, started_at=?2, finished_at=?3 WHERE id=?1").run(id, new Date(Date.now() - 60_000).toISOString(), new Date().toISOString());
36 }
37 });
38
39 afterAll(async () => {
40 app.close();
41 await rm(dataDir, { recursive: true, force: true });
42 });
43
44 test("each costliest issue is one data-nav link; j/k focus it, Enter opens it, Esc comes back, fields keep their keys", async () => {
45 const res = await http.handle(new Request("http://forge.test/board/returns", { headers: { cookie } }));
46 const markup = mainOf(await res.text());
47 const hrefs = await navHrefs(markup);
48 expect(hrefs.length).toBe(2);
49 for (const href of hrefs) expect(href).toMatch(/^\/alice\/mill\/issues\/\d+$/);
50 await expectListKeys({ path: "/board/returns", html: markup, hrefs, opened: { path: hrefs[0]!, body: "<main>issue</main>" } });
51 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs