Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

contribution-heatmap.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

contribution-heatmap.test.tsBlame188 lines · 1 contributor
4438e31Claude1/**
2 * Block J9 — Contribution heatmap tests. All pure; no DB.
3 */
4
5import { describe, it, expect } from "bun:test";
6import {
7 buildHeatmap,
8 formatDateKey,
9 levelFor,
10 startOfUtcDay,
11 daysBetween,
12 __internal,
13} from "../lib/contribution-heatmap";
14
15describe("heatmap — levelFor", () => {
16 it("0 count → level 0 regardless of max", () => {
17 expect(levelFor(0, 0)).toBe(0);
18 expect(levelFor(0, 10)).toBe(0);
19 });
20
21 it("max=0 keeps everything at 0", () => {
22 expect(levelFor(5, 0)).toBe(0);
23 });
24
25 it("buckets by quartile of max", () => {
26 expect(levelFor(1, 10)).toBe(1); // 10%
27 expect(levelFor(3, 10)).toBe(2); // 30%
28 expect(levelFor(6, 10)).toBe(3); // 60%
29 expect(levelFor(9, 10)).toBe(4); // 90%
30 });
31
32 it("edge — exactly 25% → level 1", () => {
33 expect(levelFor(25, 100)).toBe(1);
34 expect(levelFor(26, 100)).toBe(2);
35 });
36});
37
38describe("heatmap — formatDateKey + startOfUtcDay", () => {
39 it("formats YYYY-MM-DD in UTC", () => {
40 const d = new Date("2026-01-02T23:59:59Z");
41 expect(formatDateKey(d)).toBe("2026-01-02");
42 });
43
44 it("strips time to 00:00:00 UTC", () => {
45 const d = startOfUtcDay(new Date("2026-03-15T18:30:00Z"));
46 expect(d.getUTCHours()).toBe(0);
47 expect(d.getUTCMinutes()).toBe(0);
48 expect(d.getUTCSeconds()).toBe(0);
49 expect(formatDateKey(d)).toBe("2026-03-15");
50 });
51
52 it("__internal re-exports match", () => {
53 expect(__internal.formatDateKey).toBe(formatDateKey);
54 expect(__internal.startOfUtcDay).toBe(startOfUtcDay);
55 });
56});
57
58describe("heatmap — daysBetween", () => {
59 it("returns 0 for same UTC day regardless of time", () => {
60 const a = new Date("2026-01-02T00:00:00Z");
61 const b = new Date("2026-01-02T23:59:59Z");
62 expect(daysBetween(a, b)).toBe(0);
63 });
64
65 it("returns positive delta for later end", () => {
66 expect(
67 daysBetween(new Date("2026-01-01Z"), new Date("2026-01-05Z"))
68 ).toBe(4);
69 });
70});
71
72describe("heatmap — buildHeatmap", () => {
73 const TODAY = new Date("2026-12-15T12:00:00Z"); // Tuesday
74
75 it("empty activity → zero totals, no levels above 0", () => {
76 const h = buildHeatmap([], 365, TODAY);
77 expect(h.totalContributions).toBe(0);
78 expect(h.maxDayCount).toBe(0);
79 expect(h.longestStreak).toBe(0);
80 expect(h.currentStreak).toBe(0);
81 for (const w of h.weeks) {
82 for (const d of w.days) {
83 if (d) expect(d.level).toBe(0);
84 }
85 }
86 });
87
88 it("sums contributions on the same day", () => {
89 const h = buildHeatmap(
90 [
91 { createdAt: "2026-12-10T10:00:00Z" },
92 { createdAt: "2026-12-10T11:00:00Z" },
93 { createdAt: "2026-12-10T23:59:59Z" },
94 ],
95 30,
96 TODAY
97 );
98 expect(h.totalContributions).toBe(3);
99 expect(h.maxDayCount).toBe(3);
100 });
101
102 it("drops activity outside the window", () => {
103 const h = buildHeatmap(
104 [
105 { createdAt: "2025-01-01T00:00:00Z" }, // way outside
106 { createdAt: "2026-12-14T00:00:00Z" }, // inside
107 ],
108 30,
109 TODAY
110 );
111 expect(h.totalContributions).toBe(1);
112 });
113
114 it("computes longest + current streaks", () => {
115 // 3-day streak ending on 2026-12-15 (today)
116 const h = buildHeatmap(
117 [
118 { createdAt: "2026-12-13T00:00:00Z" },
119 { createdAt: "2026-12-14T00:00:00Z" },
120 { createdAt: "2026-12-15T00:00:00Z" },
121 ],
122 30,
123 TODAY
124 );
125 expect(h.longestStreak).toBe(3);
126 expect(h.currentStreak).toBe(3);
127 });
128
129 it("currentStreak is 0 when today has no activity", () => {
130 const h = buildHeatmap(
131 [
132 { createdAt: "2026-12-13T00:00:00Z" },
133 { createdAt: "2026-12-14T00:00:00Z" },
134 ],
135 30,
136 TODAY
137 );
138 expect(h.longestStreak).toBe(2);
139 expect(h.currentStreak).toBe(0);
140 });
141
142 it("weeks are Sunday-aligned with 7 entries each", () => {
143 const h = buildHeatmap([], 30, TODAY);
144 for (const w of h.weeks) {
145 expect(w.days.length).toBe(7);
146 }
147 // Every non-null day's dow matches its index in the week array.
148 for (const w of h.weeks) {
149 w.days.forEach((d, idx) => {
150 if (d) expect(d.dow).toBe(idx);
151 });
152 }
153 });
154
155 it("start/end dates reflect the window", () => {
156 const h = buildHeatmap([], 10, TODAY);
157 expect(h.endDate).toBe("2026-12-15");
158 expect(h.startDate).toBe("2026-12-06");
159 });
160
161 it("tolerates invalid createdAt entries", () => {
162 const h = buildHeatmap(
163 [
164 { createdAt: "not a date" },
165 { createdAt: "2026-12-14T00:00:00Z" },
166 ],
167 30,
168 TODAY
169 );
170 expect(h.totalContributions).toBe(1);
171 });
172
173 it("grid covers entire window continuously", () => {
174 const h = buildHeatmap([], 14, TODAY);
175 const nonNull: string[] = [];
176 for (const w of h.weeks) {
177 for (const d of w.days) {
178 if (d) nonNull.push(d.date);
179 }
180 }
181 // 14-day window → 14 non-null cells.
182 expect(nonNull.length).toBe(14);
183 // Dates are strictly increasing.
184 for (let i = 1; i < nonNull.length; i++) {
185 expect(nonNull[i] > nonNull[i - 1]).toBe(true);
186 }
187 });
188});