Skip to content
244 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 type Merged,
6 placePushes,
7 historyCovers,
8 change,
9 checksFact,
10 dayKey,
11 isTestFile,
12 landedToday,
13 mergedAutomatically,
14 parseSort,
15 parseTab,
16 pullFacts,
17 reachesBack,
18 reasonFor,
19 signedPercent,
20 sortRows,
21 summaryLine,
22 pushedByPerson,
23 pushedCommits,
24 weekOf,
25 whyFor,
26} from "./mission-control.ts";
27
28const HOUR = 3_600_000;
29const DAY = 24 * HOUR;
30// Monday 2026-10-05, 18:00 UTC.
31const NOW = Date.parse("2026-10-05T18:00:00Z");
32const repo = { namespace: "acme", name: "web" };
33
34test("each kind of need gets its reason chip", () => {
35 assert.equal(reasonFor({ kind: "deploy" }), "blocking");
36 assert.equal(reasonFor({ kind: "review" }), "needs_review");
37 assert.equal(reasonFor({ kind: "invitation" }), "invited");
38 assert.equal(reasonFor({ kind: "checks" }), "checks_failing");
39 assert.equal(reasonFor({ kind: "runner" }), "stalled");
40});
41
42test("why a need needs a person is said for every reason", () => {
43 for (const kind of ["deploy", "invitation", "review", "stuck", "checks", "ready", "conflict", "stalled", "runner"] as const) {
44 const need = { kind };
45 assert.ok(whyFor(reasonFor(need), need).length > 15, kind);
46 }
47});
48
49test("what is known lists only what is known", () => {
50 const facts = pullFacts({
51 checkStatus: "failed",
52 files: [
53 { path: "src/retry.ts", additions: 40, deletions: 2 },
54 { path: "src/retry.test.ts", additions: 60, deletions: 0 },
55 ],
56 });
57 const by = Object.fromEntries(facts.map((f) => [f.label, f]));
58 assert.equal(by["Required checks"].value, "Failed in the merge queue");
59 assert.equal(by["Required checks"].tone, "bad");
60 assert.equal(by["Files changed"].value, "2");
61 assert.equal(by.Lines.value, "+100 −2");
62 assert.equal(by.Tests.value, "1 file");
63
64 const bare = pullFacts({ checkStatus: null, files: [] });
65 assert.deepEqual(bare, [{ label: "Required checks", value: "Not known yet", tone: null }]);
66});
67
68test("the required checks are read from the pull request's check status", () => {
69 assert.deepEqual(checksFact("running"), { label: "Required checks", value: "Running", tone: null });
70 assert.equal(checksFact("passed").tone, "good");
71 assert.equal(checksFact("failed").value, "Failed in the merge queue");
72});
73
74test("test files are recognised by the names test runners use", () => {
75 assert.ok(isTestFile("src/a.test.ts"));
76 assert.ok(isTestFile("src/__tests__/a.ts"));
77 assert.ok(isTestFile("tests/api.rs"));
78 assert.ok(isTestFile("pkg/store_test.go"));
79 assert.ok(!isTestFile("src/testing-utils.ts"));
80 assert.ok(!isTestFile("src/contest.ts"));
81 assert.ok(isTestFile("test.js"));
82 assert.ok(isTestFile("lib/spec.rb"));
83});
84
85const merged = (daysAgo: number, mergedBy: string | null, number = 1): Merged => ({
86 repo,
87 number,
88 title: `Change ${number}`,
89 mergedBy,
90 mergedAt: new Date(NOW - daysAgo * DAY).toISOString(),
91 files: [],
92});
93
94test("a push to the default branch counts a person's own commits, not merges or g1t's", () => {
95 const c = (hash: string, email: string, parents = 1) => ({ hash, author: { name: "g1t", email }, parents: Array(parents).fill("p") });
96 // Every commit is named "g1t": the name decides nothing, the address does.
97 const history = [c("e", "chase@example.com"), c("d", "agent@g1t.sh"), c("m", "chase@example.com", 2), c("b", "chase@example.com"), c("a", "chase@example.com")];
98 const byG1t = (commit: { author: { email: string } }) => commit.author.email === "agent@g1t.sh";
99 assert.deepEqual(pushedCommits(history, "a", byG1t).map((x) => x.hash), ["e", "b"]);
100 // Where it pointed before was not read: everything read counts.
101 assert.deepEqual(pushedCommits(history, "zz", byG1t).map((x) => x.hash), ["e", "b", "a"]);
102});
103
104test("who pushed is the account that signed in, not the name on the commits", () => {
105 assert.ok(pushedByPerson({ actor: "usr_chase", data: {} }));
106 assert.ok(pushedByPerson({ actor: null, data: {} }));
107 assert.ok(!pushedByPerson({ actor: "usr_g1t_agent", data: {} }));
108 assert.ok(!pushedByPerson({ actor: "g1t_policy", data: {} }));
109 // A workflow job's own token is not a person either.
110 assert.ok(!pushedByPerson({ actor: "usr_chase", data: { causedByJob: "run_1" } }));
111
112 const c = (hash: string, name: string) => ({ hash, parents: ["p"], author: { name, email: `${hash}@example.com` } });
113 // A person whose git name is "g1t" pushed m1; g1t pushed w1 under a person's name.
114 const history = [c("w1", "Chase Pierce"), c("m1", "g1t"), c("old", "Chase Pierce")];
115 const pushes = [
116 { time: "2026-10-07T12:00:00Z", actor: "usr_g1t_agent", data: { after: "w1", before: "m1" } },
117 { time: "2026-10-05T12:00:00Z", actor: "usr_chase", data: { after: "m1", before: "old" } },
118 ];
119 assert.deepEqual(placePushes(history, pushes), [{ hash: "m1", at: "2026-10-05T12:00:00Z" }]);
120});
121
122test("a short read of the branch is enough when it reaches the oldest push", () => {
123 const h = (...hashes: string[]) => hashes.map((hash) => ({ hash }));
124 const pushes = [
125 { time: "2026-10-07T12:00:00Z", data: { after: "c1", before: "c2" } },
126 { time: "2026-10-05T12:00:00Z", data: { after: "c2", before: "c3" } },
127 ];
128 // Holds the oldest push's before.
129 assert.ok(historyCovers(h("c1", "c2", "c3"), pushes, 3));
130 // Full, and the oldest push reaches past it: read further.
131 assert.ok(!historyCovers(h("c1", "c2", "x"), pushes, 3));
132 // Shorter than asked: the whole branch.
133 assert.ok(historyCovers(h("c1", "c2"), pushes, 3));
134 // The oldest push made the branch: only the whole branch will do.
135 assert.ok(!historyCovers(h("c1", "c2", "c3"), [{ time: "", data: { after: "c1" } }], 3));
136});
137
138test("each push to the default branch places the commits it brought, at its time, from one read of the history", () => {
139 const c = (hash: string, parents = ["p"], email = "chase@example.com") => ({ hash, parents, author: { name: "Chase Pierce", email } });
140 // Newest first: a Wednesday push of two, a Monday push of one, a merge, and g1t's own commit.
141 const history = [c("w2"), c("w1"), c("m1"), c("merge", ["a", "b"]), c("bot", ["p"], "g1t@users.noreply.g1t.sh"), c("old")];
142 const pushes = [
143 { time: "2026-10-07T12:00:00Z", actor: "usr_chase", data: { after: "w2", before: "m1" } },
144 { time: "2026-10-05T12:00:00Z", actor: "usr_chase", data: { after: "m1", before: "old" } },
145 { time: "2026-10-01T12:00:00Z", actor: "usr_chase", data: { after: "gone", before: "older" } },
146 ];
147 const byG1t = (commit: { author: { email: string } }) => commit.author.email.endsWith("@users.noreply.g1t.sh");
148 assert.deepEqual(placePushes(history, pushes, byG1t), [
149 { hash: "m1", at: "2026-10-05T12:00:00Z" },
150 { hash: "w2", at: "2026-10-07T12:00:00Z" },
151 { hash: "w1", at: "2026-10-07T12:00:00Z" },
152 ]);
153});
154
155test("a change merged automatically when g1t merged it", () => {
156 assert.ok(mergedAutomatically({ mergedBy: "g1t" }));
157 assert.ok(mergedAutomatically({ mergedBy: null }));
158 assert.ok(!mergedAutomatically({ mergedBy: "syntaqx" }));
159});
160
161test("the week is seven days of landed changes, with the week before", () => {
162 const week = weekOf(
163 [merged(0, "g1t"), merged(0, "syntaqx"), merged(0, "syntaqx", 2), merged(1, "g1t", 3), merged(1, "g1t"), merged(6, "g1t"), merged(8, "g1t"), merged(10, "alex"), merged(20, "g1t")],
164 NOW,
165 "UTC",
166 );
167 assert.equal(week.days.length, 7);
168 assert.equal(week.days[6].key, "2026-10-05");
169 assert.equal(week.days[6].label, "Mon");
170 assert.deepEqual(
171 week.days.map((d) => d.count),
172 [1, 0, 0, 0, 0, 2, 3],
173 );
174 assert.equal(week.total, 6);
175 assert.equal(week.previous, 2);
176 // Not knowing the week before is not the same as nothing in it.
177 assert.equal(weekOf([merged(0, "g1t")], NOW, "UTC", false).previous, null);
178});
179
180test("days follow the viewer's time zone", () => {
181 // 02:00 UTC on the 5th is still the 4th in Los Angeles.
182 const late = Date.parse("2026-10-05T02:00:00Z");
183 assert.equal(dayKey(late, "America/Los_Angeles"), "2026-10-04");
184 assert.equal(dayKey(late, "UTC"), "2026-10-05");
185 assert.equal(dayKey(late, "Not/AZone"), "2026-10-05");
186 const week = weekOf([{ mergedAt: new Date(late).toISOString() }], NOW, "America/Los_Angeles");
187 assert.equal(week.days.find((d) => d.key === "2026-10-04")?.count, 1);
188});
189
190test("the change from last week, and how it reads", () => {
191 assert.equal(change(12, 10), 0.2);
192 assert.equal(change(5, 0), null);
193 assert.equal(change(5, null), null);
194 assert.equal(signedPercent(0.2), "+20%");
195 assert.equal(signedPercent(-0.054), "−5%");
196 assert.equal(signedPercent(0.001), "0%");
197});
198
199test("a repository's merged list reaches back when it is not full or its oldest is old enough", () => {
200 const since = NOW - 14 * DAY;
201 const at = (days: number) => ({ mergedAt: new Date(NOW - days * DAY).toISOString(), updatedAt: new Date(NOW).toISOString() });
202 assert.ok(reachesBack([at(1), at(2)], since, 100));
203 assert.ok(reachesBack([at(1), at(20)], since, 2));
204 assert.ok(!reachesBack([at(1), at(3)], since, 2));
205});
206
207test("landed today is the viewer's calendar day, newest first", () => {
208 const rows = landedToday([merged(0.1, "g1t", 3), merged(0.5, "syntaqx", 4), merged(1, "g1t", 2)], NOW, "UTC");
209 assert.deepEqual(
210 rows.map((r) => [r.ref, r.automatic]),
211 [
212 ["#3", true],
213 ["#4", false],
214 ],
215 );
216 assert.equal(rows[1].by?.name, "syntaqx");
217});
218
219test("tabs and sorting come from the address", () => {
220 assert.equal(parseTab("landed"), "landed");
221 assert.equal(parseTab("waiting"), null);
222 assert.equal(parseTab("nope"), null);
223 assert.equal(parseSort("newest"), "newest");
224 assert.equal(parseSort(null), "impact");
225 const rows = [{ at: 1 }, { at: 3 }, { at: 2 }];
226 assert.deepEqual(sortRows(rows, "impact"), rows);
227 assert.deepEqual(
228 sortRows(rows, "newest").map((r) => r.at),
229 [3, 2, 1],
230 );
231});
232
233test("the summary says the week plainly", () => {
234 assert.equal(summaryLine({ total: 47, needs: 0 }), "47 changes landed in the last 7 days.");
235 assert.equal(summaryLine({ total: 1, needs: 2 }), "1 change landed in the last 7 days.");
236 assert.equal(summaryLine({ total: 0, needs: 2 }), "Nothing has landed this week. What is waiting on you is below.");
237 assert.equal(summaryLine({ total: 0, needs: 0 }), "Nothing has landed this week yet.");
238});
239
240test("a job waiting for a self-hosted runner says so", () => {
241 const runner = { kind: "runner" as const };
242 assert.equal(reasonFor(runner), "stalled");
243 assert.match(whyFor(reasonFor(runner), runner), /self-hosted runner/);
244});