Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a762fbdd5 | ||
|
|
59b64ed834 |
@@ -10,15 +10,15 @@ htmx uses attributes and HTTP headers to attain its interactivity; the libraries
|
|||||||
|
|
||||||
`Giraffe.Htmx` provides extensions that facilitate using htmx on the server side, primarily reading and setting headers. `Giraffe.ViewEngine.Htmx` provides attributes and helpers to produce views that utilize htmx. Both can be installed from NuGet via standard methods.
|
`Giraffe.Htmx` provides extensions that facilitate using htmx on the server side, primarily reading and setting headers. `Giraffe.ViewEngine.Htmx` provides attributes and helpers to produce views that utilize htmx. Both can be installed from NuGet via standard methods.
|
||||||
|
|
||||||
| Server Side | View Engine |
|
| Server Side | View Engine |
|
||||||
|---|---|
|
|------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|[](https://www.nuget.org/packages/Giraffe.Htmx/)|[](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx/)|
|
| [](https://www.nuget.org/packages/Giraffe.Htmx/) | [](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx/) |
|
||||||
|
|
||||||
Both of these packages will also install `Giraffe.Htmx.Common`, which has some common definitions and provides a local-to-your-project version of the htmx JavaScript _(as of v2.0.8)_.
|
Both of these packages will also install `Giraffe.Htmx.Common`, which has some common definitions and provides a local-to-your-project version of the htmx JavaScript _(as of v2.0.8)_ and htmax htmx-plus-extensions bundle _(as of v4.0.0-beta4)_.
|
||||||
|
|
||||||
## Server Side (`Giraffe.Htmx`)
|
## Server Side (`Giraffe.Htmx`)
|
||||||
|
|
||||||
In addition to the regular HTTP request payloads, htmx sets [one or more headers](https://htmx.org/docs/#request_headers) along with the request. Once `Giraffe.Htmx` is opened, these are available as properties on `HttpContext.Request.Headers`. These consist of the header name, translated to a .NET name (ex. `HX-Current-URL` becomes `HxCurrentUrl`), and a strongly-typed property based on the expected value of that header. Additionally, they are all exposed as `Option`s, as they may or may not be present for any given request.
|
In addition to the regular HTTP request payloads, htmx sets [one or more headers](https://four.htmx.org/reference/headers#request) along with the request. Once `Giraffe.Htmx` is opened, these are available as properties on `HttpContext.Request.Headers`. These consist of the header name, translated to a .NET name (ex. `HX-Current-URL` becomes `HxCurrentUrl`), and a strongly-typed property based on the expected value of that header. Additionally, they are all exposed as `Option`s, as they may or may not be present for any given request.
|
||||||
|
|
||||||
A server may want to respond to a request that originated from htmx differently than a regular request. One way htmx can provide the same feel as a Single Page Application (SPA) is by swapping out the `body` content (or an element within it) instead of reloading the entire page. In this case, the developer can provide a partial layout to be used for these responses, while returning the full page for regular requests. The `IsHtmx` property makes this easy...
|
A server may want to respond to a request that originated from htmx differently than a regular request. One way htmx can provide the same feel as a Single Page Application (SPA) is by swapping out the `body` content (or an element within it) instead of reloading the entire page. In this case, the developer can provide a partial layout to be used for these responses, while returning the full page for regular requests. The `IsHtmx` property makes this easy...
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ let result view : HttpHandler =
|
|||||||
full view
|
full view
|
||||||
```
|
```
|
||||||
|
|
||||||
htmx also utilizes [response headers](https://htmx.org/docs/#response_headers) to affect client-side behavior. For each of these, this library provides `HttpHandler`s that can be chained along with the response. As an example, if the server returns a redirect response (301, 302, 303, 307), the `XMLHttpRequest` handler on the client will follow the redirection before htmx can do anything with it. To redirect to a new page, you would return an OK (200) response with an `HX-Redirect` header set in the response.
|
htmx also utilizes [response headers](https://four.htmx.org/reference/headers#response) to affect client-side behavior. For each of these, this library provides `HttpHandler`s that can be chained along with the response. As an example, if the server returns a redirect response (301, 302, 303, 307), the `fetch` handler on the client may follow the redirection before htmx can do anything with it. To redirect to a new page, you would return an OK (200) response with an `HX-Redirect` header set in the response.
|
||||||
|
|
||||||
```fsharp
|
```fsharp
|
||||||
let theHandler : HttpHandler =
|
let theHandler : HttpHandler =
|
||||||
@@ -44,11 +44,11 @@ let theHandler : HttpHandler =
|
|||||||
|
|
||||||
Of note is that the `HX-Trigger` headers can take either one or more events. For a single event with no parameters, use `withHxTrigger`; for a single event with parameters, or multiple events, use `withHxTriggerMany`. Both these have `AfterSettle` and `AfterSwap` versions as well.
|
Of note is that the `HX-Trigger` headers can take either one or more events. For a single event with no parameters, use `withHxTrigger`; for a single event with parameters, or multiple events, use `withHxTriggerMany`. Both these have `AfterSettle` and `AfterSwap` versions as well.
|
||||||
|
|
||||||
`HtmxScript.local` provides an `HtmlString` with a script tag to load the package-provided htmx library. This can be used in code, Razor templates, etc. (If you're using Giraffe.ViewEngine, see below.)
|
`HtmxScript.local` provides an `HtmlString` with a script tag to load the package-provided htmx library, along with `localMax` to load the htmx-plus-extensions bundle. This can be used in code, Razor templates, etc. (If you're using Giraffe.ViewEngine, see below.)
|
||||||
|
|
||||||
## View Engine (`Giraffe.ViewEngine.Htmx`)
|
## View Engine (`Giraffe.ViewEngine.Htmx`)
|
||||||
|
|
||||||
As htmx uses [attributes](https://htmx.org/docs/#attributes) to extend HTML, the primary part of this library defines attributes that can be used within Giraffe views. Simply open `Giraffe.ViewEngine.Htmx`, and these attributes, along with support modules, will be visible.
|
As htmx uses [attributes](https://four.htmx.org/reference/attributes) to extend HTML, the primary part of this library defines attributes that can be used within Giraffe views. Simply open `Giraffe.ViewEngine.Htmx`, and these attributes, along with support modules, will be visible.
|
||||||
|
|
||||||
As an example, creating a `div` that loads data once the HTML is rendered:
|
As an example, creating a `div` that loads data once the HTML is rendered:
|
||||||
|
|
||||||
@@ -70,11 +70,11 @@ let shiftClick =
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to use the package-provided htmx library, `Htmx.Script.local` will create the `script` tag for you. To load htmx from jsDelivr, `Htmx.Script.cdnMinified` or `Htmx.Script.cdnUnminified` can be used to load the script in your HTML trees. In this case, if you are using a Content Security Policy (CSP) header, `cdn.jsdelivr.net` will need to be added to the `script-src` list.
|
If you want to use the package-provided htmx library, `Htmx.Script.local` will create the `script` tag for you (as well as `Htmx.Script.Max.local` to load the htmax bundle). To load htmx from jsDelivr, `Htmx.Script.cdnMinified` or `Htmx.Script.cdnUnminified` can be used to load the script in your HTML trees (these also have `.Max` variants). In this case, if you are using a Content Security Policy (CSP) header, `cdn.jsdelivr.net` will need to be added to the `script-src` list.
|
||||||
|
|
||||||
## Feedback / Help
|
## Feedback / Help
|
||||||
|
|
||||||
The author hangs out in the #dotnet-htmx channel (and most others) of the [htmx Discord server](https://htmx.org/discord) and the #web channel of the [F# Software Foundation's Slack server](https://fsharp.org/guides/slack/).
|
The author hangs out in the #dotnet-htmx channel (and most others) of the [htmx Discord server](https://htmx.org/discord) and the #web channel (and most others) of the [F# Community Discord server](https://discord.gg/R6n7c54).
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|[<img src="https://giraffe.wiki/giraffe.png" alt="Giraffe logo" width="200">](https://giraffe.wiki)| [<img src="https://bitbadger.solutions/upload/bit-badger/2024/01/htmx-black-transparent.svg" alt="htmx logo" width="200">](https://htmx.org) |[<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main)" width="200">](https://jb.gg/OpenSource)|
|
|[<img src="https://giraffe.wiki/giraffe.png" alt="Giraffe logo" width="200">](https://giraffe.wiki)| [<img src="https://bitbadger.solutions/upload/bit-badger/2024/01/htmx-black-transparent.svg" alt="htmx logo" width="200">](https://htmx.org) |[<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main)" width="200">](https://jb.gg/OpenSource)|
|
||||||
|
|||||||
+113
-111
@@ -1,111 +1,113 @@
|
|||||||
/// <summary>Common definitions shared between attribute values and response headers</summary>
|
/// <summary>Common definitions shared between attribute values and response headers</summary>
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module Giraffe.Htmx.Common
|
module Giraffe.Htmx.Common
|
||||||
|
|
||||||
/// <summary>The version of htmx embedded in the package</summary>
|
/// <summary>The version of htmx embedded in the package</summary>
|
||||||
let HtmxVersion = "4.0.0-beta4"
|
let HtmxVersion = "4.0.0"
|
||||||
|
|
||||||
/// <summary>The path for the provided htmx script</summary>
|
/// <summary>URLs for the included htmx library static web assets</summary>
|
||||||
let internal htmxLocalScript = $"/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}"
|
module StaticAssetUrl =
|
||||||
|
/// <summary>The path for the provided htmx script</summary>
|
||||||
/// <summary>The path for the provided htmax script</summary>
|
let htmx = $"/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}"
|
||||||
let internal htmaxLocalScript = $"/_content/Giraffe.Htmx.Common/htmax.min.js?ver={HtmxVersion}"
|
|
||||||
|
/// <summary>The path for the provided htmax script</summary>
|
||||||
/// <summary>Serialize a list of key/value pairs to JSON (very rudimentary)</summary>
|
let htmax = $"/_content/Giraffe.Htmx.Common/htmax.min.js?ver={HtmxVersion}"
|
||||||
/// <param name="pairs">The key/value pairs to be serialized to JSON</param>
|
|
||||||
/// <returns>A string with the key/value pairs serialized to JSON</returns>
|
/// <summary>Serialize a list of key/value pairs to JSON (very rudimentary)</summary>
|
||||||
let internal toJson (pairs: (string * string) list) =
|
/// <param name="pairs">The key/value pairs to be serialized to JSON</param>
|
||||||
pairs
|
/// <returns>A string with the key/value pairs serialized to JSON</returns>
|
||||||
|> List.map (fun pair -> sprintf "\"%s\": \"%s\"" (fst pair) ((snd pair).Replace ("\"", "\\\"")))
|
let internal toJson (pairs: (string * string) list) =
|
||||||
|> String.concat ", "
|
pairs
|
||||||
|> sprintf "{ %s }"
|
|> List.map (fun pair -> sprintf "\"%s\": \"%s\"" (fst pair) ((snd pair).Replace ("\"", "\\\"")))
|
||||||
|
|> String.concat ", "
|
||||||
/// <summary>Convert a boolean to lowercase "true" or "false"</summary>
|
|> sprintf "{ %s }"
|
||||||
/// <param name="boolValue">The boolean value to convert</param>
|
|
||||||
/// <returns>"true" for <c>true</c>, "false" for <c>false</c></returns>
|
/// <summary>Convert a boolean to lowercase "true" or "false"</summary>
|
||||||
let internal toLowerBool (boolValue: bool) =
|
/// <param name="boolValue">The boolean value to convert</param>
|
||||||
(string boolValue).ToLowerInvariant()
|
/// <returns>"true" for <c>true</c>, "false" for <c>false</c></returns>
|
||||||
|
let internal toLowerBool (boolValue: bool) =
|
||||||
|
(string boolValue).ToLowerInvariant()
|
||||||
/// <summary>Valid values for the <c>hx-swap</c> attribute / <c>HX-Reswap</c> header</summary>
|
|
||||||
/// <remarks>May be combined with <c>swap</c> / <c>scroll</c> / <c>show</c> config)</remarks>
|
|
||||||
/// <seealso href="https://four.htmx.org/attributes/hx-swap/">Documentation</seealso>
|
/// <summary>Valid values for the <c>hx-swap</c> attribute / <c>HX-Reswap</c> header</summary>
|
||||||
[<RequireQualifiedAccess>]
|
/// <remarks>May be combined with <c>swap</c> / <c>scroll</c> / <c>show</c> config)</remarks>
|
||||||
module HxSwap =
|
/// <seealso href="https://four.htmx.org/attributes/hx-swap/">Documentation</seealso>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
/// <summary>The default, replace the inner HTML of the target element</summary>
|
module HxSwap =
|
||||||
[<Literal>]
|
|
||||||
let InnerHtml = "innerHTML"
|
/// <summary>The default, replace the inner HTML of the target element</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Replace the entire target element with the response</summary>
|
let InnerHtml = "innerHTML"
|
||||||
[<Literal>]
|
|
||||||
let OuterHtml = "outerHTML"
|
/// <summary>Replace the entire target element with the response</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Morph the inner HTML of the target to the new content</summary>
|
let OuterHtml = "outerHTML"
|
||||||
[<Literal>]
|
|
||||||
let InnerMorph = "innerMorph"
|
/// <summary>Morph the inner HTML of the target to the new content</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Morph the outer HTML of the target to the new content</summary>
|
let InnerMorph = "innerMorph"
|
||||||
[<Literal>]
|
|
||||||
let OuterMorph = "outerMorph"
|
/// <summary>Morph the outer HTML of the target to the new content</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Morph the outer HTML of the target to the new content, recreating all children</summary>
|
let OuterMorph = "outerMorph"
|
||||||
/// <remarks>This is used internally by the new history extension, but can be used by others if desired</remarks>
|
|
||||||
[<Literal>]
|
/// <summary>Morph the outer HTML of the target to the new content, recreating all children</summary>
|
||||||
let OuterSync = "outerSync"
|
/// <remarks>This is used internally by the new history extension, but can be used by others if desired</remarks>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Replace the text content of the target without parsing the response as HTML</summary>
|
let OuterSync = "outerSync"
|
||||||
[<Literal>]
|
|
||||||
let TextContent = "textContent"
|
/// <summary>Replace the text content of the target without parsing the response as HTML</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Insert the response before the target element</summary>
|
let TextContent = "textContent"
|
||||||
[<Literal>]
|
|
||||||
let Before = "before"
|
/// <summary>Insert the response before the target element</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Insert the response before the target element (pre-v4 name)</summary>
|
let Before = "before"
|
||||||
[<Literal>]
|
|
||||||
let BeforeBegin = Before
|
/// <summary>Insert the response before the target element (pre-v4 name)</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Insert the response before the first child of the target element</summary>
|
let BeforeBegin = Before
|
||||||
[<Literal>]
|
|
||||||
let Prepend = "prepend"
|
/// <summary>Insert the response before the first child of the target element</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Insert the response before the first child of the target element (pre-v4 name)</summary>
|
let Prepend = "prepend"
|
||||||
[<Literal>]
|
|
||||||
let AfterBegin = Prepend
|
/// <summary>Insert the response before the first child of the target element (pre-v4 name)</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Insert the response after the last child of the target element</summary>
|
let AfterBegin = Prepend
|
||||||
[<Literal>]
|
|
||||||
let Append = "append"
|
/// <summary>Insert the response after the last child of the target element</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Insert the response after the last child of the target element (pre-v4 name)</summary>
|
let Append = "append"
|
||||||
[<Literal>]
|
|
||||||
let BeforeEnd = Append
|
/// <summary>Insert the response after the last child of the target element (pre-v4 name)</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Insert the response after the target element</summary>
|
let BeforeEnd = Append
|
||||||
[<Literal>]
|
|
||||||
let After = "after"
|
/// <summary>Insert the response after the target element</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Insert the response after the target element (pre-v4 name)</summary>
|
let After = "after"
|
||||||
[<Literal>]
|
|
||||||
let AfterEnd = After
|
/// <summary>Insert the response after the target element (pre-v4 name)</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Delete the target element regardless of response</summary>
|
let AfterEnd = After
|
||||||
[<Literal>]
|
|
||||||
let Delete = "delete"
|
/// <summary>Delete the target element regardless of response</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Does not append content from response (out of band items will still be processed)</summary>
|
let Delete = "delete"
|
||||||
[<Literal>]
|
|
||||||
let None = "none"
|
/// <summary>Does not append content from response (out of band items will still be processed)</summary>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Update existing elements by <c>id</c> and add new ones</summary>
|
let None = "none"
|
||||||
/// <remarks>This requires the <c>upsert</c> extension</remarks>
|
|
||||||
/// <seealso href="https://four.htmx.org/extensions/upsert">Extension</seealso>
|
/// <summary>Update existing elements by <c>id</c> and add new ones</summary>
|
||||||
[<Literal>]
|
/// <remarks>This requires the <c>upsert</c> extension</remarks>
|
||||||
let Upsert = "upsert"
|
/// <seealso href="https://four.htmx.org/extensions/upsert">Extension</seealso>
|
||||||
|
[<Literal>]
|
||||||
/// <summary>Specify that the target of the htmx request should be downloaded</summary>
|
let Upsert = "upsert"
|
||||||
/// <remarks>This requires the <c>hx-download</c> extension (included in the htmax bundle)</remarks>
|
|
||||||
/// <seealso href="https://four.htmx.org/extensions/hx-download#explicit-swap-style">Documentation</seealso>
|
/// <summary>Specify that the target of the htmx request should be downloaded</summary>
|
||||||
[<Literal>]
|
/// <remarks>This requires the <c>hx-download</c> extension (included in the htmax bundle)</remarks>
|
||||||
let Download = "download"
|
/// <seealso href="https://four.htmx.org/extensions/hx-download#explicit-swap-style">Documentation</seealso>
|
||||||
|
[<Literal>]
|
||||||
|
let Download = "download"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
## Giraffe.Htmx.Common
|
## Giraffe.Htmx.Common
|
||||||
|
|
||||||
This package contains common code shared between [`Giraffe.Htmx`](https://www.nuget.org/packages/Giraffe.Htmx) and [`Giraffe.ViewEngine.Htmx`](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx), and will be automatically installed when you install either one. It also contains htmx as a static web asset, allowing it to be loaded from your local (or published) project.
|
This package contains common code shared between [`Giraffe.Htmx`](https://www.nuget.org/packages/Giraffe.Htmx) and [`Giraffe.ViewEngine.Htmx`](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx), and will be automatically installed when you install either one. It also contains htmx and htmax as static web assets, allowing them to be loaded from your local (or published) project.
|
||||||
|
|
||||||
**htmx version: 4.0.0-beta4**
|
**htmx version: 4.0.0**
|
||||||
|
|
||||||
_**NOTE:** Pay special attention to breaking changes highlighted in the packages listed above._
|
_**NOTE:** Pay special attention to breaking changes highlighted in the packages listed above._
|
||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -3,17 +3,10 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
<VersionPrefix>4.0.0</VersionPrefix>
|
<VersionPrefix>4.0.0</VersionPrefix>
|
||||||
<VersionSuffix>beta4</VersionSuffix>
|
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<PackageReleaseNotes>Update htmx 4 to beta4
|
<PackageReleaseNotes>htmx 4.0.0 Initial Release
|
||||||
- [Common] Update provided htmx 4 to 4.0.0-beta4
|
|
||||||
- [Common] Add htmax bundle to provided version
|
|
||||||
- [Server] Update HX-Target header to return tag and ID (similar to HX-Source)
|
|
||||||
- [Server] Add support for HX-Request-Type header
|
|
||||||
- [View Engine] Add hx-status attribute, outerSync swap
|
|
||||||
- [View Engine] Updated script tags to pull htmx 4.0.0-beta4, added "max" bundle links for local and CDN
|
|
||||||
|
|
||||||
See package and prior alpha release READMEs; v2 to v4 is not an update-and-forget-it release
|
See package and prior alpha/beta release READMEs; v2 to v4 is not an update-and-forget-it release. If you encounter build errors after installing this, install v4.0.0-beta5 instead; obsolete elements will generate a warning with suggestions of what to use instead.
|
||||||
</PackageReleaseNotes>
|
</PackageReleaseNotes>
|
||||||
<Authors>danieljsummers</Authors>
|
<Authors>danieljsummers</Authors>
|
||||||
<Company>Bit Badger Solutions</Company>
|
<Company>Bit Badger Solutions</Company>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Expecto" Version="11.0.0" />
|
<PackageVersion Include="Expecto" Version="11.1.0" />
|
||||||
<PackageVersion Include="Giraffe" Version="8.2.0" />
|
<PackageVersion Include="Giraffe" Version="8.3.0" />
|
||||||
<PackageVersion Include="Giraffe.ViewEngine" Version="1.4.0" />
|
<PackageVersion Include="Giraffe.ViewEngine" Version="1.4.0" />
|
||||||
<PackageVersion Include="NSubstitute" Version="5.3.0" />
|
<PackageVersion Include="NSubstitute" Version="6.2.0" />
|
||||||
<PackageVersion Include="FSharp.Core" Version="10.1.301" />
|
<PackageVersion Include="FSharp.Core" Version="10.1.400" />
|
||||||
<!-- <PackageVersion Update="FSharp.Core" Version="10.0.0" /> -->
|
<!-- <PackageVersion Update="FSharp.Core" Version="10.0.0" /> -->
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
+19
-59
@@ -8,7 +8,7 @@ open System
|
|||||||
type HxRequestTypes =
|
type HxRequestTypes =
|
||||||
/// <summary>A request targeting the <c>body</c> tag or using an <c>hx-select</c> attribute</summary>
|
/// <summary>A request targeting the <c>body</c> tag or using an <c>hx-select</c> attribute</summary>
|
||||||
| HxFullRequest
|
| HxFullRequest
|
||||||
|
|
||||||
/// <summary>A request for partial content</summary>
|
/// <summary>A request for partial content</summary>
|
||||||
| HxPartialRequest
|
| HxPartialRequest
|
||||||
|
|
||||||
@@ -18,15 +18,15 @@ let private hdr (headers : IHeaderDictionary) hdr =
|
|||||||
|
|
||||||
/// Extensions to the header dictionary
|
/// Extensions to the header dictionary
|
||||||
type IHeaderDictionary with
|
type IHeaderDictionary with
|
||||||
|
|
||||||
/// <summary>Indicates that the request is via an element using <c>hx-boost</c></summary>
|
/// <summary>Indicates that the request is via an element using <c>hx-boost</c></summary>
|
||||||
member this.HxBoosted
|
member this.HxBoosted
|
||||||
with get () = hdr this "HX-Boosted" |> Option.map bool.Parse
|
with get () = hdr this "HX-Boosted" |> Option.map bool.Parse
|
||||||
|
|
||||||
/// <summary>The current URL of the browser <em>(note that this does not update until after settle)</em></summary>
|
/// <summary>The current URL of the browser <em>(note that this does not update until after settle)</em></summary>
|
||||||
member this.HxCurrentUrl
|
member this.HxCurrentUrl
|
||||||
with get () = hdr this "HX-Current-URL" |> Option.map Uri
|
with get () = hdr this "HX-Current-URL" |> Option.map Uri
|
||||||
|
|
||||||
/// <summary><c>true</c> if the request is for history restoration after a miss in the local history cache</summary>
|
/// <summary><c>true</c> if the request is for history restoration after a miss in the local history cache</summary>
|
||||||
member this.HxHistoryRestoreRequest
|
member this.HxHistoryRestoreRequest
|
||||||
with get () = hdr this "HX-History-Restore-Request" |> Option.map bool.Parse
|
with get () = hdr this "HX-History-Restore-Request" |> Option.map bool.Parse
|
||||||
@@ -35,9 +35,9 @@ type IHeaderDictionary with
|
|||||||
/// <remarks><c>preload</c> is part of the htmax htmx-plus-extensions bundle</remarks>
|
/// <remarks><c>preload</c> is part of the htmax htmx-plus-extensions bundle</remarks>
|
||||||
member this.HxPreloaded
|
member this.HxPreloaded
|
||||||
with get () = hdr this "HX-Preloaded" |> Option.map bool.Parse
|
with get () = hdr this "HX-Preloaded" |> Option.map bool.Parse
|
||||||
|
|
||||||
/// <summary>The user response to an <c>hx-prompt</c></summary>
|
/// <summary>The user response to an <c>hx-prompt</c></summary>
|
||||||
[<Obsolete "hx-prompt is removed in v4">]
|
/// <remarks><b>NEW IN v4:</b> This functionality is dependent on the hx-prompt extension being loaded</remarks>
|
||||||
member this.HxPrompt
|
member this.HxPrompt
|
||||||
with get () = hdr this "HX-Prompt"
|
with get () = hdr this "HX-Prompt"
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ type IHeaderDictionary with
|
|||||||
/// <remarks><c>hx-ws</c> is part of the htmax htmx-plus-extensions bundle</remarks>
|
/// <remarks><c>hx-ws</c> is part of the htmax htmx-plus-extensions bundle</remarks>
|
||||||
member this.HxRequestId
|
member this.HxRequestId
|
||||||
with get () = hdr this "HX-Request-ID"
|
with get () = hdr this "HX-Request-ID"
|
||||||
|
|
||||||
/// <summary>The request type sent by htmx</summary>
|
/// <summary>The request type sent by htmx</summary>
|
||||||
/// <seealso cref="HxRequestTypes" />
|
/// <seealso cref="HxRequestTypes" />
|
||||||
member this.HxRequestType
|
member this.HxRequestType
|
||||||
@@ -59,7 +59,7 @@ type IHeaderDictionary with
|
|||||||
| Some typ when typ = "partial" -> Some HxPartialRequest
|
| Some typ when typ = "partial" -> Some HxPartialRequest
|
||||||
| Some _ -> None
|
| Some _ -> None
|
||||||
| None -> None
|
| None -> None
|
||||||
|
|
||||||
/// <summary>The tag name (fst) and <c>id</c> attribute (snd) of the element triggering this request</summary>
|
/// <summary>The tag name (fst) and <c>id</c> attribute (snd) of the element triggering this request</summary>
|
||||||
member this.HxSource
|
member this.HxSource
|
||||||
with get () =
|
with get () =
|
||||||
@@ -91,16 +91,6 @@ type IHeaderDictionary with
|
|||||||
Some (parts[0], if parts[1] <> "" then Some parts[1] else None)
|
Some (parts[0], if parts[1] <> "" then Some parts[1] else None)
|
||||||
| None -> None
|
| None -> None
|
||||||
|
|
||||||
/// <summary>The <c>id</c> attribute of the triggered element if it exists</summary>
|
|
||||||
[<Obsolete "HX-Trigger is removed in v4; use the second item of HX-Source">]
|
|
||||||
member this.HxTrigger
|
|
||||||
with get () = hdr this "HX-Trigger"
|
|
||||||
|
|
||||||
/// <summary>The <c>name</c> attribute of the triggered element if it exists</summary>
|
|
||||||
[<Obsolete "HX-Trigger-Name is removed in v4; may be available via extension, but will be removed from this library">]
|
|
||||||
member this.HxTriggerName
|
|
||||||
with get () = hdr this "HX-Trigger-Name"
|
|
||||||
|
|
||||||
|
|
||||||
/// Extensions for the request object
|
/// Extensions for the request object
|
||||||
type HttpRequest with
|
type HttpRequest with
|
||||||
@@ -119,7 +109,7 @@ type HttpRequest with
|
|||||||
module Handlers =
|
module Handlers =
|
||||||
|
|
||||||
open Giraffe.Htmx.Common
|
open Giraffe.Htmx.Common
|
||||||
|
|
||||||
/// <summary>Instruct htmx to download a response from another path / URL</summary>
|
/// <summary>Instruct htmx to download a response from another path / URL</summary>
|
||||||
/// <param name="path">The path or URL where the downloadable content is found</param>
|
/// <param name="path">The path or URL where the downloadable content is found</param>
|
||||||
/// <returns>An HTTP handler with the <c>HX-Download</c> header set</returns>
|
/// <returns>An HTTP handler with the <c>HX-Download</c> header set</returns>
|
||||||
@@ -127,14 +117,14 @@ module Handlers =
|
|||||||
/// <seealso href="https://four.htmx.org/extensions/hx-download#hx-download-header">Documentation</seealso>
|
/// <seealso href="https://four.htmx.org/extensions/hx-download#hx-download-header">Documentation</seealso>
|
||||||
let withHxDownload (path: string) : HttpHandler =
|
let withHxDownload (path: string) : HttpHandler =
|
||||||
setHttpHeader "HX-Download" path
|
setHttpHeader "HX-Download" path
|
||||||
|
|
||||||
/// <summary>Instruct htmx to perform a client-side redirect for content</summary>
|
/// <summary>Instruct htmx to perform a client-side redirect for content</summary>
|
||||||
/// <param name="path">The path where the content should be found</param>
|
/// <param name="path">The path where the content should be found</param>
|
||||||
/// <returns>An HTTP handler with the <c>HX-Location</c> header set</returns>
|
/// <returns>An HTTP handler with the <c>HX-Location</c> header set</returns>
|
||||||
/// <seealso href="https://htmx.org/headers/hx-location/">Documentation</seealso>
|
/// <seealso href="https://htmx.org/headers/hx-location/">Documentation</seealso>
|
||||||
let withHxLocation (path: string) : HttpHandler =
|
let withHxLocation (path: string) : HttpHandler =
|
||||||
setHttpHeader "HX-Location" path
|
setHttpHeader "HX-Location" path
|
||||||
|
|
||||||
/// <summary>Pushes a new url into the history stack</summary>
|
/// <summary>Pushes a new url into the history stack</summary>
|
||||||
/// <param name="url">The URL to be pushed</param>
|
/// <param name="url">The URL to be pushed</param>
|
||||||
/// <returns>An HTTP handler with the <c>HX-Push-Url</c> header set</returns>
|
/// <returns>An HTTP handler with the <c>HX-Push-Url</c> header set</returns>
|
||||||
@@ -148,7 +138,7 @@ module Handlers =
|
|||||||
/// <seealso href="https://htmx.org/headers/hx-push-url/">Documentation</seealso>
|
/// <seealso href="https://htmx.org/headers/hx-push-url/">Documentation</seealso>
|
||||||
let withHxNoPushUrl : HttpHandler =
|
let withHxNoPushUrl : HttpHandler =
|
||||||
toLowerBool false |> withHxPushUrl
|
toLowerBool false |> withHxPushUrl
|
||||||
|
|
||||||
/// <summary>Can be used to do a client-side redirect to a new location</summary>
|
/// <summary>Can be used to do a client-side redirect to a new location</summary>
|
||||||
/// <param name="url">The URL to which the client should be redirected</param>
|
/// <param name="url">The URL to which the client should be redirected</param>
|
||||||
/// <returns>An HTTP handler with the <c>HX-Redirect</c> header set</returns>
|
/// <returns>An HTTP handler with the <c>HX-Redirect</c> header set</returns>
|
||||||
@@ -175,13 +165,13 @@ module Handlers =
|
|||||||
/// <seealso href="https://htmx.org/headers/hx-replace-url/">Documentation</seealso>
|
/// <seealso href="https://htmx.org/headers/hx-replace-url/">Documentation</seealso>
|
||||||
let withHxNoReplaceUrl : HttpHandler =
|
let withHxNoReplaceUrl : HttpHandler =
|
||||||
toLowerBool false |> withHxReplaceUrl
|
toLowerBool false |> withHxReplaceUrl
|
||||||
|
|
||||||
/// <summary>Override which portion of the response will be swapped into the target document</summary>
|
/// <summary>Override which portion of the response will be swapped into the target document</summary>
|
||||||
/// <param name="target">The selector for the new response target</param>
|
/// <param name="target">The selector for the new response target</param>
|
||||||
/// <returns>An HTTP handler with the <c>HX-Reselect</c> header set</returns>
|
/// <returns>An HTTP handler with the <c>HX-Reselect</c> header set</returns>
|
||||||
let withHxReselect (target: string) : HttpHandler =
|
let withHxReselect (target: string) : HttpHandler =
|
||||||
setHttpHeader "HX-Reselect" target
|
setHttpHeader "HX-Reselect" target
|
||||||
|
|
||||||
/// <summary>Override the <c>hx-swap</c> attribute from the initiating element</summary>
|
/// <summary>Override the <c>hx-swap</c> attribute from the initiating element</summary>
|
||||||
/// <param name="swap">The swap value to override</param>
|
/// <param name="swap">The swap value to override</param>
|
||||||
/// <returns>An HTTP handler with the <c>HX-Reswap</c> header set</returns>
|
/// <returns>An HTTP handler with the <c>HX-Reswap</c> header set</returns>
|
||||||
@@ -209,46 +199,16 @@ module Handlers =
|
|||||||
let withHxTriggerMany evts : HttpHandler =
|
let withHxTriggerMany evts : HttpHandler =
|
||||||
toJson evts |> setHttpHeader "HX-Trigger"
|
toJson evts |> setHttpHeader "HX-Trigger"
|
||||||
|
|
||||||
/// <summary>Allows you to trigger a single client side event after changes have settled</summary>
|
|
||||||
/// <param name="evt">The call to the event that should be triggered</param>
|
|
||||||
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Settle</c> header set</returns>
|
|
||||||
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
|
|
||||||
[<Obsolete "Removed in v4; use withHxTrigger">]
|
|
||||||
let withHxTriggerAfterSettle (evt: string) : HttpHandler =
|
|
||||||
setHttpHeader "HX-Trigger" evt
|
|
||||||
|
|
||||||
/// <summary>Allows you to trigger multiple client side events after changes have settled</summary>
|
|
||||||
/// <param name="evts">The calls to events that should be triggered</param>
|
|
||||||
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Settle</c> header set for all given events</returns>
|
|
||||||
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
|
|
||||||
[<Obsolete "Removed in v4; use withHxTrigger">]
|
|
||||||
let withHxTriggerManyAfterSettle evts : HttpHandler =
|
|
||||||
toJson evts |> setHttpHeader "HX-Trigger"
|
|
||||||
|
|
||||||
/// <summary>Allows you to trigger a single client side event after DOM swapping occurs</summary>
|
|
||||||
/// <param name="evt">The call to the event that should be triggered</param>
|
|
||||||
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Swap</c> header set</returns>
|
|
||||||
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
|
|
||||||
[<Obsolete "Removed in v4; use withHxTrigger">]
|
|
||||||
let withHxTriggerAfterSwap (evt: string) : HttpHandler =
|
|
||||||
setHttpHeader "HX-Trigger" evt
|
|
||||||
|
|
||||||
/// <summary>Allows you to trigger multiple client side events after DOM swapping occurs</summary>
|
|
||||||
/// <param name="evts">The calls to events that should be triggered</param>
|
|
||||||
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Swap</c> header set for all given events</returns>
|
|
||||||
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
|
|
||||||
[<Obsolete "Removed in v4; use withHxTrigger">]
|
|
||||||
let withHxTriggerManyAfterSwap evts : HttpHandler =
|
|
||||||
toJson evts |> setHttpHeader "HX-Trigger"
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>Load the package-provided version of the htmx script</summary>
|
/// <summary>Load the package-provided version of the htmx script</summary>
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module HtmxScript =
|
module HtmxScript =
|
||||||
|
|
||||||
open Giraffe.Htmx.Common
|
open Giraffe.Htmx.Common
|
||||||
open Microsoft.AspNetCore.Html
|
open Microsoft.AspNetCore.Html
|
||||||
|
|
||||||
/// <summary><c>script</c> tag to load the package-provided version of the htmx script</summary>
|
/// <summary><c>script</c> tag to load the package-provided version of the htmx script</summary>
|
||||||
let local = HtmlString $"""<script src="{htmxLocalScript}"></script>"""
|
let local = HtmlString $"""<script src="{StaticAssetUrl.htmx}"></script>"""
|
||||||
|
|
||||||
|
/// <summary><c>script</c> tag to load the package-provided version of the htmax script</summary>
|
||||||
|
let localMax = HtmlString $"""<script src="{StaticAssetUrl.htmax}"></script>"""
|
||||||
|
|||||||
+3
-3
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
This package enables server-side support for [htmx](https://htmx.org) within [Giraffe](https://giraffe.wiki) and ASP.NET's `HttpContext`.
|
This package enables server-side support for [htmx](https://htmx.org) within [Giraffe](https://giraffe.wiki) and ASP.NET's `HttpContext`.
|
||||||
|
|
||||||
**htmx version: 4.0.0-beta4**
|
**htmx version: 4.0.0**
|
||||||
|
|
||||||
_Upgrading from v2.x: the [migration guide](https://four.htmx.org/docs/get-started/migration) lists changes for v4. For this package, the `HX-Trigger` and `HX-Trigger-Name` headers are marked obsolete. They are replaced by `HX-Source`, which provides the triggering tag name and `id` attribute. The `HX-Prompt` header has also been marked as obsolete, as the `hx-prompt` attribute which generated its content has been removed._
|
_Upgrading from v2.x: the [migration guide](https://four.htmx.org/docs#migrating-from-htmx-2x-to-4x) lists changes for v4. For this package, the `HX-Trigger` and `HX-Trigger-Name` headers are marked obsolete. They are replaced by `HX-Source`, which provides the triggering tag name and `id` attribute. The `HX-Prompt` header has also been marked as obsolete, as the `hx-prompt` attribute which generated its content has been removed._
|
||||||
|
|
||||||
_Obsolete elements will be removed in the first production v4 release._
|
_Obsolete elements will be removed in the first production v4 release._
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ let myHandler : HttpHander =
|
|||||||
|
|
||||||
The `HxSwap` module has constants to use for the `HX-Reswap` header. These may be extended with settle, show, and other qualifiers; see the htmx documentation for the `hx-swap` attribute for more information.
|
The `HxSwap` module has constants to use for the `HX-Reswap` header. These may be extended with settle, show, and other qualifiers; see the htmx documentation for the `hx-swap` attribute for more information.
|
||||||
|
|
||||||
To load the package-provided htmx library without using Giraffe.ViewEngine, use `HtmxScript.local`.
|
To load the package-provided htmx library without using Giraffe.ViewEngine, use `HtmxScript.local`; to load the htmax bundle, use `HtmxScript.localMax`.
|
||||||
|
|
||||||
### Learn
|
### Learn
|
||||||
|
|
||||||
|
|||||||
+18
-2
@@ -6,9 +6,25 @@ open Giraffe.Htmx
|
|||||||
/// Test to ensure the version was updated
|
/// Test to ensure the version was updated
|
||||||
let version =
|
let version =
|
||||||
test "HtmxVersion is correct" {
|
test "HtmxVersion is correct" {
|
||||||
Expect.equal HtmxVersion "4.0.0-beta4" "htmx version incorrect"
|
Expect.equal HtmxVersion "4.0.0" "htmx version incorrect"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let staticAssetUrl =
|
||||||
|
testList "StaticAssetUrl" [
|
||||||
|
test "htmx is correct" {
|
||||||
|
Expect.equal
|
||||||
|
StaticAssetUrl.htmx
|
||||||
|
$"/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}"
|
||||||
|
"Static htmx URL incorrect"
|
||||||
|
}
|
||||||
|
test "htmax is correct" {
|
||||||
|
Expect.equal
|
||||||
|
StaticAssetUrl.htmax
|
||||||
|
$"/_content/Giraffe.Htmx.Common/htmax.min.js?ver={HtmxVersion}"
|
||||||
|
"Static htmx URL incorrect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
/// Tests for the HxSwap module
|
/// Tests for the HxSwap module
|
||||||
let swap =
|
let swap =
|
||||||
testList "HxSwap" [
|
testList "HxSwap" [
|
||||||
@@ -69,4 +85,4 @@ let swap =
|
|||||||
]
|
]
|
||||||
|
|
||||||
/// All tests for this module
|
/// All tests for this module
|
||||||
let allTests = testList "Htmx.Common" [ version; swap ]
|
let allTests = testList "Htmx.Common" [ version; staticAssetUrl; swap ]
|
||||||
|
|||||||
+28
-93
@@ -97,6 +97,21 @@ let dictExtensions =
|
|||||||
Expect.isFalse ctx.Request.Headers.HxPreloaded.Value "The header should have been false"
|
Expect.isFalse ctx.Request.Headers.HxPreloaded.Value "The header should have been false"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "HxPrompt" [
|
||||||
|
test "succeeds when the header is not present" {
|
||||||
|
let ctx = Substitute.For<HttpContext>()
|
||||||
|
ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
|
||||||
|
Expect.isNone ctx.Request.Headers.HxPrompt "There should not have been a header returned"
|
||||||
|
}
|
||||||
|
test "succeeds when the header is present" {
|
||||||
|
let ctx = Substitute.For<HttpContext>()
|
||||||
|
let dic = HeaderDictionary()
|
||||||
|
dic.Add("HX-Prompt", "of course")
|
||||||
|
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
||||||
|
Expect.isSome ctx.Request.Headers.HxPrompt "There should be a header present"
|
||||||
|
Expect.equal ctx.Request.Headers.HxPrompt.Value "of course" "The header value was incorrect"
|
||||||
|
}
|
||||||
|
]
|
||||||
testList "HxRequest" [
|
testList "HxRequest" [
|
||||||
test "succeeds when the header is not present" {
|
test "succeeds when the header is not present" {
|
||||||
let ctx = Substitute.For<HttpContext>()
|
let ctx = Substitute.For<HttpContext>()
|
||||||
@@ -216,7 +231,7 @@ let dictExtensions =
|
|||||||
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
||||||
let hdr = ctx.Request.Headers.HxTarget
|
let hdr = ctx.Request.Headers.HxTarget
|
||||||
Expect.isSome hdr "There should be a header present"
|
Expect.isSome hdr "There should be a header present"
|
||||||
Expect.equal (fst hdr.Value) "div" "The target tag was incorrect"
|
Expect.equal (fst hdr.Value) "div" "The target tag was incorrect"
|
||||||
Expect.isSome (snd hdr.Value) "There should be a target ID present"
|
Expect.isSome (snd hdr.Value) "There should be a target ID present"
|
||||||
Expect.equal (snd hdr.Value).Value "leItem" "The header value was incorrect"
|
Expect.equal (snd hdr.Value).Value "leItem" "The header value was incorrect"
|
||||||
}
|
}
|
||||||
@@ -227,7 +242,7 @@ let dictExtensions =
|
|||||||
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
||||||
let hdr = ctx.Request.Headers.HxTarget
|
let hdr = ctx.Request.Headers.HxTarget
|
||||||
Expect.isSome hdr "There should be a header present"
|
Expect.isSome hdr "There should be a header present"
|
||||||
Expect.equal (fst hdr.Value) "span" "The target tag was incorrect"
|
Expect.equal (fst hdr.Value) "span" "The target tag was incorrect"
|
||||||
Expect.isNone (snd hdr.Value) "There should not be a target ID present"
|
Expect.isNone (snd hdr.Value) "There should not be a target ID present"
|
||||||
}
|
}
|
||||||
test "succeeds when the header is present and ID is missing" {
|
test "succeeds when the header is present and ID is missing" {
|
||||||
@@ -237,7 +252,7 @@ let dictExtensions =
|
|||||||
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
||||||
let hdr = ctx.Request.Headers.HxTarget
|
let hdr = ctx.Request.Headers.HxTarget
|
||||||
Expect.isSome hdr "There should be a header present"
|
Expect.isSome hdr "There should be a header present"
|
||||||
Expect.equal (fst hdr.Value) "aside" "The target tag was incorrect"
|
Expect.equal (fst hdr.Value) "aside" "The target tag was incorrect"
|
||||||
Expect.isNone (snd hdr.Value) "There should not be a target ID present"
|
Expect.isNone (snd hdr.Value) "There should not be a target ID present"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -251,7 +266,7 @@ let reqExtensions =
|
|||||||
let ctx = Substitute.For<HttpContext>()
|
let ctx = Substitute.For<HttpContext>()
|
||||||
ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
|
ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
|
||||||
Expect.isFalse ctx.Request.IsHtmx "The request should not be an htmx request"
|
Expect.isFalse ctx.Request.IsHtmx "The request should not be an htmx request"
|
||||||
}
|
}
|
||||||
test "succeeds when request is from htmx" {
|
test "succeeds when request is from htmx" {
|
||||||
let ctx = Substitute.For<HttpContext>()
|
let ctx = Substitute.For<HttpContext>()
|
||||||
let dic = HeaderDictionary()
|
let dic = HeaderDictionary()
|
||||||
@@ -288,7 +303,7 @@ open System.Threading.Tasks
|
|||||||
|
|
||||||
/// Dummy "next" parameter to get the pipeline to execute/terminate
|
/// Dummy "next" parameter to get the pipeline to execute/terminate
|
||||||
let next (ctx: HttpContext) = Task.FromResult(Some ctx)
|
let next (ctx: HttpContext) = Task.FromResult(Some ctx)
|
||||||
|
|
||||||
/// Tests for the HttpHandler functions provided in the Handlers module
|
/// Tests for the HttpHandler functions provided in the Handlers module
|
||||||
let handlers =
|
let handlers =
|
||||||
testList "HandlerTests" [
|
testList "HandlerTests" [
|
||||||
@@ -373,7 +388,7 @@ let handlers =
|
|||||||
let! _ = withHxReselect "#test" next ctx
|
let! _ = withHxReselect "#test" next ctx
|
||||||
Expect.isTrue (dic.ContainsKey "HX-Reselect") "The HX-Reselect header should be present"
|
Expect.isTrue (dic.ContainsKey "HX-Reselect") "The HX-Reselect header should be present"
|
||||||
Expect.equal dic["HX-Reselect"].[0] "#test" "The HX-Reselect value was incorrect"
|
Expect.equal dic["HX-Reselect"].[0] "#test" "The HX-Reselect value was incorrect"
|
||||||
}
|
}
|
||||||
testTask "withHxReswap succeeds" {
|
testTask "withHxReswap succeeds" {
|
||||||
let ctx = Substitute.For<HttpContext>()
|
let ctx = Substitute.For<HttpContext>()
|
||||||
let dic = HeaderDictionary()
|
let dic = HeaderDictionary()
|
||||||
@@ -381,7 +396,7 @@ let handlers =
|
|||||||
let! _ = withHxReswap HxSwap.BeforeEnd next ctx
|
let! _ = withHxReswap HxSwap.BeforeEnd next ctx
|
||||||
Expect.isTrue (dic.ContainsKey "HX-Reswap") "The HX-Reswap header should be present"
|
Expect.isTrue (dic.ContainsKey "HX-Reswap") "The HX-Reswap header should be present"
|
||||||
Expect.equal dic["HX-Reswap"].[0] HxSwap.BeforeEnd "The HX-Reswap value was incorrect"
|
Expect.equal dic["HX-Reswap"].[0] HxSwap.BeforeEnd "The HX-Reswap value was incorrect"
|
||||||
}
|
}
|
||||||
testTask "withHxRetarget succeeds" {
|
testTask "withHxRetarget succeeds" {
|
||||||
let ctx = Substitute.For<HttpContext>()
|
let ctx = Substitute.For<HttpContext>()
|
||||||
let dic = HeaderDictionary()
|
let dic = HeaderDictionary()
|
||||||
@@ -418,93 +433,13 @@ let script =
|
|||||||
$"""<script src="/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}"></script>"""
|
$"""<script src="/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}"></script>"""
|
||||||
"htmx script link is incorrect"
|
"htmx script link is incorrect"
|
||||||
}
|
}
|
||||||
]
|
test "localMax generates correct link" {
|
||||||
|
Expect.equal
|
||||||
#nowarn 44 // Obsolete items still have tests
|
(string HtmxScript.localMax)
|
||||||
let dictExtensionsObs =
|
$"""<script src="/_content/Giraffe.Htmx.Common/htmax.min.js?ver={HtmxVersion}"></script>"""
|
||||||
testList "IHeaderDictionaryExtensions (Obsolete)" [
|
"htmx script link is incorrect"
|
||||||
testList "HxPrompt" [
|
|
||||||
test "succeeds when the header is not present" {
|
|
||||||
let ctx = Substitute.For<HttpContext>()
|
|
||||||
ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
|
|
||||||
Expect.isNone ctx.Request.Headers.HxPrompt "There should not have been a header returned"
|
|
||||||
}
|
|
||||||
test "succeeds when the header is present" {
|
|
||||||
let ctx = Substitute.For<HttpContext>()
|
|
||||||
let dic = HeaderDictionary()
|
|
||||||
dic.Add("HX-Prompt", "of course")
|
|
||||||
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
|
||||||
Expect.isSome ctx.Request.Headers.HxPrompt "There should be a header present"
|
|
||||||
Expect.equal ctx.Request.Headers.HxPrompt.Value "of course" "The header value was incorrect"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "HxTrigger" [
|
|
||||||
test "succeeds when the header is not present" {
|
|
||||||
let ctx = Substitute.For<HttpContext>()
|
|
||||||
ctx.Request.Headers.ReturnsForAnyArgs (HeaderDictionary ()) |> ignore
|
|
||||||
Expect.isNone ctx.Request.Headers.HxTrigger "There should not have been a header returned"
|
|
||||||
}
|
|
||||||
test "succeeds when the header is present" {
|
|
||||||
let ctx = Substitute.For<HttpContext>()
|
|
||||||
let dic = HeaderDictionary()
|
|
||||||
dic.Add("HX-Trigger", "#trig")
|
|
||||||
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
|
||||||
Expect.isSome ctx.Request.Headers.HxTrigger "There should be a header present"
|
|
||||||
Expect.equal ctx.Request.Headers.HxTrigger.Value "#trig" "The header value was incorrect"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "HxTriggerName" [
|
|
||||||
test "succeeds when the header is not present" {
|
|
||||||
let ctx = Substitute.For<HttpContext>()
|
|
||||||
ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
|
|
||||||
Expect.isNone ctx.Request.Headers.HxTriggerName "There should not have been a header returned"
|
|
||||||
}
|
|
||||||
test "HxTriggerName succeeds when the header is present" {
|
|
||||||
let ctx = Substitute.For<HttpContext>()
|
|
||||||
let dic = HeaderDictionary()
|
|
||||||
dic.Add("HX-Trigger-Name", "click")
|
|
||||||
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
|
|
||||||
Expect.isSome ctx.Request.Headers.HxTriggerName "There should be a header present"
|
|
||||||
Expect.equal ctx.Request.Headers.HxTriggerName.Value "click" "The header value was incorrect"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
let handlerObs =
|
|
||||||
testList "Handler Tests (Obsolete)" [
|
|
||||||
testTask "withHxTriggerAfterSettle succeeds" {
|
|
||||||
let ctx = Substitute.For<HttpContext>()
|
|
||||||
let dic = HeaderDictionary()
|
|
||||||
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
|
|
||||||
let! _ = withHxTriggerAfterSettle "byTheWay" next ctx
|
|
||||||
Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present"
|
|
||||||
Expect.equal dic["HX-Trigger"].[0] "byTheWay" "The HX-Trigger value was incorrect"
|
|
||||||
}
|
|
||||||
testTask "withHxTriggerManyAfterSettle succeeds" {
|
|
||||||
let ctx = Substitute.For<HttpContext>()
|
|
||||||
let dic = HeaderDictionary()
|
|
||||||
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
|
|
||||||
let! _ = withHxTriggerManyAfterSettle [ "oof", "ouch"; "hmm", "uh" ] next ctx
|
|
||||||
Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present"
|
|
||||||
Expect.equal dic["HX-Trigger"].[0] """{ "oof": "ouch", "hmm": "uh" }""" "The HX-Trigger value was incorrect"
|
|
||||||
}
|
|
||||||
testTask "withHxTriggerAfterSwap succeeds" {
|
|
||||||
let ctx = Substitute.For<HttpContext>()
|
|
||||||
let dic = HeaderDictionary()
|
|
||||||
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
|
|
||||||
let! _ = withHxTriggerAfterSwap "justASec" next ctx
|
|
||||||
Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present"
|
|
||||||
Expect.equal dic["HX-Trigger"].[0] "justASec" "The HX-Trigger value was incorrect"
|
|
||||||
}
|
|
||||||
testTask "withHxTriggerManyAfterSwap succeeds" {
|
|
||||||
let ctx = Substitute.For<HttpContext>()
|
|
||||||
let dic = HeaderDictionary()
|
|
||||||
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
|
|
||||||
let! _ = withHxTriggerManyAfterSwap [ "this", "1"; "that", "2" ] next ctx
|
|
||||||
Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present"
|
|
||||||
Expect.equal dic["HX-Trigger"].[0] """{ "this": "1", "that": "2" }""" "The HX-Trigger value was incorrect"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
/// All tests for this module
|
/// All tests for this module
|
||||||
let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers; script; dictExtensionsObs; handlerObs ]
|
let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers; script ]
|
||||||
|
|||||||
+13
-482
@@ -395,7 +395,7 @@ let hxTrigger =
|
|||||||
testList "FromDocument" [
|
testList "FromDocument" [
|
||||||
test "succeeds when it is the first modifier" {
|
test "succeeds when it is the first modifier" {
|
||||||
Expect.equal (HxTrigger.FromDocument "") "from:document" "FromDocument modifier incorrect"
|
Expect.equal (HxTrigger.FromDocument "") "from:document" "FromDocument modifier incorrect"
|
||||||
}
|
}
|
||||||
test "succeeds when it is not the first modifier" {
|
test "succeeds when it is not the first modifier" {
|
||||||
Expect.equal (HxTrigger.FromDocument "click") "click from:document" "FromDocument modifier incorrect"
|
Expect.equal (HxTrigger.FromDocument "click") "click from:document" "FromDocument modifier incorrect"
|
||||||
}
|
}
|
||||||
@@ -500,7 +500,7 @@ let hxVals =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
/// Pipe-able assertion for a rendered node
|
/// Pipe-able assertion for a rendered node
|
||||||
let shouldRender expected node =
|
let shouldRender expected node =
|
||||||
Expect.equal (RenderView.AsString.htmlNode node) expected "Rendered HTML incorrect"
|
Expect.equal (RenderView.AsString.htmlNode node) expected "Rendered HTML incorrect"
|
||||||
|
|
||||||
@@ -569,6 +569,10 @@ let attributes =
|
|||||||
test "_hxPreserve succeeds" {
|
test "_hxPreserve succeeds" {
|
||||||
img [ _hxPreserve ] |> shouldRender """<img hx-preserve>"""
|
img [ _hxPreserve ] |> shouldRender """<img hx-preserve>"""
|
||||||
}
|
}
|
||||||
|
test "_hxPrompt succeeds" {
|
||||||
|
strong [ _hxPrompt "Who goes there?" ] []
|
||||||
|
|> shouldRender """<strong hx-prompt="Who goes there?"></strong>"""
|
||||||
|
}
|
||||||
test "_hxPushUrl succeeds" {
|
test "_hxPushUrl succeeds" {
|
||||||
dl [ _hxPushUrl "/a-b-c" ] [] |> shouldRender """<dl hx-push-url="/a-b-c"></dl>"""
|
dl [ _hxPushUrl "/a-b-c" ] [] |> shouldRender """<dl hx-push-url="/a-b-c"></dl>"""
|
||||||
}
|
}
|
||||||
@@ -677,14 +681,14 @@ let script =
|
|||||||
let html = RenderView.AsString.htmlNode Script.cdnMinified
|
let html = RenderView.AsString.htmlNode Script.cdnMinified
|
||||||
Expect.equal
|
Expect.equal
|
||||||
html
|
html
|
||||||
$"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmx.min.js" integrity="sha384-aWZK1NtOs/aWb/+YZdTM8q2JkWEshlMc9mgZ189numT9bwFhyAyYEoO4nO/2dTXt" crossorigin="anonymous"></script>"""
|
$"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmx.min.js" integrity="sha384-BvJpBiO8Kh31EqtJe5DRIeWrHWnCGkwytKs9NKFi86Hhw96dEqdEMzZDeK9iEGTc" crossorigin="anonymous"></script>"""
|
||||||
"CDN minified script tag is incorrect"
|
"CDN minified script tag is incorrect"
|
||||||
}
|
}
|
||||||
test "cdnUnminified succeeds" {
|
test "cdnUnminified succeeds" {
|
||||||
let html = RenderView.AsString.htmlNode Script.cdnUnminified
|
let html = RenderView.AsString.htmlNode Script.cdnUnminified
|
||||||
Expect.equal
|
Expect.equal
|
||||||
html
|
html
|
||||||
$"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmx.js" integrity="sha384-OFLRIZpuqI2wwFozxvDGcuF3TQ36ySMgp44WEthOiR4wFzRkhZbK72HFaBo2C3cx" crossorigin="anonymous"></script>"""
|
$"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmx.js" integrity="sha384-ESzWv77gBOAGtF3d7B8QiQ786cghRRsyhuYVVJGsrFGxwT0Dj1fLxReX9Ul6t4n9" crossorigin="anonymous"></script>"""
|
||||||
"CDN unminified script tag is incorrect"
|
"CDN unminified script tag is incorrect"
|
||||||
}
|
}
|
||||||
testList "Max" [
|
testList "Max" [
|
||||||
@@ -699,14 +703,14 @@ let script =
|
|||||||
let html = RenderView.AsString.htmlNode Script.Max.cdnMinified
|
let html = RenderView.AsString.htmlNode Script.Max.cdnMinified
|
||||||
Expect.equal
|
Expect.equal
|
||||||
html
|
html
|
||||||
$"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmax.min.js" integrity="sha384-Qoqie5IRtOE79SDFFRSb/yKi+pkzpSnfjgwr1KksyP14OaHkLHar0KrLVxUwlsJF" crossorigin="anonymous"></script>"""
|
$"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmax.min.js" integrity="sha384-F2+P/m0g8+he+KLpgsCVZlZGvOx2z83bU4rmmISSNt7br4A0RTcJ7ddMrz0v45Nv" crossorigin="anonymous"></script>"""
|
||||||
"CDN minified script tag is incorrect"
|
"CDN minified script tag is incorrect"
|
||||||
}
|
}
|
||||||
test "cdnMaxUnminified succeeds" {
|
test "cdnMaxUnminified succeeds" {
|
||||||
let html = RenderView.AsString.htmlNode Script.Max.cdnUnminified
|
let html = RenderView.AsString.htmlNode Script.Max.cdnUnminified
|
||||||
Expect.equal
|
Expect.equal
|
||||||
html
|
html
|
||||||
$"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmax.js" integrity="sha384-gGi3Urue6ZkE4NrJCmXWIZkfNkrt1IrdP3fr0kb/v06GWg3V1RnD9Pg/Ul3qhtAK" crossorigin="anonymous"></script>"""
|
$"""<script src="https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmax.js" integrity="sha384-wrTFbAj755gAdIPPR9n4aAVMkLWkzxjr1Zj+8D4kgr2App4h9doO6SXBH9wpivij" crossorigin="anonymous"></script>"""
|
||||||
"CDN unminified script tag is incorrect"
|
"CDN unminified script tag is incorrect"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -717,11 +721,11 @@ open System.Text
|
|||||||
/// Tests for the RenderFragment module
|
/// Tests for the RenderFragment module
|
||||||
let renderFragment =
|
let renderFragment =
|
||||||
testList "RenderFragment" [
|
testList "RenderFragment" [
|
||||||
|
|
||||||
/// Validate that the two object references are the same object
|
/// Validate that the two object references are the same object
|
||||||
let isSame obj1 obj2 message =
|
let isSame obj1 obj2 message =
|
||||||
Expect.isTrue (obj.ReferenceEquals(obj1, obj2)) message
|
Expect.isTrue (obj.ReferenceEquals(obj1, obj2)) message
|
||||||
|
|
||||||
testList "findIdNode" [
|
testList "findIdNode" [
|
||||||
test "fails with a Text node" {
|
test "fails with a Text node" {
|
||||||
Expect.isNone (RenderFragment.findIdNode "blue" (Text "")) "There should not have been a node found"
|
Expect.isNone (RenderFragment.findIdNode "blue" (Text "")) "There should not have been a node found"
|
||||||
@@ -787,7 +791,7 @@ let renderFragment =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
testList "AsBytes" [
|
testList "AsBytes" [
|
||||||
|
|
||||||
/// Alias for UTF-8 encoding
|
/// Alias for UTF-8 encoding
|
||||||
let utf8 = Encoding.UTF8
|
let utf8 = Encoding.UTF8
|
||||||
|
|
||||||
@@ -859,478 +863,6 @@ let renderFragment =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
#nowarn 44 // Obsolete events still have tests
|
|
||||||
|
|
||||||
let hxEventObs =
|
|
||||||
testList "HxEvent (Obsolete)" [
|
|
||||||
testList "AfterOnLoad" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string AfterOnLoad) "afterOnLoad" "AfterOnLoad event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (AfterOnLoad.ToHxOnString()) "after:init" "AfterOnLoad hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "AfterProcessNode" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string AfterProcessNode) "afterProcessNode" "AfterProcessNode event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(AfterProcessNode.ToHxOnString()) "after:process" "AfterProcessNode hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "AfterSseMessage" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string AfterSseMessage) "afterSseMessage" "AfterSseMessage event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(AfterSseMessage.ToHxOnString()) "after:sse:message" "AfterSseMessage hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "AfterSseStream" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string AfterSseStream) "afterSseStream" "AfterSseStream event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(AfterSseStream.ToHxOnString()) "after:sse:stream" "AfterSseStream hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "BeforeCleanupElement" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(string BeforeCleanupElement) "beforeCleanupElement" "BeforeCleanupElement event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(BeforeCleanupElement.ToHxOnString())
|
|
||||||
"before:cleanup"
|
|
||||||
"BeforeCleanupElement hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "BeforeHistorySave" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string BeforeHistorySave) "beforeHistorySave" "BeforeHistorySave event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(BeforeHistorySave.ToHxOnString())
|
|
||||||
"before:history:update"
|
|
||||||
"BeforeHistorySave hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "BeforeOnLoad" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string BeforeOnLoad) "beforeOnLoad" "BeforeOnLoad event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (BeforeOnLoad.ToHxOnString()) "before:init" "BeforeOnLoad hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "BeforeProcessNode" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string BeforeProcessNode) "beforeProcessNode" "BeforeProcessNode event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(BeforeProcessNode.ToHxOnString()) "before:process" "BeforeProcessNode hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "BeforeSend" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string BeforeSend) "beforeSend" "BeforeSend event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (BeforeSend.ToHxOnString()) "before:request" "BeforeSend hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "BeforeSseMessage" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string BeforeSseMessage) "beforeSseMessage" "BeforeSseMessage event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(BeforeSseMessage.ToHxOnString())
|
|
||||||
"before:sse:message"
|
|
||||||
"BeforeSseMessage hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "BeforeSseReconnect" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(string BeforeSseReconnect) "beforeSseReconnect" "BeforeSseReconnect event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(BeforeSseReconnect.ToHxOnString())
|
|
||||||
"before:sse:reconnect"
|
|
||||||
"BeforeSseReconnect hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "BeforeSseStream" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string BeforeSseStream) "beforeSseStream" "BeforeSseStream event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(BeforeSseStream.ToHxOnString()) "before:sse:stream" "BeforeSseStream hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "HistoryCacheError" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string HistoryCacheError) "historyCacheError" "HistoryCacheError event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(HistoryCacheError.ToHxOnString())
|
|
||||||
"history-cache-error"
|
|
||||||
"HistoryCacheError hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "HistoryCacheMiss" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string HistoryCacheMiss) "historyCacheMiss" "HistoryCacheMiss event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(HistoryCacheMiss.ToHxOnString())
|
|
||||||
"before:history:restore"
|
|
||||||
"HistoryCacheMiss hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "HistoryCacheMissError" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(string HistoryCacheMissError)
|
|
||||||
"historyCacheMissError"
|
|
||||||
"HistoryCacheMissError event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(HistoryCacheMissError.ToHxOnString())
|
|
||||||
"history-cache-miss-error"
|
|
||||||
"HistoryCacheMissError hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "HistoryCacheMissLoad" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(string HistoryCacheMissLoad) "historyCacheMissLoad" "HistoryCacheMissLoad event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(HistoryCacheMissLoad.ToHxOnString())
|
|
||||||
"before:history:restore"
|
|
||||||
"HistoryCacheMissLoad hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "HistoryRestore" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string HistoryRestore) "historyRestore" "HistoryRestore event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(HistoryRestore.ToHxOnString())
|
|
||||||
"before:history:restore"
|
|
||||||
"HistoryRestore hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "Load" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string Load) "load" "Load event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (Load.ToHxOnString()) "after:init" "Load hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "NoSseSourceError" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string NoSseSourceError) "noSSESourceError" "NoSseSourceError event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (NoSseSourceError.ToHxOnString()) "error" "NoSseSourceError hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "OnLoadError" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string OnLoadError) "onLoadError" "OnLoadError event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (OnLoadError.ToHxOnString()) "error" "OnLoadError hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "OobAfterSwap" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string OobAfterSwap) "oobAfterSwap" "OobAfterSwap event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (OobAfterSwap.ToHxOnString()) "after:swap" "OobAfterSwap hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "OobBeforeSwap" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string OobBeforeSwap) "oobBeforeSwap" "OobBeforeSwap event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (OobBeforeSwap.ToHxOnString()) "before:swap" "OobBeforeSwap hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "OobErrorNoTarget" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string OobErrorNoTarget) "oobErrorNoTarget" "OobErrorNoTarget event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (OobErrorNoTarget.ToHxOnString()) "error" "OobErrorNoTarget hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "Prompt" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string Prompt) "prompt" "Prompt event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (Prompt.ToHxOnString()) "prompt" "Prompt hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "PushedIntoHistory" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string PushedIntoHistory) "pushedIntoHistory" "PushedIntoHistory event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(PushedIntoHistory.ToHxOnString())
|
|
||||||
"after:push:into:history"
|
|
||||||
"PushedIntoHistory hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "SendError" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string SendError) "sendError" "SendError event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (SendError.ToHxOnString()) "error" "SendError hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "SseError" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string SseError) "sseError" "SseError event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (SseError.ToHxOnString()) "sse:error" "SseError hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "SseOpen" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string SseOpen) "sseOpen" "SseOpen event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (SseOpen.ToHxOnString()) "after:sse:connection" "SseOpen hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "SwapError" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string SwapError) "swapError" "SwapError event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (SwapError.ToHxOnString()) "error" "SwapError hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "TargetError" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string TargetError) "targetError" "TargetError event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (TargetError.ToHxOnString()) "error" "TargetError hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "Timeout" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string Timeout) "timeout" "Timeout event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (Timeout.ToHxOnString()) "error" "Timeout hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "ValidationValidate" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(string ValidationValidate) "validation:validate" "ValidationValidate event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(ValidationValidate.ToHxOnString())
|
|
||||||
"validation:validate"
|
|
||||||
"ValidationValidate hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "ValidationFailed" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string ValidationFailed) "validation:failed" "ValidationFailed event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(ValidationFailed.ToHxOnString())
|
|
||||||
"validation:failed"
|
|
||||||
"ValidationFailed hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "ValidationHalted" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string ValidationHalted) "validation:halted" "ValidationHalted event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(ValidationHalted.ToHxOnString())
|
|
||||||
"validation:halted"
|
|
||||||
"ValidationHalted hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "XhrAbort" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string XhrAbort) "xhr:abort" "XhrAbort event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (XhrAbort.ToHxOnString()) "error" "XhrAbort hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "XhrLoadEnd" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string XhrLoadEnd) "xhr:loadend" "XhrLoadEnd event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (XhrLoadEnd.ToHxOnString()) "finally:request" "XhrLoadEnd hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "XhrLoadStart" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string XhrLoadStart) "xhr:loadstart" "XhrLoadStart event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (XhrLoadStart.ToHxOnString()) "xhr:loadstart" "XhrLoadStart hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "XhrProgress" [
|
|
||||||
test "ToString succeeds" {
|
|
||||||
Expect.equal (string XhrProgress) "xhr:progress" "XhrProgress event name not correct"
|
|
||||||
}
|
|
||||||
test "ToHxOnString succeeds" {
|
|
||||||
Expect.equal (XhrProgress.ToHxOnString()) "xhr:progress" "XhrProgress hx-on event name not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
/// Tests for the HxParams module
|
|
||||||
let hxParamsObs =
|
|
||||||
testList "HxParams" [
|
|
||||||
test "All is correct" {
|
|
||||||
Expect.equal HxParams.All "*" "All is not correct"
|
|
||||||
}
|
|
||||||
test "None is correct" {
|
|
||||||
Expect.equal HxParams.None "none" "None is not correct"
|
|
||||||
}
|
|
||||||
testList "With" [
|
|
||||||
test "succeeds with empty list" {
|
|
||||||
Expect.equal (HxParams.With []) "" "With with empty list should have been blank"
|
|
||||||
}
|
|
||||||
test "succeeds with one list item" {
|
|
||||||
Expect.equal (HxParams.With [ "boo" ]) "boo" "With single item incorrect"
|
|
||||||
}
|
|
||||||
test "succeeds with multiple list items" {
|
|
||||||
Expect.equal (HxParams.With [ "foo"; "bar"; "baz" ]) "foo,bar,baz" "With multiple items incorrect"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "Except" [
|
|
||||||
test "succeeds with empty list" {
|
|
||||||
Expect.equal (HxParams.Except []) "not " "Except with empty list incorrect"
|
|
||||||
}
|
|
||||||
test "succeeds with one list item" {
|
|
||||||
Expect.equal (HxParams.Except [ "that" ]) "not that" "Except single item incorrect"
|
|
||||||
}
|
|
||||||
test "succeeds with multiple list items" {
|
|
||||||
Expect.equal (HxParams.Except [ "blue"; "green" ]) "not blue,green" "Except multiple items incorrect"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
/// Tests for the HxConfig module
|
|
||||||
let hxRequestObs =
|
|
||||||
testList "HxRequest (Obsolete)" [
|
|
||||||
testList "Configure" [
|
|
||||||
test "succeeds with an empty list" {
|
|
||||||
Expect.equal (HxRequest.Configure []) "{ }" "Configure with empty list incorrect"
|
|
||||||
}
|
|
||||||
test "succeeds with a non-empty list" {
|
|
||||||
Expect.equal
|
|
||||||
(HxRequest.Configure [ "\"a\": \"b\""; "\"c\": \"d\"" ]) """{ "a": "b", "c": "d" }"""
|
|
||||||
"Configure with a non-empty list incorrect"
|
|
||||||
}
|
|
||||||
test "succeeds with all known params configured" {
|
|
||||||
Expect.equal
|
|
||||||
(HxRequest.Configure
|
|
||||||
[ HxRequest.Timeout 1000; HxRequest.Credentials false; HxRequest.NoHeaders true ])
|
|
||||||
"""{ "timeout": 1000, "credentials": false, "noHeaders": true }"""
|
|
||||||
"Configure with all known params incorrect"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
test "Timeout succeeds" {
|
|
||||||
Expect.equal (HxRequest.Timeout 50) "\"timeout\": 50" "Timeout value incorrect"
|
|
||||||
}
|
|
||||||
testList "Credentials" [
|
|
||||||
test "succeeds when set to true" {
|
|
||||||
Expect.equal (HxRequest.Credentials true) "\"credentials\": true" "Credentials value incorrect"
|
|
||||||
}
|
|
||||||
test "succeeds when set to false" {
|
|
||||||
Expect.equal (HxRequest.Credentials false) "\"credentials\": false" "Credentials value incorrect"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
testList "NoHeaders" [
|
|
||||||
test "succeeds when set to true" {
|
|
||||||
Expect.equal (HxRequest.NoHeaders true) "\"noHeaders\": true" "NoHeaders value incorrect"
|
|
||||||
}
|
|
||||||
test "succeeds when set to false" {
|
|
||||||
Expect.equal (HxRequest.NoHeaders false) "\"noHeaders\": false" "NoHeaders value incorrect"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
let attributesObs =
|
|
||||||
testList "Attributes (Obsolete)" [
|
|
||||||
test "_hxDisabledElt succeeds" {
|
|
||||||
button [ _hxDisabledElt "this" ] [] |> shouldRender """<button hx-disabled-elt="this"></button>"""
|
|
||||||
}
|
|
||||||
test "_hxDisinherit succeeds" {
|
|
||||||
strong [ _hxDisinherit "*" ] [] |> shouldRender """<strong hx-disinherit="*"></strong>"""
|
|
||||||
}
|
|
||||||
test "_hxExt succeeds" {
|
|
||||||
section [ _hxExt "extendme" ] [] |> shouldRender """<section hx-ext="extendme"></section>"""
|
|
||||||
}
|
|
||||||
test "_hxHistory succeeds" {
|
|
||||||
span [ _hxHistory false ] [] |> shouldRender """<span hx-history="false"></span>"""
|
|
||||||
}
|
|
||||||
test "_hxHistoryElt succeeds" {
|
|
||||||
table [ _hxHistoryElt ] [] |> shouldRender """<table hx-history-elt></table>"""
|
|
||||||
}
|
|
||||||
test "_hxParams succeeds" {
|
|
||||||
br [ _hxParams "[p1,p2]" ] |> shouldRender """<br hx-params="[p1,p2]">"""
|
|
||||||
}
|
|
||||||
test "_hxPrompt succeeds" {
|
|
||||||
strong [ _hxPrompt "Who goes there?" ] []
|
|
||||||
|> shouldRender """<strong hx-prompt="Who goes there?"></strong>"""
|
|
||||||
}
|
|
||||||
test "_hxRequest succeeds" {
|
|
||||||
u [ _hxRequest "noHeaders" ] [] |> shouldRender """<u hx-request="noHeaders"></u>"""
|
|
||||||
}
|
|
||||||
test "_sseConnect succeeds" {
|
|
||||||
div [ _sseConnect "/gps/sse" ] [] |> shouldRender """<div sse-connect="/gps/sse"></div>"""
|
|
||||||
}
|
|
||||||
test "_sseSwap succeeds" {
|
|
||||||
ul [ _sseSwap "sseMessageName" ] [] |> shouldRender """<ul sse-swap="sseMessageName"></ul>"""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
let obsolete = testList "Obsolete" [ hxEventObs; hxParamsObs; hxRequestObs; attributesObs ]
|
|
||||||
|
|
||||||
/// All tests in this module
|
/// All tests in this module
|
||||||
let allTests =
|
let allTests =
|
||||||
testList "ViewEngine.Htmx" [
|
testList "ViewEngine.Htmx" [
|
||||||
@@ -1345,5 +877,4 @@ let allTests =
|
|||||||
hxTags
|
hxTags
|
||||||
script
|
script
|
||||||
renderFragment
|
renderFragment
|
||||||
obsolete
|
|
||||||
]
|
]
|
||||||
|
|||||||
+127
-425
File diff suppressed because it is too large
Load Diff
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
This package enables [htmx](https://htmx.org) support within the [Giraffe](https://giraffe.wiki) view engine.
|
This package enables [htmx](https://htmx.org) support within the [Giraffe](https://giraffe.wiki) view engine.
|
||||||
|
|
||||||
**htmx version: 4.0.0-beta4**
|
**htmx version: 4.0.0**
|
||||||
|
|
||||||
_Upgrading from v2.x: see [the migration guide](https://four.htmx.org/docs/get-started/migration) for changes, which are plentiful. htmx switches from `XMLHTTPRequest` to `fetch`, and many changes are related to the new event cycle._
|
_Upgrading from v2.x: see [the migration guide](https://four.htmx.org/docs#migrating-from-htmx-2x-to-4x) for changes, which are plentiful. htmx switches from `XMLHTTPRequest` to `fetch`, and many changes are related to the new event cycle._
|
||||||
|
|
||||||
_Inheritance is now explicit; to have an attribute's value inherited to its children, wrap the attribute in `hxInherited` (ex. `hxInherited (_hxTarget "#main")`). Values can be appended to inherited values as well using the `hxAppend` modifier._
|
_Inheritance is now explicit; to have an attribute's value inherited to its children, wrap the attribute in `hxInherited` (ex. `hxInherited (_hxTarget "#main")`). Values can be appended to inherited values as well using the `hxAppend` modifier._
|
||||||
|
|
||||||
_Several constructs have been marked obsolete in this release, and will be removed from the first production release of v4. With the exception of `_hxDisable`, though (which now functions as the deprecated `_hxDisabledElt` did), this should not introduce compile errors. Rather, this package will raise warnings for deprecated constructs, along with suggestions of what to use instead._
|
_Several constructs have been removed in this release. If you encounter compile errors, install version `4.0.0-beta5`; it will instead flag these with warnings and suggestions on how to mitigate them. Once the warnings are gone, you can reinstall this version._
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
@@ -26,11 +26,9 @@ let autoload =
|
|||||||
```
|
```
|
||||||
|
|
||||||
Support modules include:
|
Support modules include:
|
||||||
- `HxConfig` _(new in v4)_
|
- `HxConfig` _(new in v4, replaces `HxRequest`)_
|
||||||
- `HxEncoding`
|
- `HxEncoding`
|
||||||
- `HxHeaders`
|
- `HxHeaders`
|
||||||
- ~~`HxParams`~~ _(removed in v4)_
|
|
||||||
- ~~`HxRequest`~~ _(renamed to `HxConfig`)_
|
|
||||||
- `HxSwap` (requires `open Giraffe.Htmx`)
|
- `HxSwap` (requires `open Giraffe.Htmx`)
|
||||||
- `HxTrigger`
|
- `HxTrigger`
|
||||||
- `HxVals`
|
- `HxVals`
|
||||||
|
|||||||
Reference in New Issue
Block a user