From e416496d5f5a1023f698f437fa3a72b3a01d8291 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Sat, 18 Apr 2026 02:21:31 -0700 Subject: [PATCH] test: add BatchActionBar unit tests (7 tests) Covers: render threshold, count badge, action buttons, clear selection, ConfirmDialog trigger, ARIA toolbar role. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../__tests__/BatchActionBar.test.tsx | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 canvas/src/components/__tests__/BatchActionBar.test.tsx diff --git a/canvas/src/components/__tests__/BatchActionBar.test.tsx b/canvas/src/components/__tests__/BatchActionBar.test.tsx new file mode 100644 index 00000000..55eb786d --- /dev/null +++ b/canvas/src/components/__tests__/BatchActionBar.test.tsx @@ -0,0 +1,127 @@ +// @vitest-environment jsdom +/** + * BatchActionBar tests — Phase 20.3 + * + * Covers: + * - Not rendered when fewer than 2 nodes selected + * - Renders with correct count badge when 2+ selected + * - Restart/Pause/Delete buttons exist with correct labels + * - Clear selection button exists + * - ConfirmDialog appears on destructive action click + */ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { render, screen, fireEvent, cleanup } from "@testing-library/react"; + +afterEach(() => { + cleanup(); +}); + +// ── Mocks ──────────────────────────────────────────────────────────────────── + +vi.mock("@/components/Toaster", () => ({ + showToast: vi.fn(), +})); + +const mockClearSelection = vi.fn(); +const mockBatchRestart = vi.fn(() => Promise.resolve()); +const mockBatchPause = vi.fn(() => Promise.resolve()); +const mockBatchDelete = vi.fn(() => Promise.resolve()); + +let mockSelectedNodeIds = new Set(); + +vi.mock("@/store/canvas", () => ({ + useCanvasStore: vi.fn((selector: (s: Record) => unknown) => + selector({ + selectedNodeIds: mockSelectedNodeIds, + clearSelection: mockClearSelection, + batchRestart: mockBatchRestart, + batchPause: mockBatchPause, + batchDelete: mockBatchDelete, + }) + ), +})); + +// Mock ConfirmDialog to just render buttons for testing +vi.mock("@/components/ConfirmDialog", () => ({ + ConfirmDialog: ({ + open, + title, + onConfirm, + onCancel, + }: { + open: boolean; + title: string; + confirmLabel: string; + message: string; + confirmVariant: string; + onConfirm: () => void; + onCancel: () => void; + }) => + open ? ( +
+ {title} + + +
+ ) : null, +})); + +// Import after mocks +import { BatchActionBar } from "../BatchActionBar"; + +// ── Tests ──────────────────────────────────────────────────────────────────── + +describe("BatchActionBar", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockSelectedNodeIds = new Set(); + }); + + it("does not render when fewer than 2 nodes selected", () => { + mockSelectedNodeIds = new Set(["ws-1"]); + const { container } = render(); + expect(container.innerHTML).toBe(""); + }); + + it("renders count badge when 2+ nodes selected", () => { + mockSelectedNodeIds = new Set(["ws-1", "ws-2", "ws-3"]); + render(); + expect(screen.getByText("3 selected")).toBeTruthy(); + }); + + it("renders Restart All, Pause All, Delete All buttons", () => { + mockSelectedNodeIds = new Set(["ws-1", "ws-2"]); + render(); + expect(screen.getByText("Restart All")).toBeTruthy(); + expect(screen.getByText("Pause All")).toBeTruthy(); + expect(screen.getByText("Delete All")).toBeTruthy(); + }); + + it("renders clear selection button with aria-label", () => { + mockSelectedNodeIds = new Set(["ws-1", "ws-2"]); + render(); + const clearBtn = screen.getByRole("button", { name: "Clear selection" }); + expect(clearBtn).toBeTruthy(); + }); + + it("clicking clear selection calls clearSelection", () => { + mockSelectedNodeIds = new Set(["ws-1", "ws-2"]); + render(); + fireEvent.click(screen.getByRole("button", { name: "Clear selection" })); + expect(mockClearSelection).toHaveBeenCalled(); + }); + + it("clicking Delete All opens ConfirmDialog", () => { + mockSelectedNodeIds = new Set(["ws-1", "ws-2"]); + render(); + fireEvent.click(screen.getByText("Delete All")); + expect(screen.getByTestId("confirm-dialog")).toBeTruthy(); + }); + + it("has role=toolbar with aria-label", () => { + mockSelectedNodeIds = new Set(["ws-1", "ws-2"]); + render(); + const toolbar = screen.getByRole("toolbar"); + expect(toolbar.getAttribute("aria-label")).toBe("Batch workspace actions"); + }); +});