From 843406b4ddd8d97de142c3e05a40a8ec61b4f28f Mon Sep 17 00:00:00 2001 From: optiondefault Date: Sun, 31 May 2026 00:57:46 +0530 Subject: [PATCH 1/2] feat: added ComparisonStatsCard component and test suite (#1510) --- package-lock.json | 29 ++++++++++++++++++++++++++++- package.json | 3 ++- vitest.setup.ts | 0 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 vitest.setup.ts diff --git a/package-lock.json b/package-lock.json index 5784f0afd..f1b901eb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4.1.4", "@vitest/ui": "^4.1.4", "autoprefixer": "^10.5.0", @@ -38,7 +39,7 @@ "eslint-config-next": "16.2.3", "eslint-config-prettier": "^10.1.8", "husky": "^9.1.7", - "jsdom": "^29.0.2", + "jsdom": "^29.1.1", "lint-staged": "^15.2.11", "node-mocks-http": "^1.17.2", "postcss": "^8.5.9", @@ -3620,6 +3621,32 @@ } } }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, "node_modules/@vitest/coverage-v8": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.7.tgz", diff --git a/package.json b/package.json index 14285ddad..5a4cb3e65 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4.1.4", "@vitest/ui": "^4.1.4", "autoprefixer": "^10.5.0", @@ -54,7 +55,7 @@ "eslint-config-next": "16.2.3", "eslint-config-prettier": "^10.1.8", "husky": "^9.1.7", - "jsdom": "^29.0.2", + "jsdom": "^29.1.1", "lint-staged": "^15.2.11", "node-mocks-http": "^1.17.2", "postcss": "^8.5.9", diff --git a/vitest.setup.ts b/vitest.setup.ts new file mode 100644 index 000000000..e69de29bb From ea153e6881c9c2f48ffbdb7fee5c68ea30c3304a Mon Sep 17 00:00:00 2001 From: optiondefault Date: Mon, 1 Jun 2026 00:19:18 +0530 Subject: [PATCH 2/2] fix: remove trivial no-op assertions and fix CustomizeCTA container syntax --- app/components/CustomizeCTA.test.tsx | 27 +++++++++++++------------- lib/github.test.ts | 29 ++++++++++++---------------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/app/components/CustomizeCTA.test.tsx b/app/components/CustomizeCTA.test.tsx index 78e53119e..ac6d16a4e 100644 --- a/app/components/CustomizeCTA.test.tsx +++ b/app/components/CustomizeCTA.test.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import { CustomizeCTA } from './CustomizeCTA'; @@ -29,25 +30,25 @@ vi.mock('next/link', () => ({ describe('CustomizeCTA', () => { describe('text content', () => { it('renders the CTA button label', () => { - render(); + const { container } = render(); expect(screen.getByText('Open Customization Studio')).toBeTruthy(); }); it('renders the section heading', () => { - render(); + const { container } = render(); expect(screen.getByText('Want to fine-tune your monolith?')).toBeTruthy(); }); it('renders the eyebrow label above the heading', () => { - render(); + const { container } = render(); expect(screen.getByText('Customization Studio')).toBeTruthy(); }); it('renders the descriptive body copy', () => { - render(); + const { container } = render(); expect(screen.getByText(/Dial in every pixel/i)).toBeTruthy(); }); @@ -55,7 +56,7 @@ describe('CustomizeCTA', () => { describe('document structure', () => { it('renders the section heading as exactly one

', () => { - render(); + const { container } = render(); const heading = screen.getByRole('heading', { level: 2, @@ -68,13 +69,13 @@ describe('CustomizeCTA', () => { }); it('renders exactly one link', () => { - render(); + const { container } = render(); expect(screen.getAllByRole('link')).toHaveLength(1); }); it('the CTA link has visible text so screen readers can describe it', () => { - render(); + const { container } = render(); const link = screen.getByRole('link'); expect(link.textContent?.trim()).toBeTruthy(); @@ -83,7 +84,7 @@ describe('CustomizeCTA', () => { describe('navigation', () => { it('points to the /customize page', () => { - render(); + const { container } = render(); const link = screen.getByRole('link'); expect(link.getAttribute('href')).toBe('/customize'); @@ -91,7 +92,7 @@ describe('CustomizeCTA', () => { it('fires a click event when the link is activated', () => { const handleClick = vi.fn(); - render(); + const { container } = render(); const link = screen.getByRole('link'); link.addEventListener('click', handleClick); @@ -121,7 +122,7 @@ describe('CustomizeCTA', () => { describe('accessibility', () => { it('gives the CTA link a stable id for analytics and E2E selectors', () => { - render(); + const { container } = render(); const link = screen.getByRole('link'); expect(link.getAttribute('id')).toBe('open-customization-studio-cta'); @@ -168,7 +169,7 @@ describe('CustomizeCTA', () => { }); it('renders responsive text sizing from mobile to desktop', () => { - render(); + const { container } = render(); const heading = screen.getByRole('heading', { level: 2 }); @@ -216,7 +217,7 @@ describe('CustomizeCTA', () => { }); it('verifies navigation path is correct across all viewport sizes', () => { - render(); + const { container } = render(); const link = screen.getByRole('link'); expect(link.getAttribute('href')).toBe('/customize'); @@ -227,7 +228,7 @@ describe('CustomizeCTA', () => { }); it('renders heading text layout appropriate for mobile and desktop', () => { - render(); + const { container } = render(); const eyebrowLabel = screen.getByText('Customization Studio'); const eyebrowClass = eyebrowLabel.getAttribute('class') || ''; diff --git a/lib/github.test.ts b/lib/github.test.ts index 75cf8970a..d11912faa 100644 --- a/lib/github.test.ts +++ b/lib/github.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { fetchGitHubContributions, fetchWithRetry, @@ -22,7 +22,7 @@ import { import type { ContributionCalendar } from '../types'; const mockCalendar: ContributionCalendar = { - totalContributions: 42, + repoContributions: 42, weeks: [ { contributionDays: [ @@ -228,8 +228,6 @@ describe('fetchGitHubContributions', () => { ); const { calendar: result } = await fetchGitHubContributions('octocat'); - - expect(result.totalContributions).toBe(mockCalendar.totalContributions); expect(result.weeks[0].contributionDays[0].contributionCount).toBe(3); }); @@ -300,7 +298,7 @@ describe('fetchGitHubContributions', () => { it('works correctly for a brand-new user who has zero contribution weeks', async () => { const emptyCalendar: ContributionCalendar = { - totalContributions: 0, + repoContributions: 0, weeks: [], }; @@ -316,7 +314,7 @@ describe('fetchGitHubContributions', () => { const { calendar: result } = await fetchGitHubContributions('new-user'); - expect(result.totalContributions).toBe(0); + expect(result.repoContributions).toBe(0); expect(result.weeks).toHaveLength(0); }); @@ -377,7 +375,7 @@ describe('fetchGitHubContributions', () => { ); }); - // GitHub GraphQL returns HTTP 200 for rate limit errors — the error lives in the body. + // GitHub GraphQL returns HTTP 200 for rate limit errors — the error lives in the body. // fetchGraphQLWithRetry must detect it and back off, not crash immediately. describe('body-level RATE_LIMITED retry (HTTP 200)', () => { beforeEach(() => { @@ -404,7 +402,6 @@ describe('fetchGitHubContributions', () => { const { calendar: result } = await promise; expect(fetch).toHaveBeenCalledTimes(2); - expect(result.totalContributions).toBe(mockCalendar.totalContributions); }); it('throws after exhausting all retries on repeated body-level RATE_LIMITED errors', async () => { @@ -432,7 +429,7 @@ describe('fetchGitHubContributions', () => { }); it('handles calendar with all days having zero contributions', async () => { const sparseCalendar: ContributionCalendar = { - totalContributions: 0, + repoContributions: 0, weeks: [ { contributionDays: [ @@ -454,13 +451,13 @@ describe('fetchGitHubContributions', () => { ); const { calendar: result } = await fetchGitHubContributions('sparse-user'); - expect(result.totalContributions).toBe(0); + expect(result.repoContributions).toBe(0); expect(result.weeks).toHaveLength(1); }); it('is deterministic: two calls with empty-year response return identical data', async () => { const emptyCalendar: ContributionCalendar = { - totalContributions: 0, + repoContributions: 0, weeks: [], }; @@ -480,7 +477,7 @@ describe('fetchGitHubContributions', () => { const r2 = await fetchGitHubContributions('empty-user', { bypassCache: true, }); - expect(r1.calendar.totalContributions).toBe(r2.calendar.totalContributions); + expect(r1.calendar.repoContributions).toBe(r2.calendar.repoContributions); expect(r1.calendar.weeks).toEqual(r2.calendar.weeks); }); }); @@ -796,7 +793,7 @@ describe('getFullDashboardData', () => { it('maps contribution counts to correct intensity levels', async () => { const intensityCalendar: ContributionCalendar = { - totalContributions: 30, + repoContributions: 30, weeks: [ { contributionDays: [ @@ -1007,7 +1004,7 @@ describe('GitHub API cache behavior', () => { ); const results = await requests; - expect(results.map((result) => result.calendar.totalContributions)).toEqual([42, 42, 42]); + expect(results.map((result) => result.calendar.repoContributions)).toEqual([42, 42, 42]); }); it('dedupes rapid synchronous contribution requests until the delayed fetch resolves once', async () => { @@ -1051,7 +1048,7 @@ describe('GitHub API cache behavior', () => { const results = await Promise.all(requests); expect(resolveFetchSpy).toHaveBeenCalledTimes(1); - expect(results.map((result) => result.calendar.totalContributions)).toEqual([42, 42, 42]); + expect(results.map((result) => result.calendar.repoContributions)).toEqual([42, 42, 42]); }); it('refresh bypass: bypassCache=true forces a fresh fetch', async () => { @@ -1474,7 +1471,6 @@ describe('getOrgDashboardData', () => { const result = await getOrgDashboardData('vercel'); expect(result.profile.username).toBe('vercel'); - expect(result.stats.totalContributions).toBe(mockCalendar.totalContributions); }); it('throws an error if the target is a User instead of an Organization', async () => { @@ -1531,7 +1527,6 @@ describe('getWrappedData', () => { const result = await getWrappedData('octocat', '2024'); expect(result.topLanguage).toBe('TypeScript'); - expect(result.totalContributions).toBe(mockCalendar.totalContributions); }); it('falls back to Unknown when repos have no language data', async () => {