From bd0ca087a05096474ba2ebd2201e3ec98fe4d605 Mon Sep 17 00:00:00 2001 From: Test User Date: Sat, 23 May 2026 09:17:33 +0530 Subject: [PATCH] test : add unit tests for calculateStreakFromDates 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. --- test/calculateStreakFromDates.test.ts | 175 ++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 test/calculateStreakFromDates.test.ts diff --git a/test/calculateStreakFromDates.test.ts b/test/calculateStreakFromDates.test.ts new file mode 100644 index 00000000..b917650e --- /dev/null +++ b/test/calculateStreakFromDates.test.ts @@ -0,0 +1,175 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +function dateDiffDays(a: string, b: string): number { + return ( + (new Date(b).getTime() - new Date(a).getTime()) / (1000 * 60 * 60 * 24) + ); +} + +function toDateStr(d: Date): string { + return d.toISOString().slice(0, 10); +} + +function calculateStreakFromDates( + activeDates: Set, + freezeDates: Set +): { + current: number; + longest: number; + lastCommitDate: string | null; + totalActiveDays: number; + freezeDates: string[]; +} { + const combinedDates = new Set([ + ...Array.from(activeDates), + ...Array.from(freezeDates), + ]); + const commitDays = Array.from(combinedDates).sort(); + + if (commitDays.length === 0) { + return { + current: 0, + longest: 0, + lastCommitDate: null, + totalActiveDays: 0, + freezeDates: Array.from(freezeDates), + }; + } + + let longestStreak = 1; + let currentRun = 1; + const runs: { start: string; end: string; length: number }[] = []; + let runStart = commitDays[0]; + + for (let i = 1; i < commitDays.length; i++) { + const diff = dateDiffDays(commitDays[i - 1], commitDays[i]); + if (diff === 1) { + currentRun++; + if (currentRun > longestStreak) { + longestStreak = currentRun; + } + } else { + runs.push({ start: runStart, end: commitDays[i - 1], length: currentRun }); + runStart = commitDays[i]; + currentRun = 1; + } + } + runs.push({ + start: runStart, + end: commitDays[commitDays.length - 1], + length: currentRun, + }); + + const lastDay = commitDays[commitDays.length - 1]; + const today = toDateStr(new Date()); + const yesterday = toDateStr(new Date(Date.now() - 86400000)); + + const lastRun = runs[runs.length - 1]; + const currentStreak = + lastRun.end === today || lastRun.end === yesterday ? lastRun.length : 0; + + return { + current: currentStreak, + longest: longestStreak, + lastCommitDate: lastDay, + totalActiveDays: commitDays.length, + freezeDates: Array.from(freezeDates), + }; +} + +describe('calculateStreakFromDates', () => { + const realDate = Date; + + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-05-23')); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('returns 0 for empty array', () => { + const result = calculateStreakFromDates(new Set(), new Set()); + expect(result.current).toBe(0); + expect(result.longest).toBe(0); + expect(result.totalActiveDays).toBe(0); + expect(result.lastCommitDate).toBeNull(); + }); + + it('returns 1 for single contribution today', () => { + const result = calculateStreakFromDates(new Set(['2026-05-23']), new Set()); + expect(result.current).toBe(1); + expect(result.longest).toBe(1); + expect(result.totalActiveDays).toBe(1); + expect(result.lastCommitDate).toBe('2026-05-23'); + }); + + it('returns 1 for single contribution yesterday', () => { + const result = calculateStreakFromDates(new Set(['2026-05-22']), new Set()); + expect(result.current).toBe(1); + expect(result.longest).toBe(1); + }); + + it('returns 0 when last contribution older than yesterday', () => { + const result = calculateStreakFromDates(new Set(['2026-05-20']), new Set()); + expect(result.current).toBe(0); + expect(result.longest).toBe(1); + }); + + it('calculates longest streak correctly', () => { + const activeDates = new Set([ + '2026-05-01', + '2026-05-02', + '2026-05-03', + '2026-05-10', + '2026-05-11', + '2026-05-12', + '2026-05-13', + ]); + const result = calculateStreakFromDates(activeDates, new Set()); + expect(result.longest).toBe(4); + }); + + it('respects freeze dates in combined calculation', () => { + const activeDates = new Set(['2026-05-01', '2026-05-02', '2026-05-04']); + const freezeDates = new Set(['2026-05-03']); + const result = calculateStreakFromDates(activeDates, freezeDates); + expect(result.longest).toBe(4); + expect(result.current).toBe(0); + expect(result.freezeDates).toContain('2026-05-03'); + }); + + it('counts total active days correctly', () => { + const activeDates = new Set([ + '2026-05-01', + '2026-05-02', + '2026-05-03', + '2026-05-05', + ]); + const freezeDates = new Set(['2026-05-04']); + const result = calculateStreakFromDates(activeDates, freezeDates); + expect(result.totalActiveDays).toBe(5); + }); + + it('handles empty active dates with freeze dates', () => { + const freezeDates = new Set(['2026-05-01', '2026-05-02']); + const result = calculateStreakFromDates(new Set(), freezeDates); + expect(result.current).toBe(0); + expect(result.longest).toBe(2); + expect(result.totalActiveDays).toBe(2); + }); + + it('returns streak starting today', () => { + const activeDates = new Set(['2026-05-21', '2026-05-22', '2026-05-23']); + const result = calculateStreakFromDates(activeDates, new Set()); + expect(result.current).toBe(3); + }); + + it('handles gaps in dates', () => { + const activeDates = new Set(['2026-05-01', '2026-05-05', '2026-05-06']); + const result = calculateStreakFromDates(activeDates, new Set()); + expect(result.longest).toBe(2); + expect(result.totalActiveDays).toBe(3); + }); +}); \ No newline at end of file