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(); }); });