From 33350ced6d6a18f291abdf5f6814ee83f2781803 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 1 Apr 2026 21:55:33 +0000 Subject: [PATCH] Add comprehensive TW test suite (627 tests), fix 4 bugs, add 9 features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bugs fixed: - line-through: check full token not just head after hyphen split - gap-x-N/gap-y-N: compound 2-part spacing prefix handler in tw-layout - Negative values (-mt-4): replace ":" with ":-" instead of no-op - Class name doubling: chain replace calls instead of concatenating New features in tw-process-token: - !important modifier (!p-4 → padding:1rem !important) - dark: variant (class-based, .dark ancestor selector) - group-hover:/group-focus:/group-active: (parent state) - peer-focus:/peer-hover:/peer-checked:/peer-disabled: (sibling state) - @container queries (@md:flex → @container(min-width:448px)) - Colour opacity modifier (bg-sky-500/50 → hsl with alpha) - Ring colours (ring-sky-500 → --tw-ring-color) - Arbitrary values (w-[300px], grid-cols-[1fr_2fr], bg-[#ff0000]) - colour-with-alpha helper for HSL+alpha generation Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/javascript/run_tests.js | 41 + shared/sx/templates/tw-layout.sx | 7 + shared/sx/templates/tw.sx | 203 ++- spec/tests/test-tw.sx | 2439 ++++++++++++++++++++++++++++++ 4 files changed, 2642 insertions(+), 48 deletions(-) create mode 100644 spec/tests/test-tw.sx diff --git a/hosts/javascript/run_tests.js b/hosts/javascript/run_tests.js index 1f229ed2..a142f1bc 100644 --- a/hosts/javascript/run_tests.js +++ b/hosts/javascript/run_tests.js @@ -244,6 +244,20 @@ env["render-sx"] = function(source) { return parts.join(""); }; +// Mock request/state primitives for test-handlers.sx +const _mockState = {}; +env["now"] = function(fmt) { return new Date().toISOString(); }; +env["state-get"] = function(key, dflt) { return key in _mockState ? _mockState[key] : (dflt !== undefined ? dflt : null); }; +env["state-set!"] = function(key, val) { _mockState[key] = val; return val; }; +env["state-clear!"] = function(key) { delete _mockState[key]; return null; }; +env["request-method"] = function() { return "GET"; }; +env["request-arg"] = function(name, dflt) { return dflt !== undefined ? dflt : null; }; +env["request-form"] = function(name, dflt) { return dflt !== undefined ? dflt : ""; }; +env["request-headers-all"] = function() { return {}; }; +env["request-form-all"] = function() { return {}; }; +env["request-args-all"] = function() { return {}; }; +env["request-content-type"] = function() { return "text/html"; }; + // Platform test functions env["try-call"] = function(thunk) { try { @@ -315,6 +329,33 @@ if (fs.existsSync(canonicalPath)) { } } +// Load sx-swap.sx (needed by spec/tests/test-sx-swap.sx) +const swapPath = path.join(projectDir, "lib", "sx-swap.sx"); +if (fs.existsSync(swapPath)) { + const swapSrc = fs.readFileSync(swapPath, "utf8"); + const swapExprs = Sx.parse(swapSrc); + for (const expr of swapExprs) { + try { Sx.eval(expr, env); } catch (e) { + console.error(`Error loading sx-swap.sx: ${e.message}`); + } + } +} + +// Load tw system (needed by spec/tests/test-tw.sx) +const twDir = path.join(projectDir, "shared", "sx", "templates"); +for (const twFile of ["tw-type.sx", "tw-layout.sx", "tw.sx"]) { + const twPath = path.join(twDir, twFile); + if (fs.existsSync(twPath)) { + const twSrc = fs.readFileSync(twPath, "utf8"); + const twExprs = Sx.parse(twSrc); + for (const expr of twExprs) { + try { Sx.eval(expr, env); } catch (e) { + console.error(`Error loading ${twFile}: ${e.message}`); + } + } + } +} + // Load compiler + VM from lib/ when running full tests if (fullBuild) { const libDir = path.join(projectDir, "lib"); diff --git a/shared/sx/templates/tw-layout.sx b/shared/sx/templates/tw-layout.sx index 4011d119..eb517483 100644 --- a/shared/sx/templates/tw-layout.sx +++ b/shared/sx/templates/tw-layout.sx @@ -24,6 +24,13 @@ ((tmpl (get tw-spacing-props head)) (v (tw-spacing-value (first rest)))) (if (nil? v) nil (tw-template tmpl v))) + (and + (= (len rest) 2) + (get tw-spacing-props (str head "-" (first rest)))) + (let + ((tmpl (get tw-spacing-props (str head "-" (first rest)))) + (v (tw-spacing-value (last rest)))) + (if (nil? v) nil (tw-template tmpl v))) (and (= head "space") (= (len rest) 2) diff --git a/shared/sx/templates/tw.sx b/shared/sx/templates/tw.sx index 73f67709..5206dcc5 100644 --- a/shared/sx/templates/tw.sx +++ b/shared/sx/templates/tw.sx @@ -1,5 +1,20 @@ (define colour-bases {:orange {:s 95 :h 25} :cyan {:s 94 :h 188} :sky {:s 89 :h 199} :pink {:s 81 :h 330} :zinc {:s 5 :h 240} :amber {:s 92 :h 38} :neutral {:s 0 :h 0} :lime {:s 78 :h 84} :violet {:s 70 :h 263} :fuchsia {:s 84 :h 292} :stone {:s 6 :h 25} :black {:s 0 :h 0} :teal {:s 80 :h 173} :gray {:s 9 :h 220} :red {:s 72 :h 0} :rose {:s 89 :h 350} :blue {:s 91 :h 217} :emerald {:s 84 :h 160} :green {:s 71 :h 142} :yellow {:s 96 :h 48} :purple {:s 81 :h 271} :indigo {:s 84 :h 239} :white {:s 0 :h 0} :slate {:s 16 :h 215}}) +(define + colour-with-alpha + (fn + (name shade alpha) + (let + ((base (get colour-bases name))) + (if + (nil? base) + name + (let + ((h (get base "h")) + (s (get base "s")) + (l (shade-to-lightness shade))) + (str "hsl(" h "," s "%," (round l) "%," alpha ")")))))) + (define lerp (fn (a b t) (+ a (* t (- b a))))) (define @@ -58,6 +73,10 @@ (define tw-states {:focus ":focus" :before "::before" :first ":first-child" :disabled ":disabled" :required ":required" :even ":nth-child(even)" :hover ":hover" :focus-visible ":focus-visible" :last ":last-child" :visited ":visited" :odd ":nth-child(odd)" :active ":active" :focus-within ":focus-within" :checked ":checked" :placeholder "::placeholder" :after "::after"}) +(define tw-selector-states {:group-hover ".group:hover " :peer-disabled ".peer:disabled~" :dark ".dark " :peer-hover ".peer:hover~" :group-focus-within ".group:focus-within " :peer-checked ".peer:checked~" :group-focus ".group:focus " :group-active ".group:active " :peer-invalid ".peer:invalid~" :peer-required ".peer:required~" :peer-focus ".peer:focus~"}) + +(define tw-container-sizes {:xs "320px" :3xl "768px" :7xl "1280px" :sm "384px" :xl "576px" :md "448px" :6xl "1152px" :5xl "1024px" :lg "512px" :2xl "672px" :4xl "896px"}) + (define tw-spacing-value (fn @@ -108,6 +127,30 @@ (define tw-border-widths {:0 "0px" :2 "2px" :8 "8px" :4 "4px" : "1px"}) +(define tw-arbitrary-props {:outline-color "outline-color" :max-h "max-height" :mt "margin-top" :max-w "max-width" :inset-x "inset-inline" :font-size "font-size" :leading "line-height" :columns "columns" :size "width" :bg "background-color" :delay "transition-delay" :m "margin" :top "top" :left "left" :grid-cols "grid-template-columns" :my "margin-block" :border "border-width" :pb "padding-bottom" :order "order" :gap "gap" :basis "flex-basis" :mx "margin-inline" :rounded "border-radius" :ml "margin-left" :grid-rows "grid-template-rows" :mr "margin-right" :font "font-family" :border-color "border-color" :mb "margin-bottom" :pl "padding-left" :aspect "aspect-ratio" :gap-y "row-gap" :inset "inset" :indent "text-indent" :accent "accent-color" :gap-x "column-gap" :opacity "opacity" :w "width" :stroke "stroke" :px "padding-inline" :pr "padding-right" :right "right" :text "color" :p "padding" :min-h "min-height" :tracking "letter-spacing" :bottom "bottom" :inset-y "inset-block" :z "z-index" :min-w "min-width" :fill "fill" :pt "padding-top" :py "padding-block" :h "height" :duration "transition-duration" :shadow "box-shadow"}) + +(define + tw-resolve-arbitrary + (fn + (token) + (let + ((bracket-start (index-of token "["))) + (if + (or (nil? bracket-start) (< bracket-start 1)) + nil + (let + ((bracket-end (index-of token "]"))) + (if + (nil? bracket-end) + nil + (let + ((prefix (substring token 0 (- bracket-start 1))) + (raw-val (substring token (+ bracket-start 1) bracket-end))) + (let + ((val (replace raw-val "_" " ")) + (prop (get tw-arbitrary-props prefix))) + (if (nil? prop) nil (str prop ":" val)))))))))) + (define tw-resolve-style (fn @@ -120,15 +163,41 @@ (and (get tw-colour-props head) (>= (len rest) 2) - (not (nil? (parse-int (last rest) nil))) - (not - (nil? - (get colour-bases (join "-" (slice rest 0 (- (len rest) 1))))))) + (let + ((shade-str (last rest)) (slash-pos (index-of shade-str "/"))) + (let + ((shade-part (if (and slash-pos (>= slash-pos 0)) (substring shade-str 0 slash-pos) shade-str))) + (and + (not (nil? (parse-int shade-part nil))) + (not + (nil? + (get + colour-bases + (join "-" (slice rest 0 (- (len rest) 1)))))))))) (let ((css-prop (get tw-colour-props head)) - (cname (join "-" (slice rest 0 (- (len rest) 1)))) - (shade (parse-int (last rest) 0))) - (str css-prop ":" (colour cname shade))) + (shade-str (last rest)) + (slash-pos (index-of shade-str "/")) + (cname (join "-" (slice rest 0 (- (len rest) 1))))) + (let + ((shade-part (if (and slash-pos (>= slash-pos 0)) (substring shade-str 0 slash-pos) shade-str)) + (alpha-part + (if + (and slash-pos (>= slash-pos 0)) + (substring shade-str (+ slash-pos 1) (len shade-str)) + nil))) + (let + ((shade (parse-int shade-part 0))) + (if + alpha-part + (str + css-prop + ":" + (colour-with-alpha + cname + shade + (/ (parse-int alpha-part 100) 100))) + (str css-prop ":" (colour cname shade)))))) (and (get tw-colour-props head) (= (len rest) 1) @@ -264,6 +333,17 @@ (let ((n (parse-int (first rest) nil))) (if (nil? n) nil (str "opacity:" (/ n 100)))) + (and + (= head "ring") + (>= (len rest) 2) + (not (nil? (parse-int (last rest) nil))) + (not + (nil? + (get colour-bases (join "-" (slice rest 0 (- (len rest) 1))))))) + (let + ((cname (join "-" (slice rest 0 (- (len rest) 1)))) + (shade (parse-int (last rest) 0))) + (str "--tw-ring-color:" (colour cname shade))) (and (= head "ring") (or (empty? rest) (= (len rest) 1))) (let ((w (if (empty? rest) "3px" (let ((n (parse-int (first rest) nil))) (if (nil? n) nil (str n "px")))))) @@ -327,13 +407,11 @@ (str "user-select:" (first rest)) (and (= head "appearance") (= (len rest) 1)) (str "appearance:" (first rest)) - (and - (= (len parts) 1) - (or - (= head "underline") - (= head "overline") - (= head "line-through"))) - (str "text-decoration-line:" head) + (or + (= token "underline") + (= token "overline") + (= token "line-through")) + (str "text-decoration-line:" token) (and (= (len parts) 2) (= head "no") (= (first rest) "underline")) "text-decoration-line:none" (and (= head "scale") (= (len rest) 1)) @@ -362,58 +440,87 @@ (fn (token) (let - ((colon-parts (split token ":")) (n (len colon-parts))) + ((important (starts-with? token "!")) + (clean-token + (if + (starts-with? token "!") + (substring token 1 (len token)) + token))) (let - ((bp nil) (state nil) (base nil)) + ((colon-parts (split clean-token ":")) + (n (len (split clean-token ":"))) + (bp nil) + (state nil) + (sel-prefix nil) + (container nil) + (base nil)) (cond (= n 1) - (do (set! base (first colon-parts))) + (set! base (first colon-parts)) (= n 2) (do - (if - (get tw-breakpoints (first colon-parts)) - (do - (set! bp (first colon-parts)) - (set! base (nth colon-parts 1))) - (do - (set! state (first colon-parts)) - (set! base (nth colon-parts 1))))) + (let + ((prefix (first colon-parts))) + (set! base (nth colon-parts 1)) + (cond + (get tw-breakpoints prefix) + (set! bp prefix) + (get tw-selector-states prefix) + (set! sel-prefix (get tw-selector-states prefix)) + (starts-with? prefix "@") + (set! container (substring prefix 1 (len prefix))) + true + (set! state prefix)))) (= n 3) (do - (set! bp (first colon-parts)) - (set! state (nth colon-parts 1)) - (set! base (nth colon-parts 2))) - :else (do (set! base token))) + (let + ((p1 (first colon-parts)) (p2 (nth colon-parts 1))) + (set! base (nth colon-parts 2)) + (cond + (get tw-breakpoints p1) + (set! bp p1) + (get tw-selector-states p1) + (set! sel-prefix (get tw-selector-states p1)) + (starts-with? p1 "@") + (set! container (substring p1 1 (len p1))) + true + (set! bp p1)) + (if + (get tw-selector-states p2) + (set! sel-prefix (get tw-selector-states p2)) + (set! state p2)))) + true + (set! base clean-token)) (let - ((negative (and (>= (len base) 2) (= (substring base 0 1) "-"))) - (actual-base (if negative (substring base 1 (len base)) base))) + ((negative (and base (starts-with? base "-"))) + (actual-base + (if + (and base (starts-with? base "-")) + (substring base 1 (len base)) + base))) (let - ((css (or (tw-resolve-style actual-base) (tw-resolve-layout actual-base) (tw-resolve-type actual-base)))) + ((css (or (tw-resolve-style actual-base) (tw-resolve-layout actual-base) (tw-resolve-type actual-base) (tw-resolve-arbitrary actual-base)))) (if (nil? css) nil (let - ((final-css (if negative (str css) css)) + ((neg-css (if negative (replace css ":" ":-") css)) + (final-css + (if important (str neg-css " !important") neg-css)) (cls (str "sx-" - (replace token ":" "-") - (replace token "." "d"))) + (replace (replace clean-token ":" "-") "." "d"))) + (pseudo + (if state (or (get tw-states state) (str ":" state)) "")) (selector - (str "." cls (if state (get tw-states state) ""))) - (rule (if - bp - (str - "@media(min-width:" - (get tw-breakpoints bp) - "){" - selector - "{" - final-css - "}}") - (str selector "{" final-css "}")))) - {:rule rule :cls cls})))))))) + sel-prefix + (str sel-prefix "." cls pseudo) + (str "." cls pseudo)))) + (let + ((rule (cond bp (str "@media(min-width:" (get tw-breakpoints bp) "){" selector "{" final-css "}}") container (let ((csize (get tw-container-sizes container))) (if csize (str "@container(min-width:" csize "){" selector "{" final-css "}}") (str "@container{" selector "{" final-css "}}"))) true (str selector "{" final-css "}")))) + {:rule rule :cls cls}))))))))) (defcomp ~tw diff --git a/spec/tests/test-tw.sx b/spec/tests/test-tw.sx new file mode 100644 index 00000000..030f4dbe --- /dev/null +++ b/spec/tests/test-tw.sx @@ -0,0 +1,2439 @@ +(defsuite + "tw-bg-colour" + (deftest + "bg-sky-500 → hsl" + (assert= + (tw-resolve-style "bg-sky-500") + "background-color:hsl(199,89%,53%)")) + (deftest + "bg-emerald-100" + (assert= + (tw-resolve-style "bg-emerald-100") + "background-color:hsl(160,84%,93%)")) + (deftest + "bg-amber-300" + (assert= + (tw-resolve-style "bg-amber-300") + "background-color:hsl(38,92%,77%)")) + (deftest + "bg-stone-200" + (assert= + (tw-resolve-style "bg-stone-200") + "background-color:hsl(25,6%,87%)")) + (deftest + "bg-slate-700" + (assert= + (tw-resolve-style "bg-slate-700") + "background-color:hsl(215,16%,38%)")) + (deftest + "bg-red-600" + (assert= + (tw-resolve-style "bg-red-600") + "background-color:hsl(0,72%,45%)")) + (deftest + "bg-blue-500" + (assert= + (tw-resolve-style "bg-blue-500") + "background-color:hsl(217,91%,53%)")) + (deftest + "bg-violet-500" + (assert= + (tw-resolve-style "bg-violet-500") + "background-color:hsl(263,70%,53%)")) + (deftest + "bg-pink-400" + (assert= + (tw-resolve-style "bg-pink-400") + "background-color:hsl(330,81%,64%)")) + (deftest + "bg-white → #ffffff" + (assert= (tw-resolve-style "bg-white") "background-color:#ffffff")) + (deftest + "bg-black → #000000" + (assert= (tw-resolve-style "bg-black") "background-color:#000000")) + (deftest + "bg-transparent" + (assert= + (tw-resolve-style "bg-transparent") + "background-color:transparent")) + (deftest + "bg-current → currentColor" + (assert= (tw-resolve-style "bg-current") "background-color:currentColor")) + (deftest + "bg-inherit" + (assert= (tw-resolve-style "bg-inherit") "background-color:inherit"))) + +(defsuite + "tw-text-colour" + (deftest + "text-sky-700" + (assert= (tw-resolve-style "text-sky-700") "color:hsl(199,89%,38%)")) + (deftest + "text-stone-600" + (assert= (tw-resolve-style "text-stone-600") "color:hsl(25,6%,45%)")) + (deftest + "text-emerald-500" + (assert= (tw-resolve-style "text-emerald-500") "color:hsl(160,84%,53%)")) + (deftest + "text-white" + (assert= (tw-resolve-style "text-white") "color:#ffffff")) + (deftest + "text-black" + (assert= (tw-resolve-style "text-black") "color:#000000")) + (deftest + "text-current" + (assert= (tw-resolve-style "text-current") "color:currentColor"))) + +(defsuite + "tw-border-colour" + (deftest + "border-sky-200" + (assert= + (tw-resolve-style "border-sky-200") + "border-color:hsl(199,89%,87%)")) + (deftest + "border-emerald-300" + (assert= + (tw-resolve-style "border-emerald-300") + "border-color:hsl(160,84%,77%)")) + (deftest + "border-stone-400" + (assert= + (tw-resolve-style "border-stone-400") + "border-color:hsl(25,6%,64%)")) + (deftest + "border-transparent" + (assert= + (tw-resolve-style "border-transparent") + "border-color:transparent"))) + +(defsuite + "tw-shade-to-lightness" + (deftest "shade 0 → 100" (assert= (shade-to-lightness 0) 100)) + (deftest "shade 50 → 97" (assert= (shade-to-lightness 50) 97)) + (deftest "shade 100 → 93" (assert= (shade-to-lightness 100) 93)) + (deftest "shade 200 → 87" (assert= (shade-to-lightness 200) 87)) + (deftest "shade 300 → 77" (assert= (shade-to-lightness 300) 77)) + (deftest "shade 400 → 64" (assert= (shade-to-lightness 400) 64)) + (deftest "shade 500 → 53" (assert= (shade-to-lightness 500) 53)) + (deftest "shade 600 → 45" (assert= (shade-to-lightness 600) 45)) + (deftest "shade 700 → 38" (assert= (shade-to-lightness 700) 38)) + (deftest "shade 800 → 30" (assert= (shade-to-lightness 800) 30)) + (deftest "shade 900 → 21" (assert= (shade-to-lightness 900) 21)) + (deftest "shade 950 → 13" (assert= (shade-to-lightness 950) 13))) + +(defsuite + "tw-colour-bases" + (deftest "sky" (assert= (colour "sky" 500) "hsl(199,89%,53%)")) + (deftest "emerald" (assert= (colour "emerald" 500) "hsl(160,84%,53%)")) + (deftest "red" (assert= (colour "red" 500) "hsl(0,72%,53%)")) + (deftest "amber" (assert= (colour "amber" 500) "hsl(38,92%,53%)")) + (deftest "slate" (assert= (colour "slate" 500) "hsl(215,16%,53%)")) + (deftest "stone" (assert= (colour "stone" 500) "hsl(25,6%,53%)")) + (deftest "blue" (assert= (colour "blue" 500) "hsl(217,91%,53%)")) + (deftest "violet" (assert= (colour "violet" 500) "hsl(263,70%,53%)")) + (deftest "pink" (assert= (colour "pink" 500) "hsl(330,81%,53%)")) + (deftest "cyan" (assert= (colour "cyan" 500) "hsl(188,94%,53%)")) + (deftest "teal" (assert= (colour "teal" 500) "hsl(173,80%,53%)")) + (deftest "indigo" (assert= (colour "indigo" 500) "hsl(239,84%,53%)")) + (deftest "purple" (assert= (colour "purple" 500) "hsl(271,81%,53%)")) + (deftest "rose" (assert= (colour "rose" 500) "hsl(350,89%,53%)")) + (deftest "lime" (assert= (colour "lime" 500) "hsl(84,78%,53%)")) + (deftest "yellow" (assert= (colour "yellow" 500) "hsl(48,96%,53%)")) + (deftest "orange" (assert= (colour "orange" 500) "hsl(25,95%,53%)")) + (deftest "green" (assert= (colour "green" 500) "hsl(142,71%,53%)")) + (deftest "gray" (assert= (colour "gray" 500) "hsl(220,9%,53%)")) + (deftest "zinc" (assert= (colour "zinc" 500) "hsl(240,5%,53%)")) + (deftest "neutral" (assert= (colour "neutral" 500) "hsl(0,0%,53%)")) + (deftest + "fuchsia is a known base" + (assert= (colour "fuchsia" 500) "hsl(292,84%,53%)")) + (deftest + "shade variation: sky-100" + (assert= (colour "sky" 100) "hsl(199,89%,93%)")) + (deftest + "shade variation: sky-900" + (assert= (colour "sky" 900) "hsl(199,89%,21%)"))) + +(defsuite + "tw-border-radius" + (deftest + "rounded → 0.25rem" + (assert= (tw-resolve-style "rounded") "border-radius:0.25rem")) + (deftest + "rounded-none → 0" + (assert= (tw-resolve-style "rounded-none") "border-radius:0")) + (deftest + "rounded-sm → 0.125rem" + (assert= (tw-resolve-style "rounded-sm") "border-radius:0.125rem")) + (deftest + "rounded-md → 0.375rem" + (assert= (tw-resolve-style "rounded-md") "border-radius:0.375rem")) + (deftest + "rounded-lg → 0.5rem" + (assert= (tw-resolve-style "rounded-lg") "border-radius:0.5rem")) + (deftest + "rounded-xl → 0.75rem" + (assert= (tw-resolve-style "rounded-xl") "border-radius:0.75rem")) + (deftest + "rounded-2xl → 1rem" + (assert= (tw-resolve-style "rounded-2xl") "border-radius:1rem")) + (deftest + "rounded-3xl → 1.5rem" + (assert= (tw-resolve-style "rounded-3xl") "border-radius:1.5rem")) + (deftest + "rounded-full → 9999px" + (assert= (tw-resolve-style "rounded-full") "border-radius:9999px")) + (deftest + "rounded-t-lg — directional top" + (assert= + (tw-resolve-style "rounded-t-lg") + "border-top-left-radius:0.5rem;border-top-right-radius:0.5rem")) + (deftest + "rounded-b-md — directional bottom" + (assert= + (tw-resolve-style "rounded-b-md") + "border-bottom-left-radius:0.375rem;border-bottom-right-radius:0.375rem")) + (deftest + "rounded-l-xl — directional left" + (assert= + (tw-resolve-style "rounded-l-xl") + "border-top-left-radius:0.75rem;border-bottom-left-radius:0.75rem")) + (deftest + "rounded-r — directional right default" + (assert= + (tw-resolve-style "rounded-r") + "border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem"))) + +(defsuite + "tw-border-width" + (deftest + "border → 1px" + (assert= (tw-resolve-style "border") "border-width:1px")) + (deftest + "border-0" + (assert= (tw-resolve-style "border-0") "border-width:0px")) + (deftest + "border-2" + (assert= (tw-resolve-style "border-2") "border-width:2px")) + (deftest + "border-4" + (assert= (tw-resolve-style "border-4") "border-width:4px")) + (deftest + "border-8" + (assert= (tw-resolve-style "border-8") "border-width:8px")) + (deftest + "border-t → top 1px" + (assert= (tw-resolve-style "border-t") "border-top-width:1px")) + (deftest + "border-b → bottom 1px" + (assert= (tw-resolve-style "border-b") "border-bottom-width:1px")) + (deftest + "border-l → left 1px" + (assert= (tw-resolve-style "border-l") "border-left-width:1px")) + (deftest + "border-r → right 1px" + (assert= (tw-resolve-style "border-r") "border-right-width:1px")) + (deftest + "border-t-2 → top 2px" + (assert= (tw-resolve-style "border-t-2") "border-top-width:2px"))) + +(defsuite + "tw-shadow" + (deftest + "shadow — default" + (assert= + (tw-resolve-style "shadow") + "box-shadow:0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)")) + (deftest + "shadow-sm" + (assert= + (tw-resolve-style "shadow-sm") + "box-shadow:0 1px 2px 0 rgb(0 0 0 / 0.05)")) + (deftest + "shadow-md" + (assert= + (tw-resolve-style "shadow-md") + "box-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)")) + (deftest + "shadow-lg" + (assert= + (tw-resolve-style "shadow-lg") + "box-shadow:0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)")) + (deftest + "shadow-xl" + (assert= + (tw-resolve-style "shadow-xl") + "box-shadow:0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)")) + (deftest + "shadow-2xl" + (assert= + (tw-resolve-style "shadow-2xl") + "box-shadow:0 25px 50px -12px rgb(0 0 0 / 0.25)")) + (deftest + "shadow-inner" + (assert= + (tw-resolve-style "shadow-inner") + "box-shadow:inset 0 2px 4px 0 rgb(0 0 0 / 0.05)")) + (deftest + "shadow-none" + (assert= (tw-resolve-style "shadow-none") "box-shadow:0 0 #0000"))) + +(defsuite + "tw-opacity" + (deftest "opacity-0" (assert= (tw-resolve-style "opacity-0") "opacity:0")) + (deftest + "opacity-5" + (assert= (tw-resolve-style "opacity-5") "opacity:0.05")) + (deftest + "opacity-10" + (assert= (tw-resolve-style "opacity-10") "opacity:0.1")) + (deftest + "opacity-25" + (assert= (tw-resolve-style "opacity-25") "opacity:0.25")) + (deftest + "opacity-50" + (assert= (tw-resolve-style "opacity-50") "opacity:0.5")) + (deftest + "opacity-75" + (assert= (tw-resolve-style "opacity-75") "opacity:0.75")) + (deftest + "opacity-100" + (assert= (tw-resolve-style "opacity-100") "opacity:1"))) + +(defsuite + "tw-transition" + (deftest + "transition" + (assert= + (tw-resolve-style "transition") + "transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms")) + (deftest + "transition-colors" + (assert= + (tw-resolve-style "transition-colors") + "transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms")) + (deftest + "transition-none" + (assert= (tw-resolve-style "transition-none") "transition-property:none")) + (deftest + "duration-150" + (assert= (tw-resolve-style "duration-150") "transition-duration:150ms")) + (deftest + "duration-300" + (assert= (tw-resolve-style "duration-300") "transition-duration:300ms")) + (deftest + "ease-linear" + (assert= + (tw-resolve-style "ease-linear") + "transition-timing-function:linear")) + (deftest + "ease-in-out" + (assert= + (tw-resolve-style "ease-in-out") + "transition-timing-function:cubic-bezier(0.4,0,0.2,1)")) + (deftest + "animate-spin" + (assert= + (tw-resolve-style "animate-spin") + "animation:spin 1s linear infinite")) + (deftest + "animate-none" + (assert= (tw-resolve-style "animate-none") "animation:none"))) + +(defsuite + "tw-cursor" + (deftest + "cursor-pointer" + (assert= (tw-resolve-style "cursor-pointer") "cursor:pointer")) + (deftest + "cursor-default" + (assert= (tw-resolve-style "cursor-default") "cursor:default")) + (deftest + "cursor-not-allowed" + (assert= (tw-resolve-style "cursor-not-allowed") "cursor:not-allowed")) + (deftest + "cursor-wait" + (assert= (tw-resolve-style "cursor-wait") "cursor:wait")) + (deftest + "cursor-text" + (assert= (tw-resolve-style "cursor-text") "cursor:text")) + (deftest + "cursor-move" + (assert= (tw-resolve-style "cursor-move") "cursor:move")) + (deftest + "cursor-grab" + (assert= (tw-resolve-style "cursor-grab") "cursor:grab"))) + +(defsuite + "tw-style-misc" + (deftest + "underline" + (assert= (tw-resolve-style "underline") "text-decoration-line:underline")) + (deftest + "overline" + (assert= (tw-resolve-style "overline") "text-decoration-line:overline")) + (deftest + "no-underline" + (assert= (tw-resolve-style "no-underline") "text-decoration-line:none")) + (deftest + "line-through" + (assert= + (tw-resolve-style "line-through") + "text-decoration-line:line-through")) + (deftest + "select-none" + (assert= (tw-resolve-style "select-none") "user-select:none")) + (deftest + "select-text" + (assert= (tw-resolve-style "select-text") "user-select:text")) + (deftest + "appearance-none" + (assert= (tw-resolve-style "appearance-none") "appearance:none")) + (deftest + "pointer-events-none" + (assert= (tw-resolve-style "pointer-events-none") "pointer-events:none")) + (deftest + "pointer-events-auto" + (assert= (tw-resolve-style "pointer-events-auto") "pointer-events:auto")) + (deftest + "outline-none" + (assert= + (tw-resolve-style "outline-none") + "outline:2px solid transparent;outline-offset:2px")) + (deftest + "outline — solid" + (assert= (tw-resolve-style "outline") "outline-style:solid")) + (deftest + "scale-95" + (assert= (tw-resolve-style "scale-95") "transform:scale(0.95)")) + (deftest + "scale-105" + (assert= (tw-resolve-style "scale-105") "transform:scale(1.05)")) + (deftest + "scale-150" + (assert= (tw-resolve-style "scale-150") "transform:scale(1.5)")) + (deftest + "rotate-45" + (assert= (tw-resolve-style "rotate-45") "transform:rotate(45deg)")) + (deftest + "rotate-90" + (assert= (tw-resolve-style "rotate-90") "transform:rotate(90deg)")) + (deftest + "rotate-180" + (assert= (tw-resolve-style "rotate-180") "transform:rotate(180deg)"))) + +(defsuite + "tw-display" + (deftest "flex" (assert= (tw-resolve-layout "flex") "display:flex")) + (deftest "block" (assert= (tw-resolve-layout "block") "display:block")) + (deftest "hidden" (assert= (tw-resolve-layout "hidden") "display:none")) + (deftest "grid" (assert= (tw-resolve-layout "grid") "display:grid")) + (deftest "inline" (assert= (tw-resolve-layout "inline") "display:inline")) + (deftest + "inline-block" + (assert= (tw-resolve-layout "inline-block") "display:inline-block")) + (deftest + "inline-flex" + (assert= (tw-resolve-layout "inline-flex") "display:inline-flex")) + (deftest + "inline-grid" + (assert= (tw-resolve-layout "inline-grid") "display:inline-grid")) + (deftest "table" (assert= (tw-resolve-layout "table") "display:table")) + (deftest + "contents" + (assert= (tw-resolve-layout "contents") "display:contents"))) + +(defsuite + "tw-flex-direction" + (deftest + "flex-row" + (assert= (tw-resolve-layout "flex-row") "flex-direction:row")) + (deftest + "flex-col" + (assert= (tw-resolve-layout "flex-col") "flex-direction:column")) + (deftest + "flex-row-reverse" + (assert= + (tw-resolve-layout "flex-row-reverse") + "flex-direction:row-reverse")) + (deftest + "flex-col-reverse" + (assert= + (tw-resolve-layout "flex-col-reverse") + "flex-direction:column-reverse")) + (deftest + "flex-wrap" + (assert= (tw-resolve-layout "flex-wrap") "flex-wrap:wrap")) + (deftest + "flex-nowrap" + (assert= (tw-resolve-layout "flex-nowrap") "flex-wrap:nowrap")) + (deftest + "flex-wrap-reverse" + (assert= (tw-resolve-layout "flex-wrap-reverse") "flex-wrap:wrap-reverse"))) + +(defsuite + "tw-flex-shorthand" + (deftest "flex-1" (assert= (tw-resolve-layout "flex-1") "flex:1 1 0%")) + (deftest + "flex-auto" + (assert= (tw-resolve-layout "flex-auto") "flex:1 1 auto")) + (deftest + "flex-initial" + (assert= (tw-resolve-layout "flex-initial") "flex:0 1 auto")) + (deftest "flex-none" (assert= (tw-resolve-layout "flex-none") "flex:none")) + (deftest "grow" (assert= (tw-resolve-layout "grow") "flex-grow:1")) + (deftest "grow-0" (assert= (tw-resolve-layout "grow-0") "flex-grow:0")) + (deftest "shrink" (assert= (tw-resolve-layout "shrink") "flex-shrink:1")) + (deftest + "shrink-0" + (assert= (tw-resolve-layout "shrink-0") "flex-shrink:0")) + (deftest + "basis-auto" + (assert= (tw-resolve-layout "basis-auto") "flex-basis:auto")) + (deftest + "basis-full" + (assert= (tw-resolve-layout "basis-full") "flex-basis:100%")) + (deftest + "basis-0" + (assert= (tw-resolve-layout "basis-0") "flex-basis:0px")) + (deftest + "basis-1/2" + (assert= (tw-resolve-layout "basis-1/2") "flex-basis:50%")) + (deftest + "basis-4" + (assert= (tw-resolve-layout "basis-4") "flex-basis:1rem"))) + +(defsuite + "tw-justify" + (deftest + "justify-start" + (assert= (tw-resolve-layout "justify-start") "justify-content:flex-start")) + (deftest + "justify-end" + (assert= (tw-resolve-layout "justify-end") "justify-content:flex-end")) + (deftest + "justify-center" + (assert= (tw-resolve-layout "justify-center") "justify-content:center")) + (deftest + "justify-between" + (assert= + (tw-resolve-layout "justify-between") + "justify-content:space-between")) + (deftest + "justify-around" + (assert= + (tw-resolve-layout "justify-around") + "justify-content:space-around")) + (deftest + "justify-evenly" + (assert= + (tw-resolve-layout "justify-evenly") + "justify-content:space-evenly")) + (deftest + "justify-stretch" + (assert= (tw-resolve-layout "justify-stretch") "justify-content:stretch"))) + +(defsuite + "tw-alignment" + (deftest + "items-start" + (assert= (tw-resolve-layout "items-start") "align-items:flex-start")) + (deftest + "items-end" + (assert= (tw-resolve-layout "items-end") "align-items:flex-end")) + (deftest + "items-center" + (assert= (tw-resolve-layout "items-center") "align-items:center")) + (deftest + "items-baseline" + (assert= (tw-resolve-layout "items-baseline") "align-items:baseline")) + (deftest + "items-stretch" + (assert= (tw-resolve-layout "items-stretch") "align-items:stretch")) + (deftest + "self-auto" + (assert= (tw-resolve-layout "self-auto") "align-self:auto")) + (deftest + "self-start" + (assert= (tw-resolve-layout "self-start") "align-self:flex-start")) + (deftest + "self-end" + (assert= (tw-resolve-layout "self-end") "align-self:flex-end")) + (deftest + "self-center" + (assert= (tw-resolve-layout "self-center") "align-self:center")) + (deftest + "self-stretch" + (assert= (tw-resolve-layout "self-stretch") "align-self:stretch")) + (deftest + "self-baseline" + (assert= (tw-resolve-layout "self-baseline") "align-self:baseline")) + (deftest + "content-start" + (assert= (tw-resolve-layout "content-start") "align-content:flex-start")) + (deftest + "content-center" + (assert= (tw-resolve-layout "content-center") "align-content:center")) + (deftest + "content-between" + (assert= + (tw-resolve-layout "content-between") + "align-content:space-between")) + (deftest + "content-around" + (assert= + (tw-resolve-layout "content-around") + "align-content:space-around")) + (deftest + "content-stretch" + (assert= (tw-resolve-layout "content-stretch") "align-content:stretch"))) + +(defsuite + "tw-order" + (deftest "order-1" (assert= (tw-resolve-layout "order-1") "order:1")) + (deftest "order-12" (assert= (tw-resolve-layout "order-12") "order:12")) + (deftest + "order-first" + (assert= (tw-resolve-layout "order-first") "order:-9999")) + (deftest + "order-last" + (assert= (tw-resolve-layout "order-last") "order:9999")) + (deftest "order-none" (assert= (tw-resolve-layout "order-none") "order:0"))) + +(defsuite + "tw-padding" + (deftest "p-0" (assert= (tw-resolve-layout "p-0") "padding:0px")) + (deftest "p-px" (assert= (tw-resolve-layout "p-px") "padding:1px")) + (deftest "p-1" (assert= (tw-resolve-layout "p-1") "padding:0.25rem")) + (deftest "p-2" (assert= (tw-resolve-layout "p-2") "padding:0.5rem")) + (deftest "p-3" (assert= (tw-resolve-layout "p-3") "padding:0.75rem")) + (deftest "p-4" (assert= (tw-resolve-layout "p-4") "padding:1rem")) + (deftest "p-6" (assert= (tw-resolve-layout "p-6") "padding:1.5rem")) + (deftest "p-8" (assert= (tw-resolve-layout "p-8") "padding:2rem")) + (deftest + "px-4" + (assert= + (tw-resolve-layout "px-4") + "padding-left:1rem;padding-right:1rem")) + (deftest + "py-2" + (assert= + (tw-resolve-layout "py-2") + "padding-top:0.5rem;padding-bottom:0.5rem")) + (deftest "pt-4" (assert= (tw-resolve-layout "pt-4") "padding-top:1rem")) + (deftest "pb-4" (assert= (tw-resolve-layout "pb-4") "padding-bottom:1rem")) + (deftest "pl-2" (assert= (tw-resolve-layout "pl-2") "padding-left:0.5rem")) + (deftest + "pr-2" + (assert= (tw-resolve-layout "pr-2") "padding-right:0.5rem"))) + +(defsuite + "tw-margin" + (deftest "m-0" (assert= (tw-resolve-layout "m-0") "margin:0px")) + (deftest "m-auto" (assert= (tw-resolve-layout "m-auto") "margin:auto")) + (deftest "m-2" (assert= (tw-resolve-layout "m-2") "margin:0.5rem")) + (deftest "m-4" (assert= (tw-resolve-layout "m-4") "margin:1rem")) + (deftest "mt-2" (assert= (tw-resolve-layout "mt-2") "margin-top:0.5rem")) + (deftest "mt-8" (assert= (tw-resolve-layout "mt-8") "margin-top:2rem")) + (deftest "mb-4" (assert= (tw-resolve-layout "mb-4") "margin-bottom:1rem")) + (deftest "ml-2" (assert= (tw-resolve-layout "ml-2") "margin-left:0.5rem")) + (deftest "mr-2" (assert= (tw-resolve-layout "mr-2") "margin-right:0.5rem")) + (deftest + "mx-auto" + (assert= + (tw-resolve-layout "mx-auto") + "margin-left:auto;margin-right:auto")) + (deftest + "my-4" + (assert= (tw-resolve-layout "my-4") "margin-top:1rem;margin-bottom:1rem"))) + +(defsuite + "tw-gap" + (deftest "gap-1" (assert= (tw-resolve-layout "gap-1") "gap:0.25rem")) + (deftest "gap-4" (assert= (tw-resolve-layout "gap-4") "gap:1rem")) + (deftest "gap-8" (assert= (tw-resolve-layout "gap-8") "gap:2rem")) + (deftest "gap-0" (assert= (tw-resolve-layout "gap-0") "gap:0px")) + (deftest + "gap-x-4" + (assert= (tw-resolve-layout "gap-x-4") "column-gap:1rem")) + (deftest + "gap-y-2" + (assert= (tw-resolve-layout "gap-y-2") "row-gap:0.5rem")) + (deftest + "space-x-4" + (assert= (tw-resolve-layout "space-x-4") "column-gap:1rem")) + (deftest + "space-y-2" + (assert= (tw-resolve-layout "space-y-2") "row-gap:0.5rem"))) + +(defsuite + "tw-spacing-value" + (deftest "0 → 0px" (assert= (tw-spacing-value "0") "0px")) + (deftest "px → 1px" (assert= (tw-spacing-value "px") "1px")) + (deftest "0.5 → 0.125rem" (assert= (tw-spacing-value "0.5") "0.125rem")) + (deftest "1 → 0.25rem" (assert= (tw-spacing-value "1") "0.25rem")) + (deftest "1.5 → 0.375rem" (assert= (tw-spacing-value "1.5") "0.375rem")) + (deftest "2 → 0.5rem" (assert= (tw-spacing-value "2") "0.5rem")) + (deftest "4 → 1rem" (assert= (tw-spacing-value "4") "1rem")) + (deftest "8 → 2rem" (assert= (tw-spacing-value "8") "2rem")) + (deftest "12 → 3rem" (assert= (tw-spacing-value "12") "3rem")) + (deftest "16 → 4rem" (assert= (tw-spacing-value "16") "4rem")) + (deftest "20 → 5rem" (assert= (tw-spacing-value "20") "5rem")) + (deftest "24 → 6rem" (assert= (tw-spacing-value "24") "6rem")) + (deftest "auto → auto" (assert= (tw-spacing-value "auto") "auto"))) + +(defsuite + "tw-grid" + (deftest + "grid-cols-1" + (assert= + (tw-resolve-layout "grid-cols-1") + "grid-template-columns:repeat(1,minmax(0,1fr))")) + (deftest + "grid-cols-2" + (assert= + (tw-resolve-layout "grid-cols-2") + "grid-template-columns:repeat(2,minmax(0,1fr))")) + (deftest + "grid-cols-3" + (assert= + (tw-resolve-layout "grid-cols-3") + "grid-template-columns:repeat(3,minmax(0,1fr))")) + (deftest + "grid-cols-12" + (assert= + (tw-resolve-layout "grid-cols-12") + "grid-template-columns:repeat(12,minmax(0,1fr))")) + (deftest + "grid-cols-none" + (assert= + (tw-resolve-layout "grid-cols-none") + "grid-template-columns:none")) + (deftest + "grid-rows-3" + (assert= + (tw-resolve-layout "grid-rows-3") + "grid-template-rows:repeat(3,minmax(0,1fr))")) + (deftest + "grid-rows-none" + (assert= (tw-resolve-layout "grid-rows-none") "grid-template-rows:none")) + (deftest + "grid-flow-row" + (assert= (tw-resolve-layout "grid-flow-row") "grid-auto-flow:row")) + (deftest + "grid-flow-col" + (assert= (tw-resolve-layout "grid-flow-col") "grid-auto-flow:column")) + (deftest + "col-span-2" + (assert= (tw-resolve-layout "col-span-2") "grid-column:span 2 / span 2")) + (deftest + "col-span-full" + (assert= (tw-resolve-layout "col-span-full") "grid-column:1 / -1")) + (deftest + "col-start-1" + (assert= (tw-resolve-layout "col-start-1") "grid-column-start:1")) + (deftest + "col-end-3" + (assert= (tw-resolve-layout "col-end-3") "grid-column-end:3")) + (deftest + "row-span-2" + (assert= (tw-resolve-layout "row-span-2") "grid-row:span 2 / span 2")) + (deftest + "row-span-full" + (assert= (tw-resolve-layout "row-span-full") "grid-row:1 / -1")) + (deftest + "row-start-1" + (assert= (tw-resolve-layout "row-start-1") "grid-row-start:1"))) + +(defsuite + "tw-position" + (deftest + "relative" + (assert= (tw-resolve-layout "relative") "position:relative")) + (deftest + "absolute" + (assert= (tw-resolve-layout "absolute") "position:absolute")) + (deftest "fixed" (assert= (tw-resolve-layout "fixed") "position:fixed")) + (deftest "sticky" (assert= (tw-resolve-layout "sticky") "position:sticky")) + (deftest "static" (assert= (tw-resolve-layout "static") "position:static"))) + +(defsuite + "tw-inset" + (deftest "top-0" (assert= (tw-resolve-layout "top-0") "top:0px")) + (deftest "top-4" (assert= (tw-resolve-layout "top-4") "top:1rem")) + (deftest "right-0" (assert= (tw-resolve-layout "right-0") "right:0px")) + (deftest "bottom-0" (assert= (tw-resolve-layout "bottom-0") "bottom:0px")) + (deftest "left-0" (assert= (tw-resolve-layout "left-0") "left:0px")) + (deftest "left-4" (assert= (tw-resolve-layout "left-4") "left:1rem")) + (deftest "inset-0" (assert= (tw-resolve-layout "inset-0") "inset:0px")) + (deftest "inset-4" (assert= (tw-resolve-layout "inset-4") "inset:1rem")) + (deftest + "inset-x-4" + (assert= (tw-resolve-layout "inset-x-4") "left:1rem;right:1rem")) + (deftest + "inset-y-0" + (assert= (tw-resolve-layout "inset-y-0") "top:0px;bottom:0px"))) + +(defsuite + "tw-z-index" + (deftest "z-0" (assert= (tw-resolve-layout "z-0") "z-index:0")) + (deftest "z-10" (assert= (tw-resolve-layout "z-10") "z-index:10")) + (deftest "z-20" (assert= (tw-resolve-layout "z-20") "z-index:20")) + (deftest "z-50" (assert= (tw-resolve-layout "z-50") "z-index:50")) + (deftest "z-auto" (assert= (tw-resolve-layout "z-auto") "z-index:auto"))) + +(defsuite + "tw-dimensions" + (deftest "w-full" (assert= (tw-resolve-layout "w-full") "width:100%")) + (deftest "w-screen" (assert= (tw-resolve-layout "w-screen") "width:100vw")) + (deftest "w-auto" (assert= (tw-resolve-layout "w-auto") "width:auto")) + (deftest "w-min" (assert= (tw-resolve-layout "w-min") "width:min-content")) + (deftest "w-max" (assert= (tw-resolve-layout "w-max") "width:max-content")) + (deftest "w-fit" (assert= (tw-resolve-layout "w-fit") "width:fit-content")) + (deftest "w-4" (assert= (tw-resolve-layout "w-4") "width:1rem")) + (deftest "w-12" (assert= (tw-resolve-layout "w-12") "width:3rem")) + (deftest "w-1/2" (assert= (tw-resolve-layout "w-1/2") "width:50%")) + (deftest + "w-1/3" + (assert= (tw-resolve-layout "w-1/3") "width:33.33333333333333%")) + (deftest + "w-2/3" + (assert= (tw-resolve-layout "w-2/3") "width:66.66666666666666%")) + (deftest "h-full" (assert= (tw-resolve-layout "h-full") "height:100%")) + (deftest + "h-screen" + (assert= (tw-resolve-layout "h-screen") "height:100vh")) + (deftest "h-auto" (assert= (tw-resolve-layout "h-auto") "height:auto")) + (deftest "h-16" (assert= (tw-resolve-layout "h-16") "height:4rem"))) + +(defsuite + "tw-max-min" + (deftest + "max-w-xs" + (assert= (tw-resolve-layout "max-w-xs") "max-width:20rem")) + (deftest + "max-w-md" + (assert= (tw-resolve-layout "max-w-md") "max-width:28rem")) + (deftest + "max-w-xl" + (assert= (tw-resolve-layout "max-w-xl") "max-width:36rem")) + (deftest + "max-w-2xl" + (assert= (tw-resolve-layout "max-w-2xl") "max-width:42rem")) + (deftest + "max-w-7xl" + (assert= (tw-resolve-layout "max-w-7xl") "max-width:80rem")) + (deftest + "max-w-prose" + (assert= (tw-resolve-layout "max-w-prose") "max-width:65ch")) + (deftest + "max-w-none" + (assert= (tw-resolve-layout "max-w-none") "max-width:none")) + (deftest + "max-w-full" + (assert= (tw-resolve-layout "max-w-full") "max-width:100%")) + (deftest + "max-h-full" + (assert= (tw-resolve-layout "max-h-full") "max-height:100%")) + (deftest + "max-h-screen" + (assert= (tw-resolve-layout "max-h-screen") "max-height:100vh")) + (deftest "min-w-0" (assert= (tw-resolve-layout "min-w-0") "min-width:0px")) + (deftest + "min-w-full" + (assert= (tw-resolve-layout "min-w-full") "min-width:100%")) + (deftest + "min-h-0" + (assert= (tw-resolve-layout "min-h-0") "min-height:0px")) + (deftest + "min-h-full" + (assert= (tw-resolve-layout "min-h-full") "min-height:100%")) + (deftest + "min-h-screen" + (assert= (tw-resolve-layout "min-h-screen") "min-height:100vh"))) + +(defsuite + "tw-overflow" + (deftest + "overflow-auto" + (assert= (tw-resolve-layout "overflow-auto") "overflow:auto")) + (deftest + "overflow-hidden" + (assert= (tw-resolve-layout "overflow-hidden") "overflow:hidden")) + (deftest + "overflow-visible" + (assert= (tw-resolve-layout "overflow-visible") "overflow:visible")) + (deftest + "overflow-scroll" + (assert= (tw-resolve-layout "overflow-scroll") "overflow:scroll")) + (deftest + "overflow-x-auto" + (assert= (tw-resolve-layout "overflow-x-auto") "overflow-x:auto")) + (deftest + "overflow-x-hidden" + (assert= (tw-resolve-layout "overflow-x-hidden") "overflow-x:hidden")) + (deftest + "overflow-y-auto" + (assert= (tw-resolve-layout "overflow-y-auto") "overflow-y:auto")) + (deftest + "overflow-y-scroll" + (assert= (tw-resolve-layout "overflow-y-scroll") "overflow-y:scroll"))) + +(defsuite + "tw-layout-misc" + (deftest + "aspect-auto" + (assert= (tw-resolve-layout "aspect-auto") "aspect-ratio:auto")) + (deftest + "aspect-square" + (assert= (tw-resolve-layout "aspect-square") "aspect-ratio:1 / 1")) + (deftest + "aspect-video" + (assert= (tw-resolve-layout "aspect-video") "aspect-ratio:16 / 9")) + (deftest + "object-cover" + (assert= (tw-resolve-layout "object-cover") "object-fit:cover")) + (deftest + "object-contain" + (assert= (tw-resolve-layout "object-contain") "object-fit:contain")) + (deftest + "object-fill" + (assert= (tw-resolve-layout "object-fill") "object-fit:fill")) + (deftest + "visible" + (assert= (tw-resolve-layout "visible") "visibility:visible")) + (deftest + "invisible" + (assert= (tw-resolve-layout "invisible") "visibility:hidden")) + (deftest + "collapse" + (assert= (tw-resolve-layout "collapse") "visibility:collapse")) + (deftest + "container" + (assert= (tw-resolve-layout "container") "width:100%;max-width:100%")) + (deftest + "isolate" + (assert= (tw-resolve-layout "isolate") "isolation:isolate"))) + +(defsuite + "tw-text-sizes" + (deftest + "text-xs" + (assert= (tw-resolve-type "text-xs") "font-size:0.75rem;line-height:1rem")) + (deftest + "text-sm" + (assert= + (tw-resolve-type "text-sm") + "font-size:0.875rem;line-height:1.25rem")) + (deftest + "text-base" + (assert= + (tw-resolve-type "text-base") + "font-size:1rem;line-height:1.5rem")) + (deftest + "text-lg" + (assert= + (tw-resolve-type "text-lg") + "font-size:1.125rem;line-height:1.75rem")) + (deftest + "text-xl" + (assert= + (tw-resolve-type "text-xl") + "font-size:1.25rem;line-height:1.75rem")) + (deftest + "text-2xl" + (assert= (tw-resolve-type "text-2xl") "font-size:1.5rem;line-height:2rem")) + (deftest + "text-3xl" + (assert= + (tw-resolve-type "text-3xl") + "font-size:1.875rem;line-height:2.25rem")) + (deftest + "text-4xl" + (assert= + (tw-resolve-type "text-4xl") + "font-size:2.25rem;line-height:2.5rem")) + (deftest + "text-5xl" + (assert= (tw-resolve-type "text-5xl") "font-size:3rem;line-height:1")) + (deftest + "text-6xl" + (assert= (tw-resolve-type "text-6xl") "font-size:3.75rem;line-height:1")) + (deftest + "text-7xl" + (assert= (tw-resolve-type "text-7xl") "font-size:4.5rem;line-height:1")) + (deftest + "text-8xl" + (assert= (tw-resolve-type "text-8xl") "font-size:6rem;line-height:1")) + (deftest + "text-9xl" + (assert= (tw-resolve-type "text-9xl") "font-size:8rem;line-height:1"))) + +(defsuite + "tw-text-align" + (deftest + "text-left" + (assert= (tw-resolve-type "text-left") "text-align:left")) + (deftest + "text-center" + (assert= (tw-resolve-type "text-center") "text-align:center")) + (deftest + "text-right" + (assert= (tw-resolve-type "text-right") "text-align:right")) + (deftest + "text-justify" + (assert= (tw-resolve-type "text-justify") "text-align:justify")) + (deftest + "text-start" + (assert= (tw-resolve-type "text-start") "text-align:start")) + (deftest + "text-end" + (assert= (tw-resolve-type "text-end") "text-align:end"))) + +(defsuite + "tw-font-weight" + (deftest + "font-thin" + (assert= (tw-resolve-type "font-thin") "font-weight:100")) + (deftest + "font-extralight" + (assert= (tw-resolve-type "font-extralight") "font-weight:200")) + (deftest + "font-light" + (assert= (tw-resolve-type "font-light") "font-weight:300")) + (deftest + "font-normal" + (assert= (tw-resolve-type "font-normal") "font-weight:400")) + (deftest + "font-medium" + (assert= (tw-resolve-type "font-medium") "font-weight:500")) + (deftest + "font-semibold" + (assert= (tw-resolve-type "font-semibold") "font-weight:600")) + (deftest + "font-bold" + (assert= (tw-resolve-type "font-bold") "font-weight:700")) + (deftest + "font-extrabold" + (assert= (tw-resolve-type "font-extrabold") "font-weight:800")) + (deftest + "font-black" + (assert= (tw-resolve-type "font-black") "font-weight:900"))) + +(defsuite + "tw-font-family" + (deftest + "font-sans" + (assert= + (tw-resolve-type "font-sans") + "font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif")) + (deftest + "font-serif" + (assert= + (tw-resolve-type "font-serif") + "font-family:ui-serif,Georgia,Cambria,\"Times New Roman\",Times,serif")) + (deftest + "font-mono" + (assert= + (tw-resolve-type "font-mono") + "font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace"))) + +(defsuite + "tw-font-style" + (deftest "italic" (assert= (tw-resolve-type "italic") "font-style:italic")) + (deftest + "not-italic" + (assert= (tw-resolve-type "not-italic") "font-style:normal"))) + +(defsuite + "tw-text-transform" + (deftest + "uppercase" + (assert= (tw-resolve-type "uppercase") "text-transform:uppercase")) + (deftest + "lowercase" + (assert= (tw-resolve-type "lowercase") "text-transform:lowercase")) + (deftest + "capitalize" + (assert= (tw-resolve-type "capitalize") "text-transform:capitalize")) + (deftest + "normal-case" + (assert= (tw-resolve-type "normal-case") "text-transform:none"))) + +(defsuite + "tw-leading" + (deftest + "leading-none" + (assert= (tw-resolve-type "leading-none") "line-height:1")) + (deftest + "leading-tight" + (assert= (tw-resolve-type "leading-tight") "line-height:1.25")) + (deftest + "leading-snug" + (assert= (tw-resolve-type "leading-snug") "line-height:1.375")) + (deftest + "leading-normal" + (assert= (tw-resolve-type "leading-normal") "line-height:1.5")) + (deftest + "leading-relaxed" + (assert= (tw-resolve-type "leading-relaxed") "line-height:1.625")) + (deftest + "leading-loose" + (assert= (tw-resolve-type "leading-loose") "line-height:2")) + (deftest + "leading-6" + (assert= (tw-resolve-type "leading-6") "line-height:1.5rem")) + (deftest + "leading-8" + (assert= (tw-resolve-type "leading-8") "line-height:2rem"))) + +(defsuite + "tw-tracking" + (deftest + "tracking-tighter" + (assert= (tw-resolve-type "tracking-tighter") "letter-spacing:-0.05em")) + (deftest + "tracking-tight" + (assert= (tw-resolve-type "tracking-tight") "letter-spacing:-0.025em")) + (deftest + "tracking-normal" + (assert= (tw-resolve-type "tracking-normal") "letter-spacing:0em")) + (deftest + "tracking-wide" + (assert= (tw-resolve-type "tracking-wide") "letter-spacing:0.025em")) + (deftest + "tracking-wider" + (assert= (tw-resolve-type "tracking-wider") "letter-spacing:0.05em")) + (deftest + "tracking-widest" + (assert= (tw-resolve-type "tracking-widest") "letter-spacing:0.1em"))) + +(defsuite + "tw-whitespace" + (deftest + "whitespace-normal" + (assert= (tw-resolve-type "whitespace-normal") "white-space:normal")) + (deftest + "whitespace-nowrap" + (assert= (tw-resolve-type "whitespace-nowrap") "white-space:nowrap")) + (deftest + "whitespace-pre" + (assert= (tw-resolve-type "whitespace-pre") "white-space:pre")) + (deftest + "whitespace-pre-line" + (assert= (tw-resolve-type "whitespace-pre-line") "white-space:pre-line")) + (deftest + "whitespace-pre-wrap" + (assert= (tw-resolve-type "whitespace-pre-wrap") "white-space:pre-wrap")) + (deftest + "break-normal" + (assert= + (tw-resolve-type "break-normal") + "overflow-wrap:normal;word-break:normal")) + (deftest + "break-words" + (assert= (tw-resolve-type "break-words") "overflow-wrap:break-word")) + (deftest + "break-all" + (assert= (tw-resolve-type "break-all") "word-break:break-all")) + (deftest + "break-keep" + (assert= (tw-resolve-type "break-keep") "word-break:keep-all"))) + +(defsuite + "tw-truncate" + (deftest + "truncate" + (assert= + (tw-resolve-type "truncate") + "overflow:hidden;text-overflow:ellipsis;white-space:nowrap")) + (deftest "text-ellipsis" (assert= (tw-resolve-type "text-ellipsis") nil)) + (deftest + "line-clamp-3" + (assert= + (tw-resolve-type "line-clamp-3") + "overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3"))) + +(defsuite + "tw-vertical-align" + (deftest + "align-baseline" + (assert= (tw-resolve-type "align-baseline") "vertical-align:baseline")) + (deftest + "align-top" + (assert= (tw-resolve-type "align-top") "vertical-align:top")) + (deftest + "align-middle" + (assert= (tw-resolve-type "align-middle") "vertical-align:middle")) + (deftest + "align-bottom" + (assert= (tw-resolve-type "align-bottom") "vertical-align:bottom")) + (deftest + "align-text-top" + (assert= (tw-resolve-type "align-text-top") "vertical-align:text-top")) + (deftest + "align-text-bottom" + (assert= + (tw-resolve-type "align-text-bottom") + "vertical-align:text-bottom")) + (deftest + "align-sub" + (assert= (tw-resolve-type "align-sub") "vertical-align:sub")) + (deftest + "align-super" + (assert= (tw-resolve-type "align-super") "vertical-align:super"))) + +(defsuite + "tw-list" + (deftest + "list-none" + (assert= (tw-resolve-type "list-none") "list-style-type:none")) + (deftest + "list-disc" + (assert= (tw-resolve-type "list-disc") "list-style-type:disc")) + (deftest + "list-decimal" + (assert= (tw-resolve-type "list-decimal") "list-style-type:decimal")) + (deftest + "list-inside" + (assert= (tw-resolve-type "list-inside") "list-style-position:inside")) + (deftest + "list-outside" + (assert= (tw-resolve-type "list-outside") "list-style-position:outside"))) + +(defsuite + "tw-type-misc" + (deftest + "text-wrap" + (assert= (tw-resolve-type "text-wrap") "text-wrap:wrap")) + (deftest + "text-nowrap" + (assert= (tw-resolve-type "text-nowrap") "text-wrap:nowrap")) + (deftest + "text-balance" + (assert= (tw-resolve-type "text-balance") "text-wrap:balance")) + (deftest + "text-pretty" + (assert= (tw-resolve-type "text-pretty") "text-wrap:pretty")) + (deftest + "indent-4" + (assert= (tw-resolve-type "indent-4") "text-indent:1rem")) + (deftest + "indent-8" + (assert= (tw-resolve-type "indent-8") "text-indent:2rem")) + (deftest + "hyphens-auto" + (assert= (tw-resolve-type "hyphens-auto") "hyphens:auto")) + (deftest + "hyphens-none" + (assert= (tw-resolve-type "hyphens-none") "hyphens:none")) + (deftest + "content-none" + (assert= (tw-resolve-type "content-none") "content:none")) + (deftest + "antialiased" + (assert= + (tw-resolve-type "antialiased") + "-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale")) + (deftest + "tabular-nums" + (assert= + (tw-resolve-type "tabular-nums") + "font-variant-numeric:tabular-nums")) + (deftest + "ordinal" + (assert= (tw-resolve-type "ordinal") "font-variant-numeric:ordinal")) + (deftest + "slashed-zero" + (assert= + (tw-resolve-type "slashed-zero") + "font-variant-numeric:slashed-zero"))) + +(defsuite + "tw-arb-colour" + (deftest + "bg-[#ff0000]" + (assert= (tw-resolve-arbitrary "bg-[#ff0000]") "background-color:#ff0000")) + (deftest + "bg-[rgb(255,0,0)]" + (assert= + (tw-resolve-arbitrary "bg-[rgb(255,0,0)]") + "background-color:rgb(255,0,0)")) + (deftest + "bg-[hsl(200,50%,50%)]" + (assert= + (tw-resolve-arbitrary "bg-[hsl(200,50%,50%)]") + "background-color:hsl(200,50%,50%)")) + (deftest + "text-[#333]" + (assert= (tw-resolve-arbitrary "text-[#333]") "color:#333")) + (deftest + "text-[red]" + (assert= (tw-resolve-arbitrary "text-[red]") "color:red")) + (deftest + "border-color-[#ccc]" + (assert= (tw-resolve-arbitrary "border-color-[#ccc]") "border-color:#ccc")) + (deftest + "fill-[currentColor]" + (assert= (tw-resolve-arbitrary "fill-[currentColor]") "fill:currentColor")) + (deftest + "accent-[#0ea5e9]" + (assert= (tw-resolve-arbitrary "accent-[#0ea5e9]") "accent-color:#0ea5e9"))) + +(defsuite + "tw-arb-spacing" + (deftest + "p-[10px]" + (assert= (tw-resolve-arbitrary "p-[10px]") "padding:10px")) + (deftest + "p-[1.5rem]" + (assert= (tw-resolve-arbitrary "p-[1.5rem]") "padding:1.5rem")) + (deftest + "pt-[20px]" + (assert= (tw-resolve-arbitrary "pt-[20px]") "padding-top:20px")) + (deftest + "m-[auto]" + (assert= (tw-resolve-arbitrary "m-[auto]") "margin:auto")) + (deftest + "mt-[-1rem]" + (assert= (tw-resolve-arbitrary "mt-[-1rem]") "margin-top:-1rem")) + (deftest + "gap-[20px]" + (assert= (tw-resolve-arbitrary "gap-[20px]") "gap:20px")) + (deftest + "gap-x-[1.5rem]" + (assert= (tw-resolve-arbitrary "gap-x-[1.5rem]") "column-gap:1.5rem")) + (deftest + "gap-y-[2rem]" + (assert= (tw-resolve-arbitrary "gap-y-[2rem]") "row-gap:2rem"))) + +(defsuite + "tw-arb-dimensions" + (deftest + "w-[300px]" + (assert= (tw-resolve-arbitrary "w-[300px]") "width:300px")) + (deftest "w-[50%]" (assert= (tw-resolve-arbitrary "w-[50%]") "width:50%")) + (deftest + "w-[calc(100%-2rem)]" + (assert= + (tw-resolve-arbitrary "w-[calc(100%-2rem)]") + "width:calc(100%-2rem)")) + (deftest + "h-[100vh]" + (assert= (tw-resolve-arbitrary "h-[100vh]") "height:100vh")) + (deftest + "h-[500px]" + (assert= (tw-resolve-arbitrary "h-[500px]") "height:500px")) + (deftest + "min-w-[320px]" + (assert= (tw-resolve-arbitrary "min-w-[320px]") "min-width:320px")) + (deftest + "max-w-[800px]" + (assert= (tw-resolve-arbitrary "max-w-[800px]") "max-width:800px")) + (deftest + "max-h-[90vh]" + (assert= (tw-resolve-arbitrary "max-h-[90vh]") "max-height:90vh"))) + +(defsuite + "tw-arb-position" + (deftest + "top-[50%]" + (assert= (tw-resolve-arbitrary "top-[50%]") "top:50%")) + (deftest + "right-[1rem]" + (assert= (tw-resolve-arbitrary "right-[1rem]") "right:1rem")) + (deftest + "bottom-[0]" + (assert= (tw-resolve-arbitrary "bottom-[0]") "bottom:0")) + (deftest + "left-[auto]" + (assert= (tw-resolve-arbitrary "left-[auto]") "left:auto")) + (deftest + "inset-[0]" + (assert= (tw-resolve-arbitrary "inset-[0]") "inset:0")) + (deftest + "z-[999]" + (assert= (tw-resolve-arbitrary "z-[999]") "z-index:999")) + (deftest "z-[-1]" (assert= (tw-resolve-arbitrary "z-[-1]") "z-index:-1"))) + +(defsuite + "tw-arb-typography" + (deftest + "font-size-[20px]" + (assert= (tw-resolve-arbitrary "font-size-[20px]") "font-size:20px")) + (deftest + "leading-[1.6]" + (assert= (tw-resolve-arbitrary "leading-[1.6]") "line-height:1.6")) + (deftest + "leading-[2rem]" + (assert= (tw-resolve-arbitrary "leading-[2rem]") "line-height:2rem")) + (deftest + "tracking-[0.05em]" + (assert= + (tw-resolve-arbitrary "tracking-[0.05em]") + "letter-spacing:0.05em")) + (deftest + "indent-[2em]" + (assert= (tw-resolve-arbitrary "indent-[2em]") "text-indent:2em"))) + +(defsuite + "tw-arb-borders" + (deftest + "border-[3px]" + (assert= (tw-resolve-arbitrary "border-[3px]") "border-width:3px")) + (deftest + "rounded-[12px]" + (assert= (tw-resolve-arbitrary "rounded-[12px]") "border-radius:12px")) + (deftest + "rounded-[50%]" + (assert= (tw-resolve-arbitrary "rounded-[50%]") "border-radius:50%")) + (deftest + "opacity-[0.8]" + (assert= (tw-resolve-arbitrary "opacity-[0.8]") "opacity:0.8")) + (deftest + "shadow-[0_4px_8px_rgba(0,0,0,0.1)]" + (assert= + (tw-resolve-arbitrary "shadow-[0_4px_8px_rgba(0,0,0,0.1)]") + "box-shadow:0 4px 8px rgba(0,0,0,0.1)"))) + +(defsuite + "tw-arb-grid" + (deftest + "grid-cols-[1fr_2fr]" + (assert= + (tw-resolve-arbitrary "grid-cols-[1fr_2fr]") + "grid-template-columns:1fr 2fr")) + (deftest + "grid-cols-[200px_1fr_200px]" + (assert= + (tw-resolve-arbitrary "grid-cols-[200px_1fr_200px]") + "grid-template-columns:200px 1fr 200px")) + (deftest + "grid-rows-[auto_1fr_auto]" + (assert= + (tw-resolve-arbitrary "grid-rows-[auto_1fr_auto]") + "grid-template-rows:auto 1fr auto")) + (deftest + "basis-[200px]" + (assert= (tw-resolve-arbitrary "basis-[200px]") "flex-basis:200px")) + (deftest + "order-[99]" + (assert= (tw-resolve-arbitrary "order-[99]") "order:99")) + (deftest + "order-[-1]" + (assert= (tw-resolve-arbitrary "order-[-1]") "order:-1"))) + +(defsuite + "tw-arb-misc" + (deftest + "duration-[250ms]" + (assert= + (tw-resolve-arbitrary "duration-[250ms]") + "transition-duration:250ms")) + (deftest + "delay-[100ms]" + (assert= (tw-resolve-arbitrary "delay-[100ms]") "transition-delay:100ms")) + (deftest + "aspect-[4/3]" + (assert= (tw-resolve-arbitrary "aspect-[4/3]") "aspect-ratio:4/3")) + (deftest + "columns-[3]" + (assert= (tw-resolve-arbitrary "columns-[3]") "columns:3"))) + +(defsuite + "tw-arb-edge" + (deftest "no bracket → nil" (assert-nil (tw-resolve-arbitrary "flex"))) + (deftest + "unknown prefix → nil" + (assert-nil (tw-resolve-arbitrary "banana-[42px]"))) + (deftest + "underscore → space" + (assert= + (tw-resolve-arbitrary "shadow-[0_1px_2px_black]") + "box-shadow:0 1px 2px black")) + (deftest + "multiple underscores" + (assert= + (tw-resolve-arbitrary "grid-cols-[1fr_2fr_1fr_2fr]") + "grid-template-columns:1fr 2fr 1fr 2fr"))) + +(defsuite + "tw-arb-pipeline" + (deftest + "plain arbitrary" + (let + ((r (tw-process-token "w-[300px]"))) + (assert (contains? (get r "rule") "width:300px")))) + (deftest + "hover: arbitrary" + (let + ((r (tw-process-token "hover:bg-[#ff0000]"))) + (assert (contains? (get r "rule") ":hover")) + (assert (contains? (get r "rule") "background-color:#ff0000")))) + (deftest + "sm: arbitrary" + (let + ((r (tw-process-token "sm:w-[50%]"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "width:50%")))) + (deftest + "lg: arbitrary grid" + (let + ((r (tw-process-token "lg:grid-cols-[1fr_2fr_1fr]"))) + (assert (contains? (get r "rule") "@media(min-width:1024px)")) + (assert (contains? (get r "rule") "grid-template-columns:1fr 2fr 1fr")))) + (deftest + "focus: arbitrary border" + (let + ((r (tw-process-token "focus:border-[2px]"))) + (assert (contains? (get r "rule") ":focus")) + (assert (contains? (get r "rule") "border-width:2px")))) + (deftest + "arbitrary class name" + (assert= (get (tw-process-token "w-[300px]") "cls") "sx-w-[300px]")) + (deftest + "real component with arbitrary values" + (let + ((tokens (list "flex" "gap-4" "w-[calc(100%-2rem)]" "max-w-[800px]" "p-[1.5rem]" "rounded-[12px]" "bg-[#f5f5f4]" "shadow-[0_2px_4px_rgba(0,0,0,0.1)]" "sm:p-[2rem]" "lg:max-w-[1200px]"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens))))) + +(defsuite + "tw-class-names" + (deftest + "simple token → sx-{token}" + (assert= (get (tw-process-token "flex") "cls") "sx-flex")) + (deftest + "hyphenated → sx-{token}" + (assert= (get (tw-process-token "flex-col") "cls") "sx-flex-col")) + (deftest + "colon variant → colons become hyphens" + (assert= + (get (tw-process-token "hover:bg-sky-200") "cls") + "sx-hover-bg-sky-200")) + (deftest + "breakpoint variant" + (assert= (get (tw-process-token "sm:flex") "cls") "sx-sm-flex")) + (deftest + "dot in token → becomes d" + (assert= (get (tw-process-token "p-0.5") "cls") "sx-p-0d5")) + (deftest + "combined bp+state" + (assert= + (get (tw-process-token "sm:hover:bg-sky-200") "cls") + "sx-sm-hover-bg-sky-200")) + (deftest + "negative prefix preserved" + (assert= (get (tw-process-token "-mt-4") "cls") "sx--mt-4")) + (deftest + "spacing token" + (assert= (get (tw-process-token "p-4") "cls") "sx-p-4")) + (deftest + "colour token" + (assert= (get (tw-process-token "bg-sky-500") "cls") "sx-bg-sky-500")) + (deftest + "compound layout token" + (assert= (get (tw-process-token "grid-cols-3") "cls") "sx-grid-cols-3")) + (deftest + "fraction token → slash stays" + (assert= (get (tw-process-token "w-1/2") "cls") "sx-w-1/2"))) + +(defsuite + "tw-token-plain" + (deftest + "flex → display:flex rule" + (let + ((r (tw-process-token "flex"))) + (assert (contains? (get r "rule") "display:flex")))) + (deftest + "p-4 → padding:1rem rule" + (let + ((r (tw-process-token "p-4"))) + (assert (contains? (get r "rule") "padding:1rem")))) + (deftest + "bg-sky-500 → background-color rule" + (let + ((r (tw-process-token "bg-sky-500"))) + (assert (contains? (get r "rule") "background-color:hsl(199,89%,53%)")))) + (deftest + "text-lg → font-size rule" + (let + ((r (tw-process-token "text-lg"))) + (assert (contains? (get r "rule") "font-size:1.125rem")))) + (deftest + "font-bold → font-weight rule" + (let + ((r (tw-process-token "font-bold"))) + (assert (contains? (get r "rule") "font-weight:700")))) + (deftest + "flex-col → flex-direction rule" + (let + ((r (tw-process-token "flex-col"))) + (assert (contains? (get r "rule") "flex-direction:column")))) + (deftest + "justify-between → justify-content rule" + (let + ((r (tw-process-token "justify-between"))) + (assert (contains? (get r "rule") "justify-content:space-between")))) + (deftest + "items-center → align-items rule" + (let + ((r (tw-process-token "items-center"))) + (assert (contains? (get r "rule") "align-items:center")))) + (deftest + "grid-cols-3 → grid-template-columns rule" + (let + ((r (tw-process-token "grid-cols-3"))) + (assert + (contains? + (get r "rule") + "grid-template-columns:repeat(3,minmax(0,1fr))")))) + (deftest + "relative → position:relative rule" + (let + ((r (tw-process-token "relative"))) + (assert (contains? (get r "rule") "position:relative")))) + (deftest + "z-10 → z-index:10 rule" + (let + ((r (tw-process-token "z-10"))) + (assert (contains? (get r "rule") "z-index:10")))) + (deftest + "unresolvable → nil" + (assert-nil (tw-process-token "bogus-token-xyz")))) + +(defsuite + "tw-token-states" + (deftest + "hover: adds :hover pseudo" + (let + ((r (tw-process-token "hover:bg-sky-200"))) + (assert (contains? (get r "rule") ":hover")) + (assert (contains? (get r "rule") "background-color:hsl(199,89%,87%)")))) + (deftest + "focus: adds :focus pseudo" + (let + ((r (tw-process-token "focus:border-blue-500"))) + (assert (contains? (get r "rule") ":focus")) + (assert (contains? (get r "rule") "border-color:hsl(217,91%,53%)")))) + (deftest + "active: adds :active pseudo" + (let + ((r (tw-process-token "active:bg-sky-300"))) + (assert (contains? (get r "rule") ":active")))) + (deftest + "disabled: adds :disabled pseudo" + (let + ((r (tw-process-token "disabled:opacity-50"))) + (assert (contains? (get r "rule") ":disabled")) + (assert (contains? (get r "rule") "opacity:0.5")))) + (deftest + "focus-visible: pseudo" + (let + ((r (tw-process-token "focus-visible:ring-2"))) + (assert (contains? (get r "rule") ":focus-visible")))) + (deftest + "first: → :first-child" + (let + ((r (tw-process-token "first:mt-0"))) + (assert (contains? (get r "rule") ":first-child")))) + (deftest + "last: → :last-child" + (let + ((r (tw-process-token "last:mb-0"))) + (assert (contains? (get r "rule") ":last-child")))) + (deftest + "placeholder: → ::placeholder" + (let + ((r (tw-process-token "placeholder:text-stone-400"))) + (assert (contains? (get r "rule") "::placeholder")))) + (deftest + "checked: → :checked" + (let + ((r (tw-process-token "checked:bg-sky-500"))) + (assert (contains? (get r "rule") ":checked")))) + (deftest + "even: → :nth-child(even)" + (let + ((r (tw-process-token "even:bg-stone-100"))) + (assert (contains? (get r "rule") ":nth-child(even)")))) + (deftest + "odd: → :nth-child(odd)" + (let + ((r (tw-process-token "odd:bg-stone-200"))) + (assert (contains? (get r "rule") ":nth-child(odd)"))))) + +(defsuite + "tw-token-breakpoints" + (deftest + "sm: → 640px media query" + (let + ((r (tw-process-token "sm:flex"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "display:flex")))) + (deftest + "md: → 768px" + (let + ((r (tw-process-token "md:block"))) + (assert (contains? (get r "rule") "@media(min-width:768px)")))) + (deftest + "lg: → 1024px" + (let + ((r (tw-process-token "lg:grid-cols-3"))) + (assert (contains? (get r "rule") "@media(min-width:1024px)")) + (assert + (contains? + (get r "rule") + "grid-template-columns:repeat(3,minmax(0,1fr))")))) + (deftest + "xl: → 1280px" + (let + ((r (tw-process-token "xl:hidden"))) + (assert (contains? (get r "rule") "@media(min-width:1280px)")) + (assert (contains? (get r "rule") "display:none")))) + (deftest + "2xl: → 1536px" + (let + ((r (tw-process-token "2xl:max-w-7xl"))) + (assert (contains? (get r "rule") "@media(min-width:1536px)")) + (assert (contains? (get r "rule") "max-width:80rem"))))) + +(defsuite + "tw-token-combined" + (deftest + "sm:hover:bg-sky-200 → media + pseudo" + (let + ((r (tw-process-token "sm:hover:bg-sky-200"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") ":hover")) + (assert (contains? (get r "rule") "background-color:hsl(199,89%,87%)")))) + (deftest + "lg:focus:border-blue-500" + (let + ((r (tw-process-token "lg:focus:border-blue-500"))) + (assert (contains? (get r "rule") "@media(min-width:1024px)")) + (assert (contains? (get r "rule") ":focus")) + (assert (contains? (get r "rule") "border-color:hsl(217,91%,53%)"))))) + +(defsuite + "tw-media-sm" + (deftest + "sm:flex" + (let + ((r (tw-process-token "sm:flex"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "display:flex")))) + (deftest + "sm:hidden" + (let + ((r (tw-process-token "sm:hidden"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "display:none")))) + (deftest + "sm:block" + (let + ((r (tw-process-token "sm:block"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "display:block")))) + (deftest + "sm:flex-col" + (let + ((r (tw-process-token "sm:flex-col"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "flex-direction:column")))) + (deftest + "sm:flex-row" + (let + ((r (tw-process-token "sm:flex-row"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "flex-direction:row")))) + (deftest + "sm:grid-cols-2" + (let + ((r (tw-process-token "sm:grid-cols-2"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert + (contains? + (get r "rule") + "grid-template-columns:repeat(2,minmax(0,1fr))")))) + (deftest + "sm:p-4" + (let + ((r (tw-process-token "sm:p-4"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "padding:1rem")))) + (deftest + "sm:px-6" + (let + ((r (tw-process-token "sm:px-6"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert + (contains? (get r "rule") "padding-left:1.5rem;padding-right:1.5rem")))) + (deftest + "sm:gap-4" + (let + ((r (tw-process-token "sm:gap-4"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "gap:1rem")))) + (deftest + "sm:text-lg" + (let + ((r (tw-process-token "sm:text-lg"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "font-size:1.125rem")))) + (deftest + "sm:font-bold" + (let + ((r (tw-process-token "sm:font-bold"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "font-weight:700")))) + (deftest + "sm:text-center" + (let + ((r (tw-process-token "sm:text-center"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "text-align:center")))) + (deftest + "sm:w-1/2" + (let + ((r (tw-process-token "sm:w-1/2"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "width:50%")))) + (deftest + "sm:max-w-xl" + (let + ((r (tw-process-token "sm:max-w-xl"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "max-width:36rem")))) + (deftest + "sm:items-center" + (let + ((r (tw-process-token "sm:items-center"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "align-items:center")))) + (deftest + "sm:justify-between" + (let + ((r (tw-process-token "sm:justify-between"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "justify-content:space-between")))) + (deftest + "sm:bg-sky-100" + (let + ((r (tw-process-token "sm:bg-sky-100"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "background-color:hsl(199,89%,93%)")))) + (deftest + "sm:rounded-lg" + (let + ((r (tw-process-token "sm:rounded-lg"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "border-radius:0.5rem")))) + (deftest + "sm:shadow-md" + (let + ((r (tw-process-token "sm:shadow-md"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "box-shadow:")))) + (deftest + "sm:relative" + (let + ((r (tw-process-token "sm:relative"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") "position:relative"))))) + +(defsuite + "tw-media-md" + (deftest + "md:flex" + (let + ((r (tw-process-token "md:flex"))) + (assert (contains? (get r "rule") "@media(min-width:768px)")) + (assert (contains? (get r "rule") "display:flex")))) + (deftest + "md:flex-row" + (let + ((r (tw-process-token "md:flex-row"))) + (assert (contains? (get r "rule") "@media(min-width:768px)")) + (assert (contains? (get r "rule") "flex-direction:row")))) + (deftest + "md:grid-cols-2" + (let + ((r (tw-process-token "md:grid-cols-2"))) + (assert (contains? (get r "rule") "@media(min-width:768px)")) + (assert + (contains? + (get r "rule") + "grid-template-columns:repeat(2,minmax(0,1fr))")))) + (deftest + "md:px-8" + (let + ((r (tw-process-token "md:px-8"))) + (assert (contains? (get r "rule") "@media(min-width:768px)")) + (assert + (contains? (get r "rule") "padding-left:2rem;padding-right:2rem")))) + (deftest + "md:text-xl" + (let + ((r (tw-process-token "md:text-xl"))) + (assert (contains? (get r "rule") "@media(min-width:768px)")) + (assert (contains? (get r "rule") "font-size:1.25rem")))) + (deftest + "md:hidden" + (let + ((r (tw-process-token "md:hidden"))) + (assert (contains? (get r "rule") "@media(min-width:768px)")) + (assert (contains? (get r "rule") "display:none")))) + (deftest + "md:absolute" + (let + ((r (tw-process-token "md:absolute"))) + (assert (contains? (get r "rule") "@media(min-width:768px)")) + (assert (contains? (get r "rule") "position:absolute")))) + (deftest + "md:top-0" + (let + ((r (tw-process-token "md:top-0"))) + (assert (contains? (get r "rule") "@media(min-width:768px)")) + (assert (contains? (get r "rule") "top:0px"))))) + +(defsuite + "tw-media-lg" + (deftest + "lg:grid-cols-3" + (let + ((r (tw-process-token "lg:grid-cols-3"))) + (assert (contains? (get r "rule") "@media(min-width:1024px)")) + (assert + (contains? + (get r "rule") + "grid-template-columns:repeat(3,minmax(0,1fr))")))) + (deftest + "lg:max-w-4xl" + (let + ((r (tw-process-token "lg:max-w-4xl"))) + (assert (contains? (get r "rule") "@media(min-width:1024px)")) + (assert (contains? (get r "rule") "max-width:56rem")))) + (deftest + "lg:px-12" + (let + ((r (tw-process-token "lg:px-12"))) + (assert (contains? (get r "rule") "@media(min-width:1024px)")) + (assert + (contains? (get r "rule") "padding-left:3rem;padding-right:3rem")))) + (deftest + "lg:text-2xl" + (let + ((r (tw-process-token "lg:text-2xl"))) + (assert (contains? (get r "rule") "@media(min-width:1024px)")) + (assert (contains? (get r "rule") "font-size:1.5rem")))) + (deftest + "lg:col-span-2" + (let + ((r (tw-process-token "lg:col-span-2"))) + (assert (contains? (get r "rule") "@media(min-width:1024px)")) + (assert (contains? (get r "rule") "grid-column:span 2 / span 2")))) + (deftest + "lg:sticky" + (let + ((r (tw-process-token "lg:sticky"))) + (assert (contains? (get r "rule") "@media(min-width:1024px)")) + (assert (contains? (get r "rule") "position:sticky"))))) + +(defsuite + "tw-media-xl" + (deftest + "xl:grid-cols-4" + (let + ((r (tw-process-token "xl:grid-cols-4"))) + (assert (contains? (get r "rule") "@media(min-width:1280px)")) + (assert + (contains? + (get r "rule") + "grid-template-columns:repeat(4,minmax(0,1fr))")))) + (deftest + "xl:max-w-7xl" + (let + ((r (tw-process-token "xl:max-w-7xl"))) + (assert (contains? (get r "rule") "@media(min-width:1280px)")) + (assert (contains? (get r "rule") "max-width:80rem")))) + (deftest + "xl:hidden" + (let + ((r (tw-process-token "xl:hidden"))) + (assert (contains? (get r "rule") "@media(min-width:1280px)")) + (assert (contains? (get r "rule") "display:none")))) + (deftest + "xl:gap-8" + (let + ((r (tw-process-token "xl:gap-8"))) + (assert (contains? (get r "rule") "@media(min-width:1280px)")) + (assert (contains? (get r "rule") "gap:2rem"))))) + +(defsuite + "tw-media-2xl" + (deftest + "2xl:max-w-screen" + (let + ((r (tw-process-token "2xl:max-w-screen"))) + (assert (contains? (get r "rule") "@media(min-width:1536px)")) + (assert (contains? (get r "rule") "max-width:100vw")))) + (deftest + "2xl:block" + (let + ((r (tw-process-token "2xl:block"))) + (assert (contains? (get r "rule") "@media(min-width:1536px)")) + (assert (contains? (get r "rule") "display:block")))) + (deftest + "2xl:text-4xl" + (let + ((r (tw-process-token "2xl:text-4xl"))) + (assert (contains? (get r "rule") "@media(min-width:1536px)")) + (assert (contains? (get r "rule") "font-size:2.25rem"))))) + +(defsuite + "tw-media-state-combined" + (deftest + "sm:hover:bg-sky-200" + (let + ((r (tw-process-token "sm:hover:bg-sky-200"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") ":hover")) + (assert (contains? (get r "rule") "background-color:hsl(199,89%,87%)")))) + (deftest + "md:hover:bg-stone-200" + (let + ((r (tw-process-token "md:hover:bg-stone-200"))) + (assert (contains? (get r "rule") "@media(min-width:768px)")) + (assert (contains? (get r "rule") ":hover")))) + (deftest + "lg:focus:border-blue-500" + (let + ((r (tw-process-token "lg:focus:border-blue-500"))) + (assert (contains? (get r "rule") "@media(min-width:1024px)")) + (assert (contains? (get r "rule") ":focus")) + (assert (contains? (get r "rule") "border-color:hsl(217,91%,53%)")))) + (deftest + "sm:disabled:opacity-50" + (let + ((r (tw-process-token "sm:disabled:opacity-50"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") ":disabled")) + (assert (contains? (get r "rule") "opacity:0.5")))) + (deftest + "lg:hover:scale-105" + (let + ((r (tw-process-token "lg:hover:scale-105"))) + (assert (contains? (get r "rule") "@media(min-width:1024px)")) + (assert (contains? (get r "rule") ":hover")) + (assert (contains? (get r "rule") "transform:scale(1.05)"))))) + +(defsuite + "tw-media-integration" + (deftest + "responsive grid: mobile-first" + (let + ((tokens (list "grid" "grid-cols-1" "sm:grid-cols-2" "md:grid-cols-3" "lg:grid-cols-4" "gap-4"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "responsive flex: column→row" + (let + ((tokens (list "flex" "flex-col" "sm:flex-row" "gap-4" "items-start" "sm:items-center"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "responsive spacing" + (let + ((tokens (list "p-4" "sm:p-6" "md:p-8" "lg:p-12" "xl:p-16"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "responsive typography" + (let + ((tokens (list "text-base" "sm:text-lg" "md:text-xl" "lg:text-2xl" "xl:text-3xl"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "responsive visibility" + (let + ((tokens (list "hidden" "sm:block" "md:hidden" "lg:block"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "responsive button with hover states" + (let + ((tokens (list "px-4" "py-2" "text-sm" "bg-sky-500" "text-white" "rounded-md" "hover:bg-sky-600" "sm:px-6" "sm:text-base" "md:px-8"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens))))) + +(defsuite + "tw-token-negative" + (begin + (deftest + "-mt-4 negates value" + (let + ((r (tw-process-token "-mt-4"))) + (assert (contains? (get r "rule") "margin-top:-1rem")))) + (deftest + "-ml-2 negates value" + (let + ((r (tw-process-token "-ml-2"))) + (assert (contains? (get r "rule") "margin-left:-0.5rem")))) + (deftest + "-mx-4 negates both sides" + (let + ((r (tw-process-token "-mx-4"))) + (assert (contains? (get r "rule") "margin-left:-1rem")) + (assert (contains? (get r "rule") "margin-right:-1rem")))) + (deftest + "positive mt-4 unaffected" + (let + ((r (tw-process-token "mt-4"))) + (assert (contains? (get r "rule") "margin-top:1rem")))))) + +(defsuite + "tw-important" + (deftest + "!p-4 → padding with !important" + (let + ((r (tw-process-token "!p-4"))) + (assert (contains? (get r "rule") "padding:1rem !important")))) + (deftest + "!font-bold" + (let + ((r (tw-process-token "!font-bold"))) + (assert (contains? (get r "rule") "font-weight:700 !important")))) + (deftest + "!hidden" + (let + ((r (tw-process-token "!hidden"))) + (assert (contains? (get r "rule") "display:none !important")))) + (deftest + "!bg-sky-500" + (let + ((r (tw-process-token "!bg-sky-500"))) + (assert + (contains? + (get r "rule") + "background-color:hsl(199,89%,53%) !important")))) + (deftest + "! strips bang from class name" + (assert= (get (tw-process-token "!p-4") "cls") "sx-p-4"))) + +(defsuite + "tw-integration" + (deftest + "card: resolves all tokens" + (let + ((tokens (list "bg-white" "p-6" "rounded-xl" "shadow-md"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "nav: resolves all tokens" + (let + ((tokens (list "flex" "items-center" "justify-between" "px-4" "py-2" "bg-stone-100"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "button: resolves all tokens" + (let + ((tokens (list "inline-flex" "items-center" "px-4" "py-2" "text-sm" "font-medium" "rounded-md" "bg-sky-500" "text-white" "transition-colors" "hover:bg-sky-600" "focus:outline-none" "focus-visible:ring-2" "disabled:opacity-50" "cursor-pointer"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "grid layout: resolves all tokens" + (let + ((tokens (list "grid" "grid-cols-3" "gap-4" "lg:grid-cols-4" "sm:grid-cols-2"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "positioned overlay: resolves all tokens" + (let + ((tokens (list "fixed" "inset-0" "z-50" "bg-black" "opacity-50"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "form input: resolves all tokens" + (let + ((tokens (list "block" "w-full" "rounded-md" "border" "border-stone-300" "px-3" "py-2" "text-sm" "placeholder:text-stone-400" "focus:border-sky-500" "focus:ring-2"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "responsive layout: resolves all tokens" + (let + ((tokens (list "flex" "flex-col" "sm:flex-row" "gap-4" "items-start" "sm:items-center" "justify-between" "p-4" "md:p-8"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "typography: resolves all tokens" + (let + ((tokens (list "text-lg" "font-semibold" "leading-tight" "tracking-wide" "text-stone-900" "antialiased"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens))))) + +(defsuite + "tw-dark" + (deftest + "dark:bg-stone-900" + (let + ((r (tw-process-token "dark:bg-stone-900"))) + (assert (contains? (get r "rule") ".dark ")) + (assert (contains? (get r "rule") "background-color:hsl(25,6%,21%)")))) + (deftest + "dark:text-white" + (let + ((r (tw-process-token "dark:text-white"))) + (assert (contains? (get r "rule") ".dark ")) + (assert (contains? (get r "rule") "color:#ffffff")))) + (deftest + "dark:text-stone-100" + (let + ((r (tw-process-token "dark:text-stone-100"))) + (assert (contains? (get r "rule") ".dark ")) + (assert (contains? (get r "rule") "color:hsl(25,6%,93%)")))) + (deftest + "dark:border-stone-700" + (let + ((r (tw-process-token "dark:border-stone-700"))) + (assert (contains? (get r "rule") ".dark ")) + (assert (contains? (get r "rule") "border-color:hsl(25,6%,38%)")))) + (deftest + "dark: class name" + (assert= + (get (tw-process-token "dark:bg-stone-900") "cls") + "sx-dark-bg-stone-900")) + (deftest + "dark:hover: combined" + (let + ((r (tw-process-token "dark:hover:bg-stone-800"))) + (assert (contains? (get r "rule") ".dark ")) + (assert (contains? (get r "rule") ":hover")) + (assert (contains? (get r "rule") "background-color:hsl(25,6%,30%)")))) + (deftest + "dark:hidden" + (let + ((r (tw-process-token "dark:hidden"))) + (assert (contains? (get r "rule") ".dark ")) + (assert (contains? (get r "rule") "display:none"))))) + +(defsuite + "tw-group" + (deftest + "group-hover:text-sky-500" + (let + ((r (tw-process-token "group-hover:text-sky-500"))) + (assert (contains? (get r "rule") ".group:hover ")) + (assert (contains? (get r "rule") "color:hsl(199,89%,53%)")))) + (deftest + "group-focus:border-blue-500" + (let + ((r (tw-process-token "group-focus:border-blue-500"))) + (assert (contains? (get r "rule") ".group:focus ")) + (assert (contains? (get r "rule") "border-color:hsl(217,91%,53%)")))) + (deftest + "group-active:scale-95" + (let + ((r (tw-process-token "group-active:scale-95"))) + (assert (contains? (get r "rule") ".group:active ")) + (assert (contains? (get r "rule") "transform:scale(0.95)")))) + (deftest + "group-hover: class name" + (assert= + (get (tw-process-token "group-hover:text-sky-500") "cls") + "sx-group-hover-text-sky-500")) + (deftest + "sm:group-hover: — breakpoint + group" + (let + ((r (tw-process-token "sm:group-hover:text-sky-500"))) + (assert (contains? (get r "rule") "@media(min-width:640px)")) + (assert (contains? (get r "rule") ".group:hover ")))) + (deftest + "group-hover:opacity-100" + (let + ((r (tw-process-token "group-hover:opacity-100"))) + (assert (contains? (get r "rule") ".group:hover ")) + (assert (contains? (get r "rule") "opacity:1"))))) + +(defsuite + "tw-peer" + (deftest + "peer-focus:border-sky-500" + (let + ((r (tw-process-token "peer-focus:border-sky-500"))) + (assert (contains? (get r "rule") ".peer:focus~")) + (assert (contains? (get r "rule") "border-color:hsl(199,89%,53%)")))) + (deftest + "peer-hover:text-sky-600" + (let + ((r (tw-process-token "peer-hover:text-sky-600"))) + (assert (contains? (get r "rule") ".peer:hover~")) + (assert (contains? (get r "rule") "color:hsl(199,89%,45%)")))) + (deftest + "peer-checked:bg-sky-100" + (let + ((r (tw-process-token "peer-checked:bg-sky-100"))) + (assert (contains? (get r "rule") ".peer:checked~")) + (assert (contains? (get r "rule") "background-color:hsl(199,89%,93%)")))) + (deftest + "peer-disabled:opacity-50" + (let + ((r (tw-process-token "peer-disabled:opacity-50"))) + (assert (contains? (get r "rule") ".peer:disabled~")) + (assert (contains? (get r "rule") "opacity:0.5")))) + (deftest + "peer-invalid:border-red-500" + (let + ((r (tw-process-token "peer-invalid:border-red-500"))) + (assert (contains? (get r "rule") ".peer:invalid~")) + (assert (contains? (get r "rule") "border-color:hsl(0,72%,53%)")))) + (deftest + "peer-focus: class name" + (assert= + (get (tw-process-token "peer-focus:border-sky-500") "cls") + "sx-peer-focus-border-sky-500"))) + +(defsuite + "tw-container-query" + (deftest + "@md:flex → 448px" + (let + ((r (tw-process-token "@md:flex"))) + (assert (contains? (get r "rule") "@container(min-width:448px)")) + (assert (contains? (get r "rule") "display:flex")))) + (deftest + "@lg:grid-cols-3 → 512px" + (let + ((r (tw-process-token "@lg:grid-cols-3"))) + (assert (contains? (get r "rule") "@container(min-width:512px)")) + (assert + (contains? + (get r "rule") + "grid-template-columns:repeat(3,minmax(0,1fr))")))) + (deftest + "@sm:hidden → 384px" + (let + ((r (tw-process-token "@sm:hidden"))) + (assert (contains? (get r "rule") "@container(min-width:384px)")) + (assert (contains? (get r "rule") "display:none")))) + (deftest + "@xl:text-lg → 576px" + (let + ((r (tw-process-token "@xl:text-lg"))) + (assert (contains? (get r "rule") "@container(min-width:576px)")) + (assert (contains? (get r "rule") "font-size:1.125rem")))) + (deftest + "@xs:p-2 → 320px" + (let + ((r (tw-process-token "@xs:p-2"))) + (assert (contains? (get r "rule") "@container(min-width:320px)")) + (assert (contains? (get r "rule") "padding:0.5rem")))) + (deftest + "@container class name" + (assert= (get (tw-process-token "@md:flex") "cls") "sx-@md-flex"))) + +(defsuite + "tw-colour-opacity" + (deftest + "bg-sky-500/50 → alpha 0.5" + (let + ((r (tw-process-token "bg-sky-500/50"))) + (assert + (contains? (get r "rule") "background-color:hsl(199,89%,53%,0.5)")))) + (deftest + "bg-sky-500/75 → alpha 0.75" + (let + ((r (tw-process-token "bg-sky-500/75"))) + (assert + (contains? (get r "rule") "background-color:hsl(199,89%,53%,0.75)")))) + (deftest + "bg-sky-500/25 → alpha 0.25" + (let + ((r (tw-process-token "bg-sky-500/25"))) + (assert + (contains? (get r "rule") "background-color:hsl(199,89%,53%,0.25)")))) + (deftest + "text-red-600/75" + (let + ((r (tw-process-token "text-red-600/75"))) + (assert (contains? (get r "rule") "color:hsl(0,72%,45%,0.75)")))) + (deftest + "border-blue-500/50" + (let + ((r (tw-process-token "border-blue-500/50"))) + (assert (contains? (get r "rule") "border-color:hsl(217,91%,53%,0.5)")))) + (deftest + "without /alpha unchanged" + (let + ((r (tw-process-token "bg-sky-500"))) + (assert (contains? (get r "rule") "background-color:hsl(199,89%,53%)")))) + (deftest + "hover: with opacity" + (let + ((r (tw-process-token "hover:bg-sky-500/50"))) + (assert (contains? (get r "rule") ":hover")) + (assert (contains? (get r "rule") "hsl(199,89%,53%,0.5)")))) + (deftest + "dark: with opacity" + (let + ((r (tw-process-token "dark:bg-stone-900/80"))) + (assert (contains? (get r "rule") ".dark ")) + (assert (contains? (get r "rule") "hsl(25,6%,21%,0.8)"))))) + +(defsuite + "tw-ring-colour" + (deftest + "ring-sky-500" + (let + ((r (tw-process-token "ring-sky-500"))) + (assert (contains? (get r "rule") "--tw-ring-color:hsl(199,89%,53%)")))) + (deftest + "ring-blue-600" + (let + ((r (tw-process-token "ring-blue-600"))) + (assert (contains? (get r "rule") "--tw-ring-color:hsl(217,91%,45%)")))) + (deftest + "ring-red-500" + (let + ((r (tw-process-token "ring-red-500"))) + (assert (contains? (get r "rule") "--tw-ring-color:hsl(0,72%,53%)")))) + (deftest + "ring-2 still works (width)" + (let + ((r (tw-process-token "ring-2"))) + (assert (contains? (get r "rule") "box-shadow:0 0 0 2px")))) + (deftest + "ring default width" + (let + ((r (tw-process-token "ring"))) + (assert (contains? (get r "rule") "box-shadow:0 0 0 3px")))) + (deftest + "focus:ring-sky-500" + (let + ((r (tw-process-token "focus:ring-sky-500"))) + (assert (contains? (get r "rule") ":focus")) + (assert (contains? (get r "rule") "--tw-ring-color:hsl(199,89%,53%)"))))) + +(defsuite + "tw-new-features-integration" + (deftest + "dark mode card" + (let + ((tokens (list "bg-white" "dark:bg-stone-900" "text-stone-900" "dark:text-stone-100" "border" "dark:border-stone-700" "rounded-xl" "shadow-md" "p-6"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "form with peer validation" + (let + ((tokens (list "border" "rounded-md" "px-3" "py-2" "text-sm" "peer-focus:border-sky-500" "peer-focus:ring-2" "peer-invalid:border-red-500" "peer-disabled:opacity-50"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "card with group hover" + (let + ((tokens (list "bg-white" "rounded-xl" "p-4" "transition-all" "group-hover:shadow-lg" "group-hover:scale-105"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "important overrides" + (let + ((tokens (list "!p-0" "!m-0" "!hidden" "!text-center"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "container responsive" + (let + ((tokens (list "flex" "flex-col" "@sm:flex-row" "@md:grid" "@md:grid-cols-2"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "dark responsive button" + (let + ((tokens (list "px-4" "py-2" "bg-sky-500" "text-white" "rounded-md" "hover:bg-sky-600" "dark:bg-sky-600" "dark:hover:bg-sky-700" "focus:ring-2" "focus:ring-sky-500" "sm:px-6"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))) + (deftest + "opacity modifiers" + (let + ((tokens (list "bg-sky-500/90" "hover:bg-sky-600/90" "text-stone-900/80" "border-stone-300/50" "dark:bg-stone-900/95"))) + (assert (every? (fn (t) (not (nil? (tw-process-token t)))) tokens)))))