Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
02a43d1
🧪 Add tests for dashboard/stats API route
google-labs-jules[bot] Mar 13, 2026
2d57e7a
test: add tests for yearInReviewUtils functions
google-labs-jules[bot] Mar 13, 2026
718f2b1
test: add tests for fetchViewerLogin
google-labs-jules[bot] Mar 13, 2026
c7046ea
test: add dashboard summary API route tests
google-labs-jules[bot] Mar 13, 2026
1908463
test: add error path test for dashboard/year route
google-labs-jules[bot] Mar 13, 2026
69ebe16
Fix type errors in dashboard/stats/route.test.ts
google-labs-jules[bot] Mar 13, 2026
14155c3
test: fix type issue in dashboard/year test
google-labs-jules[bot] Mar 13, 2026
a05d23e
test: fix typings in dashboard summary API tests
google-labs-jules[bot] Mar 13, 2026
3104add
Merge origin/main into consolidated test PR
is0692vs Mar 14, 2026
2a4cafa
Merge PR #62 into consolidated test PR
is0692vs Mar 14, 2026
da8a075
Merge PR #63 into consolidated test PR
is0692vs Mar 14, 2026
99fed9e
Merge PR #64 into consolidated test PR
is0692vs Mar 14, 2026
8ad78de
Merge PR #65 into consolidated test PR
is0692vs Mar 14, 2026
a4d4f47
Consolidate related testing PRs
is0692vs Mar 14, 2026
a6f915d
Address consolidated test PR review feedback
is0692vs Mar 14, 2026
0d69259
Harden year-in-review utility edge cases
is0692vs Mar 14, 2026
914d88e
test: add invalid date regression tests
google-labs-jules[bot] Mar 14, 2026
cd2fefb
Expand consolidated PR test coverage and cleanup imports
is0692vs Mar 14, 2026
640d870
Merge remote review fixes into consolidated test PR
is0692vs Mar 14, 2026
96288a5
test: harden getMostActiveHour bounds checks
google-labs-jules[bot] Mar 14, 2026
cbc7036
test: harden getMostActiveHour bounds checks
google-labs-jules[bot] Mar 14, 2026
8f3ffbb
Merge branch 'main' into testing/year-in-review-utils-165845215332166…
is0692vs Mar 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions src/lib/__tests__/yearInReviewUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { describe, it, expect } from "vitest";
import {
buildHourlyHeatmapFromCommitDates,
getMostActiveHour,
getMostActiveDayFromCalendar
} from "@/lib/yearInReviewUtils";

describe("buildHourlyHeatmapFromCommitDates", () => {
it("returns a 7x24 heatmap initialized with zeros for an empty array", () => {
const heatmap = buildHourlyHeatmapFromCommitDates([]);
expect(heatmap).toHaveLength(7);
heatmap.forEach(day => {
expect(day).toHaveLength(24);
expect(day.every(count => count === 0)).toBe(true);
});
});

it("correctly counts commit dates based on UTC day and hour", () => {
const commitDates = [
"2023-01-01T10:00:00Z", // Sunday (0), Hour 10
"2023-01-01T10:30:00Z", // Sunday (0), Hour 10
"2023-01-02T15:45:00Z", // Monday (1), Hour 15
"2023-01-07T23:59:59Z", // Saturday (6), Hour 23
];
const heatmap = buildHourlyHeatmapFromCommitDates(commitDates);

expect(heatmap[0][10]).toBe(2);
expect(heatmap[1][15]).toBe(1);
expect(heatmap[6][23]).toBe(1);

// Verify other slots are 0
expect(heatmap[0][11]).toBe(0);
expect(heatmap[2][15]).toBe(0);
});

it("ignores invalid date strings", () => {
// Additional invalid date assertions to address regression tests mentioned
expect(buildHourlyHeatmapFromCommitDates(["invalid-date"])).toEqual(Array.from({ length: 7 }, () => Array.from({ length: 24 }, () => 0)));
const commitDates = [
"2023-01-01T10:00:00Z",
"invalid-date",
"not-a-date"
];
const heatmap = buildHourlyHeatmapFromCommitDates(commitDates);

expect(heatmap[0][10]).toBe(1);
// All other entries should be 0
const totalCommits = heatmap.flat().reduce((sum, count) => sum + count, 0);
expect(totalCommits).toBe(1);
});
});

describe("getMostActiveHour", () => {
it("returns 0 if heatmap is malformed (not 7x24 matrix)", () => {
expect(getMostActiveHour([])).toBe(0);
expect(getMostActiveHour([[1,2,3]])).toBe(0);
});

it("returns 0 for malformed heatmaps with NaN or Infinity", () => {
const heatmapWithNaN = Array.from({ length: 7 }, () => Array.from({ length: 24 }, () => NaN));
expect(getMostActiveHour(heatmapWithNaN)).toBe(0);
const heatmapWithInfinity = Array.from({ length: 7 }, () => Array.from({ length: 24 }, () => Infinity));
expect(getMostActiveHour(heatmapWithInfinity)).toBe(0);
});

it("returns 0 for an empty heatmap (all zeros)", () => {
const heatmap = Array.from({ length: 7 }, () => Array.from({ length: 24 }, () => 0));
expect(getMostActiveHour(heatmap)).toBe(0);
});

it("returns the hour with the most commits across all days", () => {
const heatmap = Array.from({ length: 7 }, () => Array.from({ length: 24 }, () => 0));
heatmap[0][10] = 5; // Sunday hour 10: 5 commits
heatmap[1][10] = 3; // Monday hour 10: 3 commits -> Total 8

heatmap[2][15] = 4; // Tuesday hour 15: 4 commits
heatmap[3][15] = 5; // Wednesday hour 15: 5 commits -> Total 9

expect(getMostActiveHour(heatmap)).toBe(15);
});

it("returns the first encountered hour in case of a tie", () => {
const heatmap = Array.from({ length: 7 }, () => Array.from({ length: 24 }, () => 0));
heatmap[0][5] = 10; // Total 10 for hour 5
heatmap[0][12] = 10; // Total 10 for hour 12
heatmap[0][20] = 10; // Total 10 for hour 20

// Hour 5 is encountered first in the 0..23 loop
expect(getMostActiveHour(heatmap)).toBe(5);
});
});

describe("getMostActiveDayFromCalendar", () => {
it("returns 'Sunday' when the calendar is empty", () => {
expect(getMostActiveDayFromCalendar([])).toBe("Sunday");
});

it("correctly identifies the most active day of the week", () => {
const calendar = [
{ date: "2023-01-01", count: 5 }, // Sunday
{ date: "2023-01-02", count: 10 }, // Monday
{ date: "2023-01-08", count: 3 }, // Sunday -> Sunday total: 8, Monday total: 10
{ date: "2023-01-04", count: 2 }, // Wednesday -> Wednesday total: 2
];
expect(getMostActiveDayFromCalendar(calendar)).toBe("Monday");
});

it("ignores days with zero or negative counts", () => {
const calendar = [
{ date: "2023-01-01", count: 0 }, // Sunday
{ date: "2023-01-02", count: -5 }, // Monday
{ date: "2023-01-03", count: 2 }, // Tuesday
];
expect(getMostActiveDayFromCalendar(calendar)).toBe("Tuesday");
});

it("returns the first encountered day in case of a tie", () => {
const calendar = [
{ date: "2023-01-02", count: 10 }, // Monday (index 1)
{ date: "2023-01-04", count: 10 }, // Wednesday (index 3)
];
// "Monday" should be returned since it appears earlier in the [Sunday, Monday, ...] array
expect(getMostActiveDayFromCalendar(calendar)).toBe("Monday");
});
});
14 changes: 13 additions & 1 deletion src/lib/yearInReviewUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ export function buildHourlyHeatmapFromCommitDates(commitDates: string[]): number
}

export function getMostActiveHour(heatmap: number[][]): number {
const isValidHeatmap =
Array.isArray(heatmap) &&
heatmap.length === 7 &&
heatmap.every((row) => Array.isArray(row) && row.length === 24 && row.every((count) => Number.isFinite(count)));

if (!isValidHeatmap) {
return 0;
}
let maxCount = -1;
let mostActiveHour = 0;
for (let hour = 0; hour < 24; hour += 1) {
Expand All @@ -36,7 +44,11 @@ export function getMostActiveDayFromCalendar(calendar: { date: string; count: nu
if (day.count <= 0) {
continue;
}
const weekday = new Date(`${day.date}T00:00:00Z`).getUTCDay();
const parsedDate = new Date(`${day.date}T00:00:00Z`);
if (Number.isNaN(parsedDate.getTime())) {
continue;
}
const weekday = parsedDate.getUTCDay();
totals[weekday] += day.count;
}

Expand Down
Loading