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

A test pins the review marks on a terminal pull list: approved, changes asked, nobody yet, in one query #6990

Merged opened this

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

Merged

scout/forge:scout/6984-a-test-pins-the-review-marks-on-a-termin → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/6984-a-test-pins-the-review-marks-on-a-termin was merged into main as 07d879c.

Review

Sign in to review this change.
1 file changed +152 −0
test/tui-verdicts.test.ts added +152
@@ -0,0 +1,152 @@
1 /**
2 * The mark each row of the terminal's pull list wears for where it stands with
3 * its reviewers (`src/tui/screens/verdicts.ts`): approved, changes asked,
4 * nobody yet — and a page of them costs one query.
5 */
6 import { afterAll, beforeAll, expect, test } from "bun:test";
7 import { mkdtemp, rm } from "node:fs/promises";
8 import { tmpdir } from "node:os";
9 import { join } from "node:path";
10 import { openPull } from "../src/actions/pulls.ts";
11 import { commitToBranch, createRepo } from "../src/actions/repos.ts";
12 import { createApp, type App as Forge } from "../src/app.ts";
13 import type { Pull } from "../src/core/pulls.ts";
14 import type { Repo } from "../src/core/repos.ts";
15 import { submitReview } from "../src/core/reviews.ts";
16 import type { User } from "../src/core/users.ts";
17 import { repoDir } from "../src/git/repos.ts";
18 import { createForgeApp } from "../src/tui/forge.ts";
19 import { issuesScreen } from "../src/tui/screens/issues.ts";
20 import { local } from "../src/tui/screens/where.ts";
21 import { VERDICT_MARKS } from "../src/tui/screens/common.ts";
22 import { verdictsForPulls } from "../src/tui/screens/verdicts.ts";
23 import { headlessTerminal } from "../src/tui/term/terminal.ts";
24 import { seedAdmin } from "./helpers/admin.ts";
25 import { quietLogs } from "./helpers/globals.ts";
26 import { statements } from "./helpers/statements.ts";
27 import { settleApp } from "./helpers/tui.ts";
28
29 quietLogs();
30
31 let dataDir: string;
32 let forge: Forge;
33 let alice: User;
34 let repo: Repo;
35
36 async function pull(name: string): Promise<Pull> {
37 const branch = await commitToBranch(forge, alice, repo, {
38 branch: `feature/${name}`,
39 base: "main",
40 message: `Change ${name}`,
41 files: [{ path: `${name}.ts`, content: `export const f = "${name}";\n` }],
42 });
43 if (!branch.ok) throw new Error(JSON.stringify(branch.error));
44 const opened = await openPull(forge, alice, repo, {
45 title: `Pull ${name}`,
46 body: "",
47 baseBranch: "main",
48 headBranch: `feature/${name}`,
49 });
50 if (!opened.ok) throw new Error(String(opened.error));
51 return opened.value;
52 }
53
54 const review = (p: Pull, state: "approved" | "changes_requested") => {
55 const r = submitReview(forge.db, {
56 issueId: p.id,
57 authorId: alice.id,
58 state,
59 commitOid: p.headOid,
60 });
61 if (!r.ok) throw new Error(r.error);
62 };
63
64 const dir = () => repoDir(forge.reposRoot, "alice", "marks");
65
66 beforeAll(async () => {
67 dataDir = await mkdtemp(join(tmpdir(), "forge-tui-verdicts-"));
68 forge = createApp({ dataDir, port: 0 });
69 const a = await seedAdmin(forge, {
70 username: "alice",
71 email: "[email protected]",
72 password: "a-long-passphrase",
73 });
74 if (!a.ok) throw new Error("signup failed");
75 alice = a.value;
76 const r = await createRepo(forge, alice, { name: "marks", visibility: "public" });
77 if (!r.ok) throw new Error("repo creation failed");
78 repo = r.value;
79 await commitToBranch(forge, alice, repo, {
80 message: "seed",
81 files: [{ path: "README.md", content: "# Marks\n" }],
82 });
83 });
84
85 afterAll(async () => {
86 forge.close();
87 await rm(dataDir, { recursive: true, force: true });
88 });
89
90 test("each row of the pull list wears the mark of where it stands", async () => {
91 const good = await pull("good");
92 const blocked = await pull("blocked");
93 const unseen = await pull("unseen");
94 review(good, "approved");
95 review(blocked, "changes_requested");
96
97 const terminal = headlessTerminal(120, 40);
98 const { app, session } = createForgeApp(
99 forge,
100 { sshUsername: "alice", remoteAddress: "127.0.0.1", user: alice },
101 { terminal, exitHooks: false },
102 );
103 app.start();
104 await settleApp(app);
105 app.push(issuesScreen(session, app.ctx, local(session, repo), "pull"));
106 await settleApp(app);
107 const lines = app.screen.toText().split("\n");
108 const rowOf = (p: Pull) => {
109 const line = lines.find((l) => l.includes(p.title));
110 if (line === undefined) throw new Error(`no row for ${p.title}\n${lines.join("\n")}`);
111 return line;
112 };
113 expect(rowOf(good)).toMatch(new RegExp(`${VERDICT_MARKS.approved}\\s+#${good.number}\\b`));
114 expect(rowOf(blocked)).toMatch(new RegExp(`${VERDICT_MARKS.changes_requested}\\s+#${blocked.number}\\b`));
115 expect(rowOf(unseen)).toMatch(new RegExp(`${VERDICT_MARKS.none}\\s+#${unseen.number}\\b`));
116 app.stop();
117 });
118
119 test("an approval given before a later push no longer counts for the new head", async () => {
120 const moving = await pull("moving");
121 review(moving, "approved");
122 const before = await verdictsForPulls(forge.db, dir(), repo.id, [moving.number]);
123 expect(before.get(moving.number)).toBe("approved");
124
125 const pushed = await commitToBranch(forge, alice, repo, {
126 branch: "feature/moving",
127 message: "Change moving again",
128 files: [{ path: "moving.ts", content: 'export const f = "again";\n' }],
129 });
130 if (!pushed.ok) throw new Error(JSON.stringify(pushed.error));
131 const after = await verdictsForPulls(forge.db, dir(), repo.id, [moving.number]);
132 expect(after.get(moving.number)).toBe("none");
133 });
134
135 test("the marks for a page of 30 pulls come from one query", async () => {
136 const numbers: number[] = [];
137 for (let i = 0; i < 30; i++) {
138 const p = await pull(`page${i}`);
139 if (i % 3 === 0) review(p, "approved");
140 else if (i % 3 === 1) review(p, "changes_requested");
141 numbers.push(p.number);
142 }
143 let marks = new Map<number, string>();
144 const cost = await statements(forge.db, async () => {
145 marks = await verdictsForPulls(forge.db, dir(), repo.id, numbers);
146 });
147 expect(cost).toBe(1);
148 expect(marks.get(numbers[0]!)).toBe("approved");
149 expect(marks.get(numbers[1]!)).toBe("changes_requested");
150 // Nobody reviewed it, so there is no row to report: absence reads as "none".
151 expect(marks.get(numbers[2]!) ?? "none").toBe("none");

Vacuous as written: undefined and "none" both pass. Assert marks.has(numbers[2]!) is false, which is what the comment claims.

152 }, 60_000);

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs