Skip to content

Commit a900549

Browse files
Vivek TrivediVivek Trivedi
authored andcommitted
fix: remove any types and pass eslint
1 parent 8e0b402 commit a900549

1 file changed

Lines changed: 199 additions & 25 deletions

File tree

lib/calculate.test.ts

Lines changed: 199 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { describe, it, expect } from 'vitest';
2-
import { calculateStreak, calculateMonthlyStats } from './calculate';
2+
import {
3+
calculateStreak,
4+
calculateMonthlyStats,
5+
calculateWrappedStats,
6+
aggregateCalendars,
7+
} from './calculate';
38
import type { ContributionCalendar } from '../types';
49

510
// Turns a flat array of daily counts into the ContributionCalendar shape,
@@ -386,28 +391,197 @@ describe('calculateStreak — empty and sparse year edge cases', () => {
386391
expect(result.totalContributions).toBe(2);
387392
});
388393
});
389-
it('handles different week start days correctly', () => {
390-
// Simulating 2 full weeks (Sunday start vs Monday start shouldn't break logic)
391-
const calendar = buildCalendar([
392-
1,
393-
1,
394-
1,
395-
1,
396-
1,
397-
1,
398-
1, // week 1
399-
1,
400-
1,
401-
1,
402-
1,
403-
1,
404-
1,
405-
1, // week 2
406-
]);
407-
408-
const result = calculateStreak(calendar);
409-
410-
expect(result.currentStreak).toBe(14);
411-
expect(result.longestStreak).toBe(14);
412-
expect(result.totalContributions).toBe(14);
394+
const DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
395+
396+
it('todayDate matches YYYY-MM-DD for a normal calendar', () => {
397+
// A typical calendar with contributions — todayDate must always be a valid date string
398+
// regardless of the contribution data, so the SVG pulse animation targets the right tower.
399+
const calendar = buildCalendar([1, 0, 1, 1, 0, 1, 1]);
400+
const fixedNow = new Date('2024-01-07T12:00:00Z');
401+
const result = calculateStreak(calendar, 'UTC', fixedNow);
402+
expect(result.todayDate).toMatch(DATE_REGEX);
403+
});
404+
405+
it('todayDate matches YYYY-MM-DD for an empty calendar', () => {
406+
// An empty calendar has no days to fall back on, so todayDate is derived
407+
// purely from the current date — it must still be a valid YYYY-MM-DD string.
408+
const emptyCalendar = buildCalendar([]);
409+
const fixedNow = new Date('2024-03-15T00:00:00Z');
410+
const result = calculateStreak(emptyCalendar, 'UTC', fixedNow);
411+
expect(result.todayDate).toMatch(DATE_REGEX);
412+
});
413+
414+
it('todayDate matches YYYY-MM-DD when a non-UTC timezone shifts the local date', () => {
415+
// When the caller passes a timezone like Asia/Kolkata, the local date can differ
416+
// from UTC (e.g. UTC is still Jan 14 but IST is already Jan 15).
417+
// The format must remain YYYY-MM-DD regardless of which day the timezone lands on.
418+
const calendar = buildCalendar([1, 1, 1, 1, 1, 1, 1]);
419+
const fixedNow = new Date('2024-01-07T20:00:00Z'); // 01:30 Jan 8 in IST (UTC+5:30)
420+
const result = calculateStreak(calendar, 'Asia/Kolkata', fixedNow);
421+
expect(result.todayDate).toMatch(DATE_REGEX);
422+
});
423+
424+
describe('calculateStreak — year boundary transition (Dec 31 → Jan 1)', () => {
425+
// Streak math relies on the flattened day array being chronologically ordered,
426+
// so a run that crosses from December into January must be counted as a single
427+
// continuous streak. This guards against off-by-one bugs where the calendar
428+
// year rollover (e.g. 2024-12-31 → 2025-01-01) is mistakenly treated as a gap.
429+
it('counts a streak that spans the Dec 31 → Jan 1 boundary as one continuous run', () => {
430+
const calendar: ContributionCalendar = {
431+
totalContributions: 7,
432+
weeks: [
433+
{
434+
contributionDays: [
435+
{ contributionCount: 0, date: '2024-12-26' }, // gap before the streak begins
436+
{ contributionCount: 1, date: '2024-12-27' },
437+
{ contributionCount: 1, date: '2024-12-28' },
438+
{ contributionCount: 1, date: '2024-12-29' },
439+
{ contributionCount: 1, date: '2024-12-30' },
440+
{ contributionCount: 1, date: '2024-12-31' }, // last day of the year
441+
{ contributionCount: 1, date: '2025-01-01' }, // first day of the new year
442+
],
443+
},
444+
{
445+
contributionDays: [
446+
{ contributionCount: 1, date: '2025-01-02' }, // "today"
447+
],
448+
},
449+
],
450+
};
451+
452+
// Pin "now" to Jan 2 so the final day is treated as today and the streak is live.
453+
const now = new Date('2025-01-02T12:00:00Z');
454+
const result = calculateStreak(calendar, 'UTC', now);
455+
456+
// The 7-day run (Dec 27 → Jan 2) must not be split by the year rollover.
457+
expect(result.currentStreak).toBe(7);
458+
expect(result.longestStreak).toBe(7);
459+
expect(result.totalContributions).toBe(7);
460+
expect(result.todayDate).toBe('2025-01-02');
461+
});
462+
});
463+
464+
// ---------- EPIC ENHANCEMENT TESTS ----------
465+
466+
describe('aggregateCalendars', () => {
467+
it('handles calendars with different numbers of weeks', () => {
468+
const cal1 = {
469+
totalContributions: 15,
470+
weeks: [
471+
{
472+
contributionDays: [{ date: '2024-01-01', contributionCount: 5 }],
473+
},
474+
{
475+
contributionDays: [{ date: '2024-01-08', contributionCount: 10 }],
476+
},
477+
],
478+
};
479+
480+
const cal2 = {
481+
totalContributions: 3,
482+
weeks: [
483+
{
484+
contributionDays: [{ date: '2024-01-01', contributionCount: 3 }],
485+
},
486+
],
487+
};
488+
489+
const result = aggregateCalendars([cal1, cal2]);
490+
491+
expect(result.weeks).toHaveLength(2);
492+
expect(result.weeks[0].contributionDays[0].contributionCount).toBe(8);
493+
expect(result.weeks[1].contributionDays[0].contributionCount).toBe(10);
494+
});
495+
496+
it('returns an empty calendar if no calendars are provided', () => {
497+
const result = aggregateCalendars([]);
498+
expect(result.totalContributions).toBe(0);
499+
expect(result.weeks).toEqual([]);
500+
});
501+
502+
it('aggregates multiple calendars correctly for orgs', () => {
503+
const cal1 = buildCalendar([1, 0, 2]); // total: 3
504+
const cal2 = buildCalendar([0, 3, 1]); // total: 4
505+
506+
const result = aggregateCalendars([cal1, cal2]);
507+
508+
expect(result.totalContributions).toBe(7);
509+
expect(result.weeks[0].contributionDays[0].contributionCount).toBe(1); // 1 + 0
510+
expect(result.weeks[0].contributionDays[1].contributionCount).toBe(3); // 0 + 3
511+
expect(result.weeks[0].contributionDays[2].contributionCount).toBe(3); // 2 + 1
512+
});
513+
});
514+
515+
describe('calculateWrappedStats', () => {
516+
it('returns weekendRatio as 0 when all contributions occur on weekdays', () => {
517+
const calendar = {
518+
totalContributions: 25,
519+
weeks: [
520+
{
521+
contributionDays: [
522+
{ date: '2024-01-01', contributionCount: 5 }, // Mon
523+
{ date: '2024-01-02', contributionCount: 5 }, // Tue
524+
{ date: '2024-01-03', contributionCount: 5 }, // Wed
525+
{ date: '2024-01-04', contributionCount: 5 }, // Thu
526+
{ date: '2024-01-05', contributionCount: 5 }, // Fri
527+
],
528+
},
529+
],
530+
};
531+
532+
const result = calculateWrappedStats(calendar);
533+
534+
expect(result.weekendRatio).toBe(0);
535+
});
536+
537+
it('calculates GitHub Wrapped stats accurately', () => {
538+
// 2024-01-01 was a Monday. Indices 5 (Sat) and 6 (Sun) are the weekend.
539+
const cal = buildCalendar([0, 0, 0, 0, 0, 5, 15]);
540+
541+
const result = calculateWrappedStats(cal);
542+
543+
expect(result.totalContributions).toBe(20);
544+
expect(result.highestDailyCount).toBe(15);
545+
expect(result.mostActiveDate).toBe('2024-01-07');
546+
expect(result.busiestMonth).toBe('2024-01');
547+
expect(result.weekendRatio).toBe(100);
548+
});
549+
550+
// ISSUE OBJECTIVE #1056: Verify empty calendar returns safe zero values
551+
it('verify empty calendar returns safe zero values', () => {
552+
// 1. Call calculateWrappedStats with empty data
553+
expect(() => calculateWrappedStats({ totalContributions: 0, weeks: [] })).not.toThrow();
554+
555+
// 2. Actually get the result to test its properties
556+
const result = calculateWrappedStats({ totalContributions: 0, weeks: [] });
557+
558+
// 3. Assert weekendRatio === 0 (and specifically not NaN)
559+
expect(result.weekendRatio).toBe(0);
560+
561+
// 4. Assert highestDailyCount === 0
562+
expect(result.highestDailyCount).toBe(0);
563+
});
564+
565+
// ISSUE OBJECTIVE: Verify weekendRatio is 100 when all commits are on weekends
566+
// =========================================================================
567+
it('returns weekendRatio === 100 when all contributions are on weekends', () => {
568+
// Note: 2026-05-02 is a Saturday, 2026-05-03 is a Sunday, 2026-05-04 is a Monday
569+
const weekendCalendar = {
570+
totalContributions: 10,
571+
weeks: [
572+
{
573+
contributionDays: [
574+
{ date: '2026-05-02', contributionCount: 5 }, // Saturday (Weekend)
575+
{ date: '2026-05-03', contributionCount: 5 }, // Sunday (Weekend)
576+
{ date: '2026-05-04', contributionCount: 0 }, // Monday (Weekday - 0 commits)
577+
],
578+
},
579+
],
580+
} as Parameters<typeof calculateWrappedStats>[0]; // Safely infers the exact type the function expects!
581+
582+
const result = calculateWrappedStats(weekendCalendar);
583+
584+
// Assert the ratio is exactly 100%
585+
expect(result.weekendRatio).toBe(100);
586+
});
413587
});

0 commit comments

Comments
 (0)