Files
AgentHub/client/src/components/__tests__/WebhookSettings.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

86 lines
2.6 KiB
TypeScript

/**
* @file WebhookSettings.test.tsx
* @description The webhook form must not save "only selected rules" with no
* rule ticked (the server reads an empty list as ALL rules), and a failed
* toggle must surface as an error toast instead of only a console line.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
const toastError = vi.fn();
vi.mock("../../lib/toast", () => ({
toast: { error: (m: unknown) => toastError(m), success: vi.fn(), info: vi.fn() },
}));
const update = vi.fn();
vi.mock("../../lib/api", () => ({
api: {
webhooks: {
list: vi.fn(async () => ({
targets: [
{
id: "w1",
name: "Team",
type: "generic",
url_preview: "https://x/…",
enabled: true,
rule_ids: [],
config: {},
},
],
})),
providers: vi.fn(async () => ({
providers: [
{
type: "generic",
label: "Generic",
url_required: true,
has_default_url: false,
supports_secret: false,
supports_headers: false,
fields: [],
},
],
})),
update: (...a: unknown[]) => update(...a),
create: vi.fn(),
remove: vi.fn(),
test: vi.fn(),
},
alerts: { rules: { list: vi.fn(async () => ({ rules: [{ id: "r1", name: "Rule one" }] })) } },
},
}));
import { WebhookSettings } from "../WebhookSettings";
beforeEach(() => {
toastError.mockReset();
update.mockReset();
});
describe("WebhookSettings", () => {
it("blocks 'only selected rules' with none ticked", async () => {
render(<WebhookSettings />);
await userEvent.click(await screen.findByRole("button", { name: /add/i }));
const inputs = screen.getAllByRole("textbox");
await userEvent.type(inputs[0]!, "Hook");
await userEvent.type(inputs[1]!, "https://example.com/h");
const create = screen.getByRole("button", { name: /^create/i });
expect(create).toBeEnabled();
await userEvent.click(screen.getByRole("checkbox", { name: /all/i }));
expect(create).toBeDisabled();
await userEvent.click(screen.getByRole("checkbox", { name: "Rule one" }));
expect(create).toBeEnabled();
});
it("toasts a failed toggle", async () => {
update.mockRejectedValue(new Error("boom"));
render(<WebhookSettings />);
await userEvent.click(await screen.findByRole("switch"));
await waitFor(() => expect(toastError).toHaveBeenCalled());
});
});