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
58 lines
2.0 KiB
TypeScript
58 lines
2.0 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
|
|
import { TopCountriesBarChart } from "../TopCountriesBarChart";
|
|
|
|
vi.mock("recharts", () => ({
|
|
ResponsiveContainer: ({ children }: { children: React.ReactNode }) => (
|
|
<div>{children}</div>
|
|
),
|
|
BarChart: ({ children }: { children: React.ReactNode }) => (
|
|
<div data-testid="bar-chart">{children}</div>
|
|
),
|
|
Bar: () => null,
|
|
CartesianGrid: () => null,
|
|
XAxis: () => null,
|
|
YAxis: () => null,
|
|
Tooltip: () => null,
|
|
Cell: () => null,
|
|
}));
|
|
|
|
function wrap(ui: React.ReactElement) {
|
|
return render(
|
|
<FluentProvider theme={webLightTheme}>{ui}</FluentProvider>,
|
|
);
|
|
}
|
|
|
|
describe("TopCountriesBarChart", () => {
|
|
it("shows empty state when countries is empty", () => {
|
|
wrap(<TopCountriesBarChart countries={{}} countryNames={{}} />);
|
|
expect(screen.getByText("No bans in this time range.")).toBeInTheDocument();
|
|
expect(screen.queryByTestId("bar-chart")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("renders bar chart when country data is provided", () => {
|
|
wrap(
|
|
<TopCountriesBarChart
|
|
countries={{ DE: 50, US: 30, FR: 15 }}
|
|
countryNames={{ DE: "Germany", US: "United States", FR: "France" }}
|
|
/>,
|
|
);
|
|
expect(screen.getByTestId("bar-chart")).toBeInTheDocument();
|
|
});
|
|
|
|
it("does not render more than 20 bars (TOP_N limit)", () => {
|
|
// Build 30 countries — only top 20 should appear in the chart
|
|
const countries: Record<string, number> = {};
|
|
const countryNames: Record<string, string> = {};
|
|
for (let i = 0; i < 30; i++) {
|
|
const code = `C${String(i).padStart(2, "0")}`;
|
|
countries[code] = 30 - i;
|
|
countryNames[code] = `Country ${String(i)}`;
|
|
}
|
|
wrap(<TopCountriesBarChart countries={countries} countryNames={countryNames} />);
|
|
// Chart should render (not show empty state) with data present
|
|
expect(screen.getByTestId("bar-chart")).toBeInTheDocument();
|
|
});
|
|
});
|