diff --git a/__tests__/components/session-hooks-panel.test.tsx b/__tests__/components/session-hooks-panel.test.tsx new file mode 100644 index 00000000..5750e412 --- /dev/null +++ b/__tests__/components/session-hooks-panel.test.tsx @@ -0,0 +1,102 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import SessionHooksPanel from "@/app/components/session-hooks-panel"; + +const mocks = vi.hoisted(() => ({ + searchHookActivityAction: vi.fn(), + ChevronDown: (props: any) => , + ShieldCheck: () => , + ShieldX: () => , + ShieldAlert: () => , + Shield: () => , + Copy: (props: any) => , + Check: (props: any) => , + ChevronLeft: () => , + ChevronRight: () => , +})); + +vi.mock("@/app/actions/get-hook-activity", () => ({ + searchHookActivityAction: mocks.searchHookActivityAction, +})); + +vi.mock("lucide-react", () => ({ + ChevronDown: mocks.ChevronDown, + ShieldCheck: mocks.ShieldCheck, + ShieldX: mocks.ShieldX, + ShieldAlert: mocks.ShieldAlert, + Shield: mocks.Shield, + Copy: mocks.Copy, + Check: mocks.Check, +})); + +vi.mock("@/contexts/AutoRefreshContext", () => ({ + useAutoRefresh: () => ({ intervalSec: 0 }), +})); + +vi.mock("@/lib/format-duration", () => ({ + formatRelativeTime: () => "just now", +})); + +const mockEntry = { + timestamp: Date.now(), + eventType: "PreToolUse", + integration: "claude", + toolName: "Read", + policyName: "test-policy", + policyNames: ["test-policy"], + decision: "allow" as const, + reason: "ok", + durationMs: 42, + sessionId: "sess_123", + permissionMode: "default", +}; + +const defaultPayload = { + entries: [mockEntry], + totalPages: 1, + page: 1, + stats: { totalEvents: 1, denyCount: 0, topPolicy: null, topPolicyCount: 0 }, +}; + +describe("SessionHooksPanel", () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.searchHookActivityAction.mockResolvedValue(defaultPayload); + }); + + it("renders expand row button with correct aria attributes", () => { + render(); + const btn = screen.getByRole("button", { name: /expand hook event details/i }); + expect(btn).toBeInTheDocument(); + expect(btn).toHaveAttribute("aria-expanded", "false"); + }); + + it("toggles aria-expanded on Space key press", async () => { + const user = userEvent.setup(); + render(); + const btn = screen.getByRole("button", { name: /expand hook event details/i }); + btn.focus(); + await user.keyboard(" "); + expect(btn).toHaveAttribute("aria-expanded", "true"); + await user.keyboard(" "); + expect(btn).toHaveAttribute("aria-expanded", "false"); + }); + + it("toggles aria-expanded on Enter key press", async () => { + const user = userEvent.setup(); + render(); + const btn = screen.getByRole("button", { name: /expand hook event details/i }); + btn.focus(); + await user.keyboard("{Enter}"); + expect(btn).toHaveAttribute("aria-expanded", "true"); + }); + + it("toggles aria-expanded on button click", async () => { + const user = userEvent.setup(); + render(); + const btn = screen.getByRole("button", { name: /expand hook event details/i }); + await user.click(btn); + expect(btn).toHaveAttribute("aria-expanded", "true"); + }); +}); diff --git a/app/components/session-hooks-panel.tsx b/app/components/session-hooks-panel.tsx index 1481c34a..890a277a 100644 --- a/app/components/session-hooks-panel.tsx +++ b/app/components/session-hooks-panel.tsx @@ -339,11 +339,20 @@ export default function SessionHooksPanel({ sessionId, initialData }: SessionHoo } ${isExpanded ? "bg-muted/20" : ""}`} > - + diff --git a/app/policies/hooks-client.tsx b/app/policies/hooks-client.tsx index 6cef0b47..eafb642d 100644 --- a/app/policies/hooks-client.tsx +++ b/app/policies/hooks-client.tsx @@ -687,11 +687,20 @@ function ActivityTab({ } ${isExpanded ? "bg-muted/20" : ""}`} > - +