-
Notifications
You must be signed in to change notification settings - Fork 0
🧪 [testing improvement] Add unit tests for buildEventSeries #185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
84d7ce0
63ff1ff
ec504d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 }) => <div>{children}</div>, | ||
| BarChart: ({ children }: { children: React.ReactNode }) => <div>{children}</div>, | ||
| Bar: () => <div />, | ||
| XAxis: () => <div />, | ||
| YAxis: () => <div />, | ||
| CartesianGrid: () => <div />, | ||
| Tooltip: () => <div />, | ||
| })); | ||
|
|
||
| vi.mock("@/components/ActivityHeatmapGrid", () => ({ | ||
| default: () => <div data-testid="heatmap-grid" />, | ||
| })); | ||
|
|
||
| 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<typeof useDashboardData>); | ||
| vi.mocked(useDashboardStats).mockReturnValue({ | ||
| heatmap: null, | ||
| isLoading: false, | ||
| error: null, | ||
| } as unknown as ReturnType<typeof useDashboardStats>); | ||
|
|
||
| render(<DashboardStatsClient />); | ||
| 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<typeof useDashboardData>); | ||
| vi.mocked(useDashboardStats).mockReturnValue({ | ||
| heatmap: null, | ||
| isLoading: false, | ||
| error: null, | ||
| } as unknown as ReturnType<typeof useDashboardStats>); | ||
|
|
||
| render(<DashboardStatsClient />); | ||
| 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<typeof useDashboardData>); | ||
| vi.mocked(useDashboardStats).mockReturnValue({ | ||
| heatmap: [[1, 2, 3]], | ||
| isLoading: false, | ||
| error: null, | ||
| } as unknown as ReturnType<typeof useDashboardStats>); | ||
|
|
||
| render(<DashboardStatsClient />); | ||
|
|
||
| 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(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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"], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Prompt To Fix With AIThis is a comment left during a code review.
Path: vitest.config.ts
Line: 14
Comment:
**カバレッジ閾値違反のリスク**
`DashboardStatsClient.tsx` にはテスト対象の `buildEventSeries` のほかに、`StatBarChart`・`EventBreakdownChart`・`MonthlyContributionsChart`・`DashboardStatsClient`(デフォルトエクスポート)の計4つの関数が定義されています。現行テストはこれらをまったく呼び出しておらず、ファイル単体の関数カバレッジは約 20%(1/5)になります。集計カバレッジが既存ファイル群で余裕をもって 80% を超えていれば問題ありませんが、ボーダーライン付近の場合はこの追加で `functions: 80` 閾値を下回り CI が失敗するリスクがあります。事前にローカルで `vitest run --coverage` を実行して閾値違反が起きないことを確認することを推奨します。
How can I resolve this? If you propose a fix, please make it concise. |
||
| thresholds: { | ||
| lines: 80, | ||
| functions: 80, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding individual component files to the coverage
includelist, it is more robust to use a glob pattern. This ensures that all current and future components in thesrc/componentsdirectory are automatically tracked for coverage.