feat(web): vocabulary/term/authority list+create hooks + MSW handlers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -200,6 +200,87 @@ export function useDeleteObject() {
|
||||
});
|
||||
}
|
||||
|
||||
type NewVocabularyRequest = components["schemas"]["NewVocabularyRequest"];
|
||||
type LabelInput = components["schemas"]["LabelInput"];
|
||||
|
||||
export function useVocabularies() {
|
||||
return useQuery({
|
||||
queryKey: ["vocabularies"],
|
||||
queryFn: async () => {
|
||||
const { data, error } = await api.GET("/api/admin/vocabularies");
|
||||
|
||||
if (error || !data) throw new Error("failed to load vocabularies");
|
||||
|
||||
return data;
|
||||
},
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateVocabulary() {
|
||||
const qc = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (body: NewVocabularyRequest) => {
|
||||
const { data, error } = await api.POST("/api/admin/vocabularies", { body });
|
||||
|
||||
if (error || !data) throw new Error("create vocabulary failed");
|
||||
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["vocabularies"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useAddTerm() {
|
||||
const qc = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
vocabularyId,
|
||||
external_uri,
|
||||
labels,
|
||||
}: {
|
||||
vocabularyId: string;
|
||||
external_uri: string | null;
|
||||
labels: LabelInput[];
|
||||
}) => {
|
||||
const { response } = await api.POST("/api/admin/vocabularies/{id}/terms", {
|
||||
params: { path: { id: vocabularyId } },
|
||||
body: { external_uri, labels },
|
||||
});
|
||||
|
||||
if (response.status !== 201) throw new Error("add term failed");
|
||||
},
|
||||
onSuccess: (_result, { vocabularyId }) =>
|
||||
qc.invalidateQueries({ queryKey: ["terms", vocabularyId] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateAuthority() {
|
||||
const qc = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
kind,
|
||||
external_uri,
|
||||
labels,
|
||||
}: {
|
||||
kind: string;
|
||||
external_uri: string | null;
|
||||
labels: LabelInput[];
|
||||
}) => {
|
||||
const { response } = await api.POST("/api/admin/authorities", {
|
||||
body: { kind, external_uri, labels },
|
||||
});
|
||||
|
||||
if (response.status !== 201) throw new Error("create authority failed");
|
||||
},
|
||||
onSuccess: (_result, { kind }) =>
|
||||
qc.invalidateQueries({ queryKey: ["authorities", kind] }),
|
||||
});
|
||||
}
|
||||
|
||||
type Visibility = "draft" | "internal" | "public";
|
||||
|
||||
/** Error carrying the HTTP status so callers can branch 422-gate vs 409-illegal. */
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import type { ReactNode } from "react";
|
||||
import { server } from "../test/server";
|
||||
import { useVocabularies, useCreateVocabulary, useAddTerm, useCreateAuthority } from "./queries";
|
||||
|
||||
function wrapper({ children }: { children: ReactNode }) {
|
||||
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
return <QueryClientProvider client={qc}>{children}</QueryClientProvider>;
|
||||
}
|
||||
|
||||
describe("vocab/authority hooks", () => {
|
||||
test("useVocabularies lists vocabularies", async () => {
|
||||
const { result } = renderHook(() => useVocabularies(), { wrapper });
|
||||
await waitFor(() => expect(result.current.data?.length).toBe(2));
|
||||
expect(result.current.data?.[0].key).toBe("material");
|
||||
});
|
||||
test("useCreateVocabulary POSTs the key", async () => {
|
||||
let body: unknown;
|
||||
server.use(http.post("/api/admin/vocabularies", async ({ request }) => {
|
||||
body = await request.json();
|
||||
return HttpResponse.json({ id: "v-x", key: "colour" }, { status: 201 });
|
||||
}));
|
||||
const { result } = renderHook(() => useCreateVocabulary(), { wrapper });
|
||||
await result.current.mutateAsync({ key: "colour" });
|
||||
expect((body as { key: string }).key).toBe("colour");
|
||||
});
|
||||
test("useAddTerm POSTs labels to the vocabulary", async () => {
|
||||
let body: unknown;
|
||||
server.use(http.post("/api/admin/vocabularies/:id/terms", async ({ request }) => {
|
||||
body = await request.json();
|
||||
return HttpResponse.json({ id: "t-x" }, { status: 201 });
|
||||
}));
|
||||
const { result } = renderHook(() => useAddTerm(), { wrapper });
|
||||
await result.current.mutateAsync({ vocabularyId: "v-material", external_uri: null, labels: [{ lang: "en", label: "Red" }] });
|
||||
expect((body as { labels: { label: string }[] }).labels[0].label).toBe("Red");
|
||||
});
|
||||
test("useCreateAuthority POSTs kind + labels", async () => {
|
||||
let body: unknown;
|
||||
server.use(http.post("/api/admin/authorities", async ({ request }) => {
|
||||
body = await request.json();
|
||||
return HttpResponse.json({ id: "a-x" }, { status: 201 });
|
||||
}));
|
||||
const { result } = renderHook(() => useCreateAuthority(), { wrapper });
|
||||
await result.current.mutateAsync({ kind: "person", external_uri: null, labels: [{ lang: "en", label: "Ada" }] });
|
||||
expect((body as { kind: string }).kind).toBe("person");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user