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:
parent
fd99d879c5
commit
3f0c45e10d
2 changed files with 178 additions and 9 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom";
|
||||
import { describe, it, expect, jest } from "@jest/globals";
|
||||
import DynamicJsonForm from "../DynamicJsonForm";
|
||||
import type { JsonSchemaType } from "@/utils/jsonUtils";
|
||||
|
|
@ -104,7 +105,11 @@ describe("DynamicJsonForm Array Fields", () => {
|
|||
it("should render JSON editor for complex arrays", () => {
|
||||
renderComplexArrayForm();
|
||||
|
||||
// Should render as JSON editor (textarea)
|
||||
// Initially renders form view with Switch to JSON button; switch to JSON to see textarea
|
||||
const switchBtn = screen.getByRole("button", { name: /switch to json/i });
|
||||
expect(switchBtn).toBeInTheDocument();
|
||||
fireEvent.click(switchBtn);
|
||||
|
||||
const textarea = screen.getByRole("textbox");
|
||||
expect(textarea).toHaveProperty("type", "textarea");
|
||||
|
||||
|
|
@ -173,6 +178,81 @@ describe("DynamicJsonForm Array Fields", () => {
|
|||
});
|
||||
|
||||
describe("Array with Different Item Types", () => {
|
||||
it("should render multi-select for untitled items.enum with helper text", () => {
|
||||
const schema: JsonSchemaType = {
|
||||
type: "array",
|
||||
title: "Colors",
|
||||
description: "Pick colors",
|
||||
minItems: 1,
|
||||
maxItems: 3,
|
||||
items: { type: "string", enum: ["red", "green", "blue"] },
|
||||
} as JsonSchemaType;
|
||||
const onChange = jest.fn();
|
||||
render(
|
||||
<DynamicJsonForm schema={schema} value={["red"]} onChange={onChange} />,
|
||||
);
|
||||
|
||||
// Description visible
|
||||
expect(screen.getByText("Pick colors")).toBeInTheDocument();
|
||||
// Multi-select present
|
||||
const listbox = screen.getByRole("listbox");
|
||||
expect(listbox).toHaveAttribute("multiple");
|
||||
|
||||
// Helper text shows min/max
|
||||
expect(screen.getByText(/Select at least 1/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Select at most 3/i)).toBeInTheDocument();
|
||||
|
||||
// Select another option by toggling option.selected
|
||||
const colorOptions = screen.getAllByRole("option");
|
||||
// options: red, green, blue
|
||||
colorOptions[0].selected = true; // red
|
||||
colorOptions[2].selected = true; // blue
|
||||
fireEvent.change(listbox);
|
||||
expect(onChange).toHaveBeenCalledWith(["red", "blue"]);
|
||||
});
|
||||
|
||||
it("should render titled multi-select for items.anyOf with const/title", () => {
|
||||
const schema: JsonSchemaType = {
|
||||
type: "array",
|
||||
description: "Pick fish",
|
||||
items: {
|
||||
anyOf: [
|
||||
{ const: "fish-1", title: "Tuna" },
|
||||
{ const: "fish-2", title: "Salmon" },
|
||||
{ const: "fish-3", title: "Trout" },
|
||||
],
|
||||
} as unknown as JsonSchemaType,
|
||||
} as unknown as JsonSchemaType;
|
||||
const onChange = jest.fn();
|
||||
render(
|
||||
<DynamicJsonForm
|
||||
schema={schema}
|
||||
value={["fish-1"]}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Description visible
|
||||
expect(screen.getByText("Pick fish")).toBeInTheDocument();
|
||||
|
||||
const listbox = screen.getByRole("listbox");
|
||||
expect(listbox).toHaveAttribute("multiple");
|
||||
|
||||
// Ensure options have titles as labels
|
||||
const options = screen.getAllByRole("option");
|
||||
expect(options[0]).toHaveProperty("textContent", "Tuna");
|
||||
expect(options[1]).toHaveProperty("textContent", "Salmon");
|
||||
expect(options[2]).toHaveProperty("textContent", "Trout");
|
||||
|
||||
// Select fish-2 and fish-3
|
||||
const fishOptions = screen.getAllByRole("option");
|
||||
// options: Tuna (fish-1), Salmon (fish-2), Trout (fish-3)
|
||||
fishOptions[0].selected = false; // deselect fish-1
|
||||
fishOptions[1].selected = true; // fish-2
|
||||
fishOptions[2].selected = true; // fish-3
|
||||
fireEvent.change(listbox);
|
||||
expect(onChange).toHaveBeenCalledWith(["fish-2", "fish-3"]);
|
||||
});
|
||||
it("should handle integer array items", () => {
|
||||
const schema = {
|
||||
type: "array" as const,
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
Loading…
Reference in a new issue