feat(web): /search two-pane screen (debounced query, visibility filter, load more) + nav
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
|
||||
import { server } from "../test/server";
|
||||
import { renderApp } from "../test/render";
|
||||
import { amphora } from "../test/fixtures";
|
||||
import { SearchPage } from "./search-page";
|
||||
import { SelectSearchPrompt } from "./select-search-prompt";
|
||||
import { ObjectDetail } from "../objects/object-detail";
|
||||
|
||||
function tree() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/search" element={<SearchPage />}>
|
||||
<Route index element={<SelectSearchPrompt />} />
|
||||
<Route path=":id" element={<ObjectDetail />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
test("typing searches and renders highlighted rich rows", async () => {
|
||||
renderApp(tree(), { route: "/search" });
|
||||
await userEvent.type(screen.getByLabelText(/search the collection/i), "bronze");
|
||||
|
||||
expect(await screen.findByText("Bronze figurine")).toBeInTheDocument();
|
||||
const mark = await screen.findByText("bronze");
|
||||
expect(mark.tagName).toBe("MARK");
|
||||
expect(screen.getByText(/25 results/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Load more appends the next page", async () => {
|
||||
renderApp(tree(), { route: "/search" });
|
||||
await userEvent.type(screen.getByLabelText(/search the collection/i), "bronze");
|
||||
await screen.findByText("Bronze figurine");
|
||||
|
||||
expect(screen.queryByText("Object 21")).toBeNull();
|
||||
await userEvent.click(screen.getByRole("button", { name: /load more/i }));
|
||||
expect(await screen.findByText("Object 21")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("the visibility filter adds the param to the request", async () => {
|
||||
let lastVisibility: string | null = "unset";
|
||||
server.use(
|
||||
http.get("/api/admin/search", ({ request }) => {
|
||||
lastVisibility = new URL(request.url).searchParams.get("visibility");
|
||||
return HttpResponse.json({ hits: [], estimated_total: 0 });
|
||||
}),
|
||||
);
|
||||
renderApp(tree(), { route: "/search" });
|
||||
await userEvent.type(screen.getByLabelText(/search the collection/i), "bronze");
|
||||
await userEvent.click(screen.getByRole("button", { name: /^draft$/i }));
|
||||
|
||||
await waitFor(() => expect(lastVisibility).toBe("draft"));
|
||||
});
|
||||
|
||||
test("empty query shows the prompt; zero results shows empty", async () => {
|
||||
renderApp(tree(), { route: "/search" });
|
||||
expect(screen.getByText(/type to search/i)).toBeInTheDocument();
|
||||
|
||||
server.use(
|
||||
http.get("/api/admin/search", () => HttpResponse.json({ hits: [], estimated_total: 0 })),
|
||||
);
|
||||
await userEvent.type(screen.getByLabelText(/search the collection/i), "zzz");
|
||||
expect(await screen.findByText(/no results/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("clicking a result shows the object in the detail pane", async () => {
|
||||
renderApp(tree(), { route: "/search" });
|
||||
await userEvent.type(screen.getByLabelText(/search the collection/i), "bronze");
|
||||
await userEvent.click(await screen.findByText("Bronze figurine"));
|
||||
|
||||
expect(await screen.findByText(amphora.object_name)).toBeInTheDocument();
|
||||
});
|
||||
Reference in New Issue
Block a user