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

The pulls list is keyboard-first: j/k move between pulls, enter opens one #10348

Merged opened this

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

Merged

scout/forge:scout/10337-the-pulls-list-is-keyboard-first-j-k-mov → main 1 commit fork

  • Merged. scout/10337-the-pulls-list-is-keyboard-first-j-k-mov was merged into main as 2cdcc40.

Review

Sign in to review this change.
1 file changed +58 −0
test/keys-pulls-list.test.ts added +58
@@ -0,0 +1,58 @@
1 /** The pulls list joins the shell's j/k row traversal (#10337): one data-nav per row, none on the empty page. */
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 { createRepo } from "../src/actions/repos.ts";
7 import { createApp, type App } from "../src/app.ts";
8 import { createIssue } from "../src/core/issues.ts";
9 import { createUser } from "../src/core/users.ts";
10 import { serve, type Servers } from "../src/http/server.ts";
11
12 let dataDir: string;
13 let app: App;
14 let servers: Servers;
15 const numbers: number[] = [];
16
17 beforeAll(async () => {
18 dataDir = await mkdtemp(join(tmpdir(), "forge-keys-pulls-"));
19 app = createApp({ dataDir, port: 0 });
20 servers = await serve(app);
21 const made = await createUser(app.db, {
22 origin: "seed", trusted: true, username: "alice", email: "[email protected]",
23 password: "a-good-passphrase", adminIfFirst: true,
24 });
25 if (!made.ok) throw new Error(made.error);
26 const full = await createRepo(app, made.value, { name: "full", visibility: "public" });
27 const bare = await createRepo(app, made.value, { name: "bare", visibility: "public" });
28 if (!full.ok || !bare.ok) throw new Error("repo");
29 for (const [i, title] of ["First", "Second", "Third"].entries()) {
30 const p = createIssue(app.db, { repoId: full.value.id, authorId: made.value.id, title, kind: "pull" });
31 if (!p.ok) throw new Error(String(p.error));
32 const head = String(i + 1).repeat(40);
33 app.db
34 .query(`INSERT INTO pull (issue_id, base_branch, head_branch, base_oid, head_oid, head_repo_id)
35 VALUES (?1, 'main', ?2, 'a', ?3, ?4)`)
36 .run(p.value.id, `b${i}`, head, full.value.id);
37 numbers.push(p.value.number);
38 }
39 });
40
41 afterAll(async () => {
42 await servers.stop();
43 await rm(dataDir, { recursive: true, force: true });
44 });
45
46 test("each pull row is one data-nav link to its own page", async () => {
47 const text = await (await fetch(`${servers.url}/alice/full/pulls`)).text();
48 const navs = [...text.matchAll(/<a href="([^"]+)"\s+class="row"[^>]*data-nav tabindex="-1"/g)].map((m) => m[1]);
49 expect(navs.length).toBe(3);
50 expect(text.match(/data-nav/g)!.length).toBe(3);
51 for (const n of numbers) expect(navs).toContain(`/alice/full/pulls/${n}`);
52 });
53
54 test("the empty list draws no data-nav", async () => {
55 const res = await fetch(`${servers.url}/alice/bare/pulls`);
56 expect(res.status).toBe(200);
57 expect(await res.text()).not.toContain("data-nav");
58 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs