6715fbd971
- 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.
80 lines
2.5 KiB
TypeScript
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
|
|
});
|