From 409fcf9c694e4dfd1066bec34eac4ef1628e3e2d Mon Sep 17 00:00:00 2001 From: Andrew Meier Date: Sat, 28 Feb 2026 06:36:37 -0500 Subject: [PATCH 1/2] Add Datastar object-syntax overloads and expand test coverage --- sln/src/FSharp.ViewEngine/Datastar.fs | 5 + sln/src/Tests/AlpineTests.fs | 68 ++++++++++ sln/src/Tests/{Tests.fs => CoreTests.fs} | 151 ++++++++++++++++------- sln/src/Tests/DatastarTests.fs | 115 +++++++++++++++++ sln/src/Tests/HtmxTests.fs | 45 +++++++ sln/src/Tests/Program.fs | 10 +- sln/src/Tests/SvgTests.fs | 52 ++++++++ sln/src/Tests/TailwindTests.fs | 54 ++++++++ sln/src/Tests/Tests.fsproj | 7 +- 9 files changed, 458 insertions(+), 49 deletions(-) create mode 100644 sln/src/Tests/AlpineTests.fs rename sln/src/Tests/{Tests.fs => CoreTests.fs} (59%) create mode 100644 sln/src/Tests/DatastarTests.fs create mode 100644 sln/src/Tests/HtmxTests.fs create mode 100644 sln/src/Tests/SvgTests.fs create mode 100644 sln/src/Tests/TailwindTests.fs diff --git a/sln/src/FSharp.ViewEngine/Datastar.fs b/sln/src/FSharp.ViewEngine/Datastar.fs index 4f910c5..6ed5875 100644 --- a/sln/src/FSharp.ViewEngine/Datastar.fs +++ b/sln/src/FSharp.ViewEngine/Datastar.fs @@ -2,10 +2,13 @@ namespace FSharp.ViewEngine type Datastar = // Core attributes + static member inline _dataAttr (v: string) = { Name = "data-attr"; Value = ValueSome v } static member inline _dataAttr (name: string, v: string) = { Name = $"data-attr:{name}"; Value = ValueSome v } static member inline _dataBind (name: string) = { Name = $"data-bind:{name}"; Value = ValueNone } static member inline _dataBind (name: string, v: string) = { Name = $"data-bind:{name}"; Value = ValueSome v } + static member inline _dataClass (v: string) = { Name = "data-class"; Value = ValueSome v } static member inline _dataClass (name: string, v: string) = { Name = $"data-class:{name}"; Value = ValueSome v } + static member inline _dataComputed (v: string) = { Name = "data-computed"; Value = ValueSome v } static member inline _dataComputed (name: string, v: string) = { Name = $"data-computed:{name}"; Value = ValueSome v } static member inline _dataEffect (v: string) = { Name = "data-effect"; Value = ValueSome v } static member inline _dataIgnore = { Name = "data-ignore"; Value = ValueNone } @@ -23,7 +26,9 @@ type Datastar = static member inline _dataRef (name: string) = { Name = $"data-ref:{name}"; Value = ValueNone } static member inline _dataRef (name: string, v: string) = { Name = $"data-ref:{name}"; Value = ValueSome v } static member inline _dataShow (v: string) = { Name = "data-show"; Value = ValueSome v } + static member inline _dataSignals (v: string) = { Name = "data-signals"; Value = ValueSome v } static member inline _dataSignals (name: string, v: string) = { Name = $"data-signals:{name}"; Value = ValueSome v } + static member inline _dataStyle (v: string) = { Name = "data-style"; Value = ValueSome v } static member inline _dataStyle (prop: string, v: string) = { Name = $"data-style:{prop}"; Value = ValueSome v } static member inline _dataText (v: string) = { Name = "data-text"; Value = ValueSome v } diff --git a/sln/src/Tests/AlpineTests.fs b/sln/src/Tests/AlpineTests.fs new file mode 100644 index 0000000..080f0fd --- /dev/null +++ b/sln/src/Tests/AlpineTests.fs @@ -0,0 +1,68 @@ +module AlpineTests + +open FSharp.ViewEngine +open System.Text.RegularExpressions +open Expecto +open type Html +open type Alpine + +[] +let tests = + testList "Alpine Tests" [ + test "Alpine attributes render correctly" { + let actual = + div { + _xData "{ open: false }" + _xInit "console.log('init')" + _xShow "open" + _xBind ("class", "open ? 'active' : ''") + _xOn ("click", "$dispatch('close')") + _xText "$message" + _xRef "container" + _xIf "show" + _xFor "item in items" + _xModel "name" + _xModel ("name", ".lazy") + _xModelable "value" + _xId "['dropdown']" + _xEffect "$watch('open', val => console.log(val))" + _xTransition () + _xTransition ("fade", ":enter") + _xTrap "open" + _xTrap ("open", ".noscroll") + _xCloak + _xAnchor "#trigger" + _xAnchor ("#trigger", ".bottom") + _xTeleport "#modals" + _by "x.id" + _x ("mask", "99/99/9999") + _x "collapse" + "Content" + } |> Render.toString + Expect.stringContains actual "x-data=\"{ open: false }\"" "x-data" + Expect.stringContains actual "x-init=\"console.log('init')\"" "x-init" + Expect.stringContains actual "x-show=\"open\"" "x-show" + Expect.stringContains actual "x-bind:class=\"open ? 'active' : ''\"" "x-bind" + Expect.stringContains actual "x-on:click=\"$dispatch('close')\"" "x-on with handler" + Expect.stringContains actual "x-text=\"$message\"" "x-text" + Expect.stringContains actual "x-ref=\"container\"" "x-ref" + Expect.stringContains actual "x-if=\"show\"" "x-if" + Expect.stringContains actual "x-for=\"item in items\"" "x-for" + Expect.stringContains actual "x-model=\"name\"" "x-model" + Expect.stringContains actual "x-model.lazy=\"name\"" "x-model with modifier" + Expect.stringContains actual "x-modelable=\"value\"" "x-modelable" + Expect.stringContains actual "x-id=\"['dropdown']\"" "x-id" + Expect.stringContains actual "x-effect=\"$watch('open', val => console.log(val))\"" "x-effect" + Expect.isTrue (Regex.IsMatch(actual, @"x-transition(?!=)(?!:)")) "x-transition bare" + Expect.stringContains actual "x-transition:enter=\"fade\"" "x-transition with modifier and value" + Expect.stringContains actual "x-trap=\"open\"" "x-trap" + Expect.stringContains actual "x-trap.noscroll=\"open\"" "x-trap with modifier" + Expect.stringContains actual "x-cloak" "x-cloak" + Expect.stringContains actual "x-anchor=\"#trigger\"" "x-anchor" + Expect.stringContains actual "x-anchor.bottom=\"#trigger\"" "x-anchor with modifier" + Expect.stringContains actual "x-teleport=\"#modals\"" "x-teleport" + Expect.stringContains actual "by=\"x.id\"" "by" + Expect.stringContains actual "x-mask=\"99/99/9999\"" "x generic with value" + Expect.isTrue (Regex.IsMatch(actual, @"x-collapse(?!=)")) "x generic no value" + } + ] diff --git a/sln/src/Tests/Tests.fs b/sln/src/Tests/CoreTests.fs similarity index 59% rename from sln/src/Tests/Tests.fs rename to sln/src/Tests/CoreTests.fs index ecae26d..b17c588 100644 --- a/sln/src/Tests/Tests.fs +++ b/sln/src/Tests/CoreTests.fs @@ -1,15 +1,13 @@ -module Tests +module CoreTests open FSharp.ViewEngine open System.Text open System.Web -open System.Text.Json open System.Text.RegularExpressions open Expecto open type Html open type Htmx open type Alpine -open type Datastar open type Svg open type Tailwind @@ -140,54 +138,114 @@ let expectedHtml = """ [] let tests = - testList "ViewEngine Tests" [ + testList "Core Tests" [ test "ViewEngine should render html document" { let actual = ViewEngineApi.buildDocument() |> Render.toHtmlDocString Expect.equal (String.clean actual) (String.clean expectedHtml) "Rendered HTML should match expected" } - test "Datastar attributes should render with data- prefix" { - let actual = - div { - _dataSignals ("count", "0") - _dataOn ("click", "$count++") - _dataShow "$count > 0" - _dataText "$count" - _dataBind "name" - _dataEffect "console.log($count)" - _dataClass ("active", "$isActive") - _dataAttr ("disabled", "$count === 0") - _dataComputed ("double", "$count * 2") - _dataInit "console.log('init')" - _dataIgnore - _dataIgnoreMorph - _dataStyle ("color", "red") - _dataRef "myInput" - _dataIndicator "loading" - _dataAnimate "fadeIn" - _dataPersist () - _dataPersist "count" - _dataScrollIntoView - "Content" - } |> Render.toString - Expect.stringContains actual "data-signals:count=\"0\"" "data-signals" - Expect.stringContains actual "data-on:click=\"$count++\"" "data-on" - Expect.stringContains actual "data-show=\"$count > 0\"" "data-show" - Expect.stringContains actual "data-text=\"$count\"" "data-text" - Expect.stringContains actual "data-bind:name" "data-bind" - Expect.stringContains actual "data-effect=\"console.log($count)\"" "data-effect" - Expect.stringContains actual "data-class:active=\"$isActive\"" "data-class" - Expect.stringContains actual "data-attr:disabled=\"$count === 0\"" "data-attr" - Expect.stringContains actual "data-computed:double=\"$count * 2\"" "data-computed" - Expect.stringContains actual "data-init=\"console.log('init')\"" "data-init" - Expect.stringContains actual "data-ignore-morph" "data-ignore-morph" - Expect.stringContains actual "data-ignore" "data-ignore" - Expect.stringContains actual "data-style:color=\"red\"" "data-style" - Expect.stringContains actual "data-ref:myInput" "data-ref" - Expect.stringContains actual "data-indicator:loading" "data-indicator" - Expect.stringContains actual "data-animate=\"fadeIn\"" "data-animate" - Expect.stringContains actual "data-persist data-persist:count" "data-persist" - Expect.stringContains actual "data-scroll-into-view" "data-scroll-into-view" + test "Void elements render without closing tag" { + let actual = br |> Render.toString + Expect.equal actual "
" "br" + let actual2 = hr |> Render.toString + Expect.equal actual2 "
" "hr" + let actual3 = (img { _src "/logo.png"; _alt "logo" }) |> Render.toString + Expect.equal actual3 "\"logo\"" "img with attrs" + } + + test "Regular element with no children renders open and close tags" { + let actual = div {} |> Render.toString + Expect.equal actual "
" "empty div" + } + + test "Regular element with text child and no attrs" { + let actual = span { "hello" } |> Render.toString + Expect.equal actual "hello" "span with text" + } + + test "raw bypasses encoding, text encodes" { + let rawResult = raw "hi" |> Render.toString + Expect.equal rawResult "hi" "raw passes through" + let textResult = div { text "hi" } |> Render.toString + Expect.equal textResult "
<b>hi</b>
" "text encodes" + } + + test "Html.empty (NoopElement) renders nothing" { + let actual = div { empty } |> Render.toString + Expect.equal actual "
" "empty renders nothing" + } + + test "EmptyAttr is silently dropped (boolean false)" { + let actual = input { _hidden false; _disabled false; _required false } |> Render.toString + Expect.equal actual "" "no attrs when all false" + } + + test "Boolean attributes render when true, omit when false" { + let actual = input { _hidden true; _disabled true; _required true; _checked true } |> Render.toString + Expect.stringContains actual "hidden" "hidden present" + Expect.stringContains actual "disabled" "disabled present" + Expect.stringContains actual "required" "required present" + Expect.stringContains actual "checked" "checked present" + let actual2 = input { _hidden false; _disabled false } |> Render.toString + Expect.isFalse (actual2.Contains("hidden")) "hidden absent" + Expect.isFalse (actual2.Contains("disabled")) "disabled absent" + } + + test "_class with string seq joins with spaces" { + let actual = div { _class [ "a"; "b"; "c" ] } |> Render.toString + Expect.equal actual "
" "class list joined" + } + + test "_data custom data attribute" { + let actual = div { _data ("foo", "bar"); _data "baz" } |> Render.toString + Expect.stringContains actual "data-foo=\"bar\"" "data with value" + Expect.stringContains actual "data-baz" "data without value" + } + + test "Custom element builders el and elVoid" { + let actual = (Html.el "my-component") { _id "c1"; "content" } |> Render.toString + Expect.equal actual "content" "custom regular element" + let actual2 = (Html.elVoid "my-void") { _id "v1" } |> Render.toString + Expect.equal actual2 "" "custom void element" + } + + test "title element renders correctly" { + let actual = title "My Page" |> Render.toString + Expect.equal actual "My Page" "title" + } + + test "For iteration in builder" { + let items = [ "a"; "b"; "c" ] + let actual = ul { for item in items do li { item } } |> Render.toString + Expect.equal actual "
  • a
  • b
  • c
" "for iteration" + } + + test "3+ attributes exercises attrRest branch" { + let actual = div { _id "x"; _class "y"; _style "z"; _title "w" } |> Render.toString + Expect.equal actual "
" "4 attrs" + } + + test "3+ children exercises childRest branch" { + let actual = div { span { "a" }; span { "b" }; span { "c" } } |> Render.toString + Expect.equal actual "
abc
" "3 children" + } + + test "3+ attrs on void element exercises attrRest branch" { + let actual = input { _id "x"; _class "y"; _name "z"; _type "text" } |> Render.toString + Expect.equal actual "" "4 attrs on void" + } + + test "Render.toHtmlDocString prepends DOCTYPE" { + let actual = html { body { "hi" } } |> Render.toHtmlDocString + Expect.isTrue (actual.StartsWith("")) "starts with doctype" + Expect.stringContains actual "hi" "contains html" + } + + test "StringBuilderPool reuse works across multiple renders" { + let r1 = div { "first" } |> Render.toString + let r2 = div { "second" } |> Render.toString + Expect.equal r1 "
first
" "first render" + Expect.equal r2 "
second
" "second render (reused pool)" } test "HtmlEncode should match HttpUtility.HtmlEncode" { @@ -214,5 +272,4 @@ let tests = Expect.equal actual expected $"HtmlEncode should match HttpUtility for input: {input}" } - ] diff --git a/sln/src/Tests/DatastarTests.fs b/sln/src/Tests/DatastarTests.fs new file mode 100644 index 0000000..1695b44 --- /dev/null +++ b/sln/src/Tests/DatastarTests.fs @@ -0,0 +1,115 @@ +module DatastarTests + +open FSharp.ViewEngine +open System.Text.RegularExpressions +open Expecto +open type Html +open type Datastar + +[] +let tests = + testList "Datastar Tests" [ + test "Datastar keyed attributes should render with data- prefix" { + let actual = + div { + _dataSignals ("count", "0") + _dataOn ("click", "$count++") + _dataShow "$count > 0" + _dataText "$count" + _dataBind "name" + _dataBind ("name", "'default'") + _dataEffect "console.log($count)" + _dataClass ("active", "$isActive") + _dataAttr ("disabled", "$count === 0") + _dataComputed ("double", "$count * 2") + _dataInit "console.log('init')" + _dataIgnore + _dataIgnoreMorph + _dataStyle ("color", "red") + _dataRef "myInput" + _dataRef ("myInput", "'fallback'") + _dataIndicator "loading" + _dataIndicator ("loading", "'true'") + _dataAnimate "fadeIn" + _dataPersist () + _dataPersist "count" + _dataPersist ("count", "{include: /count/}") + _dataScrollIntoView + "Content" + } |> Render.toString + Expect.stringContains actual "data-signals:count=\"0\"" "data-signals keyed" + Expect.stringContains actual "data-on:click=\"$count++\"" "data-on" + Expect.stringContains actual "data-show=\"$count > 0\"" "data-show" + Expect.stringContains actual "data-text=\"$count\"" "data-text" + Expect.stringContains actual "data-bind:name=\"'default'\"" "data-bind keyed with value" + Expect.stringContains actual "data-effect=\"console.log($count)\"" "data-effect" + Expect.stringContains actual "data-class:active=\"$isActive\"" "data-class keyed" + Expect.stringContains actual "data-attr:disabled=\"$count === 0\"" "data-attr keyed" + Expect.stringContains actual "data-computed:double=\"$count * 2\"" "data-computed keyed" + Expect.stringContains actual "data-init=\"console.log('init')\"" "data-init" + Expect.stringContains actual "data-ignore-morph" "data-ignore-morph" + Expect.isTrue (Regex.IsMatch(actual, @"data-ignore(?!-)")) "data-ignore (not data-ignore-morph)" + Expect.stringContains actual "data-style:color=\"red\"" "data-style keyed" + Expect.stringContains actual "data-ref:myInput=\"'fallback'\"" "data-ref keyed with value" + Expect.stringContains actual "data-indicator:loading=\"'true'\"" "data-indicator keyed with value" + Expect.stringContains actual "data-animate=\"fadeIn\"" "data-animate" + Expect.stringContains actual "data-persist:count=\"{include: /count/}\"" "data-persist keyed with value" + Expect.isTrue (Regex.IsMatch(actual, @"data-persist(?![:=])")) "data-persist no key" + Expect.stringContains actual "data-persist:count" "data-persist keyed" + Expect.stringContains actual "data-scroll-into-view" "data-scroll-into-view" + } + + test "Datastar object-syntax overloads should render without key suffix" { + let actual = + div { + _dataAttr "{'aria-label': $foo, disabled: $bar}" + _dataClass "{success: $foo != '', 'font-bold': $foo == 'strong'}" + _dataComputed "{foo: () => $bar + $baz}" + _dataSignals "{foo: {bar: 1, baz: 2}}" + _dataStyle "{display: $hiding ? 'none' : 'flex', 'background-color': $red ? 'red' : 'green'}" + "Content" + } |> Render.toString + Expect.stringContains actual "data-attr=\"{'aria-label': $foo, disabled: $bar}\"" "data-attr object syntax" + Expect.stringContains actual "data-class=\"{success: $foo != '', 'font-bold': $foo == 'strong'}\"" "data-class object syntax" + Expect.stringContains actual "data-computed=\"{foo: () => $bar + $baz}\"" "data-computed object syntax" + Expect.stringContains actual "data-signals=\"{foo: {bar: 1, baz: 2}}\"" "data-signals object syntax" + Expect.stringContains actual "data-style=\"{display: $hiding ? 'none' : 'flex', 'background-color': $red ? 'red' : 'green'}\"" "data-style object syntax" + } + + test "Datastar remaining attributes should render correctly" { + let actual = + div { + _dataJsonSignals () + _dataJsonSignals "{include: /user/}" + _dataOnIntersect "$intersected = true" + _dataOnInterval "$count++" + _dataOnSignalPatch "console.log('changed')" + _dataOnSignalPatchFilter "{include: /^counter$/}" + _dataPreserveAttr "open class" + _dataCustomValidity "$foo === $bar ? '' : 'Must match'" + _dataOnRaf "$count++" + _dataOnResize "$count++" + _dataQueryString () + _dataQueryString "{include: /foo/}" + _dataReplaceUrl "`/page${page}`" + _dataRocket "myRocket" + _dataViewTransition "$foo" + "Content" + } |> Render.toString + Expect.isTrue (Regex.IsMatch(actual, @"data-json-signals(?!=)")) "data-json-signals no value" + Expect.stringContains actual "data-json-signals=\"{include: /user/}\"" "data-json-signals with value" + Expect.stringContains actual "data-on-intersect=\"$intersected = true\"" "data-on-intersect" + Expect.stringContains actual "data-on-interval=\"$count++\"" "data-on-interval" + Expect.stringContains actual "data-on-signal-patch=\"console.log('changed')\"" "data-on-signal-patch" + Expect.stringContains actual "data-on-signal-patch-filter=\"{include: /^counter$/}\"" "data-on-signal-patch-filter" + Expect.stringContains actual "data-preserve-attr=\"open class\"" "data-preserve-attr" + Expect.stringContains actual "data-custom-validity=\"$foo === $bar ? '' : 'Must match'\"" "data-custom-validity" + Expect.stringContains actual "data-on-raf=\"$count++\"" "data-on-raf" + Expect.stringContains actual "data-on-resize=\"$count++\"" "data-on-resize" + Expect.isTrue (Regex.IsMatch(actual, @"data-query-string(?!=)")) "data-query-string no value" + Expect.stringContains actual "data-query-string=\"{include: /foo/}\"" "data-query-string with value" + Expect.stringContains actual "data-replace-url=\"`/page${page}`\"" "data-replace-url" + Expect.stringContains actual "data-rocket=\"myRocket\"" "data-rocket" + Expect.stringContains actual "data-view-transition=\"$foo\"" "data-view-transition" + } + ] diff --git a/sln/src/Tests/HtmxTests.fs b/sln/src/Tests/HtmxTests.fs new file mode 100644 index 0000000..820ba82 --- /dev/null +++ b/sln/src/Tests/HtmxTests.fs @@ -0,0 +1,45 @@ +module HtmxTests + +open FSharp.ViewEngine +open Expecto +open type Html +open type Htmx + +[] +let tests = + testList "Htmx Tests" [ + test "Htmx attributes render correctly" { + let actual = + div { + _hxGet "/get" + _hxPost "/post" + _hxDelete "/delete" + _hxTrigger "click" + _hxTarget "#target" + _hxIndicator "#spinner" + _hxInclude "[name='q']" + _hxSwap "innerHTML" + _hxSwapOOB "true" + _hxEncoding "multipart/form-data" + _hxOn ("click", "alert('hi')") + _hxHistory "false" + _hxVals """{"key": "val"}""" + _hx ("custom", "value") + "Content" + } |> Render.toString + Expect.stringContains actual "hx-get=\"/get\"" "hx-get" + Expect.stringContains actual "hx-post=\"/post\"" "hx-post" + Expect.stringContains actual "hx-delete=\"/delete\"" "hx-delete" + Expect.stringContains actual "hx-trigger=\"click\"" "hx-trigger" + Expect.stringContains actual "hx-target=\"#target\"" "hx-target" + Expect.stringContains actual "hx-indicator=\"#spinner\"" "hx-indicator" + Expect.stringContains actual "hx-include=\"[name='q']\"" "hx-include" + Expect.stringContains actual "hx-swap=\"innerHTML\"" "hx-swap" + Expect.stringContains actual "hx-swap-oob=\"true\"" "hx-swap-oob" + Expect.stringContains actual "hx-encoding=\"multipart/form-data\"" "hx-encoding" + Expect.stringContains actual "hx-on:click=\"alert('hi')\"" "hx-on" + Expect.stringContains actual "hx-history=\"false\"" "hx-history" + Expect.stringContains actual "hx-vals=\"{\"key\": \"val\"}\"" "hx-vals" + Expect.stringContains actual "hx-custom=\"value\"" "hx generic" + } + ] diff --git a/sln/src/Tests/Program.fs b/sln/src/Tests/Program.fs index 8b2eaf3..d4de89f 100644 --- a/sln/src/Tests/Program.fs +++ b/sln/src/Tests/Program.fs @@ -4,4 +4,12 @@ open Expecto [] let main args = - runTestsWithCLIArgs [] args Tests.tests + let allTests = testList "All Tests" [ + CoreTests.tests + DatastarTests.tests + HtmxTests.tests + AlpineTests.tests + SvgTests.tests + TailwindTests.tests + ] + runTestsWithCLIArgs [] args allTests diff --git a/sln/src/Tests/SvgTests.fs b/sln/src/Tests/SvgTests.fs new file mode 100644 index 0000000..8c0a21b --- /dev/null +++ b/sln/src/Tests/SvgTests.fs @@ -0,0 +1,52 @@ +module SvgTests + +open FSharp.ViewEngine +open Expecto +open type Html +open type Svg + +[] +let tests = + testList "SVG Tests" [ + test "SVG elements and attributes render correctly" { + let actual = + svg { + _viewBox "0 0 100 100" + _fill "none" + _stroke "black" + _strokeWidth 2 + _strokeLinecap "round" + _strokeLinejoin "miter" + Svg._width 24 + Svg._height 24 + circle { + _cx 50 + _cy 50 + _r 40 + _fill "red" + } + path { + _d "M10 10" + _fillRule "evenodd" + _clipRule "evenodd" + } + } |> Render.toString + Expect.stringContains actual "] +let tests = + testList "Tailwind Tests" [ + test "Tailwind custom elements render correctly" { + let actual = + div { + elAutocomplete { _id "ac"; "search" } + elDropdown { elMenu { "item" } } + elDialog { + elDialogBackdrop { } + elDialogPanel { "panel content" } + } + elCommandPalette { + elCommandList { + elCommandGroup { "group" } + } + elCommandPreview { "preview" } + } + elDefaults { } + elNoResults { "No results" } + elTabGroup { + elTabList { "tabs" } + elTabPanels { "panels" } + } + } |> Render.toString + Expect.stringContains actual "search" "el-autocomplete" + Expect.stringContains actual "item" "el-dropdown/menu" + Expect.stringContains actual "" "el-dialog" + Expect.stringContains actual "" "el-dialog-backdrop" + Expect.stringContains actual "panel content" "el-dialog-panel" + Expect.stringContains actual "" "el-command-palette" + Expect.stringContains actual "" "el-command-list" + Expect.stringContains actual "group" "el-command-group" + Expect.stringContains actual "preview" "el-command-preview" + Expect.stringContains actual "" "el-defaults" + Expect.stringContains actual "No results" "el-no-results" + Expect.stringContains actual "" "el-tab-group" + Expect.stringContains actual "tabs" "el-tab-list" + Expect.stringContains actual "panels" "el-tab-panels" + } + + test "Tailwind _popover and _anchor attributes" { + let actual = div { _popover; _anchor "bottom" } |> Render.toString + Expect.stringContains actual "popover" "_popover" + Expect.stringContains actual "anchor=\"bottom\"" "_anchor" + } + ] diff --git a/sln/src/Tests/Tests.fsproj b/sln/src/Tests/Tests.fsproj index fa5df33..a2db3a7 100644 --- a/sln/src/Tests/Tests.fsproj +++ b/sln/src/Tests/Tests.fsproj @@ -6,7 +6,12 @@ false - + + + + + + From d4a3f107049939d961b3e7e012bed059d692db3c Mon Sep 17 00:00:00 2001 From: Andrew Meier Date: Sat, 28 Feb 2026 06:44:15 -0500 Subject: [PATCH 2/2] Update deploy trigger to release or manual --- .github/workflows/deploy.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index eebbd2a..8ed2efd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,11 +1,8 @@ name: Deploy on: workflow_dispatch: - push: - branches: - - main - paths-ignore: - - '**/*.md' + release: + types: [published] jobs: deploy: name: Deploy