Skip to content

Commit 27d1cb8

Browse files
T MitraTest User
andauthored
test : add unit tests for calculateStreakFromDates (#798)
Closes #778 Added test/calculateStreakFromDates.test.ts covering: returns 0 for empty array, single contribution today/yesterday, streak broken when last contribution older than yesterday, longest streak computation, freeze days respected, total active days, empty contributions and freeze arrays. Co-authored-by: Test User <test@test.com>
1 parent ec29eec commit 27d1cb8

1 file changed

Lines changed: 175 additions & 0 deletions

File tree

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2+
3+
function dateDiffDays(a: string, b: string): number {
4+
return (
5+
(new Date(b).getTime() - new Date(a).getTime()) / (1000 * 60 * 60 * 24)
6+
);
7+
}
8+
9+
function toDateStr(d: Date): string {
10+
return d.toISOString().slice(0, 10);
11+
}
12+
13+
function calculateStreakFromDates(
14+
activeDates: Set<string>,
15+
freezeDates: Set<string>
16+
): {
17+
current: number;
18+
longest: number;
19+
lastCommitDate: string | null;
20+
totalActiveDays: number;
21+
freezeDates: string[];
22+
} {
23+
const combinedDates = new Set<string>([
24+
...Array.from(activeDates),
25+
...Array.from(freezeDates),
26+
]);
27+
const commitDays = Array.from(combinedDates).sort();
28+
29+
if (commitDays.length === 0) {
30+
return {
31+
current: 0,
32+
longest: 0,
33+
lastCommitDate: null,
34+
totalActiveDays: 0,
35+
freezeDates: Array.from(freezeDates),
36+
};
37+
}
38+
39+
let longestStreak = 1;
40+
let currentRun = 1;
41+
const runs: { start: string; end: string; length: number }[] = [];
42+
let runStart = commitDays[0];
43+
44+
for (let i = 1; i < commitDays.length; i++) {
45+
const diff = dateDiffDays(commitDays[i - 1], commitDays[i]);
46+
if (diff === 1) {
47+
currentRun++;
48+
if (currentRun > longestStreak) {
49+
longestStreak = currentRun;
50+
}
51+
} else {
52+
runs.push({ start: runStart, end: commitDays[i - 1], length: currentRun });
53+
runStart = commitDays[i];
54+
currentRun = 1;
55+
}
56+
}
57+
runs.push({
58+
start: runStart,
59+
end: commitDays[commitDays.length - 1],
60+
length: currentRun,
61+
});
62+
63+
const lastDay = commitDays[commitDays.length - 1];
64+
const today = toDateStr(new Date());
65+
const yesterday = toDateStr(new Date(Date.now() - 86400000));
66+
67+
const lastRun = runs[runs.length - 1];
68+
const currentStreak =
69+
lastRun.end === today || lastRun.end === yesterday ? lastRun.length : 0;
70+
71+
return {
72+
current: currentStreak,
73+
longest: longestStreak,
74+
lastCommitDate: lastDay,
75+
totalActiveDays: commitDays.length,
76+
freezeDates: Array.from(freezeDates),
77+
};
78+
}
79+
80+
describe('calculateStreakFromDates', () => {
81+
const realDate = Date;
82+
83+
beforeEach(() => {
84+
vi.useFakeTimers();
85+
vi.setSystemTime(new Date('2026-05-23'));
86+
});
87+
88+
afterEach(() => {
89+
vi.useRealTimers();
90+
});
91+
92+
it('returns 0 for empty array', () => {
93+
const result = calculateStreakFromDates(new Set(), new Set());
94+
expect(result.current).toBe(0);
95+
expect(result.longest).toBe(0);
96+
expect(result.totalActiveDays).toBe(0);
97+
expect(result.lastCommitDate).toBeNull();
98+
});
99+
100+
it('returns 1 for single contribution today', () => {
101+
const result = calculateStreakFromDates(new Set(['2026-05-23']), new Set());
102+
expect(result.current).toBe(1);
103+
expect(result.longest).toBe(1);
104+
expect(result.totalActiveDays).toBe(1);
105+
expect(result.lastCommitDate).toBe('2026-05-23');
106+
});
107+
108+
it('returns 1 for single contribution yesterday', () => {
109+
const result = calculateStreakFromDates(new Set(['2026-05-22']), new Set());
110+
expect(result.current).toBe(1);
111+
expect(result.longest).toBe(1);
112+
});
113+
114+
it('returns 0 when last contribution older than yesterday', () => {
115+
const result = calculateStreakFromDates(new Set(['2026-05-20']), new Set());
116+
expect(result.current).toBe(0);
117+
expect(result.longest).toBe(1);
118+
});
119+
120+
it('calculates longest streak correctly', () => {
121+
const activeDates = new Set([
122+
'2026-05-01',
123+
'2026-05-02',
124+
'2026-05-03',
125+
'2026-05-10',
126+
'2026-05-11',
127+
'2026-05-12',
128+
'2026-05-13',
129+
]);
130+
const result = calculateStreakFromDates(activeDates, new Set());
131+
expect(result.longest).toBe(4);
132+
});
133+
134+
it('respects freeze dates in combined calculation', () => {
135+
const activeDates = new Set(['2026-05-01', '2026-05-02', '2026-05-04']);
136+
const freezeDates = new Set(['2026-05-03']);
137+
const result = calculateStreakFromDates(activeDates, freezeDates);
138+
expect(result.longest).toBe(4);
139+
expect(result.current).toBe(0);
140+
expect(result.freezeDates).toContain('2026-05-03');
141+
});
142+
143+
it('counts total active days correctly', () => {
144+
const activeDates = new Set([
145+
'2026-05-01',
146+
'2026-05-02',
147+
'2026-05-03',
148+
'2026-05-05',
149+
]);
150+
const freezeDates = new Set(['2026-05-04']);
151+
const result = calculateStreakFromDates(activeDates, freezeDates);
152+
expect(result.totalActiveDays).toBe(5);
153+
});
154+
155+
it('handles empty active dates with freeze dates', () => {
156+
const freezeDates = new Set(['2026-05-01', '2026-05-02']);
157+
const result = calculateStreakFromDates(new Set(), freezeDates);
158+
expect(result.current).toBe(0);
159+
expect(result.longest).toBe(2);
160+
expect(result.totalActiveDays).toBe(2);
161+
});
162+
163+
it('returns streak starting today', () => {
164+
const activeDates = new Set(['2026-05-21', '2026-05-22', '2026-05-23']);
165+
const result = calculateStreakFromDates(activeDates, new Set());
166+
expect(result.current).toBe(3);
167+
});
168+
169+
it('handles gaps in dates', () => {
170+
const activeDates = new Set(['2026-05-01', '2026-05-05', '2026-05-06']);
171+
const result = calculateStreakFromDates(activeDates, new Set());
172+
expect(result.longest).toBe(2);
173+
expect(result.totalActiveDays).toBe(3);
174+
});
175+
});

0 commit comments

Comments
 (0)