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

@@ -8,15 +8,13 @@
import { useState } from "react";
import {
MessageBar,
MessageBarBody,
Spinner,
Text,
ToggleButton,
Toolbar,
makeStyles,
tokens,
} from "@fluentui/react-components";
import { ChartStateWrapper } from "../components/ChartStateWrapper";
import { BanTable } from "../components/BanTable";
import { BanTrendChart } from "../components/BanTrendChart";
import { JailDistributionChart } from "../components/JailDistributionChart";
@@ -109,7 +107,7 @@ export function DashboardPage(): React.JSX.Element {
const [timeRange, setTimeRange] = useState<TimeRange>("24h");
const [originFilter, setOriginFilter] = useState<BanOriginFilter>("all");
const { countries, countryNames, isLoading: countryLoading, error: countryError } =
const { countries, countryNames, isLoading: countryLoading, error: countryError, reload: reloadCountry } =
useDashboardCountryData(timeRange, originFilter);
return (
@@ -143,27 +141,28 @@ export function DashboardPage(): React.JSX.Element {
</Text>
</div>
<div className={styles.tabContent}>
{countryError != null && (
<MessageBar intent="error">
<MessageBarBody>{countryError}</MessageBarBody>
</MessageBar>
)}
{countryLoading && countryError == null ? (
<Spinner label="Loading chart data…" />
) : (
<ChartStateWrapper
isLoading={countryLoading}
error={countryError}
onRetry={reloadCountry}
isEmpty={!countryLoading && Object.keys(countries).length === 0}
emptyMessage="No ban data for the selected period."
>
<div className={styles.chartsRow}>
<div className={styles.chartCard}>
<TopCountriesPieChart
countries={countries}
countryNames={countryNames}
/>
</div> <div className={styles.chartCard}>
</div>
<div className={styles.chartCard}>
<TopCountriesBarChart
countries={countries}
countryNames={countryNames}
/>
</div> </div>
)}
</div>
</div>
</ChartStateWrapper>
</div>
</div>