Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

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