diff --git a/src/components/__tests__/DashboardStatsClient.test.ts b/src/components/__tests__/DashboardStatsClient.test.ts deleted file mode 100644 index 4e20558..0000000 --- a/src/components/__tests__/DashboardStatsClient.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { buildEventSeries } from "../DashboardStatsClient"; - -describe("buildEventSeries", () => { - it("should return an empty array when given an empty array", () => { - expect(buildEventSeries([])).toEqual([]); - }); - - it("should replace 'Event' with an empty string in the name property", () => { - const input = [ - { type: "PushEvent", count: 10 }, - { type: "PullRequestEvent", count: 5 }, - { type: "IssuesEvent", count: 2 }, - ]; - const expected = [ - { name: "Push", count: 10 }, - { name: "PullRequest", count: 5 }, - { name: "Issues", count: 2 }, - ]; - expect(buildEventSeries(input)).toEqual(expected); - }); - - it("should return at most 6 elements", () => { - const input = Array.from({ length: 10 }, (_, i) => ({ - type: `Type${i}Event`, - count: i, - })); - const result = buildEventSeries(input); - expect(result.length).toBe(6); - expect(result).toEqual([ - { name: "Type0", count: 0 }, - { name: "Type1", count: 1 }, - { name: "Type2", count: 2 }, - { name: "Type3", count: 3 }, - { name: "Type4", count: 4 }, - { name: "Type5", count: 5 }, - ]); - }); - - it("should correctly map the count property", () => { - const input = [ - { type: "PushEvent", count: 42 }, - { type: "CreateEvent", count: 1 }, - ]; - const expected = [ - { name: "Push", count: 42 }, - { name: "Create", count: 1 }, - ]; - expect(buildEventSeries(input)).toEqual(expected); - }); - - it("should not modify type if it does not contain 'Event'", () => { - const input = [{ type: "Push", count: 10 }]; - const expected = [{ name: "Push", count: 10 }]; - expect(buildEventSeries(input)).toEqual(expected); - }); -}); diff --git a/src/components/__tests__/DashboardStatsClient.test.tsx b/src/components/__tests__/DashboardStatsClient.test.tsx new file mode 100644 index 0000000..62d5ffc --- /dev/null +++ b/src/components/__tests__/DashboardStatsClient.test.tsx @@ -0,0 +1,152 @@ +// @vitest-environment jsdom +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import DashboardStatsClient, { buildEventSeries } from "../DashboardStatsClient"; +import { useDashboardData, useDashboardStats } from "@/hooks/useDashboardData"; +import "@testing-library/jest-dom"; + +// Mock recharts to avoid rendering errors in jsdom +vi.mock("recharts", () => ({ + ResponsiveContainer: ({ children }: { children: React.ReactNode }) =>
{children}
, + BarChart: ({ children }: { children: React.ReactNode }) =>
{children}
, + Bar: () =>
, + XAxis: () =>
, + YAxis: () =>
, + CartesianGrid: () =>
, + Tooltip: () =>
, +})); + +vi.mock("@/components/ActivityHeatmapGrid", () => ({ + default: () =>
, +})); + +vi.mock("@/hooks/useDashboardData", () => ({ + useDashboardData: vi.fn(), + useDashboardStats: vi.fn(), +})); + +describe("buildEventSeries", () => { + it("should return an empty array when given an empty array", () => { + expect(buildEventSeries([])).toEqual([]); + }); + + it("should replace 'Event' with an empty string in the name property", () => { + const input = [ + { type: "PushEvent", count: 10 }, + { type: "PullRequestEvent", count: 5 }, + { type: "IssuesEvent", count: 2 }, + ]; + const expected = [ + { name: "Push", count: 10 }, + { name: "PullRequest", count: 5 }, + { name: "Issues", count: 2 }, + ]; + expect(buildEventSeries(input)).toEqual(expected); + }); + + it("should return at most 6 elements", () => { + const input = Array.from({ length: 10 }, (_, i) => ({ + type: `Type${i}Event`, + count: i, + })); + const result = buildEventSeries(input); + expect(result.length).toBe(6); + expect(result).toEqual([ + { name: "Type0", count: 0 }, + { name: "Type1", count: 1 }, + { name: "Type2", count: 2 }, + { name: "Type3", count: 3 }, + { name: "Type4", count: 4 }, + { name: "Type5", count: 5 }, + ]); + }); + + it("should correctly map the count property", () => { + const input = [ + { type: "PushEvent", count: 42 }, + { type: "CreateEvent", count: 1 }, + ]; + const expected = [ + { name: "Push", count: 42 }, + { name: "Create", count: 1 }, + ]; + expect(buildEventSeries(input)).toEqual(expected); + }); + + it("should not modify type if it does not contain 'Event'", () => { + const input = [{ type: "Push", count: 10 }]; + const expected = [{ name: "Push", count: 10 }]; + expect(buildEventSeries(input)).toEqual(expected); + }); +}); + +describe("DashboardStatsClient", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should render loading state when data is loading", () => { + vi.mocked(useDashboardData).mockReturnValue({ + summary: null, + isLoading: true, + error: null, + } as unknown as ReturnType); + vi.mocked(useDashboardStats).mockReturnValue({ + heatmap: null, + isLoading: false, + error: null, + } as unknown as ReturnType); + + render(); + expect(screen.getByText("Loading stats...")).toBeInTheDocument(); + }); + + it("should render error state when there is an error", () => { + vi.mocked(useDashboardData).mockReturnValue({ + summary: null, + isLoading: false, + error: new Error("Failed to load"), + } as unknown as ReturnType); + vi.mocked(useDashboardStats).mockReturnValue({ + heatmap: null, + isLoading: false, + error: null, + } as unknown as ReturnType); + + render(); + expect(screen.getByText("Failed to load stats.")).toBeInTheDocument(); + }); + + it("should render charts and heatmap when data is loaded successfully", () => { + const mockSummary = { + activity: { + eventBreakdown: [ + { type: "PushEvent", count: 10 }, + ], + }, + contributions: { + calendar: [ + { date: "2023-01-01", count: 5 }, + ], + }, + }; + + vi.mocked(useDashboardData).mockReturnValue({ + summary: mockSummary, + isLoading: false, + error: null, + } as unknown as ReturnType); + vi.mocked(useDashboardStats).mockReturnValue({ + heatmap: [[1, 2, 3]], + isLoading: false, + error: null, + } as unknown as ReturnType); + + render(); + + expect(screen.getByText("Activity Stats")).toBeInTheDocument(); + expect(screen.getByText("Recent Event Breakdown")).toBeInTheDocument(); + expect(screen.getByText("Monthly Contributions")).toBeInTheDocument(); + expect(screen.getByTestId("heatmap-grid")).toBeInTheDocument(); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index f5c9aea..4bb05e9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ coverage: { provider: "v8", reporter: ["text", "lcov"], - include: ["src/lib/**/*.ts", "src/hooks/**/*.ts"], + include: ["src/lib/**/*.ts", "src/hooks/**/*.ts", "src/components/DashboardStatsClient.tsx"], thresholds: { lines: 80, functions: 80,