Skip to content
194 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { BranchDrifts, Commit } from "@g1t/contracts";
5
6import { activeBranches, branchSections, branchTab, branchesToRead, driftScale, isMerged, isStale, isYours } from "./branches.ts";
7
8const commit = (hash: string, at: string, message = `${hash}\n\nbody`): Commit => ({
9 hash,
10 treeHash: `t${hash}`,
11 message,
12 author: { name: "Ada", email: "ada@example.com" },
13 parents: [],
14 authoredAt: at,
15});
16
17const branches = [
18 { name: "main", hash: "m3" },
19 { name: "old", hash: "o1" },
20 { name: "fix", hash: "f2" },
21 { name: "idea", hash: "i1" },
22];
23const pulls = [{ branch: "fix", number: 7, title: "Fix it", checkStatus: "passed" as const, status: "open" as const }];
24
25test("branches with an open pull request are read first, the default branch never", () => {
26 const read = branchesToRead({ defaultBranch: "main", branches, pulls }, 2);
27 assert.deepEqual(read.reading.map((b) => b.name), ["fix", "old"]);
28 assert.equal(read.total, 3);
29 assert.equal(read.mainHead, "m3");
30});
31
32test("no default branch head, nothing to measure against", () => {
33 assert.equal(branchesToRead({ defaultBranch: "trunk", branches, pulls: [] }, 10).mainHead, null);
34});
35
36test("each branch gets its measured commit and drift, newest first, by head hash", () => {
37 const measured: BranchDrifts = {
38 base: commit("m3", "2026-10-08T00:00:00Z"),
39 branches: [
40 { head: "f2", commit: commit("f2", "2026-10-07T00:00:00Z"), drift: { ahead: 2, behind: 1 } },
41 { head: "o1", commit: commit("o1", "2026-01-01T00:00:00Z"), drift: null },
42 { head: "i1", commit: commit("i1", "2026-10-08T00:00:00Z", "one line"), drift: { ahead: 1, behind: 0 } },
43 ],
44 };
45 const { reading } = branchesToRead({ defaultBranch: "main", branches, pulls }, 10);
46 const shown = activeBranches(reading, measured, { pulls, previews: [{ number: 7, url: "https://fix.g1t.page" }] });
47 assert.deepEqual(shown.map((b) => b.name), ["idea", "fix", "old"]);
48 assert.deepEqual(shown[1], {
49 name: "fix",
50 head: "f2",
51 // No accounts given: the name on the commit, and never its address.
52 commit: { hash: "f2", message: "f2", author: { kind: "author", name: "Ada", username: null, avatar: null }, coAuthors: [], at: "2026-10-07T00:00:00Z" },
53 drift: { ahead: 2, behind: 1 },
54 pull: { number: 7, title: "Fix it", checkStatus: "passed", draft: false, status: "open" },
55 preview: "https://fix.g1t.page",
56 });
57 assert.equal(shown[2]?.drift, null);
58});
59
60test("when repos could not answer, the branches still show, without commits or counts", () => {
61 const { reading } = branchesToRead({ defaultBranch: "main", branches, pulls: [] }, 10);
62 const shown = activeBranches(reading, null, { pulls: [], previews: [] });
63 assert.equal(shown.length, 3);
64 assert.ok(shown.every((b) => b.commit == null && b.drift == null && b.pull == null));
65});
66
67test("repos' answer, as its JSON reads, keeps every count (flagon-io/hello's farewell)", () => {
68 // What services/repos serializes for `branch_drift` (drift.rs
69 // `the_answer_has_the_names_the_site_reads`): a name that differs here
70 // would drop every count without an error.
71 const wire = JSON.stringify({
72 base: {
73 hash: "8407eba",
74 treeHash: "t1",
75 message: "Use farewell() in the --both call",
76 author: { name: "g1t agent", email: "agent@example.com" },
77 parents: ["ac45e10"],
78 authoredAt: "2026-10-03T09:11:34.000Z",
79 },
80 branches: [
81 {
82 head: "bab14ff",
83 commit: {
84 hash: "bab14ff",
85 treeHash: "t2",
86 message: "Add a farewell\n\nbody",
87 author: { name: "syntaqx", email: "s@example.com" },
88 parents: ["c2ef68d"],
89 authoredAt: "2026-10-02T07:52:16.000Z",
90 },
91 drift: { ahead: 1, behind: 41 },
92 },
93 ],
94 });
95 const measured = JSON.parse(wire) as BranchDrifts;
96 const shown = activeBranches([{ name: "farewell", hash: "bab14ff" }], measured, { pulls: [], previews: [] });
97 assert.deepEqual(shown[0]?.drift, { ahead: 1, behind: 41 });
98 assert.deepEqual(shown[0]?.commit, {
99 hash: "bab14ff",
100 message: "Add a farewell",
101 author: { kind: "author", name: "syntaqx", username: null, avatar: null },
102 coAuthors: [],
103 at: "2026-10-02T07:52:16.000Z",
104 });
105});
106
107test("a branch's pull request is the open one, else the newest merged or closed", () => {
108 const reading = [
109 { name: "fix", hash: "f2" },
110 { name: "done", hash: "d1" },
111 ];
112 const newestFirst = [
113 { branch: "done", number: 9, title: "Second try", checkStatus: null, status: "merged" as const },
114 { branch: "fix", number: 8, title: "Closed", checkStatus: null, status: "closed" as const },
115 { branch: "done", number: 5, title: "First try", checkStatus: null, status: "closed" as const },
116 { branch: "fix", number: 7, title: "Fix it", checkStatus: "passed" as const, status: "open" as const },
117 ];
118 const shown = activeBranches(reading, null, { pulls: newestFirst, previews: [] });
119 assert.deepEqual(
120 shown.map((b) => [b.name, b.pull?.number, b.pull?.status]),
121 [
122 ["fix", 7, "open"],
123 ["done", 9, "merged"],
124 ],
125 );
126 // Only an open pull request puts a branch first in line to be read.
127 const all = [
128 { name: "main", hash: "m" },
129 { name: "a", hash: "a" },
130 { name: "done", hash: "d1" },
131 ];
132 assert.deepEqual(branchesToRead({ defaultBranch: "main", branches: all, pulls: newestFirst }, 1).reading.map((b) => b.name), ["a"]);
133});
134
135const now = Date.parse("2026-10-11T00:00:00Z");
136const row = (name: string, at: string | null, username: string | null = null, drift: { ahead: number; behind: number } | null = null) => ({
137 name,
138 head: name,
139 commit: at ? { hash: name, message: name, author: { kind: "user" as const, name, username, avatar: null }, coAuthors: [], at } : null,
140 drift,
141 pull: null,
142 preview: null,
143});
144
145test("a tab comes from the address, the overview otherwise", () => {
146 assert.equal(branchTab("stale"), "stale");
147 assert.equal(branchTab("STALE"), "overview");
148 assert.equal(branchTab(null), "overview");
149});
150
151test("stale is no commits in 90 days; yours is the viewer's latest commit", () => {
152 assert.equal(isStale(row("old", "2026-07-01T00:00:00Z"), now), true);
153 assert.equal(isStale(row("new", "2026-09-01T00:00:00Z"), now), false);
154 assert.equal(isStale(row("unread", null), now), false);
155 assert.equal(isYours(row("mine", "2026-10-01T00:00:00Z", "Ada"), "ada"), true);
156 assert.equal(isYours(row("theirs", "2026-10-01T00:00:00Z", "grace"), "ada"), false);
157 assert.equal(isYours(row("nobody", "2026-10-01T00:00:00Z", null), null), false);
158});
159
160test("a branch with nothing the default branch lacks, or a merged pull request, is merged", () => {
161 assert.equal(isMerged({ drift: { ahead: 0, behind: 3 }, pull: null }), true);
162 assert.equal(isMerged({ drift: { ahead: 2, behind: 0 }, pull: null }), false);
163 assert.equal(isMerged({ drift: null, pull: null }), false);
164 assert.equal(isMerged({ drift: { ahead: 2, behind: 0 }, pull: { number: 1, title: "", checkStatus: null, draft: false, status: "merged" } }), true);
165});
166
167test("the overview shows a few of yours, the active and the stale ones; each tab lists its own", () => {
168 const rows = [
169 row("a", "2026-10-10T00:00:00Z", "ada"),
170 row("b", "2026-10-09T00:00:00Z", "grace"),
171 row("c", "2026-10-08T00:00:00Z", "ada"),
172 row("d", "2026-01-01T00:00:00Z", "ada"),
173 ];
174 const viewer = { username: "ada", now };
175 assert.deepEqual(
176 branchSections(rows, "overview", viewer, 2).map((s) => [s.tab, s.branches.map((b) => b.name), s.more]),
177 [
178 ["yours", ["a", "c"], 1],
179 ["active", ["a", "b"], 1],
180 ["stale", ["d"], 0],
181 ],
182 );
183 // Someone with no branches of their own sees no section of theirs.
184 assert.deepEqual(branchSections(rows, "overview", { username: "linus", now }).map((s) => s.tab), ["active", "stale"]);
185 assert.deepEqual(branchSections(rows, "stale", viewer)[0]?.branches.map((b) => b.name), ["d"]);
186 assert.deepEqual(branchSections(rows, "active", viewer)[0]?.branches.map((b) => b.name), ["a", "b", "c"]);
187 assert.deepEqual(branchSections(rows, "yours", viewer)[0]?.branches.map((b) => b.name), ["a", "c", "d"]);
188 assert.equal(branchSections(rows, "all", viewer)[0]?.branches.length, 4);
189});
190
191test("the bars are drawn against the longest count shown", () => {
192 assert.equal(driftScale([row("a", null, null, { ahead: 3, behind: 40 }), row("b", null, null, { ahead: 12, behind: 0 })]), 40);
193 assert.equal(driftScale([row("a", null)]), 1);
194});