diff --git a/web/src/objects/field-input.test.tsx b/web/src/objects/field-input.test.tsx index afb5c78..458e85a 100644 --- a/web/src/objects/field-input.test.tsx +++ b/web/src/objects/field-input.test.tsx @@ -1,10 +1,13 @@ import { expect, test } from "vitest"; -import { screen } from "@testing-library/react"; +import { screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { useForm } from "react-hook-form"; import { renderApp } from "../test/render"; import { FieldInput } from "./field-input"; import { fieldDefinitions } from "../test/fixtures"; +type FormValues = { fields: Record }; + function Harness({ defKey }: { defKey: string }) { const def = fieldDefinitions.find((d) => d.key === defKey)!; const form = useForm({ defaultValues: { fields: {} as Record } }); @@ -12,6 +15,24 @@ function Harness({ defKey }: { defKey: string }) { return ; } +function FormHarness({ + defKey, + onSubmit, +}: { + defKey: string; + onSubmit: (values: FormValues) => void; +}) { + const def = fieldDefinitions.find((d) => d.key === defKey)!; + const form = useForm({ defaultValues: { fields: {} as Record } }); + + return ( +
+ + + + ); +} + test("text field renders a text input labelled in the active locale", async () => { renderApp(); expect(await screen.findByLabelText("Inscription")).toBeInTheDocument(); @@ -27,12 +48,40 @@ test("localized_text renders a single input for the default language", async () expect(await screen.findByLabelText(/^title/i)).toBeInTheDocument(); }); -test("term field renders a select populated from the vocabulary", async () => { - renderApp(); - expect(await screen.findByText("Bronze")).toBeInTheDocument(); +test("term field filters and selects from the vocabulary combobox", async () => { + const user = userEvent.setup(); + const submitted: FormValues[] = []; + + renderApp( submitted.push(v)} />); + + const input = await screen.findByPlaceholderText("— select —"); + + await user.click(input); + await user.type(input, "bro"); + + const body = within(document.body); + + await user.click(await body.findByText("Bronze")); + await user.click(screen.getByRole("button", { name: "Submit" })); + + expect(submitted[0]?.fields.material).toBe("t-bronze"); }); -test("authority field renders a select populated by kind", async () => { - renderApp(); - expect(await screen.findByText("Ada Lovelace")).toBeInTheDocument(); +test("authority field filters and selects from the authority combobox", async () => { + const user = userEvent.setup(); + const submitted: FormValues[] = []; + + renderApp( submitted.push(v)} />); + + const input = await screen.findByPlaceholderText("— select —"); + + await user.click(input); + await user.type(input, "ada"); + + const body = within(document.body); + + await user.click(await body.findByText("Ada Lovelace")); + await user.click(screen.getByRole("button", { name: "Submit" })); + + expect(submitted[0]?.fields.maker).toBe("a-ada"); }); diff --git a/web/src/objects/field-input.tsx b/web/src/objects/field-input.tsx index 5fc88e8..c3ab8b3 100644 --- a/web/src/objects/field-input.tsx +++ b/web/src/objects/field-input.tsx @@ -4,12 +4,13 @@ import { useTranslation } from "react-i18next"; import type { components } from "../api/schema"; import { useAuthorities, useTerms } from "../api/queries"; import { useConfig } from "../config/config-context"; +import { labelText } from "../lib/labels"; +import { OptionsCombobox } from "./options-combobox"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; type FieldDefinitionView = components["schemas"]["FieldDefinitionView"]; -type LabelView = components["schemas"]["LabelView"]; type FieldForm }> = UseFormReturn; @@ -19,50 +20,6 @@ function fieldPath }>( return `fields.${key}` as Path; } -function labelIn(labels: LabelView[], lang: string): string { - return ( - labels.find((l) => l.lang === lang)?.label ?? - labels.find((l) => l.lang === "en")?.label ?? - labels[0]?.label ?? - "" - ); -} - -// A native onChange(e.target.value)} - > - - - {options.map((o) => ( - - ))} - - ); -} - export function FieldInput }>({ definition, form, @@ -73,7 +30,7 @@ export function FieldInput }>( const { t, i18n } = useTranslation(); const { default_language } = useConfig(); const lang = i18n.language.startsWith("sv") ? "sv" : "en"; - const label = labelIn(definition.labels, lang); + const label = labelText(definition.labels, lang); const name = fieldPath(definition.key); const placeholder = t("form.selectPlaceholder"); @@ -201,7 +158,7 @@ function TermField }>({ name={fieldPath(definition.key)} rules={{ required: definition.required }} render={({ field }) => ( - }>({ name={fieldPath(definition.key)} rules={{ required: definition.required }} render={({ field }) => ( - l.lang === lang)?.label ?? - labels.find((l) => l.lang === "en")?.label ?? - labels[0]?.label ?? - "" - ); -} - export function OptionsCombobox({ id, value, @@ -47,7 +39,7 @@ export function OptionsCombobox({ items={options} value={selected} onValueChange={(option) => onChange(option?.id ?? "")} - itemToStringLabel={(option) => (option ? labelIn(option.labels, lang) : "")} + itemToStringLabel={(option) => (option ? labelText(option.labels, lang) : "")} isItemEqualToValue={(a, b) => a?.id === b?.id} > @@ -62,7 +54,7 @@ export function OptionsCombobox({ {(option: Option) => ( - {labelIn(option.labels, lang)} + {labelText(option.labels, lang)} )}