import { describe, expect, it } from "vitest"; import { getToolcraftComponentContract } from "./component-contracts"; describe("Toolcraft template component contracts: inputs", () => { it("documents slider visual variants as explicit schema choices", () => { const slider = getToolcraftComponentContract("slider"); const rangeSlider = getToolcraftComponentContract("rangeSlider"); expect(slider.decisionCatalog.requiredAcceptance).toContain( "Prove dragging the slider changes product output or the intended runtime side effect while the drag is in progress, not only after pointer release, blur, Apply, or a final commit.", ); expect(slider.aiUsageRules).toContain( "Sliders are live canvas controls: dragging must update runtime state and product output in real time by default.", ); expect(slider.aiUsageRules).toContain( "Do not implement slider values as deferred local drafts, Apply-only updates, pointer-up-only commits, or renderer changes that appear only after the user asks again.", ); expect(slider.aiUsageRules).toContain( "Slider performance coverage must use a real control-drag scenario; control-change coverage is not enough to prove live canvas feedback or drag smoothness.", ); expect(slider.aiUsageRules).toContain( "If a live slider causes jank, optimize the renderer path first: update uniforms or stable buffers, cache expensive inputs, coalesce preview work to requestAnimationFrame, cancel stale async renders, move heavy work off React, or switch renderer strategy.", ); expect(slider.aiUsageRules).toContain( "Only in an extreme documented performance ceiling may a slider use a degraded live preview or delayed heavy refinement; the user must still see immediate canvas feedback while dragging and the worklog must record the measured reason.", ); expect(slider.aiUsageRules).toContain( "Slider step means numeric snapping only; it does not make the slider visually discrete by itself.", ); expect(slider.aiUsageRules).toContain( 'Every slider declares sliderValueKind: "continuous" or "discrete" from the product value model. Labels and descriptions are not slider-kind evidence.', ); expect(slider.aiUsageRules).toContain( 'Use variant: "discrete" only with sliderValueKind: "discrete"; the typed value kind lets validation stay language-independent.', ); expect(slider.aiUsageRules).toContain( "Classify every stepped slider as stepped continuous or visual discrete before writing the schema.", ); expect(slider.aiUsageRules).toContain( 'Small semantic integer domains such as rows, cols, gaps, jitter, counts, levels, bands, passes, points, tiles, and segments must use variant: "discrete".', ); expect(slider.aiUsageRules).toContain( 'Finite animation step domains such as flip depth, character count, glyph steps, and frame steps must use variant: "discrete" when the marker count stays within the Toolcraft visual budget.', ); expect(slider.aiUsageRules).toContain( 'Visual discrete slider and range-slider variants require authored finite min and max plus a positive step, and may expose at most 32 value positions including endpoints. At 33 or more positions, keep sliderValueKind "discrete" and step but use the continuous visual variant or another built-in control.', ); expect(slider.aiUsageRules).toContain( "Large or precision stepped ranges such as speed, FPS, rate, duration, density, size, and intensity stay visually continuous even when they declare step.", ); expect(slider.aiUsageRules).toContain( "Use slider unit only for real measurement suffixes such as %, px, °, s, ms, fps, rows/cols, or similar domain units.", ); expect(slider.aiUsageRules).toContain( "Do not use unit for repeated entity nouns already named by the section or label, such as Letters + letters, Shape Density / Count + shapes, Words + words, Symbols + symbols, Items + items, Particles + particles, or Layers + layers.", ); expect(slider.aiUsageRules).toContain( 'Do not use unit: "x"; scale, multiplier, intensity, opacity, strength, depth, and shader amount sliders display plain numbers unless a real measurement unit applies.', ); expect(slider.aiUsageRules).toContain( "When the value needs an entity noun to make sense, improve the label or section title instead of appending that noun as the value unit.", ); expect(slider.aiUsageRules).toContain( "Compact symbol/CSS units render tight, such as 70%, 24px, and 8s; word units render with a space, such as 5 cols, when they are truly needed.", ); expect(slider.aiUsageRules).toContain( "Slider valueLabel is editable only when it contains a numeric value; textual state labels such as Normal are display-only and must not expose hover or click editing affordances.", ); expect(slider.aiUsageRules).toContain( "Schema sliders render stacked at full width; do not put sliders in two-column inline layout groups.", ); expect(slider.aiUsageRules).toContain( "The fontPicker component is the only built-in exception with two internal footer sliders for letter spacing and line height.", ); expect(slider.aiUsageRules).toContain( 'Browser verification can inspect [data-slot="slider"][data-variant="discrete"] plus slider markers to prove the Toolcraft component variant rendered.', ); expect(slider.aiUsageRules).toContain( "Visual discrete sliders must still drag smoothly; their canonical performance path adapter should use dragToolcraftSliderByTarget for real pointer drag and let the central path profile own the budget.", ); expect(slider.aiUsageRules).toContain( 'Every product slider declares applicability as mode: "always" or mode: "conditional"; omitted applicability and control-level visibleWhen are rejected.', ); expect(slider.aiUsageRules).toContain( "Use conditional applicability for sliders that are meaningful only in some mode/type/source/include/count states; every predicate must match and inactive branches disappear.", ); expect(slider.aiUsageRules).toContain( "Do not use schema disabled: true or disabledWhen for product sliders, and do not leave a visible slider in a branch where its value has no product effect.", ); expect(rangeSlider.decisionCatalog.requiredAcceptance).toContain( "Prove dragging rangeSlider.lower and rangeSlider.upper both affect product output while the drag is in progress, not only after pointer release, blur, Apply, or a final commit.", ); expect(rangeSlider.aiUsageRules).toContain( "Range sliders are live canvas controls: dragging either thumb must update runtime state and product output in real time by default.", ); expect(rangeSlider.aiUsageRules).toContain( "Do not implement range slider values as deferred local drafts, Apply-only updates, pointer-up-only commits, or renderer changes that appear only after the user asks again.", ); expect(rangeSlider.aiUsageRules).toContain( "Range slider performance coverage must use a real control-drag scenario; control-change coverage is not enough to prove live canvas feedback or drag smoothness.", ); expect(rangeSlider.aiUsageRules).toContain( "If a live range slider causes jank, optimize the renderer path first: update uniforms or stable buffers, cache expensive inputs, coalesce preview work to requestAnimationFrame, cancel stale async renders, move heavy work off React, or switch renderer strategy.", ); expect(rangeSlider.aiUsageRules).toContain( "Only in an extreme documented performance ceiling may a range slider use a degraded live preview or delayed heavy refinement; the user must still see immediate canvas feedback while dragging and the worklog must record the measured reason.", ); expect(rangeSlider.aiUsageRules).toContain( "Range slider step means numeric snapping only; it does not make the range slider visually discrete by itself.", ); expect(rangeSlider.aiUsageRules).toContain( "Classify every stepped range slider as stepped continuous or visual discrete before writing the schema.", ); expect(rangeSlider.aiUsageRules).toContain( 'Small semantic integer domains such as rows, cols, gaps, jitter, counts, levels, bands, passes, points, tiles, and segments must use variant: "discrete".', ); expect(rangeSlider.aiUsageRules).toContain( 'Finite animation step domains such as flip depth, character count, glyph steps, and frame steps must use variant: "discrete" when the marker count stays within the Toolcraft visual budget.', ); expect(rangeSlider.aiUsageRules).toContain( 'Visual discrete slider and range-slider variants require authored finite min and max plus a positive step, and may expose at most 32 value positions including endpoints. At 33 or more positions, keep sliderValueKind "discrete" and step but use the continuous visual variant or another built-in control.', ); expect(rangeSlider.aiUsageRules).toContain( "Large or precision stepped ranges such as speed, FPS, rate, duration, density, size, and intensity stay visually continuous even when they declare step.", ); expect(rangeSlider.aiUsageRules).toContain( "Use rangeSlider unit only for real measurement suffixes; do not use it for repeated entity nouns already named by the section or label, and do not use x as a unit.", ); expect(rangeSlider.aiUsageRules).toContain( "When a range label needs an entity noun to make sense, improve the label or section title instead of appending that noun as the value unit.", ); expect(rangeSlider.aiUsageRules).toContain( "Compact symbol/CSS units render tight, such as 20% – 80% or 12px – 48px; word units render with a space when truly needed.", ); expect(rangeSlider.aiUsageRules).toContain( "RangeSlider is always a full-width two-thumb control; never place it in an inline two-column layout group with another slider or range slider.", ); expect(rangeSlider.aiUsageRules).toContain( "RangeSlider defaultValue must start with different lower and upper values so the two-thumb control does not collapse into a single-value slider.", ); expect(rangeSlider.aiUsageRules).toContain( "Manual range value editing accepts common separators such as slash, hyphen, spaces, and dashes, including when values include unit suffixes such as 30%-150% or 30% - 90%; do not create custom parsers for RangeSlider labels.", ); expect(rangeSlider.aiUsageRules).toContain( "Visual discrete sliders must still drag smoothly; their canonical performance path adapter should use dragToolcraftSliderByTarget for real pointer drag and let the central path profile own the budget.", ); expect(rangeSlider.aiUsageRules).toContain( 'Every product range slider declares applicability as mode: "always" or mode: "conditional"; omitted applicability and control-level visibleWhen are rejected.', ); expect(rangeSlider.aiUsageRules).toContain( "Use conditional applicability for range sliders that are meaningful only in some mode/type/source/include/count states; every predicate must match and inactive branches disappear.", ); expect(rangeSlider.aiUsageRules).toContain( "Do not use schema disabled: true or disabledWhen for product range sliders, and do not leave a visible range slider in a branch where its value has no product effect.", ); expect(rangeSlider.aiUsageRules).toContain( "Acceptance must prove both rangeSlider.lower and rangeSlider.upper change the product output; testing one handle is not enough.", ); }); it("documents CodeTextarea as generic multiline text input", () => { const contract = getToolcraftComponentContract("code"); expect(contract.visualComponent).toBe("CodeTextarea"); expect(contract.defaultSectionLayout).toBe("standalone"); expect(contract.labelPolicy).toBe("required"); expect(contract.aiUsageRules).toContain( "CodeTextarea is the multiline text input for any potentially long value, not only source code.", ); expect(contract.aiUsageRules).toContain( 'Every CodeTextarea declares textValueKind: "multiline" or "structured". Control selection must not be inferred from English labels or a short default value.', ); expect(contract.aiUsageRules).toContain( "Do not use CodeTextarea for short single-line canvas text, button labels, names, titles, captions, badges, or short tokens; use TextInput.", ); expect(contract.aiUsageRules).toContain( "Use text for short single-line strings such as names, button labels, small numeric values, compact prompts, titles, captions, and short tokens.", ); expect(contract.aiUsageRules).toContain( "Use code only when the user may enter long prompts, multiline text, instructions, JSON, CSS, shader code, scripts, templates, or other long structured data.", ); expect(contract.aiUsageRules).toContain( "If CodeTextarea has a short single-line default value, the schema description must make the long, multiline, or structured-content reason explicit.", ); expect(contract.aiUsageRules).toContain( "CodeTextarea is a content editor and applies values while typing; do not wait for blur, Enter, or Cmd/Ctrl+Enter to update runtime state.", ); expect(contract.aiUsageRules).toContain( "CodeTextarea height is capped at 12 visible text lines; long content scrolls inside the textarea instead of making the controls panel taller.", ); }); it("documents TextInput content and setting commit modes", () => { const contract = getToolcraftComponentContract("text"); expect(contract.visualComponent).toBe("TextInput"); expect(contract.defaultSectionLayout).toBe("grouped"); expect(contract.labelPolicy).toBe("required"); expect(contract.aiUsageRules).toContain( "TextInput owns short single-line product text: button labels, labels on the canvas, names, titles, captions, badges, short tokens, and compact prompts.", ); expect(contract.aiUsageRules).toContain( 'Every TextInput declares textValueKind: "single-line". Control selection must not be inferred from the label, description, or default string length.', ); expect(contract.aiUsageRules).toContain( 'TextInput commitMode defaults to "content": text content, prompts, names, tokens, titles, and instructions apply while typing.', ); expect(contract.aiUsageRules).toContain( 'Use commitMode: "setting" for text inputs that edit settings such as font size, numeric-like style values, dimensions, ids, or configuration fields; setting text commits on blur or Enter.', ); expect(contract.aiUsageRules).toContain( "Canvas width and Canvas height are runtime editable-size fields and always commit on blur or Enter like editor size fields.", ); }); });