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