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( "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 legacy visibleWhen are runtime compatibility only.', ); 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( "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 legacy visibleWhen are runtime compatibility only.', ); 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.", ); }); });