Files
AgentHub/client/src/components/__tests__/AlertsNotifications.test.tsx
T
nntrivi2001 6715fbd971 feat(settings): data-safe editors, deep-linked search, Data/About/Cost pages
- EditorModal loads once per path (a parent re-render no longer reloads the
  file over the user's typing), refuses Save after a failed load, opens a
  truncated (>256 KB) file read-only, and asks before overwriting a file
  that changed on disk or could not be re-read.
- Settings/Hooks editors unset only keys from the snapshot taken at Edit,
  warn when the file changed underneath, keep a renamed key's value, and
  refuse a new key named like a redacted one (env) or a read-only hook
  event, which used to replace the secret/hooks.
- Project scope shows its resolved folder and is disabled inside the
  plugin cache. Account-profile, pricing-row and pet deletes confirm;
  two-click actions disarm after 3 s; unsaved drafts ask before closing.
- Search indexes every Claude Code / Codex sub-tab, opens it via ?tab= and
  scrolls to + highlights the row; the active config tab is in the URL.
- General splits into General / Data / About; Pricing moves to a Cost
  group with one shared load; config tabs group as Content / Extensions /
  Config with arrow-key roving; Promise.allSettled per tab.
- Data: browser-preferences export/import (allowlisted keys), per-section
  reset, housekeeping dry-run first, clear-all behind a typed phrase.
  Server: purge days must be a whole number >= 1 (INVALID_PURGE_DAYS) and
  dry_run returns exactly what the real run would delete.
- Alerts can be edited; webhooks/alerts/remote-source failures toast;
  copies go through copyText; modals get dialog roles and useDialog.
- Sagi: Ctrl/Cmd+B only when shown and not typing, default spot clear of
  row controls, arrow keys move it, reduced motion honoured; the dead
  /run launch link goes to the workspace.
2026-10-05 13:22:08 +07:00

80 lines
2.5 KiB
TypeScript

/**
* @file AlertsNotifications.test.tsx
* @description Alert rules are editable through the create form (PATCH, type
* fixed), and an invalid cooldown blocks saving instead of silently becoming 300.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it, expect, vi } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter } from "react-router-dom";
const update = vi.fn(async () => ({ rule: {} }));
const create = vi.fn(async () => ({ rule: {} }));
vi.mock("../../lib/api", () => ({
api: {
alerts: {
rules: {
list: vi.fn(async () => ({
rules: [
{
id: "r1",
name: "Idle",
rule_type: "inactivity",
config: { minutes: 30 },
enabled: true,
cooldown_seconds: 60,
},
],
})),
update: (...a: unknown[]) => update(...(a as [])),
create: (...a: unknown[]) => create(...(a as [])),
remove: vi.fn(),
},
list: vi.fn(async () => ({ alerts: [], total: 0, unacked: 0 })),
ack: vi.fn(),
ackAll: vi.fn(),
},
webhooks: {
list: vi.fn(async () => ({ targets: [] })),
providers: vi.fn(async () => ({ providers: [] })),
},
},
}));
import { AlertsNotifications } from "../AlertsNotifications";
const renderIt = () =>
render(
<MemoryRouter>
<AlertsNotifications />
</MemoryRouter>
);
describe("AlertsNotifications rules", () => {
it("edits a rule in place and validates the cooldown", async () => {
renderIt();
await userEvent.click(await screen.findByRole("button", { name: /edit rule/i }));
const name = screen.getByDisplayValue("Idle");
const cooldown = screen.getByDisplayValue("60");
await userEvent.clear(cooldown);
await userEvent.type(cooldown, "-5");
const save = screen.getByRole("button", { name: /save rule/i });
expect(save).toBeDisabled();
await userEvent.clear(cooldown);
await userEvent.type(cooldown, "120");
await userEvent.clear(name);
await userEvent.type(name, "Idle long");
await userEvent.click(save);
await waitFor(() =>
expect(update).toHaveBeenCalledWith("r1", {
name: "Idle long",
config: { minutes: 30 },
cooldown_seconds: 120,
})
);
expect(create).not.toHaveBeenCalled();
}, 15_000); // many typed fields: slow under a parallel run
});