import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { NewConnectionScreen } from "./NewConnectionScreen"; import { useSettingsStore } from "../../stores/settingsStore"; const { createConnection, notify, testConnection } = vi.hoisted(() => ({ createConnection: vi.fn().mockResolvedValue({}), notify: vi.fn(), testConnection: vi.fn().mockResolvedValue({ ok: true }), })); vi.mock("../../stores/connectionStore", () => ({ createConnection, useConnectionStore: (selector: (s: { createConnection: typeof createConnection }) => unknown) => selector({ createConnection }), })); vi.mock("../../stores/notificationStore", () => ({ notify, useNotificationStore: (selector: (s: { notify: typeof notify }) => unknown) => selector({ notify }), })); vi.mock("../../lib/commands", () => ({ testConnection, })); describe("NewConnectionScreen", () => { beforeEach(() => { vi.clearAllMocks(); useSettingsStore.setState({ settings: null, loading: false, error: null }); }); it("entry stage: renders Connection URI input and provider grid, not the full form", () => { render(); expect(screen.getByLabelText(/connection uri/i)).toBeInTheDocument(); expect(screen.getByRole("button", { name: /PostgreSQL/i })).toBeInTheDocument(); expect(screen.getByRole("button", { name: /MySQL/i })).toBeInTheDocument(); expect(screen.getByRole("button", { name: /Supabase/i })).toBeInTheDocument(); expect(screen.queryByLabelText(/connection label/i)).not.toBeInTheDocument(); }); it("paste a recognized URL reveals the form and fills fields", async () => { const user = userEvent.setup(); render(); await user.type(screen.getByLabelText(/connection uri/i), "postgresql://u:p@localhost:5432/db"); expect(screen.getByLabelText(/connection label/i)).toBeInTheDocument(); expect(screen.getByLabelText("Host")).toHaveValue("localhost"); expect(screen.getByLabelText("Port")).toHaveValue(5432); expect(screen.getByLabelText("User")).toHaveValue("u"); }); it("paste a Neon URL pre-fills every required setting, including SSL mode", async () => { const user = userEvent.setup(); render(); await user.type( screen.getByLabelText(/connection uri/i), "postgresql://neondb_owner:npg_secret@ep-hidden-star-b3hqtp4x-pooler.c-4.ap-southeast-1.aws.neon.tech/neondb?sslmode=require&channel_binding=require", ); // Every field the URL can supply is filled, so no manual cleanup is needed expect(screen.getByLabelText("Host")).toHaveValue( "ep-hidden-star-b3hqtp4x-pooler.c-4.ap-southeast-1.aws.neon.tech", ); expect(screen.getByLabelText("Port")).toHaveValue(5432); expect(screen.getByLabelText("User")).toHaveValue("neondb_owner"); expect(screen.getByLabelText("Password")).toHaveValue("npg_secret"); expect(screen.getByLabelText("Database")).toHaveValue("neondb"); // The label stays untouched but is suggested from the database name. expect(screen.getByLabelText(/connection label/i)).toHaveValue(""); expect(screen.getByLabelText(/connection label/i)).toHaveAttribute( "placeholder", "neondb", ); // SSL mode is visible on the SSH / SSL tab... await user.click(screen.getByRole("button", { name: /ssh \/ ssl/i })); await user.click(screen.getByRole("button", { name: "SSL" })); expect(screen.getByRole("button", { name: "SSL Mode" })).toHaveTextContent("Require"); // ...and reaches the save payload with no extra clicks. await user.click(screen.getByText("Save Connection")); await waitFor(() => expect(createConnection).toHaveBeenCalledTimes(1)); expect(createConnection).toHaveBeenCalledWith( expect.objectContaining({ name: "neondb", db_type: "postgresql", ssl_mode: "require", username: "neondb_owner", password: "npg_secret", database: "neondb", }), ); }); it("a typed label wins over the URL-derived suggestion", async () => { const user = userEvent.setup(); render(); await user.type(screen.getByLabelText(/connection uri/i), "postgresql://u:p@localhost:5432/db"); await user.type(screen.getByLabelText(/connection label/i), "My DB"); await user.click(screen.getByText("Save Connection")); await waitFor(() => expect(createConnection).toHaveBeenCalledTimes(1)); expect(createConnection).toHaveBeenCalledWith( expect.objectContaining({ name: "My DB" }), ); }); it("clicking a provider tab reveals the form with that db_type", async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole("button", { name: /MySQL/i })); expect(screen.getByLabelText(/connection label/i)).toBeInTheDocument(); expect(screen.getByLabelText("Port")).toHaveValue(3306); }); it("NeonDB card preselects Require SSL in the save payload", async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole("button", { name: /NeonDB/i })); await user.type(screen.getByLabelText(/connection label/i), "Neon prod"); await user.type(screen.getByLabelText("Host"), "ep-x-pooler.eu.aws.neon.tech"); await user.click(screen.getByText("Save Connection")); await waitFor(() => expect(createConnection).toHaveBeenCalledTimes(1)); expect(createConnection).toHaveBeenCalledWith( expect.objectContaining({ name: "Neon prod", ssl_mode: "require" }), ); }); it("SQLite tab swaps the URI input for a File Path input", async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole("button", { name: /SQLite/i })); expect(screen.getByLabelText(/file path/i)).toBeInTheDocument(); expect(screen.queryByLabelText(/connection uri/i)).not.toBeInTheDocument(); }); it("shows validation error when saving an empty configured form", async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole("button", { name: /PostgreSQL/i })); await user.click(screen.getByText("Save Connection")); expect(notify).toHaveBeenCalledWith("name is required", "error"); expect(createConnection).not.toHaveBeenCalled(); }); it("saves a connection with label + parsed URL", async () => { const user = userEvent.setup(); const onSaved = vi.fn(); render(); await user.type(screen.getByLabelText(/connection uri/i), "postgresql://u:p@localhost:5432/db"); await user.type(screen.getByLabelText(/connection label/i), "Local DB"); await user.click(screen.getByText("Save Connection")); await waitFor(() => expect(createConnection).toHaveBeenCalledTimes(1)); expect(createConnection).toHaveBeenCalledWith(expect.objectContaining({ name: "Local DB", db_type: "postgresql", host: "localhost", port: 5432, username: "u", password: "p", database: "db", })); expect(notify).toHaveBeenCalledWith("Connection saved", "success"); expect(onSaved).toHaveBeenCalled(); }); it("calls testConnection when Test Connection is clicked", async () => { const user = userEvent.setup(); render(); await user.type(screen.getByLabelText(/connection uri/i), "postgresql://u:p@localhost:5432/db"); await user.type(screen.getByLabelText(/connection label/i), "Local DB"); await user.click(screen.getByText("Test Connection")); await waitFor(() => expect(testConnection).toHaveBeenCalledTimes(1)); expect(notify).toHaveBeenCalledWith("Connection successful", "success"); }); it("tests a pasted URL with its SSL mode", async () => { const user = userEvent.setup(); render(); await user.type( screen.getByLabelText(/connection uri/i), "postgresql://u:p@ep-x-pooler.eu.aws.neon.tech/db?sslmode=verify-full", ); await user.click(screen.getByText("Test Connection")); await waitFor(() => expect(testConnection).toHaveBeenCalledTimes(1)); expect(testConnection).toHaveBeenCalledWith( expect.objectContaining({ ssl_mode: "verify-full" }), ); }); it("invokes onCancel when Back is clicked", async () => { const user = userEvent.setup(); const onCancel = vi.fn(); render(); await user.click(screen.getByRole("button", { name: "Back" })); expect(onCancel).toHaveBeenCalled(); }); it("prefills from prefilledConnectionString and reveals the form", async () => { render(); expect(screen.getByLabelText(/connection uri/i)).toHaveValue("postgresql://u:p@localhost:5432/db"); expect(screen.getByLabelText(/connection label/i)).toBeInTheDocument(); expect(screen.getByLabelText("Host")).toHaveValue("localhost"); }); });