diff --git a/components/dashboard/tooltipUtils.mock-integrations.test.ts b/components/dashboard/tooltipUtils.mock-integrations.test.ts new file mode 100644 index 000000000..44da3baa5 --- /dev/null +++ b/components/dashboard/tooltipUtils.mock-integrations.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it, vi } from 'vitest'; +import { + formatTooltipDate, + getActivityInsight, + getContributionLabel, + getLocalActiveStreak, + getStreakLabel, +} from './tooltipUtils'; + +describe('tooltipUtils mock integrations', () => { + it('mocks async contribution fetch successfully', async () => { + const mockFetch = vi.fn().mockResolvedValue({ + count: 5, + }); + + const result = await mockFetch(); + + expect(result.count).toBe(5); + expect(mockFetch).toHaveBeenCalled(); + }); + + it('uses cached tooltip label when available', () => { + const cache = new Map(); + + cache.set('label', getContributionLabel(3)); + + expect(cache.get('label')).toBe('3 contributions'); + }); + + it('formats cached tooltip dates correctly', () => { + const cacheDate = formatTooltipDate('2024-01-10'); + + expect(cacheDate).toBe('Jan 10, 2024'); + }); + + it('falls back safely during async timeout simulation', async () => { + const mockTimeout = vi.fn().mockRejectedValue(new Error('Timeout')); + + await expect(mockTimeout()).rejects.toThrow('Timeout'); + }); + + it('syncs streak cache correctly after async update', async () => { + const mockData = [ + { date: '2024-01-01', count: 1, intensity: 1 }, + { date: '2024-01-02', count: 2, intensity: 2 }, + ]; + + const mockSync = vi.fn().mockResolvedValue(mockData); + + const result = await mockSync(); + + expect(getLocalActiveStreak(result, 0)).toBe(2); + expect(getActivityInsight(2, 2)).toBe('Steady contribution day'); + expect(getStreakLabel(2)).toBe('2-day active streak'); + }); +}); diff --git a/components/dashboard/tooltipUtils.timezone-boundaries.test.ts b/components/dashboard/tooltipUtils.timezone-boundaries.test.ts new file mode 100644 index 000000000..84200c452 --- /dev/null +++ b/components/dashboard/tooltipUtils.timezone-boundaries.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest'; +import { formatTooltipDate, getLocalActiveStreak, getStreakLabel } from './tooltipUtils'; + +describe('tooltipUtils timezone boundaries', () => { + it('keeps UTC date stable across timezone boundaries', () => { + expect(formatTooltipDate('2024-01-01')).toBe('Jan 1, 2024'); + }); + + it('correctly formats leap year boundary dates', () => { + expect(formatTooltipDate('2024-02-29')).toBe('Feb 29, 2024'); + }); + + it('correctly formats year-end boundary dates', () => { + expect(formatTooltipDate('2024-12-31')).toBe('Dec 31, 2024'); + }); + + it('preserves active streak across consecutive calendar days', () => { + const data: { + date: string; + count: number; + intensity: 1 | 2 | 3 | 4; + }[] = [ + { date: '2024-01-01', count: 1, intensity: 1 }, + { date: '2024-01-02', count: 2, intensity: 2 }, + { date: '2024-01-03', count: 3, intensity: 3 }, + ]; + + expect(getLocalActiveStreak(data, 1)).toBe(3); + }); + + it('returns correct streak label after boundary calculations', () => { + expect(getStreakLabel(3)).toBe('3-day active streak'); + }); +}); diff --git a/components/dashboard/tooltipUtils.type-compiler.test.ts b/components/dashboard/tooltipUtils.type-compiler.test.ts new file mode 100644 index 000000000..d46aefc2c --- /dev/null +++ b/components/dashboard/tooltipUtils.type-compiler.test.ts @@ -0,0 +1,58 @@ +import { describe, expectTypeOf, it } from 'vitest'; +import type { ActivityData } from '@/types/dashboard'; + +describe('tooltipUtils type compiler tests', () => { + it('validates ActivityData structure correctly', () => { + const validData: ActivityData = { + date: '2024-01-01', + count: 5, + intensity: 2, + }; + + expectTypeOf(validData.date).toBeString(); + expectTypeOf(validData.count).toBeNumber(); + expectTypeOf(validData.intensity).toBeNumber(); + }); + + it('ensures ActivityData array typing works', () => { + const data: ActivityData[] = [ + { + date: '2024-01-01', + count: 1, + intensity: 1, + }, + ]; + + expectTypeOf(data).toBeArray(); + }); + + it('accepts optional compatible values safely', () => { + const data: ActivityData = { + date: '2024-01-02', + count: 0, + intensity: 0, + }; + + expectTypeOf(data.count).toEqualTypeOf(); + }); + it('rejects invalid property types during compilation', () => { + expectTypeOf().toBeObject(); + + const invalidCount: ActivityData = { + date: '2024-01-01', + // @ts-expect-error count must be number + count: 'five', + intensity: 2, + }; + }); + + it('rejects missing required properties', () => { + expectTypeOf().toBeObject(); + + // @ts-expect-error intensity is required + const invalidData: ActivityData = { + date: '2024-01-01', + count: 5, + }; + }); +}); diff --git a/package-lock.json b/package-lock.json index 0259b992d..f468167e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "dependencies": { "@resvg/resvg-js": "^2.6.2", "@tailwindcss/postcss": "^4.2.2", - "@unrs/resolver-binding-linux-x64-gnu": "*", "@vercel/analytics": "^1.6.1", "dompurify": "^3.4.7", "framer-motion": "^12.38.0",