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" : ""}`}
>
|
-
+
|
|