import React from "react"; import "@testing-library/jest-dom"; import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; import { api } from "./api"; import { I18nProvider } from "./i18n/I18nProvider"; import QuickCommandDialog from "./components/QuickCommandDialog"; const mockedApi = api as jest.Mocked; beforeEach(() => { jest.clearAllMocks(); localStorage.removeItem("uiLanguage"); mockedApi.get.mockImplementation((url: string) => Promise.resolve({ data: url === "/companies" ? [{ id: 7, name: "Nordlys AS" }] : { items: [{ id: 42, jobTitle: "Platform engineer", company: { name: "Nordlys AS" } }] } } as never)); }); test.each([ ["Nordlys AS - Platform engineer", "/jobs/42"], ["Nordlys AS", "/companies?edit=7"], ])("[PRES-E07] command search opens %s at its existing destination", async (label, destination) => { const onNavigate = jest.fn(); const onClose = jest.fn(); render(); expect(screen.getByRole("dialog", { name: "Quick Search" })).toBeVisible(); fireEvent.change(screen.getByRole("textbox", { name: "Quick Search" }), { target: { value: "Nordlys" } }); fireEvent.click(await screen.findByText(label, { exact: true })); expect(onNavigate).toHaveBeenCalledWith(destination); expect(onClose).toHaveBeenCalledTimes(1); expect(mockedApi.post).not.toHaveBeenCalled(); }); test("[PRES-E07 PRES-J04] the capture command opens review without creating a job, and closing clears search", async () => { const onOpenAddJob = jest.fn(); const props = { onClose: jest.fn(), onNavigate: jest.fn(), onOpenAddJob }; const view = (open: boolean) => ; const { rerender } = render(view(true)); fireEvent.click(screen.getByRole("button", { name: /add new job/i })); expect(onOpenAddJob).toHaveBeenCalledTimes(1); expect(mockedApi.post).not.toHaveBeenCalled(); fireEvent.change(screen.getByRole("textbox"), { target: { value: "Nordlys" } }); await screen.findByText("Nordlys AS - Platform engineer"); rerender(view(false)); rerender(view(true)); await waitFor(() => expect(screen.getByRole("textbox")).toHaveValue("")); expect(screen.queryByText("Nordlys AS - Platform engineer")).not.toBeInTheDocument(); }); test("[P5-00 PRES-E07] command and empty results use valid list semantics", async () => { render(); expect(screen.getAllByRole("listitem")).toHaveLength(6); expect(screen.getAllByRole("list").every(list => Array.from(list.children).every(item => item.tagName === "LI"))).toBe(true); fireEvent.change(screen.getByRole("textbox"), { target: { value: "no-such-result" } }); mockedApi.get.mockResolvedValue({ data: [] } as never); await screen.findByText("No matching commands or records."); expect(screen.queryAllByRole("list").every(list => Array.from(list.children).every(item => item.tagName === "LI"))).toBe(true); expect(mockedApi.post).not.toHaveBeenCalled(); }); test("[P5-02 PRES-E07] search failure is distinct from no matches and Retry recovers", async () => { mockedApi.get.mockRejectedValueOnce(new Error("offline")); render(); fireEvent.change(screen.getByRole("textbox", { name: "Quick Search" }), { target: { value: "Nordlys" } }); expect(await screen.findByRole("alert")).toHaveTextContent("Search could not be loaded"); expect(screen.queryByText("No matching commands or records.")).not.toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: "Retry" })); expect(await screen.findByText("Nordlys AS - Platform engineer")).toBeInTheDocument(); }); test("[P5-02 PRES-E09] closed and superseded searches cannot display stale account results", async () => { let resolveOld!: (value: unknown) => void; mockedApi.get.mockImplementation((url: string) => url === "/jobapplications" ? new Promise(resolve => { resolveOld = resolve; }) as never : Promise.resolve({ data: [] }) as never); const props = { onClose: jest.fn(), onNavigate: jest.fn(), onOpenAddJob: jest.fn() }; const view = (open: boolean) => ; const { rerender } = render(view(true)); fireEvent.change(screen.getByRole("textbox"), { target: { value: "old" } }); await waitFor(() => expect(resolveOld).toBeDefined()); rerender(view(false)); rerender(view(true)); await act(async () => resolveOld({ data: { items: [{ id: 42, jobTitle: "Account A job", company: { name: "Private" } }] } })); expect(screen.queryByText(/Account A job/)).not.toBeInTheDocument(); expect(screen.getByRole("textbox")).toHaveValue(""); }); test("[P5-02 PRES-E07] a slower old query cannot replace the newer matching results", async () => { let resolveOld!: (value: unknown) => void; mockedApi.get.mockImplementation((url: string, config?: any) => url === "/companies" ? Promise.resolve({ data: [] }) as never : config?.params?.q === "old" ? new Promise(resolve => { resolveOld = resolve; }) as never : Promise.resolve({ data: { items: [{ id: 9, jobTitle: "New result", company: { name: "Current" } }] } }) as never); render(); const search = screen.getByRole("textbox", { name: "Quick Search" }); fireEvent.change(search, { target: { value: "old" } }); await waitFor(() => expect(resolveOld).toBeDefined()); fireEvent.change(search, { target: { value: "new" } }); expect(await screen.findByText("Current - New result")).toBeInTheDocument(); await act(async () => resolveOld({ data: { items: [{ id: 8, jobTitle: "Old result", company: { name: "Private" } }] } })); expect(screen.queryByText("Private - Old result")).not.toBeInTheDocument(); }); test("[P5-02 PRES-E07] arrows, Home, End and Enter activate one existing command; composition does not", () => { const onNavigate = jest.fn(); render(); const search = screen.getByRole("textbox", { name: "Quick Search" }); fireEvent.keyDown(search, { key: "End" }); fireEvent.keyDown(search, { key: "Enter", isComposing: true }); expect(onNavigate).not.toHaveBeenCalled(); fireEvent.keyDown(search, { key: "Home" }); fireEvent.keyDown(search, { key: "ArrowDown" }); fireEvent.keyDown(search, { key: "Enter" }); expect(onNavigate).toHaveBeenCalledWith("/jobs"); }); test("[P5-02 PRES-E07] arrowing from a focused result keeps focus and activation on the same item", () => { const onNavigate = jest.fn(); render(); const dashboard = screen.getByRole("button", { name: /go to dashboard/i }); act(() => dashboard.focus()); fireEvent.keyDown(dashboard, { key: "ArrowDown" }); const jobs = screen.getByRole("button", { name: /go to jobs/i }); expect(jobs).toHaveFocus(); fireEvent.click(jobs); expect(onNavigate).toHaveBeenCalledWith("/jobs"); });