import { describe, expect, it } from "vitest"; import { defineToolcraft, fireEvent, renderControlsPanelWithSchema, screen, waitFor, } from "../testing/controls-panel-test-utils"; describe("ControlsPanel conditional control visibility", () => { it("disables a dependent switch through disabledWhen", async () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { background: { defaultValue: false, label: "Background", target: "export.includeBackground", type: "switch", }, infinity: { defaultValue: false, disabledWhen: { equals: false, target: "export.includeBackground", }, label: "Infinity canvas", target: "fixture.infinity", type: "switch", }, }, title: "Setup", }, ], title: "Controls", }, }, }); renderControlsPanelWithSchema(schema); const backgroundField = screen .getByText("Background") .closest('[data-slot="field"]'); const infinityField = screen .getByText("Infinity canvas") .closest('[data-slot="field"]'); const backgroundSwitch = backgroundField?.querySelector('[role="switch"]'); const infinitySwitch = infinityField?.querySelector('[role="switch"]'); expect(infinitySwitch?.hasAttribute("data-disabled")).toBe(true); fireEvent.click(backgroundSwitch!); await waitFor(() => { expect(infinitySwitch?.hasAttribute("data-disabled")).toBe(false); }); }); it("disables sliders from dependent mode values", async () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { fillMode: { defaultValue: "full", label: "Fill mode", options: [ { label: "Full", value: "full" }, { label: "Partial", value: "partial" }, ], target: "distribution.fillMode", type: "segmented", }, fillAmount: { defaultValue: 41, disabledWhen: { equals: "full", target: "distribution.fillMode", }, label: "Fill level", max: 100, min: 0, step: 1, target: "distribution.fillAmount", type: "slider", unit: "%", }, clusterBias: { defaultValue: 63, disabledWhen: { equals: "full", target: "distribution.fillMode", }, label: "Islands", max: 100, min: 0, step: 1, target: "distribution.clusterBias", type: "slider", unit: "%", }, }, title: "Distribution", }, ], title: "Token Grid", }, }, }); const { container } = renderControlsPanelWithSchema(schema); const fillLevelField = screen.getByText("Fill level").closest('[data-slot="field"]'); const islandsField = screen.getByText("Islands").closest('[data-slot="field"]'); expect(fillLevelField?.getAttribute("data-disabled")).toBe("true"); expect(islandsField?.getAttribute("data-disabled")).toBe("true"); expect( container.querySelectorAll('[data-slot="slider"][data-disabled]'), ).toHaveLength(2); fireEvent.click(screen.getByRole("button", { name: "Partial" })); await waitFor(() => { expect(fillLevelField?.getAttribute("data-disabled")).not.toBe("true"); expect(islandsField?.getAttribute("data-disabled")).not.toBe("true"); }); expect( container.querySelectorAll('[data-slot="slider"][data-disabled]'), ).toHaveLength(0); }); it("renders only applicable controls and preserves hidden values", async () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { identityMode: { applicability: { mode: "always" }, defaultValue: "text", label: "Identity", options: [ { label: "Text", value: "text" }, { label: "Logo", value: "logo" }, ], target: "coBrand.identityMode", type: "segmented", }, partnerName: { applicability: { all: [ { equals: "text", target: "coBrand.identityMode" }, { equals: true, target: "coBrand.partnerEnabled" }, ], mode: "conditional", }, defaultValue: "tRPC", label: "Partner", target: "coBrand.partnerName", type: "text", }, partnerLogo: { applicability: { all: [ { equals: "logo", target: "coBrand.identityMode" }, { equals: true, target: "coBrand.partnerEnabled" }, ], mode: "conditional", }, defaultValue: "logo.svg", label: "Partner logo", target: "coBrand.partnerLogo", type: "text", }, }, title: "Partner Lockup", }, ], title: "Cover Builder", }, }, }); renderControlsPanelWithSchema(schema, {}, { values: { "coBrand.partnerEnabled": true }, }); const partnerInput = screen.getByDisplayValue("tRPC"); expect(screen.getByText("Partner")).toBeTruthy(); expect(screen.queryByText("Partner logo")).toBeNull(); fireEvent.change(partnerInput, { target: { value: "Databricks" } }); fireEvent.blur(partnerInput); fireEvent.click(screen.getByRole("button", { name: "Logo" })); await waitFor(() => { expect(screen.queryByText("Partner")).toBeNull(); expect(screen.getByText("Partner logo")).toBeTruthy(); }); fireEvent.click(screen.getByRole("button", { name: "Text" })); await waitFor(() => { expect(screen.getByDisplayValue("Databricks")).toBeTruthy(); expect(screen.queryByText("Partner logo")).toBeNull(); }); }); it("renders count-dependent controls through numeric applicability", () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { shadeCount: { applicability: { mode: "always" }, defaultValue: 2, label: "Shades", max: 5, min: 1, step: 1, target: "shapes.shadeCount", type: "slider", variant: "discrete", }, shade1: { applicability: { mode: "always" }, defaultValue: { hex: "#FFFFFF" }, label: "Shade 1", target: "shapes.color1", type: "color", }, shade2: { applicability: { all: [ { greaterThanOrEqual: 2, target: "shapes.shadeCount", }, ], mode: "conditional", }, defaultValue: { hex: "#DDDDDD" }, label: "Shade 2", target: "shapes.color2", type: "color", }, shade3: { applicability: { all: [ { greaterThanOrEqual: 3, target: "shapes.shadeCount", }, ], mode: "conditional", }, defaultValue: { hex: "#BBBBBB" }, label: "Shade 3", target: "shapes.color3", type: "color", }, }, title: "Shapes", }, ], title: "Pattern Controls", }, }, }); const { unmount } = renderControlsPanelWithSchema(schema); expect(screen.getByText("Shade 1")).toBeTruthy(); expect(screen.getByText("Shade 2")).toBeTruthy(); expect(screen.queryByText("Shade 3")).toBeNull(); unmount(); renderControlsPanelWithSchema(schema, {}, { values: { "shapes.shadeCount": 3, }, }); expect(screen.getByText("Shade 1")).toBeTruthy(); expect(screen.getByText("Shade 2")).toBeTruthy(); expect(screen.getByText("Shade 3")).toBeTruthy(); }); it("skips sections that do not match visibleWhen", async () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { template: { defaultValue: "editorial", label: "Template", options: [ { label: "Editorial", value: "editorial" }, { label: "Code", value: "code" }, ], target: "cover.templateId", type: "segmented", }, }, title: "Cover Format", }, { controls: { codeBody: { defaultValue: "const value = true;", label: "Code", target: "code.body", type: "code", }, }, title: "Code Sample", visibleWhen: { equals: "code", target: "cover.templateId", }, }, ], title: "Cover Builder", }, }, }); renderControlsPanelWithSchema(schema); expect(screen.queryByText("Code Sample")).toBeNull(); expect(screen.queryByDisplayValue("const value = true;")).toBeNull(); fireEvent.click(screen.getByRole("button", { name: "Code" })); await waitFor(() => { expect(screen.getByText("Code Sample")).toBeTruthy(); expect(screen.getByDisplayValue("const value = true;")).toBeTruthy(); }); }); it("skips sections when every control inside is hidden by visibleWhen", async () => { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { identityMode: { defaultValue: "text", label: "Identity", options: [ { label: "Text", value: "text" }, { label: "Logo", value: "logo" }, ], target: "coBrand.identityMode", type: "segmented", }, }, title: "Identity", }, { controls: { logoScale: { defaultValue: 80, label: "Scale", target: "coBrand.logoScale", type: "slider", visibleWhen: { equals: "logo", target: "coBrand.identityMode", }, }, logoRadius: { defaultValue: 12, label: "Radius", target: "coBrand.logoRadius", type: "slider", visibleWhen: { equals: "logo", target: "coBrand.identityMode", }, }, }, title: "Logo Options", }, ], title: "Cover Builder", }, }, }); renderControlsPanelWithSchema(schema); expect(screen.queryByText("Logo Options")).toBeNull(); expect(screen.queryByText("Scale")).toBeNull(); expect(screen.queryByText("Radius")).toBeNull(); fireEvent.click(screen.getByRole("button", { name: "Logo" })); await waitFor(() => { expect(screen.getByText("Logo Options")).toBeTruthy(); expect(screen.getByText("Scale")).toBeTruthy(); expect(screen.getByText("Radius")).toBeTruthy(); }); }); });