import { screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; import AddCompanyWizardPage from "@/app/(app)/companies/new/page"; import { renderWithQueryClient } from "./test-utils"; vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn(), replace: vi.fn() }), useSearchParams: () => new URLSearchParams(), })); const DISCOVERED_PROFILE = { name: "Acme Widgets", official_website: "https://acmewidgets.com", description: "Acme Widgets designs and sells handcrafted mechanical widgets.", monitoring_focus: null, industry: null, country: null, region: null, headquarters: "Austin, Texas", aliases: ["Acme Industries"], competitors: [], public_identifiers: {}, potential_sources: [ { source_type: "website", name: "Acme Widgets - Website", base_url: "https://acmewidgets.com" }, ], sources_consulted: ["https://acmewidgets.com"], }; const EXISTING_COMPANIES = [ { id: "existing-1", name: "Acme Widgets", slug: "acme-widgets", official_website: null, description: null, monitoring_focus: null, industry: null, country: null, region: null, headquarters: null, public_identifiers: {}, status: "active", created_at: "2026-01-01T00:00:00Z", updated_at: "2026-01-01T00:00:00Z", aliases: [], competitors: [], monitor_configuration: null, report_count: 0, unresolved_alert_count: 0, }, ]; const CURRENT_USER = { id: "user-1", email: "team@example.com", display_name: "Team Person", timezone: "America/New_York", is_active: true, is_admin: false, auth_mode: "local", }; function mockFetchImplementation(existingCompanies: unknown[] = [], isLocalhost = false) { return vi.fn().mockImplementation((url: string, init?: RequestInit) => { const path = url.replace("http://localhost:8000", ""); if (path === "/api/v1/companies/discover" && init?.method === "POST") { return Promise.resolve({ ok: true, status: 200, json: async () => DISCOVERED_PROFILE }); } if (path === "/api/v1/companies" && (!init?.method || init.method === "GET")) { return Promise.resolve({ ok: true, status: 200, json: async () => existingCompanies }); } if (path === "/api/v1/auth/me") { return Promise.resolve({ ok: true, status: 200, json: async () => CURRENT_USER }); } if (path === "/api/v1/system/status") { return Promise.resolve({ ok: true, status: 200, json: async () => ({ app_env: "development", auth_mode: "local", llm_provider: "mock", search_provider: "mock", sms_enabled: false, sms_provider: "twilio", ninjapear_configured: false, ninjapear_credit_balance: null, ninjapear_estimated_credits_per_company: null, is_localhost: isLocalhost, components: [], }), }); } return Promise.resolve({ ok: false, status: 404, json: async () => ({ detail: "not found" }) }); }); } beforeEach(() => { vi.stubGlobal("fetch", mockFetchImplementation()); }); describe("AddCompanyWizardPage", () => { it("only requires a company name on the Discover step", async () => { const user = userEvent.setup(); renderWithQueryClient(); await user.click(screen.getByRole("button", { name: /discover company/i })); expect(await screen.findByText(/company name is required/i)).toBeInTheDocument(); }); it("discovers a profile and pre-fills the Review step, editable", async () => { const user = userEvent.setup(); renderWithQueryClient(); await user.type(screen.getByLabelText("Company name"), "Acme Widgets"); await user.click(screen.getByRole("button", { name: /discover company/i })); await waitFor(() => { expect(screen.getByText(/here's what we found for/i)).toBeInTheDocument(); }); const website = screen.getByLabelText("Official website") as HTMLInputElement; expect(website.value).toBe("https://acmewidgets.com"); const headquarters = screen.getByLabelText("Headquarters") as HTMLInputElement; expect(headquarters.value).toBe("Austin, Texas"); const description = screen.getByLabelText("Description") as HTMLTextAreaElement; expect(description.value).toBe( "Acme Widgets designs and sells handcrafted mechanical widgets.", ); const aliases = screen.getByLabelText(/known aliases/i) as HTMLInputElement; expect(aliases.value).toBe("Acme Industries"); expect(screen.getByText(/we'll start monitoring 1 source/i)).toBeInTheDocument(); // Still editable - the whole point of the review step. await user.clear(headquarters); await user.type(headquarters, "Denver, Colorado"); expect(headquarters.value).toBe("Denver, Colorado"); }); it("warns about a likely duplicate before discovering, and proceeds after Continue anyway", async () => { vi.stubGlobal("fetch", mockFetchImplementation(EXISTING_COMPANIES)); const user = userEvent.setup(); renderWithQueryClient(); await user.type(screen.getByLabelText("Company name"), "Acme Widgets"); await user.click(screen.getByRole("button", { name: /discover company/i })); expect(await screen.findByText(/you might already be monitoring/i)).toBeInTheDocument(); // Discovery must not have fired yet - still on the Discover step. expect(screen.queryByText(/here's what we found for/i)).not.toBeInTheDocument(); await user.click(screen.getByRole("button", { name: /continue anyway/i })); await user.click(screen.getByRole("button", { name: /discover company/i })); await waitFor(() => { expect(screen.getByText(/here's what we found for/i)).toBeInTheDocument(); }); expect(screen.queryByText(/you might already be monitoring/i)).not.toBeInTheDocument(); }); it("pre-fills the notification email with the account's own email, still editable", async () => { const user = userEvent.setup(); renderWithQueryClient(); await user.type(screen.getByLabelText("Company name"), "Acme Widgets"); await user.click(screen.getByRole("button", { name: /discover company/i })); await waitFor(() => { expect(screen.getByText(/here's what we found for/i)).toBeInTheDocument(); }); await user.click(screen.getByRole("button", { name: /^continue$/i })); // Review -> Schedule await user.click(screen.getByRole("button", { name: /^continue$/i })); // Schedule -> Notifications const email = (await screen.findByLabelText("Notification email")) as HTMLInputElement; await waitFor(() => expect(email.value).toBe("team@example.com")); // Still editable - prefilling must not lock the field. await user.clear(email); await user.type(email, "someone-else@example.com"); expect(email.value).toBe("someone-else@example.com"); }); it("leaves the notification email blank for the local-dev bypass account", async () => { // local@ci-agent.local isn't a real, deliverable address (no mail relay // serves that domain) - prefilling it would just be noise to clear. vi.stubGlobal("fetch", mockFetchImplementation([], true)); const user = userEvent.setup(); renderWithQueryClient(); await user.type(screen.getByLabelText("Company name"), "Acme Widgets"); await user.click(screen.getByRole("button", { name: /discover company/i })); await waitFor(() => { expect(screen.getByText(/here's what we found for/i)).toBeInTheDocument(); }); await user.click(screen.getByRole("button", { name: /^continue$/i })); // Review -> Schedule await user.click(screen.getByRole("button", { name: /^continue$/i })); // Schedule -> Notifications const email = (await screen.findByLabelText("Notification email")) as HTMLInputElement; // Give any async prefill effect a chance to run before asserting it didn't. await new Promise((resolve) => setTimeout(resolve, 50)); expect(email.value).toBe(""); }); });