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.

200 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { Claim, WorkItem, WorkSession } from "@g1t/contracts";
5
6import {
7 ITEM_KEYS,
8 activityCounts,
9 activityFilterOf,
10 activityParts,
11 budgetOf,
12 itemCommandOf,
13 itemStage,
14 keyFor,
15 neighbours,
16 planSummary,
17 sameSet,
18 toggled,
19 trailOf,
20 trailState,
21 typedNumber,
22 typedUsername,
23} from "./work-item-page.ts";
24
25test("each property has its key, and the page's keys are letters alone", () => {
26 assert.equal(itemCommandOf({ key: "a" }, false), "assignee");
27 assert.equal(itemCommandOf({ key: "p" }, false), "project");
28 assert.equal(itemCommandOf({ key: "h" }, false), "plan");
29 assert.equal(itemCommandOf({ key: "m" }, false), "milestone");
30 assert.equal(itemCommandOf({ key: "l" }, false), "labels");
31 assert.equal(itemCommandOf({ key: "b" }, false), "blocked");
32 assert.equal(itemCommandOf({ key: "d" }, false), "agent");
33 assert.equal(itemCommandOf({ key: "e" }, false), "edit");
34 assert.equal(itemCommandOf({ key: "i" }, false), "claim");
35 assert.equal(itemCommandOf({ key: "j" }, false), "next");
36 assert.equal(itemCommandOf({ key: "k" }, false), "previous");
37 // Caps Lock on still means the key.
38 assert.equal(itemCommandOf({ key: "A" }, false), "assignee");
39 assert.equal(itemCommandOf({ key: "x" }, false), null);
40 assert.equal(itemCommandOf({ key: "Enter" }, false), null);
41});
42
43test("Shift C closes, Shift Y moves to the next cycle, and Shift with any other letter does nothing", () => {
44 assert.equal(itemCommandOf({ key: "C", shiftKey: true }, false), "close");
45 assert.equal(itemCommandOf({ key: "Y", shiftKey: true }, false), "cycle-next");
46 assert.equal(itemCommandOf({ key: "c" }, false), null);
47 assert.equal(itemCommandOf({ key: "A", shiftKey: true }, false), null);
48});
49
50test("no key fires while typing, with a modifier held, or held down", () => {
51 assert.equal(itemCommandOf({ key: "a" }, true), null);
52 assert.equal(itemCommandOf({ key: "C", shiftKey: true }, true), null);
53 assert.equal(itemCommandOf({ key: "k", metaKey: true }, false), null);
54 assert.equal(itemCommandOf({ key: "k", ctrlKey: true }, false), null);
55 assert.equal(itemCommandOf({ key: "j", altKey: true }, false), null);
56 assert.equal(itemCommandOf({ key: "j", repeat: true }, false), null);
57});
58
59test("every command the keys give is listed for the shortcuts dialog", () => {
60 for (const key of "aphmlbdeijky") {
61 const command = itemCommandOf({ key }, false);
62 assert.ok(command && ITEM_KEYS.some((one) => one.command === command), key);
63 assert.equal(keyFor(command!), key.toUpperCase());
64 }
65 assert.equal(keyFor("close"), "Shift C");
66 assert.equal(keyFor("cycle-next"), "Shift Y");
67});
68
69test("the list an item was opened from travels in its link's state and comes back clean", () => {
70 const trail = { numbers: [12, 14, 12, 9], label: "All items", href: "/acme/-/work/all" };
71 const state = trailState(trail);
72 assert.deepEqual(state, { workTrail: { numbers: [12, 14, 9], label: "All items", href: "/acme/-/work/all" } });
73 assert.deepEqual(trailOf(state), { numbers: [12, 14, 9], label: "All items", href: "/acme/-/work/all" });
74 assert.equal(trailState({ ...trail, numbers: [] }), undefined);
75 assert.equal(trailState(null), undefined);
76});
77
78test("anything else in the location state is no list", () => {
79 assert.equal(trailOf(null), null);
80 assert.equal(trailOf("12,14"), null);
81 assert.equal(trailOf({ workTrail: { numbers: "12", label: "x", href: "/" } }), null);
82 assert.equal(trailOf({ workTrail: { numbers: [0, -1, 1.5], label: "x", href: "/" } }), null);
83 assert.deepEqual(trailOf({ workTrail: { numbers: [3, "4", 5], label: "x", href: "/" } }), { numbers: [3, 5], label: "x", href: "/" });
84});
85
86test("J and K step to the next and previous item, and stop at the ends", () => {
87 assert.deepEqual(neighbours([12, 14, 9], 14), { previous: 12, next: 9, index: 2, total: 3 });
88 assert.deepEqual(neighbours([12, 14, 9], 12), { previous: null, next: 14, index: 1, total: 3 });
89 assert.deepEqual(neighbours([12, 14, 9], 9), { previous: 14, next: null, index: 3, total: 3 });
90 assert.equal(neighbours([12, 14, 9], 30), null);
91});
92
93const comment = (id: string, kind: "comment" | "event") => ({ id, kind });
94
95test("Activity's filter keeps comments, events or both", () => {
96 const comments = [comment("c1", "comment"), comment("e1", "event"), comment("c2", "comment")];
97 const extras = [{ id: "ses_1" }, { id: "claim_1" }];
98 assert.deepEqual(activityParts(comments, extras, "all"), { comments, extras });
99 assert.deepEqual(activityParts(comments, extras, "comments"), { comments: [comments[0], comments[2]], extras: [] });
100 assert.deepEqual(activityParts(comments, extras, "events"), { comments: [comments[1]], extras });
101 assert.deepEqual(activityCounts(comments, extras.length), { all: 5, comments: 2, events: 3 });
102 assert.equal(activityFilterOf("events"), "events");
103 assert.equal(activityFilterOf("everything"), "all");
104 assert.equal(activityFilterOf(null), "all");
105});
106
107const item = (over: Partial<WorkItem> = {}): WorkItem =>
108 ({
109 id: "wki_1",
110 workspace: "acme",
111 number: 12,
112 title: "Item page",
113 body: "",
114 labels: [],
115 state: "open",
116 reason: null,
117 resolutions: [],
118 author: { id: "usr_1", username: "ana" },
119 requestedBy: null,
120 createdAt: "2026-10-01T00:00:00Z",
121 updatedAt: "2026-10-01T00:00:00Z",
122 closedAt: null,
123 pullCount: 0,
124 commentCount: 0,
125 assignees: [],
126 blockedBy: [],
127 agent: null,
128 repos: [],
129 horizon: "now",
130 appetite: null,
131 budgetUsd: null,
132 urgent: false,
133 plannedAt: null,
134 ...over,
135 }) as WorkItem;
136
137const now = Date.parse("2026-10-21T00:00:00Z");
138
139test("the stage says why: a pull request open is in review", () => {
140 const stage = itemStage(item(), { repos: [{ path: "acme/web", pulls: [{ number: 318, status: "open" }] }] }, now);
141 assert.deepEqual(stage, { stage: "review", label: "In review", why: "#318 open in acme/web" });
142});
143
144test("claimed, a session on it, or a draft is in progress, saying which", () => {
145 const claim = { state: "active", actor: { username: "ana" } } as Claim;
146 const session = { state: "active", parentId: null } as WorkSession;
147 const stage = itemStage(item(), { claims: [claim], sessions: [session], repos: [{ path: "acme/web", pulls: [{ number: 4, status: "draft" }] }] }, now);
148 assert.equal(stage.stage, "progress");
149 assert.equal(stage.why, "Claimed by ana, a session on it, #4 draft in acme/web");
150 assert.equal(itemStage(item({ assignees: ["bo"] }), {}, now).why, "Assigned to bo");
151});
152
153test("nobody on it is up next; landing names what is still to land; closed ones say how", () => {
154 assert.deepEqual(itemStage(item(), {}, now), { stage: "next", label: "Up next", why: "Nobody is on it yet" });
155 const landing = item({
156 repos: [
157 { repoId: "r1", path: "acme/web", role: "target", landed: true },
158 { repoId: "r2", path: "acme/api", role: "target", landed: false },
159 ] as WorkItem["repos"],
160 });
161 assert.equal(itemStage(landing, {}, now).why, "Still to land in acme/api");
162 const done = item({
163 state: "closed",
164 reason: "completed",
165 closedAt: "2026-10-20T00:00:00Z",
166 resolutions: [{ repo: "acme/web", pull: 3 }] as WorkItem["resolutions"],
167 });
168 assert.deepEqual(itemStage(done, {}, now), { stage: "done", label: "Done", why: "Landed in acme/web" });
169 assert.equal(itemStage(item({ state: "closed", reason: "not_planned", closedAt: "2026-10-20T00:00:00Z" }), {}, now).why, "Closed as not planned");
170});
171
172test("the pickers read a budget, a number and a username from what is typed", () => {
173 assert.equal(budgetOf("5"), 5);
174 assert.equal(budgetOf("$12.50"), 12.5);
175 assert.equal(budgetOf("none"), null);
176 assert.equal(budgetOf("now"), undefined);
177 assert.equal(budgetOf("0"), undefined);
178 assert.equal(budgetOf("1.234"), undefined);
179 assert.equal(typedNumber("#212"), 212);
180 assert.equal(typedNumber("212"), 212);
181 assert.equal(typedNumber("#0"), null);
182 assert.equal(typedNumber("fix 212"), null);
183 assert.equal(typedUsername("@Ana"), "ana");
184 assert.equal(typedUsername("bo-2"), "bo-2");
185 assert.equal(typedUsername("two words"), null);
186 assert.equal(typedUsername(""), null);
187});
188
189test("a multi-choice picker toggles and saves only a change", () => {
190 assert.deepEqual(toggled(["a", "b"], "b"), ["a"]);
191 assert.deepEqual(toggled(["a"], "b"), ["a", "b"]);
192 assert.equal(sameSet(["a", "b"], ["b", "a"]), true);
193 assert.equal(sameSet(["a"], ["a", "b"]), false);
194});
195
196test("the plan reads as one line", () => {
197 const label = (h: string) => ({ now: "Now", next: "Next" })[h as "now"] ?? h;
198 assert.equal(planSummary({ horizon: "now", appetite: "medium", budgetUsd: 5, urgent: false }, label), "Now · M · $5");
199 assert.equal(planSummary({ horizon: "next", appetite: null, budgetUsd: 2.5, urgent: true }, label), "Next · $2.50 · Urgent");
200});