From c71855c0ffb7dc6948f41811ff23bd77f6806f9a Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 25 May 2026 07:38:44 +0530 Subject: [PATCH 1/3] test: added unit tests for useCountUp hook --- test/useCountUp.test.ts | 127 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 test/useCountUp.test.ts diff --git a/test/useCountUp.test.ts b/test/useCountUp.test.ts new file mode 100644 index 00000000..e1482672 --- /dev/null +++ b/test/useCountUp.test.ts @@ -0,0 +1,127 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +// Define globally mocked React hooks and tracking variables +const mockSetCount = vi.fn(); +let mockEffectCallback: any = null; + +vi.mock('react', () => { + return { + useState: vi.fn().mockImplementation((initial) => [initial, mockSetCount]), + useRef: vi.fn().mockImplementation((initial) => ({ current: initial })), + useEffect: vi.fn().mockImplementation((cb) => { + mockEffectCallback = cb; + }), + }; +}); + +// Setup mock window and animation frame globals +let mockAnimateCallback: any = null; +let mockCancelId: any = null; + +global.window = { + matchMedia: vi.fn().mockReturnValue({ matches: false }), +} as any; + +global.requestAnimationFrame = vi.fn().mockImplementation((cb) => { + mockAnimateCallback = cb; + return 999; +}); + +global.cancelAnimationFrame = vi.fn().mockImplementation((id) => { + mockCancelId = id; +}); + +// Import the actual hook from source! +import { useCountUp } from '../src/hooks/useCountUp'; + +describe('useCountUp hook behavior', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockEffectCallback = null; + mockAnimateCallback = null; + mockCancelId = null; + }); + + it('returns initial count of 0', () => { + const count = useCountUp(100); + expect(count).toBe(0); + }); + + it('immediately sets count to target if prefers-reduced-motion is enabled', () => { + // Mock reduced motion + window.matchMedia = vi.fn().mockReturnValue({ matches: true }) as any; + + useCountUp(100); + expect(mockEffectCallback).toBeDefined(); + + mockEffectCallback(); + expect(mockSetCount).toHaveBeenCalledWith(100); + expect(global.requestAnimationFrame).not.toHaveBeenCalled(); + }); + + it('immediately sets count to 0 for target of 0 or negative', () => { + window.matchMedia = vi.fn().mockReturnValue({ matches: false }) as any; + + useCountUp(0); + mockEffectCallback(); + expect(mockSetCount).toHaveBeenCalledWith(0); + + vi.clearAllMocks(); + useCountUp(-5); + mockEffectCallback(); + expect(mockSetCount).toHaveBeenCalledWith(0); + }); + + it('animates count smoothly using easeOutQuint and correct duration', () => { + window.matchMedia = vi.fn().mockReturnValue({ matches: false }) as any; + + // Target 100, adaptive duration should be 800ms + useCountUp(100); + mockEffectCallback(); + + expect(global.requestAnimationFrame).toHaveBeenCalled(); + expect(mockAnimateCallback).toBeDefined(); + + // First animation frame (elapsed 0ms) + mockAnimateCallback(1000); + expect(mockSetCount).toHaveBeenCalledWith(0); + + // Second animation frame (elapsed 400ms - 50% progress) + // easeOutQuint = 1 - (1 - 0.5)^5 = 1 - 0.03125 = 0.96875 + // expectedCount = Math.round(0.96875 * 100) = 97 + mockAnimateCallback(1400); + expect(mockSetCount).toHaveBeenCalledWith(97); + + // Final animation frame (elapsed 800ms - 100% progress) + mockAnimateCallback(1800); + expect(mockSetCount).toHaveBeenCalledWith(100); + }); + + it('uses explicitly supplied custom duration if provided', () => { + window.matchMedia = vi.fn().mockReturnValue({ matches: false }) as any; + + // Target 100, custom duration 2000ms + useCountUp(100, 2000); + mockEffectCallback(); + + // First animation frame (elapsed 0ms) + mockAnimateCallback(1000); + + // Second animation frame at 1000ms (50% progress for a 2000ms duration) + // easeOutQuint = 1 - (1 - 0.5)^5 = 0.96875 + // expectedCount = 97 + mockAnimateCallback(2000); + expect(mockSetCount).toHaveBeenCalledWith(97); + }); + + it('cancels animation frame on unmount', () => { + window.matchMedia = vi.fn().mockReturnValue({ matches: false }) as any; + + useCountUp(100); + const cleanup = mockEffectCallback(); + + expect(typeof cleanup).toBe('function'); + cleanup(); + expect(global.cancelAnimationFrame).toHaveBeenCalledWith(999); + }); +}); From 5e83955fe75ed37a8e04c8b2217aec5da2270a9f Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 25 May 2026 08:16:03 +0530 Subject: [PATCH 2/3] fix: add missing E2E dashboard widget mocks --- e2e/dashboard-widgets.spec.js | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/e2e/dashboard-widgets.spec.js b/e2e/dashboard-widgets.spec.js index 40ab6562..7ec3ee59 100644 --- a/e2e/dashboard-widgets.spec.js +++ b/e2e/dashboard-widgets.spec.js @@ -15,6 +15,7 @@ test.beforeEach(async ({ page }) => { accessToken: "test-token", }, maxAge: 60 * 60, + cookieName: "next-auth.session-token", }); await page.context().addCookies([ @@ -93,6 +94,45 @@ test.beforeEach(async ({ page }) => { }); }); + await page.route("**/api/goals/sync", async (route) => { + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ updated: 1, commitCount: 4 }), + }); + }); + + await page.route("**/api/ai-insights**", async (route) => { + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ + data: { + insights: [ + { + id: "insight-1", + type: "productivity", + title: "High Consistency", + description: "You have coded 5 days this week!", + severity: "positive", + }, + ], + trend: { direction: "up", percentage: 15 }, + aiSummary: "Great job shipping features this week. Keep up the high standard!", + generatedAt: "2026-05-18T12:00:00.000Z", + }, + }), + }); + }); + + await page.route("**/api/notifications**", async (route) => { + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ + notifications: [], + unreadCount: 0, + }), + }); + }); + const metricRoutes = [ "**/api/metrics/prs**", "**/api/metrics/pr-breakdown**", From aa13349e9880991808d5366c4ba889ab377985de Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 25 May 2026 14:04:33 +0530 Subject: [PATCH 3/3] chore: add test script to package.json to fix CI run --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 8dbab4ec..2bb6df5d 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "start": "next start", "lint": "next lint", "type-check": "tsc --noEmit", + "test": "vitest run", "test:e2e": "playwright test" }, "dependencies": {