import { describe, expect, it } from "vitest"; import { defineToolcraft, fireEvent, renderControlsPanelWithSchema, screen, } from "../testing/controls-panel-test-utils"; describe("ControlsPanel slider rendering and layout", () => { it("renders schema discrete sliders with Toolcraft variant and markers", () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { grain: { defaultValue: 3, label: "Grain", max: 5, min: 0, step: 1, target: "shader.grain", type: "slider", variant: "discrete", }, }, title: "Texture", }, ], title: "Generation Controls", }, }, }); const { container } = renderControlsPanelWithSchema(schema); const slider = container.querySelector( '[data-slot="slider"][data-variant="discrete"]', ); const markers = Array.from( container.querySelectorAll('[data-slot="slider-marker"]'), ); expect(slider).toBeTruthy(); expect(markers).toHaveLength(4); for (const marker of markers) { expect(marker.className).toContain("group-hover/slider-control:opacity-100"); expect(marker.className.split(/\s+/)).not.toContain("opacity-100"); } fireEvent.pointerDown(slider as HTMLElement, { button: 0 }); for (const marker of markers) { expect(marker.className.split(/\s+/)).toContain("opacity-100"); } }); it("renders one discrete slider marker for each fractional step", () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { duration: { defaultValue: 0.3, label: "Duration", markerCount: 3, max: 0.5, min: 0, step: 0.1, target: "animation.duration", type: "slider", variant: "discrete", }, }, title: "Timing", }, ], title: "Generation Controls", }, }, }); const { container } = renderControlsPanelWithSchema(schema); const slider = container.querySelector( '[data-slot="slider"][data-variant="discrete"]', ); const markers = Array.from( container.querySelectorAll('[data-slot="slider-marker"]'), ); expect(slider).toBeTruthy(); expect(markers).toHaveLength(4); }); it("keeps stepped continuous sliders free of discrete markers", () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { speed: { defaultValue: 118, label: "Reveal speed", max: 150, min: 0, step: 1, target: "animation.speed", type: "slider", unit: " cols/s", }, }, title: "Reveal", }, ], title: "Generation Controls", }, }, }); const { container } = renderControlsPanelWithSchema(schema); expect( container.querySelector('[data-slot="slider"][data-variant="discrete"]'), ).toBeNull(); expect(container.querySelectorAll('[data-slot="slider-marker"]')).toHaveLength(0); expect(container.textContent).toContain("118 cols/s"); }); it("keeps textual slider value labels non-editable", () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { letterSpacing: { defaultValue: 2, label: "Letter spacing", max: 4, min: 0, step: 1, target: "text.letterSpacing", type: "slider", valueLabel: "Normal", }, }, title: "Text", }, ], title: "Generation Controls", }, }, }); renderControlsPanelWithSchema(schema); const valueLabel = screen .getAllByText("Normal") .find((node) => node.getAttribute("aria-hidden") !== "true"); expect(screen.queryByRole("button", { name: "Edit Letter spacing value" })).toBeNull(); expect(valueLabel).toBeTruthy(); expect(valueLabel?.className).toContain("cursor-default"); expect(valueLabel?.className).not.toContain("cursor-text"); }); it("keeps schema sliders stacked even when a layout group asks for an inline row", () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { fps: { defaultValue: 17, label: "FPS", max: 30, min: 1, step: 1, target: "animation.fps", type: "slider", unit: "fps", variant: "discrete", }, speed: { defaultValue: 3.7, label: "Speed", max: 4, min: 0.1, step: 0.1, target: "animation.speed", type: "slider", }, }, layoutGroups: [ { columns: 2, controls: ["fps", "speed"], layout: "inline", }, ], title: "Pattern Animation", }, ], title: "Generation Controls", }, }, }); const { container } = renderControlsPanelWithSchema(schema); const fpsSlider = container.querySelector( '[data-slot="slider"][data-variant="discrete"]', ); expect(container.querySelector('[data-control-layout="inline"]')).toBeNull(); expect(fpsSlider).toBeTruthy(); expect(container.querySelectorAll('[data-slot="slider-marker"]').length).toBeGreaterThan(0); expect(container.textContent).toContain("17 fps"); expect(container.textContent).toContain("3.7"); }); it("passes schema disabled state into slider controls", () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { opacity: { defaultValue: 70, disabled: true, label: "Opacity", max: 100, min: 0, step: 1, target: "shader.opacity", type: "slider", }, }, title: "Output", }, ], title: "Generation Controls", }, }, }); const { container } = renderControlsPanelWithSchema(schema); const field = screen.getByText("Opacity").closest('[data-slot="field"]'); const slider = container.querySelector('[data-slot="slider"]'); expect(field?.getAttribute("data-disabled")).toBe("true"); expect(slider).toBeTruthy(); expect(slider?.hasAttribute("data-disabled")).toBe(true); }); it("does not place any schema slider in inline layout rows", () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { range: { defaultValue: [20, 80], label: "Range", max: 100, min: 0, step: 1, target: "shape.range", type: "rangeSlider", unit: "%", }, strength: { defaultValue: 50, label: "Strength", max: 100, min: 0, step: 1, target: "shape.strength", type: "slider", unit: "%", }, }, layoutGroups: [ { columns: 2, controls: ["range", "strength"], layout: "inline", }, ], title: "Shape", }, ], title: "Controls", }, }, }); const { container } = renderControlsPanelWithSchema(schema); expect(container.querySelector('[data-control-layout="inline"]')).toBeNull(); expect(screen.getByText("Range")).toBeTruthy(); expect(screen.getByText("Strength")).toBeTruthy(); }); });