diff --git a/canvas/src/hooks/__tests__/useTemplateDeploy.test.tsx b/canvas/src/hooks/__tests__/useTemplateDeploy.test.tsx new file mode 100644 index 00000000..6dac5bbb --- /dev/null +++ b/canvas/src/hooks/__tests__/useTemplateDeploy.test.tsx @@ -0,0 +1,316 @@ +// @vitest-environment jsdom +/** + * Tests for useTemplateDeploy — the shared preflight + POST + modal + * hook used by TemplatePalette (sidebar) and EmptyState (welcome grid). + * + * Behavioural coverage for the three flows the hook owns: + * 1. Happy path: preflight ok → POST /workspaces → onDeployed fires + * 2. Preflight errors: network throw vs not-ok-with-missing-keys + * (different code paths — the throw must NOT strand `deploying`, + * see the inline comment in the SUT for the prior bug) + * 3. Modal lifecycle: keys-added retries POST without re-running + * preflight; cancel closes without POST + * + * Issue: #2071 (Canvas test gaps follow-up). + */ +import { + describe, + it, + expect, + vi, + beforeEach, + afterEach, + type Mock, +} from "vitest"; +import { act, render, cleanup, screen, fireEvent } from "@testing-library/react"; +import { renderHook } from "@testing-library/react"; +import type { Template } from "@/lib/deploy-preflight"; + +// ── Hoisted mocks ──────────────────────────────────────────────────────────── +const { mockApiPost, mockCheckDeploySecrets, mockResolveRuntime } = vi.hoisted( + () => ({ + mockApiPost: vi.fn(), + mockCheckDeploySecrets: vi.fn(), + mockResolveRuntime: vi.fn(), + }), +); + +vi.mock("@/lib/api", () => ({ + api: { post: mockApiPost }, +})); + +vi.mock("@/lib/deploy-preflight", async () => { + // Re-export the real types; only swap the runtime functions. + const actual = await vi.importActual< + typeof import("@/lib/deploy-preflight") + >("@/lib/deploy-preflight"); + return { + ...actual, + checkDeploySecrets: mockCheckDeploySecrets, + resolveRuntime: mockResolveRuntime, + }; +}); + +// MissingKeysModal: render a minimal stand-in that exposes the two +// callbacks the hook wires up. The real modal pulls in radix + the +// secrets store, neither of which is relevant to this hook's behavior. +vi.mock("@/components/MissingKeysModal", () => ({ + MissingKeysModal: (props: { + open: boolean; + onKeysAdded: () => void; + onCancel: () => void; + }) => + props.open ? ( +
+ + +
+ ) : null, +})); + +// Import the hook AFTER the mocks are declared. +import { useTemplateDeploy } from "../useTemplateDeploy"; + +// ── Helpers ────────────────────────────────────────────────────────────────── + +function makeTemplate(over: Partial