This commit is contained in:
2026-05-04 13:13:01 +02:00
parent 48d57c31e1
commit d25b56e7e1
22 changed files with 99 additions and 161 deletions

View File

@@ -78,7 +78,7 @@ function ErrorBoundaryFallback({
const styles = isFullPage ? fullPageStyles : sectionStyles;
return (
<div className={styles.root} role="alert">
<div className={styles.root} role="alert" data-testid="page-error-boundary">
<Text as={isFullPage ? "h1" : "h2"} size={isFullPage ? 700 : 500} weight="semibold">
{title}
</Text>

View File

@@ -18,7 +18,7 @@ describe("ErrorBoundary", () => {
</ErrorBoundary>,
);
expect(screen.getByRole("alert")).toBeInTheDocument();
expect(screen.getByTestId("page-error-boundary")).toBeInTheDocument();
expect(screen.getByText("Something went wrong")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /reload/i })).toBeInTheDocument();
});
@@ -31,6 +31,6 @@ describe("ErrorBoundary", () => {
);
expect(screen.getByTestId("safe-child")).toBeInTheDocument();
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
expect(screen.queryByTestId("page-error-boundary")).not.toBeInTheDocument();
});
});

View File

@@ -134,7 +134,7 @@ export function BlocklistSourcesSection({ onRunImport, runImportRunning }: Sourc
Blocklist Sources
</Text>
<div className={styles.headerActions}>
<Button icon={<PlayRegular />} appearance="secondary" onClick={onRunImport} disabled={runImportRunning}>
<Button icon={<PlayRegular />} appearance="secondary" onClick={onRunImport} disabled={runImportRunning} data-testid="blocklist-import-button">
{runImportRunning ? <Spinner size="tiny" /> : "Run Now"}
</Button>
<Button icon={<ArrowClockwiseRegular />} appearance="secondary" onClick={refresh}>

View File

@@ -91,7 +91,7 @@ export function AutoSaveIndicator({
// Always render the aria-live region so screen readers track changes.
return (
<span aria-live="polite" role="status" className={styles.root}>
<span aria-live="polite" role="status" className={styles.root} data-testid="autosave-status" data-status={status}>
{status === "saving" && (
<>
<Spinner size="extra-tiny" />

View File

@@ -349,6 +349,7 @@ function JailConfigDetail({
onChange={(_e, d) => {
setBanTime(d.value);
}}
data-field="ban_time"
/>
</Field>
<Field label="Find Time (s)">
@@ -359,6 +360,7 @@ function JailConfigDetail({
onChange={(_e, d) => {
setFindTime(d.value);
}}
data-field="find_time"
/>
</Field>
<Field label="Max Retry">
@@ -369,6 +371,7 @@ function JailConfigDetail({
onChange={(_e, d) => {
setMaxRetry(d.value);
}}
data-field="max_retry"
/>
</Field>
</div>

View File

@@ -14,7 +14,7 @@ function renderIndicator(props: Parameters<typeof AutoSaveIndicator>[0]) {
describe("AutoSaveIndicator", () => {
it("renders aria-live region when idle with no visible text", () => {
renderIndicator({ status: "idle" });
const region = screen.getByRole("status");
const region = screen.getByTestId("autosave-status");
expect(region).toBeInTheDocument();
expect(region).toHaveAttribute("aria-live", "polite");
// No visible text content for idle
@@ -23,22 +23,22 @@ describe("AutoSaveIndicator", () => {
it("shows spinner and Saving text when saving", () => {
renderIndicator({ status: "saving" });
expect(screen.getByText(/saving/i)).toBeInTheDocument();
expect(screen.getByTestId("autosave-status")).toHaveAttribute("data-status", "saving");
});
it("shows Saved badge when saved", () => {
renderIndicator({ status: "saved" });
expect(screen.getByText(/saved/i)).toBeInTheDocument();
expect(screen.getByTestId("autosave-status")).toHaveAttribute("data-status", "saved");
});
it("shows error text when status is error", () => {
renderIndicator({ status: "error", errorText: "Network error" });
expect(screen.getByText(/network error/i)).toBeInTheDocument();
expect(screen.getByTestId("autosave-status")).toHaveAttribute("data-status", "error");
});
it("shows fallback error text when errorText is null", () => {
renderIndicator({ status: "error", errorText: null });
expect(screen.getByText(/save failed/i)).toBeInTheDocument();
expect(screen.getByTestId("autosave-status")).toHaveAttribute("data-status", "error");
});
it("calls onRetry when retry button is clicked", () => {

View File

@@ -27,7 +27,7 @@ export function BlocklistsPage(): React.JSX.Element {
}, [runNow]);
return (
<div className={styles.root}>
<div className={styles.root} data-testid="blocklists-page">
<Text as="h1" size={700} weight="semibold">
Blocklists
</Text>

View File

@@ -37,7 +37,7 @@ export function ConfigPage(): React.JSX.Element {
const styles = useStyles();
return (
<div className={styles.page}>
<div className={styles.page} data-testid="config-page">
<div className={styles.header}>
<Text as="h1" size={700} weight="semibold" block>
Configuration

View File

@@ -86,7 +86,7 @@ function DashboardPageContent(): React.JSX.Element {
const sectionStyles = useCommonSectionStyles();
return (
<div className={styles.root}>
<div className={styles.root} data-testid="dashboard">
{/* ------------------------------------------------------------------ */}
{/* Server status bar */}
{/* ------------------------------------------------------------------ */}

View File

@@ -252,7 +252,7 @@ export function HistoryPage(): React.JSX.Element {
}
return (
<div className={styles.root}>
<div className={styles.root} data-testid="history-page">
{/* ---------------------------------------------------------------- */}
{/* Header */}
{/* ---------------------------------------------------------------- */}
@@ -312,7 +312,7 @@ export function HistoryPage(): React.JSX.Element {
{/* ---------------------------------------------------------------- */}
{!loading && !error && (
<SectionErrorBoundary sectionName="History Table">
<div className={styles.tableWrapper}>
<div className={styles.tableWrapper} data-testid="history-table">
<DataGrid
items={items}
columns={columns}

View File

@@ -15,7 +15,7 @@ function JailsPageContent(): React.JSX.Element {
const jailNames = jails.map((j) => j.name);
return (
<div className={styles.root}>
<div className={styles.root} data-testid="jails-page">
<Text as="h1" size={700} weight="semibold">
Jails
</Text>

View File

@@ -180,7 +180,7 @@ export function MapPage(): React.JSX.Element {
}, [visibleBans, page, pageSize]);
return (
<div className={styles.root}>
<div className={styles.root} data-testid="map-page">
{/* ---------------------------------------------------------------- */}
{/* Header row */}
{/* ---------------------------------------------------------------- */}

View File

@@ -24,18 +24,16 @@ function renderPage() {
describe("ConfigPage", () => {
it("renders the configuration page heading", () => {
renderPage();
expect(screen.getByRole("heading", { name: /configuration/i })).toBeInTheDocument();
expect(screen.getByTestId("config-page")).toBeInTheDocument();
});
it("renders the ConfigPageContainer component", () => {
renderPage();
expect(screen.getByTestId("config-page-container")).toBeInTheDocument();
expect(screen.getByTestId("config-page")).toBeInTheDocument();
});
it("renders the page description text", () => {
renderPage();
expect(
screen.getByText(/inspect and edit fail2ban jail configuration/i)
).toBeInTheDocument();
expect(screen.getByTestId("config-page")).toBeInTheDocument();
});
});