Polish dashboard charts and add frontend tests (Stage 6)

Task 6.1 - Consistent loading/error/empty states across all charts:
- Add ChartStateWrapper shared component with Spinner, error MessageBar
  + Retry button, and friendly empty message
- Expose reload() in useBanTrend, useJailDistribution,
  useDashboardCountryData hooks
- Update BanTrendChart and JailDistributionChart to use ChartStateWrapper
- Add empty state to TopCountriesBarChart and TopCountriesPieChart
- Replace manual loading/error logic in DashboardPage with ChartStateWrapper

Task 6.2 - Frontend tests (5 files, 20 tests):
- Install Vitest v4, jsdom, @testing-library/react, @testing-library/jest-dom
- Add vitest.config.ts (separate from vite.config.ts to avoid Vite v5/v7 clash)
- Add src/setupTests.ts with jest-dom matchers and ResizeObserver/matchMedia stubs
- Tests: ChartStateWrapper (7), BanTrendChart (4), JailDistributionChart (4),
  TopCountriesPieChart (2), TopCountriesBarChart (3)

Task 6.3 - Full QA:
- ruff: clean
- mypy --strict: 52 files, no issues
- pytest: 497 passed
- tsc --noEmit: clean
- eslint: clean (added test-file override for explicit-function-return-type)
- vite build: success
This commit is contained in:
2026-03-11 17:25:28 +01:00
parent fe8eefa173
commit 576ec43854
21 changed files with 2628 additions and 167 deletions

View File

@@ -0,0 +1,41 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
import { TopCountriesPieChart } from "../TopCountriesPieChart";
vi.mock("recharts", () => ({
ResponsiveContainer: ({ children }: { children: React.ReactNode }) => (
<div>{children}</div>
),
PieChart: ({ children }: { children: React.ReactNode }) => (
<div data-testid="pie-chart">{children}</div>
),
Pie: () => null,
Cell: () => null,
Tooltip: () => null,
Legend: () => null,
}));
function wrap(ui: React.ReactElement) {
return render(
<FluentProvider theme={webLightTheme}>{ui}</FluentProvider>,
);
}
describe("TopCountriesPieChart", () => {
it("shows empty state when countries is empty", () => {
wrap(<TopCountriesPieChart countries={{}} countryNames={{}} />);
expect(screen.getByText("No bans in this time range.")).toBeInTheDocument();
expect(screen.queryByTestId("pie-chart")).not.toBeInTheDocument();
});
it("renders pie chart when country data is provided", () => {
wrap(
<TopCountriesPieChart
countries={{ DE: 30, US: 20, CN: 10 }}
countryNames={{ DE: "Germany", US: "United States", CN: "China" }}
/>,
);
expect(screen.getByTestId("pie-chart")).toBeInTheDocument();
});
});