url tests
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
import { fireEvent, render, screen } from "@testing-library/react";
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||||
import { MemoryRouter } from "react-router-dom";
|
import { MemoryRouter, Route, Routes, useLocation } from "react-router-dom";
|
||||||
|
|
||||||
import { OrchestratorPage } from "./OrchestratorPage";
|
import { OrchestratorPage } from "./OrchestratorPage";
|
||||||
import type { Job } from "../../shared/types";
|
import type { Job } from "../../shared/types";
|
||||||
|
import type { FilterTab } from "./orchestrator/constants";
|
||||||
|
|
||||||
const jobFixture: Job = {
|
const jobFixture: Job = {
|
||||||
id: "job-1",
|
id: "job-1",
|
||||||
@@ -63,6 +64,8 @@ const jobFixture: Job = {
|
|||||||
updatedAt: "2025-01-02T00:00:00Z",
|
updatedAt: "2025-01-02T00:00:00Z",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const job2: Job = { ...jobFixture, id: "job-2", status: "discovered" };
|
||||||
|
|
||||||
const createMatchMedia = (matches: boolean) =>
|
const createMatchMedia = (matches: boolean) =>
|
||||||
vi.fn().mockImplementation((query: string) => ({
|
vi.fn().mockImplementation((query: string) => ({
|
||||||
matches,
|
matches,
|
||||||
@@ -76,9 +79,9 @@ const createMatchMedia = (matches: boolean) =>
|
|||||||
|
|
||||||
vi.mock("./orchestrator/useOrchestratorData", () => ({
|
vi.mock("./orchestrator/useOrchestratorData", () => ({
|
||||||
useOrchestratorData: () => ({
|
useOrchestratorData: () => ({
|
||||||
jobs: [jobFixture],
|
jobs: [jobFixture, job2],
|
||||||
stats: {
|
stats: {
|
||||||
discovered: 0,
|
discovered: 1,
|
||||||
processing: 0,
|
processing: 0,
|
||||||
ready: 1,
|
ready: 1,
|
||||||
applied: 0,
|
applied: 0,
|
||||||
@@ -109,7 +112,21 @@ vi.mock("./orchestrator/OrchestratorSummary", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("./orchestrator/OrchestratorFilters", () => ({
|
vi.mock("./orchestrator/OrchestratorFilters", () => ({
|
||||||
OrchestratorFilters: () => <div data-testid="filters" />,
|
OrchestratorFilters: ({
|
||||||
|
onTabChange,
|
||||||
|
onSearchQueryChange,
|
||||||
|
onSortChange,
|
||||||
|
}: {
|
||||||
|
onTabChange: (t: FilterTab) => void;
|
||||||
|
onSearchQueryChange: (q: string) => void;
|
||||||
|
onSortChange: (s: any) => void;
|
||||||
|
}) => (
|
||||||
|
<div data-testid="filters">
|
||||||
|
<button onClick={() => onTabChange("discovered")}>To Discovered</button>
|
||||||
|
<button onClick={() => onSearchQueryChange("test search")}>Set Search</button>
|
||||||
|
<button onClick={() => onSortChange({ key: "title", direction: "asc" })}>Set Sort</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("./orchestrator/JobDetailPanel", () => ({
|
vi.mock("./orchestrator/JobDetailPanel", () => ({
|
||||||
@@ -120,7 +137,12 @@ vi.mock("./orchestrator/JobListPanel", () => ({
|
|||||||
JobListPanel: ({ onSelectJob, selectedJobId }: { onSelectJob: (id: string) => void; selectedJobId: string | null }) => (
|
JobListPanel: ({ onSelectJob, selectedJobId }: { onSelectJob: (id: string) => void; selectedJobId: string | null }) => (
|
||||||
<div>
|
<div>
|
||||||
<div data-testid="selected-job">{selectedJobId ?? "none"}</div>
|
<div data-testid="selected-job">{selectedJobId ?? "none"}</div>
|
||||||
<button type="button" onClick={() => onSelectJob("job-1")}>Select job</button>
|
<button data-testid="select-job-1" type="button" onClick={() => onSelectJob("job-1")}>
|
||||||
|
Select job 1
|
||||||
|
</button>
|
||||||
|
<button data-testid="select-job-2" type="button" onClick={() => onSelectJob("job-2")}>
|
||||||
|
Select job 2
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
@@ -129,23 +151,109 @@ vi.mock("../components", () => ({
|
|||||||
ManualImportSheet: () => <div data-testid="manual-import" />,
|
ManualImportSheet: () => <div data-testid="manual-import" />,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const LocationWatcher = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
return <div data-testid="location">{location.pathname + location.search}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
describe("OrchestratorPage", () => {
|
describe("OrchestratorPage", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("syncs tab selection to the URL", () => {
|
||||||
|
window.matchMedia = createMatchMedia(true) as unknown as typeof window.matchMedia;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MemoryRouter initialEntries={["/ready"]}>
|
||||||
|
<LocationWatcher />
|
||||||
|
<Routes>
|
||||||
|
<Route path="/:tab" element={<OrchestratorPage />} />
|
||||||
|
<Route path="/:tab/:jobId" element={<OrchestratorPage />} />
|
||||||
|
</Routes>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("To Discovered"));
|
||||||
|
expect(screen.getByTestId("location").textContent).toContain("/discovered");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("syncs job selection to the URL", async () => {
|
||||||
|
window.matchMedia = createMatchMedia(true) as unknown as typeof window.matchMedia;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MemoryRouter initialEntries={["/all"]}>
|
||||||
|
<LocationWatcher />
|
||||||
|
<Routes>
|
||||||
|
<Route path="/:tab" element={<OrchestratorPage />} />
|
||||||
|
<Route path="/:tab/:jobId" element={<OrchestratorPage />} />
|
||||||
|
</Routes>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initial load will auto-select the first matching job (job-1 for all tab)
|
||||||
|
const locationText = () => screen.getByTestId("location").textContent;
|
||||||
|
expect(locationText()).toContain("/all/job-1");
|
||||||
|
|
||||||
|
// Clicking job-2 should update URL
|
||||||
|
const job2Button = screen.getByTestId("select-job-2");
|
||||||
|
fireEvent.click(job2Button);
|
||||||
|
|
||||||
|
// Wait for URL to update
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(locationText()).toContain("/all/job-2");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("syncs search query to URL as a parameter", () => {
|
||||||
|
window.matchMedia = createMatchMedia(true) as unknown as typeof window.matchMedia;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MemoryRouter initialEntries={["/ready"]}>
|
||||||
|
<LocationWatcher />
|
||||||
|
<Routes>
|
||||||
|
<Route path="/:tab" element={<OrchestratorPage />} />
|
||||||
|
<Route path="/:tab/:jobId" element={<OrchestratorPage />} />
|
||||||
|
</Routes>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("Set Search"));
|
||||||
|
expect(screen.getByTestId("location").textContent).toContain("q=test+search");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("syncs sorting to URL and removes it when default", () => {
|
||||||
|
window.matchMedia = createMatchMedia(true) as unknown as typeof window.matchMedia;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MemoryRouter initialEntries={["/ready"]}>
|
||||||
|
<LocationWatcher />
|
||||||
|
<Routes>
|
||||||
|
<Route path="/:tab" element={<OrchestratorPage />} />
|
||||||
|
<Route path="/:tab/:jobId" element={<OrchestratorPage />} />
|
||||||
|
</Routes>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("Set Sort"));
|
||||||
|
expect(screen.getByTestId("location").textContent).toContain("sort=title-asc");
|
||||||
|
});
|
||||||
|
|
||||||
it("opens the detail drawer on mobile when a job is selected", () => {
|
it("opens the detail drawer on mobile when a job is selected", () => {
|
||||||
window.matchMedia = createMatchMedia(false) as unknown as typeof window.matchMedia;
|
window.matchMedia = createMatchMedia(false) as unknown as typeof window.matchMedia;
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter initialEntries={["/ready"]}>
|
||||||
<OrchestratorPage />
|
<Routes>
|
||||||
|
<Route path="/:tab" element={<OrchestratorPage />} />
|
||||||
|
<Route path="/:tab/:jobId" element={<OrchestratorPage />} />
|
||||||
|
</Routes>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.queryByTestId("detail-panel")).not.toBeInTheDocument();
|
expect(screen.queryByTestId("detail-panel")).not.toBeInTheDocument();
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: /select job/i }));
|
fireEvent.click(screen.getByTestId("select-job-1"));
|
||||||
|
|
||||||
expect(screen.getByTestId("detail-panel")).toBeInTheDocument();
|
expect(screen.getByTestId("detail-panel")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -154,8 +262,11 @@ describe("OrchestratorPage", () => {
|
|||||||
window.matchMedia = createMatchMedia(true) as unknown as typeof window.matchMedia;
|
window.matchMedia = createMatchMedia(true) as unknown as typeof window.matchMedia;
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter initialEntries={["/ready"]}>
|
||||||
<OrchestratorPage />
|
<Routes>
|
||||||
|
<Route path="/:tab" element={<OrchestratorPage />} />
|
||||||
|
<Route path="/:tab/:jobId" element={<OrchestratorPage />} />
|
||||||
|
</Routes>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export const JobListPanel: React.FC<JobListPanelProps> = ({
|
|||||||
key={job.id}
|
key={job.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onSelectJob(job.id)}
|
onClick={() => onSelectJob(job.id)}
|
||||||
|
data-testid={`select-${job.id}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full items-center gap-3 px-4 py-3 text-left transition-colors",
|
"flex w-full items-center gap-3 px-4 py-3 text-left transition-colors",
|
||||||
isSelected
|
isSelected
|
||||||
|
|||||||
Reference in New Issue
Block a user