|
1 | | -import { describe, it, expect, vi } from 'vitest'; |
| 1 | +import { describe, it, expect } from 'vitest'; |
2 | 2 | import { |
3 | 3 | calculateStreak, |
4 | 4 | calculateMonthlyStats, |
| 5 | + isStreakAlive, |
5 | 6 | aggregateCalendars, |
6 | 7 | calculateWrappedStats, |
7 | | - findTodayIndex, |
8 | 8 | } from './calculate'; |
9 | 9 | import type { ContributionCalendar } from '../types'; |
10 | 10 |
|
@@ -58,18 +58,32 @@ describe('calculateStreak', () => { |
58 | 58 | expect(s.longestStreak).toBe(2); |
59 | 59 | }); |
60 | 60 |
|
| 61 | + it('handles a massive single-day commit spike without affecting streak calculations', () => { |
| 62 | + const calendar = buildCalendar([ |
| 63 | + 1, 0, 1, 0, 1, 0, 1, |
| 64 | + |
| 65 | + 0, 0, 125, 0, 0, 0, 0, |
| 66 | + |
| 67 | + 1, 1, 1, 1, 1, 0, 0, |
| 68 | + |
| 69 | + 1, 1, 1, 1, 1, 1, 1, |
| 70 | + ]); |
| 71 | + |
| 72 | + const result = calculateStreak(calendar); |
| 73 | + |
| 74 | + expect(result.currentStreak).toBe(7); |
| 75 | + expect(result.longestStreak).toBe(7); |
| 76 | + expect(result.totalContributions).toBe(141); |
| 77 | + }); |
| 78 | + |
61 | 79 | it('handles multiple weeks of zero contributions separating active streaks', () => { |
62 | 80 | const calendar = buildCalendar([ |
63 | | - // Week 1 - active streak |
64 | 81 | 1, 1, 1, 1, 1, 1, 1, |
65 | 82 |
|
66 | | - // Week 2 - gap |
67 | 83 | 0, 0, 0, 0, 0, 0, 0, |
68 | 84 |
|
69 | | - // Week 3 - gap |
70 | 85 | 0, 0, 0, 0, 0, 0, 0, |
71 | 86 |
|
72 | | - // Week 4 - new streak |
73 | 87 | 1, 1, 1, 1, 1, 1, 1, |
74 | 88 | ]); |
75 | 89 |
|
@@ -696,17 +710,6 @@ describe('calculateStreak', () => { |
696 | 710 | }); |
697 | 711 | }); |
698 | 712 |
|
699 | | -it('handles massive single-day commit spike timeline', () => { |
700 | | - const calendar = buildCalendar([ |
701 | | - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, |
702 | | - ]); |
703 | | - |
704 | | - const result = calculateStreak(calendar); |
705 | | - |
706 | | - expect(result.currentStreak).toBe(7); |
707 | | - expect(result.longestStreak).toBe(7); |
708 | | -}); |
709 | | - |
710 | 713 | describe('calculateStreak — timezone awareness', () => { |
711 | 714 | const tzCalendar = { |
712 | 715 | totalContributions: 3, |
@@ -786,41 +789,27 @@ describe('calculateStreak — timezone awareness', () => { |
786 | 789 | const result = calculateStreak(tzCalendar, 'UTC', nowUTC); |
787 | 790 | expect(result.todayDate).toBe('2024-06-16'); |
788 | 791 | }); |
| 792 | +}); |
789 | 793 |
|
790 | | - it('calculates streak correctly during a spring-forward DST transition edge case', () => { |
791 | | - // 1. We must mock the system clock so 'new Date()' behaves predictably |
792 | | - vi.useFakeTimers(); |
793 | | - |
794 | | - // 2. Use America/New_York (spring-forward: 2024-03-10) |
795 | | - process.env.TZ = 'America/New_York'; |
796 | | - |
797 | | - // 3. Set `now` to early UTC on March 10. |
798 | | - // 03:00:00 UTC on March 10 is 22:00:00 (10:00 PM) on March 9 in New York (EST). |
799 | | - const mockNow = new Date('2024-03-10T03:00:00.000Z'); |
800 | | - vi.setSystemTime(mockNow); |
| 794 | +describe('isStreakAlive', () => { |
| 795 | + it('returns true when both today and yesterday have contributions', () => { |
| 796 | + expect(isStreakAlive({ contributionCount: 1 }, { contributionCount: 1 })).toBe(true); |
| 797 | + }); |
801 | 798 |
|
802 | | - // 4. Build a calendar with contributions on March 9 and March 10 |
803 | | - const dstCalendar = { |
804 | | - totalContributions: 2, |
805 | | - weeks: [ |
806 | | - { |
807 | | - contributionDays: [ |
808 | | - { contributionCount: 1, date: '2024-03-09' }, |
809 | | - { contributionCount: 1, date: '2024-03-10' }, |
810 | | - ], |
811 | | - }, |
812 | | - ], |
813 | | - } as Parameters<typeof calculateStreak>[0]; |
| 799 | + it('returns true when only today has contributions', () => { |
| 800 | + expect(isStreakAlive({ contributionCount: 1 }, { contributionCount: 0 })).toBe(true); |
| 801 | + }); |
814 | 802 |
|
815 | | - // 5. Assert currentStreak is calculated correctly |
816 | | - const result = calculateStreak(dstCalendar, 'America/New_York'); |
| 803 | + it('returns true when only yesterday has contributions', () => { |
| 804 | + expect(isStreakAlive({ contributionCount: 0 }, { contributionCount: 1 })).toBe(true); |
| 805 | + }); |
817 | 806 |
|
818 | | - // Because it is currently March 9th in New York, the current streak should securely be 1 |
819 | | - expect(result.currentStreak).toBe(1); |
| 807 | + it('returns false when both today and yesterday have zero contributions', () => { |
| 808 | + expect(isStreakAlive({ contributionCount: 0 }, { contributionCount: 0 })).toBe(false); |
| 809 | + }); |
820 | 810 |
|
821 | | - // 6. Cleanup to prevent breaking other tests |
822 | | - vi.useRealTimers(); |
823 | | - process.env.TZ = ''; |
| 811 | + it('returns false when yesterday is null and today has no contributions', () => { |
| 812 | + expect(isStreakAlive({ contributionCount: 0 }, null)).toBe(false); |
824 | 813 | }); |
825 | 814 | }); |
826 | 815 |
|
@@ -942,9 +931,9 @@ describe('calculateMonthlyStats', () => { |
942 | 931 | expect(result.previousMonthTotal).toBe(10); |
943 | 932 | expect(result.currentMonthName).toBe('January'); |
944 | 933 | }); |
945 | | - // ========================================================================= |
| 934 | + // ================================================================== |
946 | 935 | // ISSUE OBJECTIVE: Empty calendar passed to calculateMonthlyStats |
947 | | - // ========================================================================= |
| 936 | + // ================================================================== |
948 | 937 | it('returns zeros and does not crash when given an empty calendar', () => { |
949 | 938 | const emptyCalendar = { |
950 | 939 | totalContributions: 0, |
@@ -995,9 +984,9 @@ describe('calculateStreak — empty and sparse year edge cases', () => { |
995 | 984 | expect(result.totalContributions).toBe(2); |
996 | 985 | }); |
997 | 986 |
|
998 | | - // ========================================================================= |
| 987 | + // ================================================================== |
999 | 988 | // ISSUE #1503 — Variation 4: Full year (52 weeks × 7 days) of 0 contributions |
1000 | | - // ========================================================================= |
| 989 | + // ================================================================== |
1001 | 990 | // Background: streak computation is susceptible to off-by-one errors when |
1002 | 991 | // managing calendar offsets and date boundaries. A full year of zero commits |
1003 | 992 | // is the most exhaustive boundary stress-test: the loop must traverse all 364 |
@@ -1201,7 +1190,7 @@ describe('calculateWrappedStats', () => { |
1201 | 1190 | }); |
1202 | 1191 |
|
1203 | 1192 | // ISSUE OBJECTIVE: Verify weekendRatio is 100 when all commits are on weekends |
1204 | | - // ========================================================================= |
| 1193 | + // ================================================================== |
1205 | 1194 | it('returns weekendRatio === 100 when all contributions are on weekends', () => { |
1206 | 1195 | // Note: 2026-05-02 is a Saturday, 2026-05-03 is a Sunday, 2026-05-04 is a Monday |
1207 | 1196 | const weekendCalendar = { |
@@ -1259,35 +1248,3 @@ describe('calculateWrappedStats', () => { |
1259 | 1248 | expect(resultUTCPlus5.todayDate).toBe('2024-01-15'); |
1260 | 1249 | }); |
1261 | 1250 | }); |
1262 | | - |
1263 | | -describe('findTodayIndex', () => { |
1264 | | - it('returns index when date is found', () => { |
1265 | | - const days = [ |
1266 | | - { date: '2024-01-01', contributionCount: 1 }, |
1267 | | - { date: '2024-01-02', contributionCount: 2 }, |
1268 | | - { date: '2024-01-03', contributionCount: 3 }, |
1269 | | - ]; |
1270 | | - |
1271 | | - const result = findTodayIndex(days, 'UTC', new Date('2024-01-02T12:00:00Z')); |
1272 | | - |
1273 | | - expect(result).toBe(1); |
1274 | | - }); |
1275 | | - |
1276 | | - it('falls back to last index when date is not found', () => { |
1277 | | - const days = [ |
1278 | | - { date: '2024-01-01', contributionCount: 1 }, |
1279 | | - { date: '2024-01-02', contributionCount: 2 }, |
1280 | | - { date: '2024-01-03', contributionCount: 3 }, |
1281 | | - ]; |
1282 | | - |
1283 | | - const result = findTodayIndex(days, 'UTC', new Date('2024-01-10T12:00:00Z')); |
1284 | | - |
1285 | | - expect(result).toBe(2); |
1286 | | - }); |
1287 | | - |
1288 | | - it('returns -1 for empty days array', () => { |
1289 | | - const result = findTodayIndex([], 'UTC', new Date('2024-01-10T12:00:00Z')); |
1290 | | - |
1291 | | - expect(result).toBe(-1); |
1292 | | - }); |
1293 | | -}); |
0 commit comments