module Htmx open System open Expecto open Giraffe.Htmx open Microsoft.AspNetCore.Http open NSubstitute /// Tests for the IHeaderDictionary extension properties let dictExtensions = testList "IHeaderDictionaryExtensions" [ testList "HxBoosted" [ test "succeeds when the header is not present" { let ctx = Substitute.For() ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore Expect.isNone ctx.Request.Headers.HxBoosted "There should not have been a header returned" } test "succeeds when the header is present and true" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Boosted", "true") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxBoosted "There should be a header present" Expect.isTrue ctx.Request.Headers.HxBoosted.Value "The header value should have been true" } test "succeeds when the header is present and false" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Boosted", "false") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxBoosted "There should be a header present" Expect.isFalse ctx.Request.Headers.HxBoosted.Value "The header value should have been false" } ] testList "HxCurrentUrl" [ test "succeeds when the header is not present" { let ctx = Substitute.For() ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore Expect.isNone ctx.Request.Headers.HxCurrentUrl "There should not have been a header returned" } test "succeeds when the header is present" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Current-URL", "http://localhost/test.htm") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxCurrentUrl "There should be a header present" Expect.equal ctx.Request.Headers.HxCurrentUrl.Value (Uri "http://localhost/test.htm") "The header value was not correct" } ] testList "HxHistoryRestoreRequest" [ test "succeeds when the header is not present" { let ctx = Substitute.For() ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore Expect.isNone ctx.Request.Headers.HxHistoryRestoreRequest "There should not have been a header returned" } test "succeeds when the header is present and true" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-History-Restore-Request", "true") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxHistoryRestoreRequest "There should be a header present" Expect.isTrue ctx.Request.Headers.HxHistoryRestoreRequest.Value "The header value should have been true" } test "succeeds when the header is present and false" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-History-Restore-Request", "false") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxHistoryRestoreRequest "There should be a header present" Expect.isFalse ctx.Request.Headers.HxHistoryRestoreRequest.Value "The header should have been false" } ] testList "HxPreloaded" [ test "succeeds when the header is not present" { let ctx = Substitute.For() ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore Expect.isNone ctx.Request.Headers.HxPreloaded "There should not have been a header returned" } test "succeeds when the header is present and true" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Preloaded", "true") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxPreloaded "There should be a header present" Expect.isTrue ctx.Request.Headers.HxPreloaded.Value "The header should have been true" } // This is not a condition fired by the extension; the header will be either absent or true test "succeeds when the header is present and false" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Preloaded", "false") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxPreloaded "There should be a header present" 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() 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() 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" [ test "succeeds when the header is not present" { let ctx = Substitute.For() ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore Expect.isNone ctx.Request.Headers.HxRequest "There should not have been a header returned" } test "succeeds when the header is present and true" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Request", "true") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxRequest "There should be a header present" Expect.isTrue ctx.Request.Headers.HxRequest.Value "The header should have been true" } test "succeeds when the header is present and false" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Request", "false") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxRequest "There should be a header present" Expect.isFalse ctx.Request.Headers.HxRequest.Value "The header should have been false" } ] testList "HxRequestId" [ test "succeeds when the header is not present" { let ctx = Substitute.For() ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore Expect.isNone ctx.Request.Headers.HxRequestId "There should not have been a header returned" } test "succeeds when the header is present" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Request-ID", "abcd1234") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxRequestId "There should be a header present" Expect.equal ctx.Request.Headers.HxRequestId.Value "abcd1234" "The header value was incorrect" } ] testList "HxRequestType" [ test "succeeds when the header is not present" { let ctx = Substitute.For() ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore Expect.isNone ctx.Request.Headers.HxRequestType "There should not have been a header returned" } test "succeeds when the header is invalid" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Request-Type", "relaxed") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isNone ctx.Request.Headers.HxRequestType "There should not have been a header returned" } test "succeeds for a full request" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Request-Type", "full") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxRequestType "There have been a header returned" Expect.equal ctx.Request.Headers.HxRequestType.Value HxFullRequest "The header value is incorrect" } test "succeeds for a partial request" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Request-Type", "partial") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isSome ctx.Request.Headers.HxRequestType "There have been a header returned" Expect.equal ctx.Request.Headers.HxRequestType.Value HxPartialRequest "The header value is incorrect" } ] testList "HxSource" [ test "succeeds when the header is not present" { let ctx = Substitute.For() 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() 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() 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() 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" [ test "succeeds when the header is not present" { let ctx = Substitute.For() ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore Expect.isNone ctx.Request.Headers.HxTarget "There should not have been a header returned" } test "succeeds when the header is present and both parts exist" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Target", "div#leItem") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore let hdr = ctx.Request.Headers.HxTarget Expect.isSome hdr "There should be a header present" Expect.equal (fst hdr.Value) "div" "The target tag was incorrect" Expect.isSome (snd hdr.Value) "There should be a target ID present" Expect.equal (snd hdr.Value).Value "leItem" "The header value was incorrect" } test "succeeds when the header is present and ID is blank" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Target", "span#") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore let hdr = ctx.Request.Headers.HxTarget Expect.isSome hdr "There should be a header present" Expect.equal (fst hdr.Value) "span" "The target tag was incorrect" Expect.isNone (snd hdr.Value) "There should not be a target ID present" } test "succeeds when the header is present and ID is missing" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Target", "aside") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore let hdr = ctx.Request.Headers.HxTarget Expect.isSome hdr "There should be a header present" Expect.equal (fst hdr.Value) "aside" "The target tag was incorrect" Expect.isNone (snd hdr.Value) "There should not be a target ID present" } ] ] /// Tests for the HttpRequest extension properties let reqExtensions = testList "HttpRequestExtensions" [ testList "IsHtmx" [ test "succeeds when request is not from htmx" { let ctx = Substitute.For() ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore Expect.isFalse ctx.Request.IsHtmx "The request should not be an htmx request" } test "succeeds when request is from htmx" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Request", "true") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isTrue ctx.Request.IsHtmx "The request should have been an htmx request" } ] testList "IsHtmxRefresh" [ test "succeeds when request is not from htmx" { let ctx = Substitute.For() ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore Expect.isFalse ctx.Request.IsHtmxRefresh "The request should not have been an htmx refresh" } test "succeeds when request is from htmx, but not a refresh" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Request", "true") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isFalse ctx.Request.IsHtmxRefresh "The request should not have been an htmx refresh" } test "IsHtmxRefresh succeeds when request is from htmx and is a refresh" { let ctx = Substitute.For() let dic = HeaderDictionary() dic.Add("HX-Request", "true") dic.Add("HX-History-Restore-Request", "true") ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore Expect.isTrue ctx.Request.IsHtmxRefresh "The request should have been an htmx refresh" } ] ] open System.Threading.Tasks /// Dummy "next" parameter to get the pipeline to execute/terminate let next (ctx: HttpContext) = Task.FromResult(Some ctx) /// Tests for the HttpHandler functions provided in the Handlers module let handlers = testList "HandlerTests" [ testTask "withHxDownload succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxDownload "/files/stuff.pdf" next ctx Expect.isTrue (dic.ContainsKey "HX-Download") "The HX-Download header should be present" Expect.equal dic["HX-Download"].[0] "/files/stuff.pdf" "The HX-Download value was incorrect" } testTask "withHxLocation succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxLocation "/pagina-otro.html" next ctx Expect.isTrue (dic.ContainsKey "HX-Location") "The HX-Location header should be present" Expect.equal dic["HX-Location"].[0] "/pagina-otro.html" "The HX-Location value was incorrect" } testTask "withHxPushUrl succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxPushUrl "/a-new-url" next ctx Expect.isTrue (dic.ContainsKey "HX-Push-Url") "The HX-Push-Url header should be present" Expect.equal dic["HX-Push-Url"].[0] "/a-new-url" "The HX-Push-Url value was incorrect" } testTask "withHxNoPushUrl succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxNoPushUrl next ctx Expect.isTrue (dic.ContainsKey "HX-Push-Url") "The HX-Push-Url header should be present" Expect.equal dic["HX-Push-Url"].[0] "false" "The HX-Push-Url value was incorrect" } testTask "withHxRedirect succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxRedirect "/somewhere-else" next ctx Expect.isTrue (dic.ContainsKey "HX-Redirect") "The HX-Redirect header should be present" Expect.equal dic["HX-Redirect"].[0] "/somewhere-else" "The HX-Redirect value was incorrect" } testList "withHxRefresh" [ testTask "succeeds when set to true" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxRefresh true next ctx Expect.isTrue (dic.ContainsKey "HX-Refresh") "The HX-Refresh header should be present" Expect.equal dic["HX-Refresh"].[0] "true" "The HX-Refresh value was incorrect" } testTask "succeeds when set to false" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxRefresh false next ctx Expect.isTrue (dic.ContainsKey "HX-Refresh") "The HX-Refresh header should be present" Expect.equal dic["HX-Refresh"].[0] "false" "The HX-Refresh value was incorrect" } ] testTask "withHxReplaceUrl succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxReplaceUrl "/a-substitute-url" next ctx Expect.isTrue (dic.ContainsKey "HX-Replace-Url") "The HX-Replace-Url header should be present" Expect.equal dic["HX-Replace-Url"].[0] "/a-substitute-url" "The HX-Replace-Url value was incorrect" } testTask "withHxNoReplaceUrl succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxNoReplaceUrl next ctx Expect.isTrue (dic.ContainsKey "HX-Replace-Url") "The HX-Replace-Url header should be present" Expect.equal dic["HX-Replace-Url"].[0] "false" "The HX-Replace-Url value was incorrect" } testTask "withHxReselect succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxReselect "#test" next ctx 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" } testTask "withHxReswap succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxReswap HxSwap.BeforeEnd next ctx 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" } testTask "withHxRetarget succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxRetarget "#somewhereElse" next ctx Expect.isTrue (dic.ContainsKey "HX-Retarget") "The HX-Retarget header should be present" Expect.equal dic["HX-Retarget"].[0] "#somewhereElse" "The HX-Retarget value was incorrect" } testTask "withHxTrigger succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxTrigger "doSomething" next ctx Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present" Expect.equal dic["HX-Trigger"].[0] "doSomething" "The HX-Trigger value was incorrect" } testTask "withHxTriggerMany succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore let! _ = withHxTriggerMany [ "blah", "foo"; "bleh", "bar" ] next ctx Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present" Expect.equal dic["HX-Trigger"].[0] """{ "blah": "foo", "bleh": "bar" }""" "The HX-Trigger value was incorrect" } ] /// Tests for the HtmxScript module let script = testList "HtmxScript" [ test "local generates correct link" { Expect.equal (string HtmxScript.local) $"""""" "htmx script link is incorrect" } test "localMax generates correct link" { Expect.equal (string HtmxScript.localMax) $"""""" "htmx script link is incorrect" } ] /// All tests for this module let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers; script ]