import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import * as React from "react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { Grid } from "../src/widgets/grid";
vi.mock("@cloudflare/kumo", () => ({
Checkbox: ({ checked, onCheckedChange, "aria-label": ariaLabel }: any) => (
onCheckedChange?.(e.target.checked)}
/>
),
Input: ({ value, onChange, "aria-label": ariaLabel, type }: any) => (
),
Select: ({ value, onValueChange, items, "aria-label": ariaLabel }: any) => (
),
}));
afterEach(() => cleanup());
const rows = [
{ key: "mon", label: "Mon" },
{ key: "tue", label: "Tue" },
];
const columns = [
{ key: "am", label: "AM" },
{ key: "pm", label: "PM" },
];
describe("Grid widget", () => {
it("renders all cells as toggle checkboxes by default", () => {
render( {}} label="Grid" id="g" options={{ rows, columns }} />);
const boxes = screen.getAllByRole("checkbox");
expect(boxes).toHaveLength(4); // 2 rows × 2 cols
});
it("reflects existing toggle values", () => {
render(
{}}
label="Grid"
id="g"
options={{ rows, columns }}
/>,
);
expect((screen.getByLabelText("Mon — AM") as HTMLInputElement).checked).toBe(true);
expect((screen.getByLabelText("Mon — PM") as HTMLInputElement).checked).toBe(false);
expect((screen.getByLabelText("Tue — AM") as HTMLInputElement).checked).toBe(true);
});
it("normalizes legacy array format on read", () => {
render(
{}}
label="Grid"
id="g"
options={{ rows, columns }}
/>,
);
expect((screen.getByLabelText("Mon — AM") as HTMLInputElement).checked).toBe(true);
expect((screen.getByLabelText("Mon — PM") as HTMLInputElement).checked).toBe(true);
expect((screen.getByLabelText("Tue — AM") as HTMLInputElement).checked).toBe(true);
expect((screen.getByLabelText("Tue — PM") as HTMLInputElement).checked).toBe(false);
});
it("emits object-shape on toggle write (even when input was array format)", () => {
const onChange = vi.fn();
render(
,
);
fireEvent.click(screen.getByLabelText("Mon — PM"));
expect(onChange).toHaveBeenCalledWith({
mon: { am: true, pm: true },
tue: {},
});
});
it("renders text cells when cell is 'text'", () => {
render(
{}}
label="Grid"
id="g"
options={{ rows, columns, cell: "text" }}
/>,
);
expect(screen.getAllByRole("textbox")).toHaveLength(4);
});
it("renders select cells with cellOptions", () => {
render(
{}}
label="Grid"
id="g"
options={{
rows,
columns,
cell: "select",
cellOptions: [
{ label: "A", value: "a" },
{ label: "B", value: "b" },
],
}}
/>,
);
const selects = screen.getAllByRole("combobox");
expect(selects).toHaveLength(4);
});
it("preserves unknown cell keys on write so evolving schemas don't drop data", () => {
const onChange = vi.fn();
render(
,
);
fireEvent.click(screen.getByLabelText("Mon — PM"));
expect(onChange).toHaveBeenCalledWith({
mon: { am: true, pm: true, legacy: "keep-me" },
tue: {},
});
});
it("shows misconfigured warning when rows or columns are missing", () => {
render(
{}}
label="Grid"
id="g"
options={{ rows: [], columns: [] }}
/>,
);
expect(screen.queryByText(/Widget misconfigured/i)).not.toBeNull();
});
});