Rearrange obsolete tests; test new v4 code

This commit is contained in:
2026-01-07 06:30:28 -05:00
parent 9a1b10c51f
commit dca0f2721a
7 changed files with 852 additions and 675 deletions

View File

@@ -2,4 +2,6 @@
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 as a static web asset, allowing it to be loaded from your local (or published) project.
**htmx version: 2.0.8** **htmx version: 4.0.0-alpha6**
_**NOTE:** Pay special attention to breaking changes highlighted in the packages listed above._

View File

@@ -2,16 +2,16 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<VersionPrefix>2.0.8</VersionPrefix> <VersionPrefix>4.0.0</VersionPrefix>
<VersionSuffix>alpha6a</VersionSuffix>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageReleaseNotes>- Adds package-provided script (available via Giraffe.Htmx.Common); use app.MapStaticAssets() for publish support, use builder.UseStaticWebAssets() for non-development/non-published execution <PackageReleaseNotes>First htmx 4 alpha release of these libraries
- [View Engine] Deprecates Script.minified and Script.unminified (use Script.cdnMinified and Script.cdnUnminified instead) - [Server] Marked removed headers as obsolete; added new HX-Source header
- Updates script tags to pull htmx 2.0.8 (no header or attribute changes) - [View Engine] Marked removed attributes as obsolete
- Adds .NET 10 support - [View Engine] Added new attributes, modifiers, and support for new hx-partial tag
- Updated script tags to pull htmx 4.0.0-alpha6
See full release notes, including more info about the package-provided script, at https://git.bitbadger.solutions/bit-badger/Giraffe.Htmx/releases/tag/v2.0.8 See package READMEs; this is not an update-and-forget-it release
NOTE: As of 2.0.6, the CDN for htmx changed from unpkg.com to cdn.jsdelivr.net; sites with Content-Security-Policy headers will want to update their allowed script-src domains accordingly
</PackageReleaseNotes> </PackageReleaseNotes>
<Authors>danieljsummers</Authors> <Authors>danieljsummers</Authors>
<Company>Bit Badger Solutions</Company> <Company>Bit Badger Solutions</Company>

View File

@@ -2,9 +2,11 @@
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: 2.0.8** **htmx version: 4.0.0-alpha6**
_Upgrading from v1.x: the [migration guide](https://htmx.org/migration-guide-htmx-1/) does not currently specify any request or response header changes. This means that there are no required code changes in moving from v1.* to v2.*._ _Upgrading from v2.x: the [migration guide](https://four.htmx.org/migration-guide-htmx-4/) 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._
### Setup ### Setup
@@ -18,9 +20,9 @@ To obtain a request header, using the `IHeaderDictionary` extension properties:
```fsharp ```fsharp
let myHandler : HttpHander = let myHandler : HttpHander =
fun next ctx -> fun next ctx ->
match ctx.HxPrompt with match ctx.Target with
| Some prompt -> ... // do something with the text the user provided | Some elt -> ... // do something with id of the target element
| None -> ... // no text provided | None -> ... // no target element provided
``` ```
To set a response header: To set a response header:

View File

@@ -3,7 +3,6 @@ module Htmx
open System open System
open Expecto open Expecto
open Giraffe.Htmx open Giraffe.Htmx
open Microsoft.AspNetCore.Html
open Microsoft.AspNetCore.Http open Microsoft.AspNetCore.Http
open NSubstitute open NSubstitute
@@ -74,21 +73,6 @@ let dictExtensions =
ctx.Request.Headers.HxHistoryRestoreRequest.Value "The header should have been false" ctx.Request.Headers.HxHistoryRestoreRequest.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>()
@@ -112,6 +96,44 @@ let dictExtensions =
Expect.isFalse ctx.Request.Headers.HxRequest.Value "The header should have been false" Expect.isFalse ctx.Request.Headers.HxRequest.Value "The header should have been false"
} }
] ]
testList "HxSource" [
test "succeeds when the header is not present" {
let ctx = Substitute.For<HttpContext>()
ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isNone ctx.Request.Headers.HxSource "There should not have been a header returned"
}
test "succeeds when the header is present and both parts exist" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
dic.Add("HX-Source", "button#theId")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
let hdr = ctx.Request.Headers.HxSource
Expect.isSome hdr "There should be a header present"
Expect.equal (fst hdr.Value) "button" "The source tag was incorrect"
Expect.isSome (snd hdr.Value) "There should be a source ID present"
Expect.equal (snd hdr.Value).Value "theId" "The source ID was incorrect"
}
test "succeeds when the header is present and ID is blank" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
dic.Add("HX-Source", "a#")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
let hdr = ctx.Request.Headers.HxSource
Expect.isSome hdr "There should be a header present"
Expect.equal (fst hdr.Value) "a" "The source tag was incorrect"
Expect.isNone (snd hdr.Value) "There should not be a source ID present"
}
test "succeeds when the header is present and ID is missing" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
dic.Add("HX-Source", "form")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
let hdr = ctx.Request.Headers.HxSource
Expect.isSome hdr "There should be a header present"
Expect.equal (fst hdr.Value) "form" "The source tag was incorrect"
Expect.isNone (snd hdr.Value) "There should not be a source ID present"
}
]
testList "HxTarget" [ testList "HxTarget" [
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>()
@@ -127,36 +149,6 @@ let dictExtensions =
Expect.equal ctx.Request.Headers.HxTarget.Value "#leItem" "The header value was incorrect" Expect.equal ctx.Request.Headers.HxTarget.Value "#leItem" "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"
}
]
] ]
/// Tests for the HttpRequest extension properties /// Tests for the HttpRequest extension properties
@@ -366,5 +358,55 @@ let script =
} }
] ]
#nowarn 44 // Obsolete items still have tests
let dictExtensionsObs =
testList "IHeaderDictionaryExtensions (Obsolete)" [
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"
}
]
]
/// All tests for this module /// All tests for this module
let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers; script ] let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers; script; dictExtensionsObs ]

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,41 @@
/// <summary>Types and functions supporting htmx attributes in Giraffe View Engine</summary> /// <summary>Types and functions supporting htmx attributes in Giraffe View Engine</summary>
module Giraffe.ViewEngine.Htmx module Giraffe.ViewEngine.Htmx
open System.Net.Http /// <summary>Helpers to define <c>hx-config</c> attribute values</summary>
/// <seealso href="https://four.htmx.org/attributes/hx-config/">Documentation</seealso>
[<RequireQualifiedAccess>]
module HxConfig =
open Giraffe.Htmx.Common
/// <summary>Configure the request with various options</summary>
/// <param name="opts">The options to configure</param>
/// <returns>A string with the configured options</returns>
let Configure (opts: string list) =
opts
|> String.concat ", "
|> sprintf "{ %s }"
/// <summary>Set a timeout (in milliseconds)</summary>
/// <param name="ms">The milliseconds for the request timeout</param>
/// <returns>A string with the configured request timeout</returns>
let Timeout (ms: int) =
$"\"timeout\": {ms}"
/// <summary>Include or exclude credentials from the request</summary>
/// <param name="send"><c>true</c> if credentials should be sent, <c>false</c> if not</param>
/// <returns>A string with the configured credential options</returns>
let Credentials send =
(toLowerBool >> sprintf "\"credentials\": %s") send
/// <summary>Exclude or include headers from the request</summary>
/// <param name="exclude">
/// <c>true</c> if no headers should be sent; <c>false</c> if headers should be sent
/// </param>
/// <returns>A string with the configured header options</returns>
let NoHeaders exclude =
(toLowerBool >> sprintf "\"noHeaders\": %s") exclude
/// <summary>Valid values for the <c>hx-encoding</c> attribute</summary> /// <summary>Valid values for the <c>hx-encoding</c> attribute</summary>
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
@@ -289,6 +323,7 @@ module HxHeaders =
/// <summary>Values / helpers for the <c>hx-params</c> attribute</summary> /// <summary>Values / helpers for the <c>hx-params</c> attribute</summary>
/// <seealso href="https://htmx.org/attributes/hx-params/">Documentation</seealso> /// <seealso href="https://htmx.org/attributes/hx-params/">Documentation</seealso>
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
[<System.Obsolete "Removed in v4; filter parameters via JavaScript in configRequest event">]
module HxParams = module HxParams =
/// <summary>Include all parameters</summary> /// <summary>Include all parameters</summary>
@@ -315,6 +350,7 @@ module HxParams =
/// <summary>Helpers to define <c>hx-request</c> attribute values</summary> /// <summary>Helpers to define <c>hx-request</c> attribute values</summary>
/// <seealso href="https://htmx.org/attributes/hx-request/">Documentation</seealso> /// <seealso href="https://htmx.org/attributes/hx-request/">Documentation</seealso>
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
[<System.Obsolete "Removed in v4; change to HxConfig">]
module HxRequest = module HxRequest =
open Giraffe.Htmx.Common open Giraffe.Htmx.Common
@@ -564,6 +600,7 @@ module HxVals =
let From = Giraffe.Htmx.Common.toJson let From = Giraffe.Htmx.Common.toJson
open System.Net.Http
open Giraffe.Htmx open Giraffe.Htmx
/// <summary>Attributes and flags for htmx</summary> /// <summary>Attributes and flags for htmx</summary>
@@ -913,10 +950,10 @@ module HxModifiers =
| KeyValue (name, value) -> attr $"{name}:append" value | KeyValue (name, value) -> attr $"{name}:append" value
| Boolean name -> flag $"{name}:append" | Boolean name -> flag $"{name}:append"
/// <summary>Explicity inherit the value for this attribute</summary> /// <summary>Explicitly propagate inheritance for the value for this attribute</summary>
/// <param name="attrib">The attribute whose value should be inherited</param> /// <param name="attrib">The attribute whose value should be inherited</param>
/// <returns>The given attribute with <c>:inherited</c> applied</returns> /// <returns>The given attribute with <c>:inherited</c> applied</returns>
let hxInherit attrib = let hxInherited attrib =
match attrib with match attrib with
| KeyValue (name, value) -> attr $"{name}:inherited" value | KeyValue (name, value) -> attr $"{name}:inherited" value
| Boolean name -> flag $"{name}:inherited" | Boolean name -> flag $"{name}:inherited"

View File

@@ -2,9 +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: 2.0.8** **htmx version: 4.0.0-alpha6**
_Upgrading from v1.x: see [the migration guide](https://htmx.org/migration-guide-htmx-1/) for changes_ _Upgrading from v2.x: see [the migration guide](https://four.htmx.org/migration-guide-htmx-4/) 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._
_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._
### Setup ### Setup
@@ -21,10 +25,11 @@ let autoload =
``` ```
Support modules include: Support modules include:
- `HxConfig` _(new in v4)_
- `HxEncoding` - `HxEncoding`
- `HxHeaders` - `HxHeaders`
- `HxParams` - ~~`HxParams`~~ _(removed in v4)_
- `HxRequest` - ~~`HxRequest`~~ _(renamed to `HxConfig`)_
- `HxSwap` (requires `open Giraffe.Htmx`) - `HxSwap` (requires `open Giraffe.Htmx`)
- `HxTrigger` - `HxTrigger`
- `HxVals` - `HxVals`