import { within } from "@testing-library/react"; import { describe, expect, it } from "vitest"; import { defineToolcraft, fireEvent, renderControlsPanelWithSchema, screen } from "../testing/controls-panel-test-utils"; import { sliderRangeDefinition } from "../../../state/slider-range.test-support"; function edit(name: string, draft: string, key?: string) { fireEvent.click(screen.getByRole("button", { name: `Edit ${name} value` })); const editor = screen.getByRole("textbox", { name: `${name} value` }); editor.textContent = draft; if (key) fireEvent.keyDown(editor, { key }); fireEvent.blur(editor); } const bandThumbs = () => within(screen.getByRole("button", { name: "Edit Band value" }).closest('[data-slot="field"]') as HTMLElement).getAllByRole("slider", { hidden: true }); describe("editable range panel", () => { it("applies both scale and values on blur, then shrinks on Enter without blocking", () => { renderControlsPanelWithSchema(defineToolcraft(sliderRangeDefinition)); edit("Band", "0–40"); expect(screen.getByTestId("values-json").textContent).toContain('"shape.band":[0,40]'); expect(bandThumbs()[1]?.getAttribute("max")).toBe("40"); expect(bandThumbs()[1]?.getAttribute("aria-valuenow")).toBe("40"); edit("Band", "0–10", "Enter"); expect(bandThumbs()[1]?.getAttribute("max")).toBe("10"); expect(bandThumbs()[1]?.getAttribute("aria-valuenow")).toBe("10"); expect(document.querySelector('[data-toolcraft-control-target="shape.band"] [aria-busy="true"]')).toBeNull(); expect(bandThumbs()[1]?.getAttribute("aria-disabled")).not.toBe("true"); edit("Radius", "1000"); expect(screen.getByTestId("values-json").textContent).toContain('"shape.radius":1000'); }); it("retains last valid state on malformed and forbidden input; Escape cancels even with blur", () => { renderControlsPanelWithSchema(defineToolcraft(sliderRangeDefinition)); edit("Band", "0–40"); for (const draft of ["−10–40", "40–10", "0–1001", "oops 50", "NaN", ""]) { edit("Band", draft); expect(screen.getByTestId("values-json").textContent).toContain('"shape.band":[0,40]'); expect(bandThumbs()[1]?.getAttribute("max")).toBe("40"); expect(document.querySelector('[data-slot="field-error"]')).not.toBeNull(); } edit("Band", "0–10", "Escape"); expect(bandThumbs()[1]?.getAttribute("max")).toBe("40"); edit("Band", "0–10"); expect(document.querySelector('[data-slot="field-error"]')).toBeNull(); edit("Position", "−10–20"); expect(screen.getByTestId("values-json").textContent).toContain('"shape.position":[-10,20]'); }); it("restores thumb defaults outside a narrowed scale and only commits keyboard draft steps on blur", () => { renderControlsPanelWithSchema(defineToolcraft(sliderRangeDefinition)); edit("Radius", "10"); const radiusField = screen.getByRole("button", { name: "Edit Radius value" }).closest('[data-slot="field"]')!; fireEvent.doubleClick(radiusField.querySelector('[data-slot="slider-thumb"]')!); expect(screen.getByTestId("values-json").textContent).toContain('"shape.radius":20'); expect(radiusField.querySelector('input[type="range"]')?.getAttribute("max")).toBe("20"); fireEvent.click(screen.getByRole("button", { name: "Edit Radius value" })); const editor = screen.getByRole("textbox", { name: "Radius value" }); fireEvent.keyDown(editor, { key: "ArrowUp" }); expect(screen.getByTestId("values-json").textContent).toContain('"shape.radius":20'); fireEvent.blur(editor); expect(screen.getByTestId("values-json").textContent).toContain('"shape.radius":21'); edit("Band", "0–10"); const field = screen.getByRole("button", { name: "Edit Band value" }).closest('[data-slot="field"]')!; fireEvent.doubleClick(field.querySelectorAll('[data-slot="slider-thumb"]')[1]!); expect(screen.getByTestId("values-json").textContent).toContain('"shape.band":[0,20]'); expect(bandThumbs()[1]?.getAttribute("max")).toBe("20"); }); it("falls back from dense discrete markers without changing the numeric step", () => { const definition = JSON.parse(JSON.stringify(sliderRangeDefinition)); definition.base.panels.controls.sections[0].controls.radius.variant = "discrete"; renderControlsPanelWithSchema(defineToolcraft(definition)); const field = screen.getByRole("button", { name: "Edit Radius value" }).closest('[data-slot="field"]')!; expect(field.querySelector('[data-slot="slider"]')?.getAttribute("data-variant")).toBe("discrete"); edit("Radius", "1000"); expect(field.querySelector('[data-slot="slider"]')?.getAttribute("data-variant")).toBe("continuous"); expect(field.querySelector('input[type="range"]')?.getAttribute("step")).toBe("1"); edit("Radius", "10"); expect(field.querySelector('[data-slot="slider"]')?.getAttribute("data-variant")).toBe("discrete"); }); });