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.

169 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 DEFAULT_QUERY,
6 type Listed,
7 PAGE_SIZE,
8 facetsOf,
9 filterProjects,
10 isFiltered,
11 nextRow,
12 pageOf,
13 projectQueryString,
14 readProjectQuery,
15} from "./project-list.ts";
16
17function project(slug: string, more: Partial<Listed> = {}): Listed {
18 return {
19 id: `prj_${slug}`,
20 slug,
21 name: slug,
22 description: null,
23 private: false,
24 archived: false,
25 kind: "app",
26 ecosystem: null,
27 updatedAt: "2026-10-01T00:00:00.000Z",
28 pushedAt: null,
29 activity: 0,
30 deploying: false,
31 ...more,
32 };
33}
34
35const q = (search: string) => readProjectQuery(new URLSearchParams(search));
36
37test("an empty address is the default list", () => {
38 assert.deepEqual(q(""), DEFAULT_QUERY);
39 assert.equal(projectQueryString(DEFAULT_QUERY), "");
40 assert.equal(isFiltered(DEFAULT_QUERY), false);
41});
42
43test("what the address says is read, and nonsense is the default", () => {
44 const query = q("q=%20api%20&visibility=private&kind=library&language=Rust&deployments=on&archived=only&sort=active&view=grid&page=3");
45 assert.deepEqual(query, {
46 q: "api",
47 visibility: "private",
48 kind: "library",
49 language: "Rust",
50 deployments: true,
51 archived: "only",
52 sort: "active",
53 view: "grid",
54 page: 3,
55 });
56 assert.deepEqual(q("visibility=secret&sort=random&page=-2&view=table"), DEFAULT_QUERY);
57});
58
59test("the address round-trips, and a change goes back to page one", () => {
60 const query = q("q=web&sort=name&page=4");
61 assert.equal(projectQueryString(query, { page: 4 }), "?q=web&sort=name&page=4");
62 assert.equal(projectQueryString(query, { kind: "library" }), "?q=web&kind=library&sort=name");
63 assert.deepEqual(q(projectQueryString(query, { page: 4 }).slice(1)), query);
64});
65
66test("archived projects are left out unless asked for", () => {
67 const list = [project("live"), project("old", { archived: true })];
68 assert.deepEqual(
69 filterProjects(list, DEFAULT_QUERY).map((p) => p.slug),
70 ["live"],
71 );
72 assert.deepEqual(
73 filterProjects(list, { ...DEFAULT_QUERY, archived: "only" }).map((p) => p.slug),
74 ["old"],
75 );
76 assert.equal(filterProjects(list, { ...DEFAULT_QUERY, archived: "include" }).length, 2);
77});
78
79test("filters narrow by visibility, kind, language and deployments", () => {
80 const list = [
81 project("site", { deploying: true, ecosystem: "npm" }),
82 project("lib", { kind: "library", ecosystem: "cargo", private: true }),
83 project("tool", { ecosystem: "go" }),
84 ];
85 const slugs = (change: object) =>
86 filterProjects(list, { ...DEFAULT_QUERY, ...change })
87 .map((p) => p.slug)
88 .sort();
89 assert.deepEqual(slugs({ visibility: "private" }), ["lib"]);
90 assert.deepEqual(slugs({ visibility: "public" }), ["site", "tool"]);
91 assert.deepEqual(slugs({ kind: "library" }), ["lib"]);
92 assert.deepEqual(slugs({ language: "Rust" }), ["lib"]);
93 assert.deepEqual(slugs({ language: "JavaScript" }), ["site"]);
94 assert.deepEqual(slugs({ deployments: true }), ["site"]);
95});
96
97test("search matches every word, in the name, slug or description, names that start with it first", () => {
98 const list = [
99 project("docs-site", { name: "Docs site", description: "The API reference" }),
100 project("api", { name: "API" }),
101 project("billing", { description: "charges cards" }),
102 ];
103 assert.deepEqual(
104 filterProjects(list, { ...DEFAULT_QUERY, q: "api" }).map((p) => p.slug),
105 ["api", "docs-site"],
106 );
107 assert.deepEqual(
108 filterProjects(list, { ...DEFAULT_QUERY, q: "api reference" }).map((p) => p.slug),
109 ["docs-site"],
110 );
111 assert.deepEqual(
112 filterProjects(list, { ...DEFAULT_QUERY, q: "CARDS" }).map((p) => p.slug),
113 ["billing"],
114 );
115});
116
117test("each sort orders as it says, ties by name", () => {
118 const list = [
119 project("b", { updatedAt: "2026-10-01T00:00:00Z", pushedAt: "2026-10-05T00:00:00Z", activity: 1 }),
120 project("a", { updatedAt: "2026-10-03T00:00:00Z", activity: 9 }),
121 project("c", { updatedAt: "2026-10-02T00:00:00Z", pushedAt: "2026-10-04T00:00:00Z", activity: 1 }),
122 ];
123 const order = (sort: string) => filterProjects(list, { ...DEFAULT_QUERY, sort: sort as never }).map((p) => p.slug);
124 assert.deepEqual(order("name"), ["a", "b", "c"]);
125 // Updated is the later of its own changes and its last push.
126 assert.deepEqual(order("updated"), ["b", "c", "a"]);
127 assert.deepEqual(order("pushed"), ["b", "c", "a"]);
128 assert.deepEqual(order("active"), ["a", "b", "c"]);
129});
130
131test("pages hold PAGE_SIZE, and a page past the end is the last", () => {
132 const list = Array.from({ length: 412 }, (_, i) => i);
133 const first = pageOf(list, 1);
134 assert.equal(first.items.length, PAGE_SIZE);
135 assert.equal(first.pages, 14);
136 assert.deepEqual([first.from, first.to], [1, 30]);
137 const last = pageOf(list, 99);
138 assert.equal(last.page, 14);
139 assert.deepEqual([last.from, last.to], [391, 412]);
140 assert.deepEqual(pageOf([], 1), { items: [], page: 1, pages: 1, from: 0, to: 0 });
141});
142
143test("facets count the whole workspace", () => {
144 const facets = facetsOf([
145 project("a", { ecosystem: "npm", deploying: true }),
146 project("b", { ecosystem: "npm", private: true, kind: "library" }),
147 project("c", { ecosystem: "go", archived: true }),
148 project("d", { kind: "tool" }),
149 ]);
150 assert.deepEqual(facets.visibility, { public: 3, private: 1 });
151 assert.deepEqual(facets.kind, { app: 2, library: 1, tool: 1, docs: 0, other: 0 });
152 assert.deepEqual(facets.languages, [
153 { name: "JavaScript", count: 2 },
154 { name: "Go", count: 1 },
155 ]);
156 assert.equal(facets.deploying, 1);
157 assert.equal(facets.archived, 1);
158});
159
160test("the keyboard moves through rows and stops at the ends", () => {
161 assert.equal(nextRow("ArrowDown", -1, 3), 0);
162 assert.equal(nextRow("j", 0, 3), 1);
163 assert.equal(nextRow("ArrowDown", 2, 3), 2);
164 assert.equal(nextRow("k", 0, 3), 0);
165 assert.equal(nextRow("End", 0, 3), 2);
166 assert.equal(nextRow("Home", 2, 3), 0);
167 assert.equal(nextRow("x", 0, 3), null);
168 assert.equal(nextRow("ArrowDown", 0, 0), null);
169});