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 { Routes, Route } from "react-router-dom"; import { server } from "../test/server"; import { renderApp } from "../test/render"; import { VocabulariesPage } from "./vocabularies-page"; import { VocabularyTerms } from "./vocabulary-terms"; import { SelectVocabularyPrompt } from "./select-vocabulary-prompt"; function tree() { return ( }> } /> } /> ); } test("lists vocabularies and creates one", async () => { let body: unknown; server.use( http.post("/api/admin/vocabularies", async ({ request }) => { body = await request.json(); return HttpResponse.json({ id: "v-c", key: "colour" }, { status: 201 }); }), ); renderApp(tree(), { route: "/vocabularies" }); expect(await screen.findByText("material")).toBeInTheDocument(); await userEvent.type(screen.getByLabelText(/key/i), "colour"); await userEvent.click(screen.getByRole("button", { name: /create/i })); await waitFor(() => expect((body as { key: string })?.key).toBe("colour")); }); test("selecting a vocabulary shows its terms and adds one", async () => { let termBody: unknown; server.use( http.post("/api/admin/vocabularies/:id/terms", async ({ request }) => { termBody = await request.json(); return HttpResponse.json({ id: "t-c" }, { status: 201 }); }), ); renderApp(tree(), { route: "/vocabularies/v-material" }); expect(await screen.findByText("Bronze")).toBeInTheDocument(); await userEvent.type(screen.getByLabelText(/^label$/i), "Stone"); await userEvent.click(screen.getByRole("button", { name: /add term/i })); await waitFor(() => expect((termBody as { labels: { label: string }[] })?.labels[0].label).toBe("Stone"), ); }); test("terms endpoint error shows vocab loadError", async () => { server.use( http.get("/api/admin/vocabularies/:id/terms", () => new HttpResponse(null, { status: 500 })), ); renderApp(tree(), { route: "/vocabularies/v-material" }); expect(await screen.findByText(/could not load/i)).toBeInTheDocument(); }); test("add term without EN label shows required alert and does not POST", async () => { let posted = false; server.use( http.post("/api/admin/vocabularies/:id/terms", () => { posted = true; return HttpResponse.json({ id: "t-x" }, { status: 201 }); }), ); renderApp(tree(), { route: "/vocabularies/v-material" }); expect(await screen.findByText("Bronze")).toBeInTheDocument(); await userEvent.click(screen.getByRole("button", { name: /add term/i })); expect(screen.getByRole("alert")).toBeInTheDocument(); expect(posted).toBe(false); }); test("vocabularies render sorted by key", async () => { server.use( http.get("/api/admin/vocabularies", () => HttpResponse.json([ { id: "v-zeta", key: "zeta" }, { id: "v-alpha", key: "alpha" }, ]), ), ); renderApp(tree(), { route: "/vocabularies" }); expect(await screen.findByText("alpha")).toBeInTheDocument(); const links = screen.getAllByRole("link"); const keys = links.map((link) => link.textContent); expect(keys.indexOf("alpha")).toBeLessThan(keys.indexOf("zeta")); }); test("filter narrows the vocabulary list", async () => { renderApp(tree(), { route: "/vocabularies" }); expect(await screen.findByText("material")).toBeInTheDocument(); expect(screen.getByText("technique")).toBeInTheDocument(); await userEvent.type(screen.getByRole("textbox", { name: /filter/i }), "mat"); expect(screen.getByText("material")).toBeInTheDocument(); expect(screen.queryByText("technique")).not.toBeInTheDocument(); }); test("renaming a vocabulary to an empty key does not call the rename endpoint", async () => { let renamed = false; server.use( http.patch("/api/admin/vocabularies/:id", () => { renamed = true; return new HttpResponse(null, { status: 204 }); }), ); renderApp(tree(), { route: "/vocabularies" }); expect(await screen.findByText("material")).toBeInTheDocument(); await userEvent.click(screen.getAllByRole("button", { name: /rename/i })[0]); const keyInputs = screen.getAllByRole("textbox", { name: /key/i }); const input = keyInputs[keyInputs.length - 1]; await userEvent.clear(input); await userEvent.click(screen.getByRole("button", { name: /save/i })); expect(renamed).toBe(false); }); test("term read row shows its external_uri as a link", async () => { server.use( http.get("/api/admin/vocabularies/:id/terms", () => HttpResponse.json([ { id: "t-bronze", external_uri: "https://example.org/bronze", labels: [{ lang: "en", label: "Bronze" }] }, ]), ), ); renderApp(tree(), { route: "/vocabularies/v-material" }); expect(await screen.findByText("Bronze")).toBeInTheDocument(); expect(screen.getByRole("link", { name: /example\.org/ })).toBeInTheDocument(); });