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.

275 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 FALLBACK_FOCUS_MS,
6 FALLBACK_POLL_MS,
7 LIVE_DEBOUNCE_MS,
8 LIVE_HOLD_CHECK_MS,
9 LIVE_MAX_WAIT_MS,
10 LIVE_MIN_GAP_MS,
11 type RepoChange,
12 atBottom,
13 branchOf,
14 fallbackDue,
15 gate,
16 isTyping,
17 newCommits,
18 repoPages,
19 tagOf,
20 workPages,
21} from "./repo-live.ts";
22
23const AT = "2026-10-11T10:00:00Z";
24const NOW = Date.parse(AT);
25
26function change(kind: string, more: Partial<RepoChange> = {}): RepoChange {
27 return { repo_id: "rep_1", repo: "acme/rocket", kind, at: AT, ...more };
28}
29
30const push = (branch: string, commits?: number) => change("git.push", { ref: `refs/heads/${branch}`, sha: "abc", commits: commits ?? null });
31
32test("refs name their branch or tag", () => {
33 assert.equal(branchOf("refs/heads/feature/x"), "feature/x");
34 assert.equal(branchOf("refs/tags/v1"), null);
35 assert.equal(tagOf("refs/tags/v1.2"), "v1.2");
36 assert.equal(tagOf(null), null);
37});
38
39test("a project's overview follows what it runs, who is working on it and what may need someone, not every push", () => {
40 const overview = repoPages.overview();
41 for (const c of [
42 change("deployment_status.created"),
43 change("status.created", { sha: "abc" }),
44 change("release.published"),
45 change("work.claimed", { number: 3 }),
46 change("session.started"),
47 change("session.progressed"),
48 change("pull.ready", { number: 4 }),
49 change("review.completed", { number: 4 }),
50 ]) {
51 assert.equal(overview(c), true, c.kind);
52 }
53 assert.equal(overview(push("feature")), false);
54 assert.equal(overview(change("comment.created", { number: 3 })), false);
55});
56
57test("the overview hears its own commit's checks, and every pull request's finished ones", () => {
58 const overview = repoPages.overview("abc");
59 assert.equal(overview(change("status.created", { sha: "abc" })), true);
60 assert.equal(overview(change("run.updated", { sha: "abc", run: "run_1" })), true);
61 assert.equal(overview(change("status.created", { sha: "f00d" })), false, "another commit's");
62 assert.equal(overview(change("run.updated", { sha: "f00d", run: "run_2" })), false);
63 assert.equal(overview(change("checks.completed", { sha: "f00d", number: 4 })), true, "how often checks pass");
64 assert.equal(overview(change("status.created")), true, "a check that names no commit may be its");
65});
66
67test("the files page follows refs, checks, releases and deployments", () => {
68 const files = repoPages.files();
69 for (const c of [push("main"), push("other"), change("git.deleted", { ref: "refs/heads/old" }), change("status.created", { sha: "abc" }), change("release.published"), change("deployment_status.created")]) {
70 assert.equal(files(c), true, c.kind);
71 }
72 assert.equal(files(change("work_item.opened", { number: 3 })), false);
73 assert.equal(files(change("comment.created", { number: 3 })), false);
74});
75
76test("a page of files hears its own branch and its commit's checks, not every branch's", () => {
77 const files = repoPages.files({ branch: "main", branches: ["main", "feature"], sha: "abc" });
78 assert.equal(files(push("main")), true, "its files and latest commit");
79 assert.equal(files(push("feature")), false, "another branch moving changes nothing here");
80 assert.equal(files(push("brand-new")), true, "a new branch: the menu and count");
81 assert.equal(files(change("git.deleted", { ref: "refs/heads/feature" })), true, "one of the menu's gone");
82 assert.equal(files(change("git.deleted", { ref: "refs/heads/never-listed" })), false);
83 assert.equal(files(change("git.push", { ref: "refs/tags/v2" })), true, "the About's tags");
84 assert.equal(files(change("branch.renamed", { ref: "refs/heads/feature" })), true);
85 assert.equal(files(change("repo.default_branch_changed")), true);
86 assert.equal(files(change("status.created", { sha: "abc" })), true, "its latest commit's checks");
87 assert.equal(files(change("check_run.completed", { sha: "f00d" })), false, "another commit's checks");
88 assert.equal(files(change("run.updated", { run: "run_1" })), true, "a check that names no commit may be its");
89 assert.equal(files(change("deployment_status.created")), true);
90 assert.equal(files(change("release.published")), true);
91 // A tag or a commit shown: no branch of its own moves it.
92 const tag = repoPages.files({ branch: null, branches: ["main"], sha: "abc" });
93 assert.equal(tag(push("main")), false);
94 // Without the branches, any branch may be new: only its own and deletions count.
95 const unknown = repoPages.files({ branch: "main", branches: null, sha: "abc" });
96 assert.equal(unknown(push("other")), false);
97 assert.equal(unknown(change("git.deleted", { ref: "refs/heads/other" })), true);
98});
99
100test("the commits page hears pushes to its own branch only", () => {
101 const commits = repoPages.commits("main");
102 assert.equal(commits(push("main")), true);
103 assert.equal(commits(push("feature")), false);
104 assert.equal(commits(change("git.push", { ref: "refs/tags/v1" })), false);
105 assert.equal(commits(change("workflow.completed", { sha: "abc", run: "run_1" })), true, "checks on its commits");
106 assert.equal(commits(change("pull.opened", { number: 1 })), false);
107 const shown = repoPages.commits("main", ["abc", "def"]);
108 assert.equal(shown(change("status.created", { sha: "def" })), true, "checks on a commit it lists");
109 assert.equal(shown(change("status.created", { sha: "f00d" })), false, "not on one it does not");
110});
111
112test("branches, tags, and the list of pull requests", () => {
113 assert.equal(repoPages.refs()(push("x")), true);
114 assert.equal(repoPages.refs()(change("pull.opened", { number: 1 })), true);
115 assert.equal(repoPages.refs()(change("work_item.opened", { number: 1 })), false);
116 assert.equal(repoPages.tags()(change("git.push", { ref: "refs/tags/v2" })), true);
117 assert.equal(repoPages.tags()(push("main")), false);
118 assert.equal(repoPages.tags()(change("release.published")), true);
119 assert.equal(repoPages.pulls()(change("pull.updated", { number: 4 })), true);
120 assert.equal(repoPages.pulls()(change("review.completed", { number: 4 })), true);
121 assert.equal(repoPages.pulls()(change("work_item.opened", { number: 5 })), false);
122});
123
124test("a pull request hears its own changes, its head branch and its head's checks", () => {
125 const pull = repoPages.pull(7, { branch: "feature", sha: "f00d" });
126 assert.equal(pull(change("pull.updated", { number: 7 })), true);
127 assert.equal(pull(change("comment.created", { number: 7 })), true);
128 assert.equal(pull(change("review.completed", { number: 7 })), true);
129 assert.equal(pull(change("checks.completed", { number: 7, sha: "f00d" })), true);
130 assert.equal(pull(change("pull.merged", { number: 7 })), true);
131 assert.equal(pull(push("feature")), true, "new commits");
132 assert.equal(pull(change("status.created", { sha: "f00d" })), true);
133 assert.equal(pull(change("run.updated", { run: "run_1", number: 7 })), true, "a run for it");
134 assert.equal(pull(change("comment.created", { number: 8 })), false);
135 assert.equal(pull(push("main")), false);
136 assert.equal(pull(change("status.created", { sha: "beef" })), false);
137});
138
139test("a pull request does not hear a work item that shares its number", () => {
140 assert.equal(repoPages.pull(3)(change("work_item.closed", { number: 3 })), false);
141});
142
143test("the Sessions page hears claims, sessions starting and ending, what the queue finds, and merges", () => {
144 const page = repoPages.work();
145 for (const kind of ["work.claimed", "work.claim_expired", "session.started", "session.ended", "dependency.changed", "overlap.detected", "integration.failed", "decision.recorded", "queue.changed", "pull.merged"]) {
146 assert.equal(page(change(kind, { number: 3 })), true, kind);
147 }
148 assert.equal(page(change("session.progressed")), true, "a session going on");
149 assert.equal(page(change("session.appended", { number: 3 })), false);
150 assert.equal(page(change("comment.created", { number: 3 })), false);
151 assert.equal(page(push("main")), false);
152});
153
154test("a session going on reaches the pull request it is on, and no other", () => {
155 assert.equal(repoPages.pull(9)(change("session.progressed", { number: 9 })), true);
156 assert.equal(repoPages.pull(9)(change("session.progressed", { number: 3 })), false);
157 assert.equal(repoPages.pull(9)(change("session.progressed")), false);
158});
159
160test("a session's page hears every session, claim and overlap in the repository", () => {
161 const page = repoPages.session();
162 for (const kind of ["session.started", "session.progressed", "session.ended", "work.claimed", "work.released", "overlap.detected"]) {
163 assert.equal(page(change(kind)), true, kind);
164 }
165 assert.equal(page(change("comment.created", { number: 3 })), false);
166 assert.equal(page(change("pull.merged", { number: 3 })), false);
167 assert.equal(page(push("main")), false);
168});
169
170test("runs, one run, and security", () => {
171 assert.equal(repoPages.runs()(change("run.updated", { run: "run_1" })), true);
172 assert.equal(repoPages.runs()(change("status.created", { sha: "abc" })), true, "a new run reports pending on its commit");
173 assert.equal(repoPages.runs()(change("work_item.opened", { number: 1 })), false);
174 const run = repoPages.run("run_1", "abc");
175 assert.equal(run(change("run.updated", { run: "run_1" })), true);
176 assert.equal(run(change("workflow.completed", { run: "run_1" })), true);
177 assert.equal(run(change("run.updated", { run: "run_2" })), false);
178 assert.equal(run(change("status.created", { sha: "abc" })), true);
179 assert.equal(repoPages.security()(change("secret_scanning_alert.created")), true);
180 assert.equal(repoPages.security()(change("code_scanning_alert.fixed", { number: 2 })), true);
181 assert.equal(repoPages.security()(change("work_item.opened", { number: 1 })), false);
182});
183
184test("new commits are counted, and uncounted when a push was not", () => {
185 assert.equal(newCommits([push("main", 2), push("main", 3), push("other", 9)], "main"), 5);
186 assert.equal(newCommits([push("main", 2), push("main")], "main"), null);
187 assert.equal(newCommits([change("status.created")], "main"), 0);
188});
189
190test("a change waits a second for more, five at most, then refreshes", () => {
191 const idle = { now: NOW, visible: true, busy: false, navigating: false };
192 assert.deepEqual(gate({ pendingSince: null, lastChangeAt: null, lastRunAt: null }, idle), { action: "idle" });
193 assert.deepEqual(gate({ pendingSince: NOW, lastChangeAt: NOW, lastRunAt: null }, idle), { action: "wait", ms: LIVE_DEBOUNCE_MS });
194 assert.deepEqual(gate({ pendingSince: NOW, lastChangeAt: NOW, lastRunAt: null }, { ...idle, now: NOW + LIVE_DEBOUNCE_MS }), { action: "run" });
195 // A steady stream: refreshed after five seconds anyway.
196 const stream = { pendingSince: NOW, lastChangeAt: NOW + LIVE_MAX_WAIT_MS - 100, lastRunAt: null };
197 assert.deepEqual(gate(stream, { ...idle, now: NOW + LIVE_MAX_WAIT_MS - 50 }), { action: "wait", ms: 50 });
198 assert.deepEqual(gate(stream, { ...idle, now: NOW + LIVE_MAX_WAIT_MS }), { action: "run" });
199});
200
201test("never under someone typing, a dialog, or a navigation: it waits for them", () => {
202 const due = { pendingSince: NOW - 10_000, lastChangeAt: NOW - 10_000, lastRunAt: null };
203 for (const held of [{ busy: true, navigating: false }, { busy: false, navigating: true }]) {
204 assert.deepEqual(gate(due, { now: NOW, visible: true, ...held }), { action: "wait", ms: LIVE_HOLD_CHECK_MS });
205 }
206 assert.deepEqual(gate(due, { now: NOW, visible: true, busy: false, navigating: false }), { action: "run" });
207});
208
209test("a burst of changes refreshes at most every two seconds", () => {
210 const idle = { now: NOW, visible: true, busy: false, navigating: false };
211 // Quiet for a second, but the page refreshed half a second ago: it waits out the gap.
212 const justRan = { pendingSince: NOW - LIVE_DEBOUNCE_MS, lastChangeAt: NOW - LIVE_DEBOUNCE_MS, lastRunAt: NOW - 500 };
213 assert.deepEqual(gate(justRan, idle), { action: "wait", ms: LIVE_MIN_GAP_MS - 500 });
214 assert.deepEqual(gate(justRan, { ...idle, now: NOW - 500 + LIVE_MIN_GAP_MS }), { action: "run" });
215 // A steady stream after a refresh: one more at most five seconds on, never sooner than the gap.
216 const stream = { pendingSince: NOW - LIVE_MAX_WAIT_MS, lastChangeAt: NOW, lastRunAt: NOW - LIVE_MAX_WAIT_MS - 10 };
217 assert.deepEqual(gate(stream, idle), { action: "run" });
218 // Changes arriving every 100 ms for a minute refresh no more than once per gap.
219 let state = { pendingSince: null as number | null, lastChangeAt: null as number | null, lastRunAt: NOW };
220 let runs = 0;
221 for (let t = NOW; t < NOW + 60_000; t += 100) {
222 state = { ...state, pendingSince: state.pendingSince ?? t, lastChangeAt: t };
223 if (gate(state, { ...idle, now: t }).action === "run") {
224 runs++;
225 state = { pendingSince: null, lastChangeAt: null, lastRunAt: t };
226 }
227 }
228 assert.ok(runs <= 60_000 / LIVE_MIN_GAP_MS, `${runs} runs`);
229 assert.ok(runs >= 60_000 / LIVE_MAX_WAIT_MS - 1, `${runs} runs: still kept up to date`);
230});
231
232test("a tab out of view never refreshes; back in view, it catches up at once", () => {
233 const hidden = { now: NOW, visible: false, busy: false, navigating: false };
234 const waiting = { pendingSince: NOW - 5000, lastChangeAt: NOW - 5000, lastRunAt: NOW - 20 * 60_000 };
235 assert.deepEqual(gate(waiting, hidden), { action: "hold" }, "no timer: coming back asks again");
236 assert.deepEqual(gate({ pendingSince: null, lastChangeAt: null, lastRunAt: null }, hidden), { action: "idle" });
237 assert.deepEqual(gate(waiting, { ...hidden, visible: true }), { action: "run" });
238});
239
240test("without the socket: back in view every 30 s at most, a poll every minute in view, never with it", () => {
241 const base = { live: false, visible: true, now: NOW };
242 assert.equal(fallbackDue({ ...base, lastRunAt: null, reason: "focus" }), true);
243 assert.equal(fallbackDue({ ...base, lastRunAt: NOW - FALLBACK_FOCUS_MS + 1, reason: "focus" }), false);
244 assert.equal(fallbackDue({ ...base, lastRunAt: NOW - FALLBACK_FOCUS_MS, reason: "focus" }), true);
245 assert.equal(fallbackDue({ ...base, lastRunAt: NOW - FALLBACK_FOCUS_MS, reason: "poll" }), false);
246 assert.equal(fallbackDue({ ...base, lastRunAt: NOW - FALLBACK_POLL_MS, reason: "poll" }), true);
247 assert.equal(fallbackDue({ ...base, visible: false, lastRunAt: null, reason: "poll" }), false, "a hidden tab is not polled");
248 assert.equal(fallbackDue({ ...base, live: true, lastRunAt: null, reason: "focus" }), false);
249});
250
251test("typing is in text boxes, text areas and editors", () => {
252 assert.equal(isTyping({ tagName: "TEXTAREA" }), true);
253 assert.equal(isTyping({ tagName: "input", type: "text" }), true);
254 assert.equal(isTyping({ tagName: "INPUT", type: "search" }), true);
255 assert.equal(isTyping({ tagName: "INPUT" }), true, "an input is text unless it says otherwise");
256 assert.equal(isTyping({ tagName: "DIV", isContentEditable: true }), true);
257 assert.equal(isTyping({ tagName: "INPUT", type: "checkbox" }), false);
258 assert.equal(isTyping({ tagName: "BUTTON" }), false);
259 assert.equal(isTyping({ tagName: "TEXTAREA", readOnly: true }), false);
260 assert.equal(isTyping(null), false);
261});
262
263test("the bottom of the page, with some slack", () => {
264 assert.equal(atBottom({ top: 880, height: 2000, viewport: 1000 }), true);
265 assert.equal(atBottom({ top: 500, height: 2000, viewport: 1000 }), false);
266});
267
268test("a workspace's Work pages: the list hears every work item, an item page its own", () => {
269 const change = (number: number | null) => ({ workspace: "acme", kind: "work_item.updated", number, at: "2026-10-17T00:00:00Z" });
270 assert.equal(workPages.list()(change(7)), true);
271 assert.equal(workPages.list()(change(null)), true);
272 assert.equal(workPages.item(7)(change(7)), true);
273 assert.equal(workPages.item(7)(change(8)), false);
274 assert.equal(workPages.item(7)(change(null)), false);
275});