import { describe, expect, it } from "vitest"; import { contractSchemaFixture, defineContractSchemaFixture, validateContractAcceptance, } from "./starter-acceptance.contract-fixtures"; describe("starter acceptance slider rule contract", () => { it("accepts stepped continuous sliders without forcing the discrete visual variant", () => { const schemaWithAmbiguousSlider = defineContractSchemaFixture({ canvas: { enabled: true, sizing: { mode: "editable-output" }, upload: true, }, panels: { controls: { sections: [ { controls: { grain: { defaultValue: 0.08, label: "Grain", max: 0.35, min: 0, step: 0.01, target: "shader.grain", type: "slider", variant: "continuous", }, }, id: "volume", title: "Volume", }, ], title: "Shader", }, timeline: undefined, }, toolbar: { history: true, radar: true, zoom: true, }, }); expect( validateContractAcceptance({ schema: schemaWithAmbiguousSlider, acceptance: [ { automated: true, automatedTestName: "grain changes rendered output", browser: true, browserTestName: "browser: grain slider changes rendered output", componentType: "slider", evidence: "rendered-pixels", expectedObservable: "Changing Grain changes pixel variance.", fixture: "grain fixture", id: "shader.grain", kind: "control", target: "shader.grain", userAction: "Drag the Grain slider.", }, ], }), ).toEqual([]); }); it("uses typed discrete intent instead of English slider wording", () => { const schemaWithMissingDiscreteSliders = defineContractSchemaFixture({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { maskGap: { defaultValue: 5, label: "Separación", max: 12, min: 0, orderRole: "detail", step: 1, sliderValueKind: "discrete" as const, target: "ascii.maskGap", type: "slider", unit: "cols", }, verticalJitter: { defaultValue: 1, label: "Desplazamiento", max: 4, min: 0, orderRole: "detail", step: 1, sliderValueKind: "discrete", target: "ascii.verticalJitter", type: "slider", unit: "rows", }, }, title: "Mask", }, ], title: "ASCII", }, }, }); expect( validateContractAcceptance({ schema: schemaWithMissingDiscreteSliders, acceptance: [ { automated: true, automatedTestName: "mask gap changes rendered output", browser: true, browserTestName: "browser: mask gap slider changes rendered output", componentType: "slider", evidence: "rendered-pixels", expectedObservable: "Changing Mask gap changes row reveal boundaries.", fixture: "ASCII fixture", id: "ascii.maskGap", kind: "control", target: "ascii.maskGap", userAction: "Drag the Mask gap slider.", }, { automated: true, automatedTestName: "vertical jitter changes rendered output", browser: true, browserTestName: "browser: vertical jitter slider changes rendered output", componentType: "slider", evidence: "rendered-pixels", expectedObservable: "Changing Vertical jitter changes row displacement.", fixture: "ASCII fixture", id: "ascii.verticalJitter", kind: "control", target: "ascii.verticalJitter", userAction: "Drag the Vertical jitter slider.", }, ], }), ).toEqual( expect.arrayContaining([ 'Mask / maskGap (ascii.maskGap) has 13 semantic integer positions and must use variant "discrete" so Toolcraft renders tick markers.', 'Mask / verticalJitter (ascii.verticalJitter) has 5 semantic integer positions and must use variant "discrete" so Toolcraft renders tick markers.', ]), ); }); it("requires flip-depth integer sliders to use the visual discrete variant", () => { const schemaWithFlipDepthSlider = defineContractSchemaFixture({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { speed: { defaultValue: 1.1, label: "Speed", max: 2.5, min: 0.5, step: 0.1, target: "animation.speed", type: "slider", variant: "continuous", }, depth: { defaultValue: 12, label: "Flip depth", max: 28, min: 4, sliderValueKind: "discrete", step: 1, target: "animation.depth", type: "slider", variant: "continuous", }, }, title: "Motion", }, ], title: "Board", }, }, }); expect( validateContractAcceptance({ schema: schemaWithFlipDepthSlider, acceptance: [ { automated: true, automatedTestName: "speed changes animation timing", browser: true, browserTestName: "browser: speed slider changes animation timing", componentType: "slider", evidence: "rendered-pixels", expectedObservable: "Changing Speed changes the animation cadence.", fixture: "board speed fixture", id: "animation.speed", kind: "control", target: "animation.speed", userAction: "Drag the Speed slider.", }, { automated: true, automatedTestName: "flip depth changes intermediate characters", browser: true, browserTestName: "browser: flip depth slider changes intermediate characters", componentType: "slider", evidence: "rendered-pixels", expectedObservable: "Changing Flip depth changes the number of intermediate character steps.", fixture: "board flip depth fixture", id: "animation.depth", kind: "control", target: "animation.depth", userAction: "Drag the Flip depth slider.", }, ], }), ).toEqual( expect.arrayContaining([ 'Motion / depth (animation.depth) has 25 semantic integer positions and must use variant "discrete" so Toolcraft renders tick markers.', ]), ); }); it("accepts large or precision stepped sliders as visually continuous", () => { const schemaWithContinuousSteppedSliders = defineContractSchemaFixture({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { revealSpeed: { defaultValue: 118, label: "Reveal speed", max: 150, min: 0, orderRole: "primary", step: 1, target: "ascii.speed", type: "slider", unit: "cols/s", }, flipDuration: { defaultValue: 0.6, label: "Flip duration", max: 5, min: 0, orderRole: "strength", step: 0.1, target: "ascii.flipDurationSec", type: "slider", unit: "s", }, }, title: "Timing", }, ], title: "ASCII", }, }, }); expect( validateContractAcceptance({ schema: schemaWithContinuousSteppedSliders, acceptance: [ { automated: true, automatedTestName: "reveal speed changes rendered output", browser: true, browserTestName: "browser: reveal speed slider changes rendered output", componentType: "slider", evidence: "rendered-pixels", expectedObservable: "Changing Reveal speed changes reveal density.", fixture: "ASCII fixture", id: "ascii.speed", kind: "control", target: "ascii.speed", userAction: "Drag the Reveal speed slider.", }, { automated: true, automatedTestName: "flip duration changes rendered output", browser: true, browserTestName: "browser: flip duration slider changes rendered output", componentType: "slider", evidence: "rendered-pixels", expectedObservable: "Changing Flip duration changes animation timing.", fixture: "ASCII fixture", id: "ascii.flipDurationSec", kind: "control", target: "ascii.flipDurationSec", userAction: "Drag the Flip duration slider.", }, ], }), ).toEqual([]); }); it("requires discrete slider markerCount to match the step count", () => { const schemaWithDiscreteSlider = { ...contractSchemaFixture, panels: { ...contractSchemaFixture.panels, controls: { sections: [ { controls: { grain: { defaultValue: 0.08, label: "Grain", markerCount: 6, max: 1, min: 0, sliderValueKind: "discrete" as const, step: 0.1, target: "shader.grain", type: "slider", variant: "discrete", }, }, id: "volume", title: "Volume", }, ], title: "Shader", }, }, }; expect( validateContractAcceptance({ schema: schemaWithDiscreteSlider, acceptance: [ { automated: true, automatedTestName: "grain changes rendered output", browser: true, browserTestName: "browser: grain slider changes rendered output", componentType: "slider", evidence: "rendered-pixels", expectedObservable: "Changing Grain changes pixel variance.", fixture: "grain fixture", id: "shader.grain", kind: "control", target: "shader.grain", userAction: "Drag the Grain slider.", }, ], }), ).toEqual( expect.arrayContaining([ "Volume / grain (shader.grain) discrete slider must render one marker per step; expected markerCount 11, received 6.", ]), ); }); it("rejects visual discrete sliders with too many positions", () => { const schemaWithDenseDiscreteSlider = defineContractSchemaFixture({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { revealSpeed: { defaultValue: 118, label: "Reveal speed", max: 150, min: 0, orderRole: "primary", sliderValueKind: "discrete", step: 1, target: "ascii.speed", type: "slider", unit: "cols/s", variant: "discrete", }, }, title: "Timing", }, ], title: "ASCII", }, }, }); expect( validateContractAcceptance({ schema: schemaWithDenseDiscreteSlider, acceptance: [ { automated: true, automatedTestName: "reveal speed changes rendered output", browser: true, browserTestName: "browser: reveal speed slider changes rendered output", componentType: "slider", evidence: "rendered-pixels", expectedObservable: "Changing Reveal speed changes reveal density.", fixture: "ASCII fixture", id: "ascii.speed", kind: "control", target: "ascii.speed", userAction: "Drag the Reveal speed slider.", }, ], }), ).toEqual( expect.arrayContaining([ 'Timing / revealSpeed (ascii.speed) declares variant "discrete" with 151 positions, which would overload tick markers. Keep it stepped continuous or use a different control.', ]), ); }); it("accepts continuous stepped sliders without visual markers", () => { const schemaWithNormalizedSlider = defineContractSchemaFixture({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { grain: { defaultValue: 0.08, label: "Grain", max: 1, min: 0, step: 0.1, target: "shader.grain", type: "slider", }, }, title: "Volume", }, ], title: "Shader", }, }, }); expect( validateContractAcceptance({ schema: schemaWithNormalizedSlider, acceptance: [ { automated: true, automatedTestName: "grain changes rendered output", browser: true, browserTestName: "browser: grain slider changes rendered output", componentType: "slider", evidence: "rendered-pixels", expectedObservable: "Changing Grain changes pixel variance.", fixture: "grain fixture", id: "shader.grain", kind: "control", target: "shader.grain", userAction: "Drag the Grain slider.", }, ], }), ).toEqual([]); }); });