update DynamicJsonForm tests; add enum coverage and JSON-mode assertions

- Align tests with new form-first rendering and UI behavior:
  - Labels prefer schema `title` over property key.
  - Descriptions shown for boolean checkboxes and select fields.
  - Explicit mode switching between Form/JSON when needed.
- Add comprehensive coverage for enum schema variants:
  - Titled single-select via `oneOf`/`anyOf` with `{ const, title }`.
  - Untitled single-select via `enum` (with legacy `enumNames` labels).
  - Untitled multi-select via `items.enum` with `minItems`/`maxItems` helper text.
  - Titled multi-select via `items.anyOf`/`oneOf` with `{ const, title }`.
- Adjust complex/JSON-mode tests to click “Switch to JSON” before asserting textarea and clipboard interactions.
- Fix debounced JSON update expectations to assert parsed objects rather than raw text.

Files updated:
- `client/src/components/__tests__/DynamicJsonForm.test.tsx`
- `client/src/components/__tests__/DynamicJsonForm.array.test.tsx`
This commit is contained in:
cliffhall 2025-12-03 09:58:18 -05:00
commit 3f0c45e10d

View file

@ -202,6 +202,55 @@ describe("DynamicJsonForm String Fields", () => {
fireEvent.change(select, { target: { value: "amber" } });
expect(onChange).toHaveBeenCalledWith("amber");
});
it("should render anyOf with const/title for labeled options and show description", () => {
const onChange = jest.fn();
const schema: JsonSchemaType = {
type: "string",
title: "Heroes",
description: "Choose a hero",
anyOf: [
{ const: "hero-1", title: "Superman" },
{ const: "hero-2", title: "Batman" },
],
};
render(<DynamicJsonForm schema={schema} value="" onChange={onChange} />);
// Description should be visible above the select
expect(screen.getByText("Choose a hero")).toBeInTheDocument();
const select = screen.getByRole("combobox");
const options = screen.getAllByRole("option");
expect(options).toHaveLength(3);
expect(options[1]).toHaveProperty("textContent", "Superman");
expect(options[2]).toHaveProperty("textContent", "Batman");
fireEvent.change(select, { target: { value: "hero-2" } });
expect(onChange).toHaveBeenCalledWith("hero-2");
});
it("should render legacy enum with enumNames as labels", () => {
const onChange = jest.fn();
const schema: JsonSchemaType = {
type: "string",
title: "Pets",
description: "Choose a pet",
enum: ["pet-1", "pet-2", "pet-3"],
enumNames: ["Cat", "Dog", "Bird"],
} as unknown as JsonSchemaType; // enumNames is legacy extension
render(<DynamicJsonForm schema={schema} value="" onChange={onChange} />);
// Description should be visible above the select
expect(screen.getByText("Choose a pet")).toBeInTheDocument();
const options = screen.getAllByRole("option");
expect(options[1]).toHaveProperty("textContent", "Cat");
expect(options[2]).toHaveProperty("textContent", "Dog");
expect(options[3]).toHaveProperty("textContent", "Bird");
const select = screen.getByRole("combobox");
fireEvent.change(select, { target: { value: "pet-2" } });
expect(onChange).toHaveBeenCalledWith("pet-2");
});
});
describe("Validation Attributes", () => {
@ -535,8 +584,8 @@ describe("DynamicJsonForm Object Fields", () => {
<DynamicJsonForm schema={schema} value={{}} onChange={jest.fn()} />,
);
const nameLabel = screen.getByText("name");
const optionalLabel = screen.getByText("optional");
const nameLabel = screen.getByText("Name");
const optionalLabel = screen.getByText("Optional");
const nameInput = nameLabel.closest("div")?.querySelector("input");
const optionalInput = optionalLabel
@ -567,30 +616,46 @@ describe("DynamicJsonForm Complex Fields", () => {
};
describe("Basic Operations", () => {
it("should render textbox and autoformat button, but no switch-to-form button", () => {
it("should allow switching to JSON mode and show copy/format buttons", () => {
renderForm();
// Initially renders as a form with a Switch to JSON button
const switchToJson = screen.getByRole("button", {
name: /switch to json/i,
});
expect(switchToJson).toBeInTheDocument();
// Switch to JSON mode
fireEvent.click(switchToJson);
// Now a textarea and JSON helpers should be visible
const input = screen.getByRole("textbox");
expect(input).toHaveProperty("type", "textarea");
const buttons = screen.getAllByRole("button");
expect(buttons).toHaveLength(2); // Copy JSON + Format JSON
const copyButton = screen.getByRole("button", { name: /copy json/i });
const formatButton = screen.getByRole("button", { name: /format json/i });
expect(copyButton).toBeTruthy();
expect(formatButton).toBeTruthy();
// And a Switch to Form button should appear
expect(
screen.getByRole("button", { name: /switch to form/i }),
).toBeInTheDocument();
});
it("should pass changed values to onChange", () => {
it("should pass changed values to onChange in JSON mode", () => {
const onChange = jest.fn();
renderForm({ onChange });
// Switch to JSON mode first
fireEvent.click(screen.getByRole("button", { name: /switch to json/i }));
const input = screen.getByRole("textbox");
fireEvent.change(input, {
target: { value: `{ "nested": "i am string" }` },
});
// The onChange handler is debounced when using the JSON view, so we need to wait a little bit
waitFor(() => {
expect(onChange).toHaveBeenCalledWith(`{ "nested": "i am string" }`);
return waitFor(() => {
expect(onChange).toHaveBeenCalledWith({ nested: "i am string" });
});
});
});
@ -626,6 +691,10 @@ describe("DynamicJsonForm Copy JSON Functionality", () => {
it("should render Copy JSON button when in JSON mode", () => {
renderFormInJsonMode();
// Switch to JSON mode to reveal copy/format buttons
const switchBtn = screen.getByRole("button", { name: /switch to json/i });
fireEvent.click(switchBtn);
const copyButton = screen.getByRole("button", { name: "Copy JSON" });
expect(copyButton).toBeTruthy();
});
@ -653,6 +722,10 @@ describe("DynamicJsonForm Copy JSON Functionality", () => {
renderFormInJsonMode({ value: testValue });
// Switch to JSON mode first
const switchBtn = screen.getByRole("button", { name: /switch to json/i });
fireEvent.click(switchBtn);
const copyButton = screen.getByRole("button", { name: "Copy JSON" });
fireEvent.click(copyButton);
@ -768,6 +841,10 @@ describe("DynamicJsonForm Validation Functionality", () => {
it("should return valid for valid JSON in JSON mode", () => {
renderFormWithRef();
// Switch to JSON mode to enable textarea editing/validation
const switchBtn = screen.getByRole("button", { name: /switch to json/i });
fireEvent.click(switchBtn);
const validateButton = screen.getByTestId("validate-button");
fireEvent.click(validateButton);
@ -778,6 +855,10 @@ describe("DynamicJsonForm Validation Functionality", () => {
it("should return invalid for malformed JSON in JSON mode", async () => {
renderFormWithRef();
// Switch to JSON mode first
const switchBtn = screen.getByRole("button", { name: /switch to json/i });
fireEvent.click(switchBtn);
// Enter invalid JSON
const textarea = screen.getByRole("textbox");
fireEvent.change(textarea, { target: { value: '{ "invalid": json }' } });
@ -799,6 +880,10 @@ describe("DynamicJsonForm Validation Functionality", () => {
it("should return valid for empty JSON in JSON mode", () => {
renderFormWithRef();
// Switch to JSON mode first
const switchBtn = screen.getByRole("button", { name: /switch to json/i });
fireEvent.click(switchBtn);
// Clear the textarea
const textarea = screen.getByRole("textbox");
fireEvent.change(textarea, { target: { value: "" } });
@ -813,6 +898,10 @@ describe("DynamicJsonForm Validation Functionality", () => {
it("should set error state when validation fails", async () => {
renderFormWithRef();
// Switch to JSON mode first
const switchBtn = screen.getByRole("button", { name: /switch to json/i });
fireEvent.click(switchBtn);
// Enter invalid JSON
const textarea = screen.getByRole("textbox");
fireEvent.change(textarea, {