Skip to content
151 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { Contributors, Profile, ProfileWorkspace, RepoPath, Result, Viewer } from "@g1t/contracts";
5
6import { type CardSources, buildCard, cardHref, committedLabel, committedWithin, parseRepo } from "./hovercard.ts";
7
8const NOW = Date.parse("2026-10-08T12:00:00Z");
9const DAY = 86_400_000;
10
11const ada: Profile = {
12 username: "ada",
13 name: "Ada Lovelace",
14 bio: "Engines.",
15 location: "London",
16 website: "https://ada.example",
17 pronouns: "she/her",
18 timezone: "Europe/London",
19 avatar: "cafe",
20 createdAt: "2026-01-01T00:00:00Z",
21};
22
23const me: Viewer = { id: "usr_me", username: "me", kind: "user", verified: true } as Viewer;
24
25/**
26 * Stand-ins for identity and repos that behave as they do: workspaces are
27 * shown only when the viewer shares them, or when ada has a public project
28 * there; the repository answers only viewers who may read it.
29 */
30function sources(over: Partial<CardSources> = {}): CardSources & { asked: { viewer: Viewer; publicIn: string[] }[] } {
31 const asked: { viewer: Viewer; publicIn: string[] }[] = [];
32 const memberships: ProfileWorkspace[] = [
33 { slug: "acme", name: "Acme", avatar: null },
34 { slug: "secret-lab", name: "Secret Lab", avatar: null },
35 ];
36 return {
37 asked,
38 profile: async (username) => (username === "ada" ? ada : null),
39 accountId: async (username) => (username === "ada" ? "usr_ada" : null),
40 publicNamespaces: async () => ["acme"],
41 profileWorkspaces: async (_username, viewer, publicIn) => {
42 asked.push({ viewer, publicIn });
43 // secret-lab is private: only someone who belongs to it sees it.
44 return memberships.filter((one) => publicIn.includes(one.slug) || (viewer?.username === "insider" && one.slug === "secret-lab"));
45 },
46 contributors: async (path: RepoPath, viewer: Viewer): Promise<Result<Contributors>> => {
47 if (path.name === "private" && viewer?.username !== "insider") {
48 return { ok: false, error: { code: "not_found", message: "Repository not found." } } as Result<Contributors>;
49 }
50 return {
51 ok: true,
52 value: {
53 head: "h",
54 commit: "h",
55 computedAt: null,
56 pending: false,
57 partial: false,
58 total: 1,
59 commits: 3,
60 weeks: [],
61 contributors: [{ kind: "user", name: "ada", username: "ada", avatar: "cafe", commits: 3, firstAt: "2026-09-01T00:00:00Z", lastAt: new Date(NOW - 3 * DAY).toISOString(), weeks: [] }],
62 },
63 };
64 },
65 ...over,
66 };
67}
68
69test("the card is the public profile, signed out too", async () => {
70 const card = await buildCard("Ada", null, null, sources(), NOW);
71 assert.ok(card && card.kind === "user");
72 assert.equal(card.username, "ada");
73 assert.equal(card.name, "Ada Lovelace");
74 assert.equal(card.pronouns, "she/her");
75 assert.equal(card.bio, "Engines.");
76 assert.equal(card.location, "London");
77 assert.equal(card.avatar, "cafe");
78 assert.equal(card.committed, null, "no repository, nothing about commits");
79 // Never an address.
80 assert.ok(!JSON.stringify(card).includes("@"));
81});
82
83test("a private workspace is hidden from someone outside it", async () => {
84 const outside = sources();
85 const outsider = await buildCard("ada", me, null, outside, NOW);
86 assert.deepEqual(outsider?.kind === "user" && outsider.workspaces.map((one) => one.slug), ["acme"]);
87 // The viewer is passed through, for identity to decide.
88 assert.equal(outside.asked[0]!.viewer, me);
89 assert.deepEqual(outside.asked[0]!.publicIn, ["acme"]);
90 const insider = await buildCard("ada", { ...me, username: "insider" } as Viewer, null, sources(), NOW);
91 assert.deepEqual(insider?.kind === "user" && insider.workspaces.map((one) => one.slug), ["acme", "secret-lab"]);
92});
93
94test("commits to a repository show only to those who may read it", async () => {
95 const open = await buildCard("ada", null, { namespace: "acme", name: "web" }, sources(), NOW);
96 assert.equal(open?.kind === "user" && open.committed, "week");
97 const hidden = await buildCard("ada", me, { namespace: "acme", name: "private" }, sources(), NOW);
98 assert.equal(hidden?.kind === "user" && hidden.committed, null);
99});
100
101test("the card carries the time zone a profile gives, and none when it gives none", async () => {
102 const card = await buildCard("ada", me, null, sources(), NOW);
103 assert.equal(card?.kind === "user" && card.timezone, "Europe/London");
104 const without = await buildCard("ada", me, null, sources({ profile: async () => ({ ...ada, timezone: null }) }), NOW);
105 assert.equal(without?.kind === "user" && without.timezone, null);
106});
107
108test("a failing service leaves its part out, not the card", async () => {
109 const card = await buildCard(
110 "ada",
111 me,
112 { namespace: "acme", name: "web" },
113 sources({ profileWorkspaces: async () => Promise.reject(new Error("down")), contributors: async () => Promise.reject(new Error("down")) }),
114 NOW,
115 );
116 assert.ok(card && card.kind === "user");
117 assert.deepEqual(card.workspaces, []);
118 assert.equal(card.committed, null);
119});
120
121test("g1t has its own card; ghost and strangers have none", async () => {
122 assert.deepEqual(await buildCard("g1t", null, null, sources(), NOW), { kind: "g1t", username: "g1t" });
123 assert.equal(await buildCard("ghost", me, null, sources(), NOW), null);
124 assert.equal(await buildCard("nobody", me, null, sources(), NOW), null);
125 assert.equal(await buildCard("../etc", me, null, sources(), NOW), null);
126});
127
128test("workspaces past three are counted", async () => {
129 const many = Array.from({ length: 5 }, (_, index) => ({ slug: `w${index}`, name: `W${index}`, avatar: null }));
130 const card = await buildCard("ada", me, null, sources({ profileWorkspaces: async () => many }), NOW);
131 assert.ok(card && card.kind === "user");
132 assert.equal(card.workspaces.length, 3);
133 assert.equal(card.more_workspaces, 2);
134});
135
136test("how recently, in the words the card uses", () => {
137 assert.equal(committedWithin(new Date(NOW - 2 * 3_600_000).toISOString(), NOW), "day");
138 assert.equal(committedWithin(new Date(NOW - 6 * DAY).toISOString(), NOW), "week");
139 assert.equal(committedWithin(new Date(NOW - 20 * DAY).toISOString(), NOW), "month");
140 assert.equal(committedWithin(new Date(NOW - 60 * DAY).toISOString(), NOW), null);
141 assert.equal(committedWithin(null, NOW), null);
142 assert.equal(committedLabel("week"), "Committed to this repository in the past week");
143});
144
145test("the card's address and its repository", () => {
146 assert.equal(cardHref("Ada", "acme/web"), "/-/hovercard/user/ada?repo=acme%2Fweb");
147 assert.equal(cardHref("ada", null), "/-/hovercard/user/ada");
148 assert.deepEqual(parseRepo("acme/web"), { namespace: "acme", name: "web" });
149 assert.equal(parseRepo("acme"), null);
150 assert.equal(parseRepo("../x/y"), null);
151});