diff --git a/test/test_effects.ml b/test/test_effects.ml index c8b8e49f..c917ebd4 100644 --- a/test/test_effects.ml +++ b/test/test_effects.ml @@ -256,17 +256,15 @@ let test_inset_shadow_theme_override () = Tw.Scheme.with_overrides Tw.Scheme.default [ ("inset-shadow-sm", "inset 0 1px 1px rgb(0 0 0 / 0.05)") ] in - let css = - Tw.to_css ~theme ~base:false - [ Result.get_ok (Tw.of_string ~theme "inset-shadow-sm") ] - |> Tw.Css.to_string ~minify:true - in - Alcotest.(check bool) - "inset-shadow-sm @theme override flows to [inset 0 1px 1px]" true - (Astring.String.is_infix ~affix:"inset 0 1px 1px" css); - Alcotest.(check bool) - "inset-shadow-sm @theme override drops the default [inset 0 2px 4px]" false - (Astring.String.is_infix ~affix:"inset 0 2px 4px" css) + (* The whole list, which is where "drops the default" is said: it used to be a + second search for a spelling that must not appear, and a sheet with no rule + in it satisfies that too. *) + Test_helpers.check_declarations ~theme "inset-shadow-sm" + [ + "--tw-inset-shadow:inset 0 1px 1px var(--tw-inset-shadow-color,rgb(0 0 \ + 0/.05))"; + composes_box_shadow; + ] (* The override is read through the CSS shadow grammar, so it carries whatever that grammar allows: a length in any unit, a fourth length for the spread, @@ -275,28 +273,26 @@ let test_inset_shadow_theme_override () = the rest, which put the built-in [inset 0 2px 4px] in the sheet and lost the override without saying so. *) let test_inset_shadow_theme_override_grammar () = - let sheet override = + (* The whole list per override. "The default is not substituted" was a search + for a spelling that must not appear; the list says it by construction, and + also that the colour the project wrote reached the fallback slot. *) + let holds ~override body = let theme = Tw.Scheme.with_overrides Tw.Scheme.default [ ("inset-shadow-sm", override) ] in - Tw.to_css ~theme ~base:false - [ Result.get_ok (Tw.of_string ~theme "inset-shadow-sm") ] - |> Tw.Css.to_string ~minify:true - in - let holds ~name ~override affix = - Alcotest.(check bool) - name true - (Astring.String.is_infix ~affix (sheet override)) + Test_helpers.check_declarations ~theme "inset-shadow-sm" + [ + "--tw-inset-shadow:" ^ body + ^ " var(--tw-inset-shadow-color,rgb(0 0 0/.05))"; + composes_box_shadow; + ] in - holds ~name:"an em override keeps its unit" - ~override:"inset 0 0.125em 0.25em rgb(0 0 0 / 0.05)" "inset 0 .125em .25em"; - holds ~name:"a fourth length is the spread" - ~override:"inset 0 1px 2px 3px rgb(0 0 0 / 0.05)" "inset 0 1px 2px 3px"; - Alcotest.(check bool) - "the default is not substituted" false - (Astring.String.is_infix ~affix:"inset 0 2px 4px" - (sheet "inset 0 0.125em 0.25em rgb(0 0 0 / 0.05)")) + (* an em override keeps its unit *) + holds ~override:"inset 0 0.125em 0.25em rgb(0 0 0 / 0.05)" + "inset 0 .125em .25em"; + (* a fourth length is the spread *) + holds ~override:"inset 0 1px 2px 3px rgb(0 0 0 / 0.05)" "inset 0 1px 2px 3px" (* A shadeless colour has no shade segment, so shadow-white never reached the colour parse: the size cases claimed the segment and rejected it. The class @@ -609,14 +605,9 @@ let test_bracket_hex_opacity_var () = comes from a separate, correctly-scaled computation, so --tw-shadow-alpha here is a plain, unconverted echo of what the class wrote. *) let test_shadow_bracket_alpha_tracking () = - let css cls = - match Tw.of_string cls with - | Ok u -> Tw.to_css ~base:false [ u ] |> Tw.Css.to_string ~minify:true - | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m - in - let lacks cls affix = - Alcotest.(check bool) cls false (Astring.String.is_infix ~affix (css cls)) - in + (* The [lacks] checks that used to sit beside each list here - "no + --tw-shadow-alpha:2500%" - said nothing the list does not already say, and + passed on a sheet with no rule in it. *) let shadow_lg alpha painted = [ "--tw-shadow-alpha:" ^ alpha; @@ -627,7 +618,6 @@ let test_shadow_bracket_alpha_tracking () = in Test_helpers.check_declarations "shadow-lg/[25]" (shadow_lg "25" "oklab(0%0 0/25)"); - lacks "shadow-lg/[25]" "--tw-shadow-alpha:2500%"; Test_helpers.check_declarations "shadow-[0_1px_2px_#000]/[25]" [ "--tw-shadow-alpha:25"; @@ -641,7 +631,6 @@ let test_shadow_bracket_alpha_tracking () = 0/25))"; composes_box_shadow; ]; - lacks "inset-shadow-sm/[25]" "--tw-inset-shadow-alpha:2500%"; (* A bracket alpha that does carry a [%] sign, or the plain percent form, both keep behaving as a percentage. *) Test_helpers.check_declarations "shadow-lg/[25%]" @@ -784,13 +773,13 @@ let test_project_shadow_tokens () = ("inset-shadow-dent", "inset 0 1px 2px #000"); ] in - let css cls = - match Tw.of_string ~theme cls with - | Ok u -> Tw.to_css ~theme ~base:false [ u ] |> Tw.Css.to_string - | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m - in - let emits affix cls = - Alcotest.(check bool) cls true (Astring.String.is_infix ~affix (css cls)) + let emits decl cls = + Test_helpers.check_declarations ~theme ~minify:false cls + [ + decl; + "box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), \ + var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)"; + ] in emits "--tw-shadow: 0 0 8px var(--tw-shadow-color, #f00)" "shadow-halo"; emits "--tw-inset-shadow: inset 0 1px 2px var(--tw-inset-shadow-color, #000)" diff --git a/test/test_masks.ml b/test/test_masks.ml index 69635b98..505ca22c 100644 --- a/test/test_masks.ml +++ b/test/test_masks.ml @@ -47,15 +47,13 @@ let test_typed () = (* [mask-[]] takes any background-image, not only a linear-gradient. *) let test_bracket_image () = - let css cls = - match Tw.of_string cls with - | Ok u -> Tw.to_css ~base:false [ u ] |> Tw.Css.to_string ~minify:true - | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m - in - Alcotest.(check bool) - "radial-gradient reaches mask-image" true - (Astring.String.is_infix ~affix:"mask-image:radial-gradient(white,black)" - (css "mask-[radial-gradient(white,black)]")); + (* The whole list. A mask utility aliases its property for WebKit, so the + affix this replaces was satisfied by the unaliased arm alone. *) + Test_helpers.check_declarations "mask-[radial-gradient(white,black)]" + [ + "-webkit-mask-image:radial-gradient(white,black)"; + "mask-image:radial-gradient(white,black)"; + ]; check "mask-[radial-gradient(white,black)]"; check "mask-[conic-gradient(white,black)]"; check "mask-[linear-gradient(white,black)]" @@ -69,19 +67,12 @@ let test_bracket_layer_list () = [30% 50%] compacts to [30%50%] - still two components, still valid, and it round-trips - while a different minifier folds the pair to its [x] alone. Pinning either spelling pins the minifier instead of the reading. *) - let css cls = - match Tw.of_string cls with - | Ok u -> Tw.to_css ~base:false [ u ] |> Tw.Css.to_string - | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m + let both cls prop value = + Test_helpers.check_declarations ~minify:false cls + [ "-webkit-" ^ prop ^ ": " ^ value; prop ^ ": " ^ value ] in - Alcotest.(check bool) - "two url layers stay two" true - (Astring.String.is_infix ~affix:"mask-image: url(/a.png), url(/b.png)" - (css "mask-[url(/a.png),url(/b.png)]")); - Alcotest.(check bool) - "two positions stay two" true - (Astring.String.is_infix ~affix:"mask-position: 30% 50%, 70% 50%" - (css "mask-position-[30%_50%,70%_50%]")) + both "mask-[url(/a.png),url(/b.png)]" "mask-image" "url(/a.png), url(/b.png)"; + both "mask-position-[30%_50%,70%_50%]" "mask-position" "30% 50%, 70% 50%" (* A bracket that would end the declaration or swallow what follows it is the one thing no mask utility can hold, and Tailwind writes nothing for it @@ -168,17 +159,13 @@ let test_bracket_falls_through_to_a_longhand () = rem before; every CSS length unit does now, matching real Tailwind's mask-size-[2em]. *) let test_bracket_size_units () = - let css cls = - match Tw.of_string cls with - | Ok u -> Tw.to_css ~base:false [ u ] |> Tw.Css.to_string ~minify:true - | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m - in - let has cls affix = - Alcotest.(check bool) cls true (Astring.String.is_infix ~affix (css cls)) + let has cls = + Test_helpers.check_declarations cls + [ "-webkit-mask-size:2em"; "mask-size:2em" ] in - has "mask-size-[2em]" "mask-size:2em"; - has "mask-[size:2em]" "mask-size:2em"; - has "mask-[length:2em]" "mask-size:2em" + has "mask-size-[2em]"; + has "mask-[size:2em]"; + has "mask-[length:2em]" (* A mask-position bracket takes the whole CSS grammar, the same as background-position: a single edge keyword and the edge/offset form. Both @@ -187,20 +174,16 @@ let test_bracket_position_grammar () = (* Unminified, so the assertions read as the grammar they are about. Compact spelling belongs to the minifier: [10px 20px] keeps its space while [30% 50%] loses it, because a [%] already ends the token. *) - let css cls = - match Tw.of_string cls with - | Ok u -> Tw.to_css ~base:false [ u ] |> Tw.Css.to_string - | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m - in - let has cls affix = - Alcotest.(check bool) cls true (Astring.String.is_infix ~affix (css cls)) + let has cls value = + Test_helpers.check_declarations ~minify:false cls + [ "-webkit-mask-position: " ^ value; "mask-position: " ^ value ] in - has "mask-[position:top]" "mask-position: top"; - has "mask-position-[top]" "mask-position: top"; - has "mask-[top]" "mask-position: top"; + has "mask-[position:top]" "top"; + has "mask-position-[top]" "top"; + has "mask-[top]" "top"; (* the lengths and layer-list forms are unchanged *) - has "mask-[position:10px_20px]" "mask-position: 10px 20px"; - has "mask-position-[30%_50%,70%_50%]" "mask-position: 30% 50%, 70% 50%" + has "mask-[position:10px_20px]" "10px 20px"; + has "mask-position-[30%_50%,70%_50%]" "30% 50%, 70% 50%" (* Masks sit between the backgrounds and fill/stroke, and the mask-gradient utilities lead them. Sharing padding's slot interleaved the two families with @@ -228,15 +211,8 @@ let order_matches_tailwind () = (* A mask image is an arbitrary value, so [_] is a space and [\_] a literal underscore: a file name carrying one is written with the escape. *) let test_bracket_image_underscore_escape () = - let css cls = - match Tw.of_string cls with - | Ok u -> Tw.to_css ~base:false [ u ] |> Tw.Css.to_string - | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m - in - Alcotest.(check bool) - "an escaped underscore stays in the file name" true - (Astring.String.is_infix ~affix:"mask-image: url('a_b.png')" - (css {|mask-[url('a\_b.png')]|})) + Test_helpers.check_declarations ~minify:false {|mask-[url('a\_b.png')]|} + [ "-webkit-mask-image: url('a_b.png')"; "mask-image: url('a_b.png')" ] (* Tailwind leaves a bare [_] alone inside [url()], where it is part of the file name rather than an encoded space, so [mask-[url('a_b.png')]] names @@ -252,22 +228,14 @@ let test_bracket_url_keeps_underscores () = name too. Only the [_] outside the url is a space, which [image-set()] is the case that tells the two apart. *) let test_bracket_url_underscore () = - let css cls = - match Tw.of_string cls with - | Ok u -> Tw.to_css ~base:false [ u ] |> Tw.Css.to_string - | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m - in - let has cls affix = - Alcotest.(check bool) - (cls ^ " emits " ^ affix) - true - (Astring.String.is_infix ~affix (css cls)) + let has cls value = + Test_helpers.check_declarations ~minify:false cls + [ "-webkit-mask-image: " ^ value; "mask-image: " ^ value ] in - has "mask-[url('a_b.png')]" "mask-image: url('a_b.png')"; + has "mask-[url('a_b.png')]" "url('a_b.png')"; (* Tailwind writes the inner url quoted. The quoting is cascade's canonical spelling of the same URL; the underscore is the point. *) - has "mask-[image-set(url('a_b.png')_1x)]" - "mask-image: image-set(url(a_b.png) 1x)" + has "mask-[image-set(url('a_b.png')_1x)]" "image-set(url(a_b.png) 1x)" (* A [url()] carrying a position is no [url()] token, so the typed reading has no answer for it and the whole bracket goes out verbatim instead. What must diff --git a/test/test_prose.ml b/test/test_prose.ml index 8550bcd8..11b7507d 100644 --- a/test/test_prose.ml +++ b/test/test_prose.ml @@ -23,26 +23,29 @@ let test_combinations () = Alcotest.(check string) "complex prose" "prose prose-xl prose-gray mx-auto" complex +(* [prose] is a component, so its 77 rules are not worth spelling out; what + these check is that the right selectors and the right declarations are among + them. Membership in the lists is exact where a substring was not: the affix + [.prose] matched every descendant rule as well as the root one, and the h1 + affix stopped mid-selector. *) let test_css_generation () = - (* Test that prose generates CSS rules *) - let css = to_css [ prose ] in - let css_string = Css.to_string css in - - (* Check that CSS variables are included *) + let utility = + match Tw.of_string "prose" with + | Ok u -> u + | Error (`Msg m) -> Alcotest.failf "prose: %s" m + in + let selectors = Test_helpers.selectors_of_utility utility in + let has_selector sel = + Alcotest.(check bool) ("has " ^ sel) true (List.mem sel selectors) + in + has_selector ".prose"; + has_selector + {|.prose :where(h1):not(:where([class~="not-prose"], [class~="not-prose"] *))|}; + (* The root rule reads the body variable rather than inlining a colour. *) Alcotest.(check bool) "has prose body variable" true - (Astring.String.is_infix ~affix:"--tw-prose-body" css_string); - - (* Check that prose class is generated *) - Alcotest.(check bool) - "has prose class" true - (Astring.String.is_infix ~affix:".prose" css_string); - - (* Check that descendant selectors are generated *) - Alcotest.(check bool) - "has prose h1 selector" true - (Astring.String.is_infix - ~affix:".prose :where(h1):not(:where([class~=\"not-prose\"]" css_string) + (List.mem "color:var(--tw-prose-body)" + (Test_helpers.declarations_of_class "prose")) let test_inline_styles () = (* Prose utilities can generate inline styles from their rules, but CSS @@ -63,15 +66,17 @@ let test_inline_styles () = (* prose-invert remaps the palette to the inverted vars; prose-orange overrides the link accent colours. Both used to be no-ops / unknown. *) let test_color_variants () = - let invert = Css.to_string (to_css ~base:false [ prose_invert ]) in Alcotest.(check bool) "prose-invert remaps body to the invert var" true - (Astring.String.is_infix ~affix:"var(--tw-prose-invert-body)" invert); - let orange = Css.to_string (to_css ~base:false [ prose_orange ]) in - Alcotest.(check bool) - "prose-orange sets the link accent" true - (Astring.String.is_infix ~affix:"--tw-prose-links" orange - && Astring.String.is_infix ~affix:"--tw-prose-invert-links" orange) + (List.mem "--tw-prose-body:var(--tw-prose-invert-body)" + (Test_helpers.declarations_of_class "prose-invert")); + (* A colour variant sets only the accents it names, which the pair of searches + this replaces could not say. *) + Test_helpers.check_declarations "prose-orange" + [ + "--tw-prose-links:oklch(64.6%.222 41.116)"; + "--tw-prose-invert-links:oklch(70.5%.213 47.604)"; + ] (* The colours [prose] sets on its own are the gray palette, normal and inverted. Nothing else in the suite reads them, so an edit to [gray_normal] diff --git a/test/test_svg.ml b/test/test_svg.ml index 62ef8c6f..f9b2cbed 100644 --- a/test/test_svg.ml +++ b/test/test_svg.ml @@ -10,25 +10,15 @@ let basic_svg () = stroke-width case and rejected; the width case now only matches integers, so they reach the colour parse. *) let stroke_shadeless_colors () = - let css cls = - match Tw.of_string cls with - | Ok u -> Tw.to_css ~base:false [ u ] |> Tw.Css.to_string ~minify:true - | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m - in - Alcotest.(check bool) - "stroke-white references --color-white" true - (Astring.String.is_infix ~affix:"stroke:var(--color-white)" - (css "stroke-white")); - Alcotest.(check bool) - "stroke-black references --color-black" true - (Astring.String.is_infix ~affix:"stroke:var(--color-black)" - (css "stroke-black")); + (* The whole list, which is what "parses as a colour, not a width" means: the + affix on [stroke:] left open whether a [stroke-width] was written beside + it. *) + Test_helpers.check_declarations "stroke-white" [ "stroke:var(--color-white)" ]; + Test_helpers.check_declarations "stroke-black" [ "stroke:var(--color-black)" ]; (* Integer widths still parse as widths, not colours. The [px] is Tailwind's minifier reading its generator's bare [2], and it is the spelling the upstream corpus records and [--diff] compares. *) - Alcotest.(check bool) - "stroke-2 stays a width" true - (Astring.String.is_infix ~affix:"stroke-width:2px" (css "stroke-2")) + Test_helpers.check_declarations "stroke-2" [ "stroke-width:2px" ] let stroke_light_dark_color () = let cls = "stroke-[light-dark(red,blue)]" in @@ -36,24 +26,13 @@ let stroke_light_dark_color () = | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m | Ok u -> Alcotest.(check string) "class" cls (Tw.pp u); - let css = Tw.to_css ~base:false [ u ] |> Tw.Css.to_string ~minify:true in - Alcotest.(check bool) - "light-dark() is routed as a stroke colour" true - (Astring.String.is_infix ~affix:"stroke:light-dark(red,blue)" css) + Test_helpers.check_declarations cls [ "stroke:light-dark(red,blue)" ] (* An arbitrary stroke width is read with the whole CSS length grammar, so a unit the reader does not name is not silently rendered as a zero width. *) let stroke_arbitrary_width_units () = - let width cls = - match Tw.of_string cls with - | Ok u -> Tw.to_css ~base:false [ u ] |> Tw.Css.to_string ~minify:true - | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m - in let emits cls value = - Alcotest.(check bool) - (cls ^ " emits " ^ value) - true - (Astring.String.is_infix ~affix:("stroke-width:" ^ value) (width cls)) + Test_helpers.check_declarations cls [ "stroke-width:" ^ value ] in emits "stroke-[1.5rem]" "1.5rem"; emits "stroke-[2em]" "2em"; @@ -105,14 +84,7 @@ let stroke_width_rejects_ocaml_literals () = stroke read the bracket text back as a hex and answered black for every colour with no hex spelling. *) let bracket_named_color () = - let css cls = - match Tw.of_string cls with - | Ok u -> Tw.to_css ~base:false [ u ] |> Tw.Css.to_string ~minify:true - | Error (`Msg m) -> Alcotest.failf "%s: %s" cls m - in - let emits affix cls = - Alcotest.(check bool) cls true (Astring.String.is_infix ~affix (css cls)) - in + let emits decl cls = Test_helpers.check_declarations cls [ decl ] in emits "stroke:rebeccapurple" "stroke-[rebeccapurple]"; emits "stroke:currentColor" "stroke-[currentColor]"; (* the modifier mixes into the colour the bracket named, not into black *)