From f42351e94fc13bc8fc191ac4883f9a3e6478e371 Mon Sep 17 00:00:00 2001 From: maddalax Date: Thu, 31 Oct 2024 11:36:57 -0500 Subject: [PATCH] more tests --- framework/h/attribute_test.go | 160 ++++++++++++++++++++++++++++++++ framework/h/command_test.go | 15 +++ framework/h/header_test.go | 57 ++++++++++++ framework/h/qs_test.go | 27 ++++++ framework/h/serialize.go | 3 + framework/h/serialize_test.go | 28 ++++++ framework/h/swap_test.go | 88 ++++++++++++++++++ framework/h/tag_test.go | 168 ++++++++++++++++++++++++++++++++++ 8 files changed, 546 insertions(+) create mode 100644 framework/h/attribute_test.go create mode 100644 framework/h/header_test.go create mode 100644 framework/h/serialize_test.go create mode 100644 framework/h/swap_test.go create mode 100644 framework/h/tag_test.go diff --git a/framework/h/attribute_test.go b/framework/h/attribute_test.go new file mode 100644 index 00000000..c2a56f11 --- /dev/null +++ b/framework/h/attribute_test.go @@ -0,0 +1,160 @@ +package h + +import ( + "github.com/maddalax/htmgo/framework/hx" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAttributes(t *testing.T) { + tests := []struct { + name string + attribute *AttributeR + expectedKey string + expectedValue string + }{ + {"NoSwap", NoSwap(), "hx-swap", "none"}, + {"Checked", Checked().(*AttributeR), "checked", ""}, + {"Id", Id("myID").(*AttributeR), "id", "myID"}, + {"Disabled", Disabled(), "disabled", ""}, + {"HxTarget", HxTarget("#myTarget").(*AttributeR), "hx-target", "#myTarget"}, + {"Name", Name("myName").(*AttributeR), "name", "myName"}, + {"HxConfirm", HxConfirm("Are you sure?").(*AttributeR), "hx-confirm", "Are you sure?"}, + {"Class", Class("class1", "class2"), "class", "class1 class2 "}, + {"ReadOnly", ReadOnly(), "readonly", ""}, + {"Required", Required(), "required", ""}, + {"Multiple", Multiple(), "multiple", ""}, + {"Selected", Selected(), "selected", ""}, + {"MaxLength", MaxLength(10), "maxlength", "10"}, + {"MinLength", MinLength(5), "minlength", "5"}, + {"Size", Size(3), "size", "3"}, + {"Width", Width(100), "width", "100"}, + {"Height", Height(200), "height", "200"}, + {"Download", Download(true), "download", "true"}, + {"Rel", Rel("noopener"), "rel", "noopener"}, + {"Pattern", Pattern("[A-Za-z]+"), "pattern", "[A-Za-z]+"}, + {"Action", Action("/submit"), "action", "/submit"}, + {"Method", Method("POST"), "method", "POST"}, + {"Enctype", Enctype("multipart/form-data"), "enctype", "multipart/form-data"}, + {"AutoComplete", AutoComplete("on"), "autocomplete", "on"}, + {"AutoFocus", AutoFocus(), "autofocus", ""}, + {"NoValidate", NoValidate(), "novalidate", ""}, + {"Step", Step("0.1"), "step", "0.1"}, + {"Max", Max("100"), "max", "100"}, + {"Min", Min("0"), "min", "0"}, + {"Cols", Cols(30), "cols", "30"}, + {"Rows", Rows(10), "rows", "10"}, + {"Wrap", Wrap("soft"), "wrap", "soft"}, + {"Role", Role("button"), "role", "button"}, + {"AriaLabel", AriaLabel("Close Dialog"), "aria-label", "Close Dialog"}, + {"AriaHidden", AriaHidden(true), "aria-hidden", "true"}, + {"TabIndex", TabIndex(1), "tabindex", "1"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expectedKey, tt.attribute.Name) + assert.Equal(t, tt.expectedValue, tt.attribute.Value) + }) + } +} + +func TestClassF(t *testing.T) { + attribute := ClassF("class-%d", 123) + assert.Equal(t, "class", attribute.Name) + assert.Equal(t, "class-123", attribute.Value) +} + +func TestClassX(t *testing.T) { + classMap := ClassMap{"visible": true, "hidden": false} + attribute := ClassX("base", classMap).(*AttributeR) + assert.Equal(t, "class", attribute.Name) + assert.Equal(t, "base visible ", attribute.Value) +} + +func TestJoinAttributes(t *testing.T) { + attr1 := Attribute("data-attr", "one") + attr2 := Attribute("data-attr", "two") + joined := JoinAttributes(", ", attr1, attr2) + assert.Equal(t, "data-attr", joined.Name) + assert.Equal(t, "one, two", joined.Value) +} + +func TestTarget(t *testing.T) { + attr := Target("_blank") + assert.Equal(t, "target", attr.(*AttributeR).Name) + assert.Equal(t, "_blank", attr.(*AttributeR).Value) +} + +func TestD(t *testing.T) { + attr := D("M10 10 H 90 V 90 H 10 Z") + assert.Equal(t, "d", attr.(*AttributeR).Name) + assert.Equal(t, "M10 10 H 90 V 90 H 10 Z", attr.(*AttributeR).Value) +} + +func TestHxExtension(t *testing.T) { + attr := HxExtension("trigger-children") + assert.Equal(t, "hx-ext", attr.Name) + assert.Equal(t, "trigger-children", attr.Value) +} + +func TestHxExtensions(t *testing.T) { + attr := HxExtensions("foo", "bar") + assert.Equal(t, "hx-ext", attr.(*AttributeR).Name) + assert.Equal(t, "foo,bar", attr.(*AttributeR).Value) +} + +func TestHxTrigger(t *testing.T) { + trigger := hx.NewTrigger(hx.OnClick()) // This assumes hx.NewTrigger is a correct call + attr := HxTrigger(hx.OnClick()) + assert.Equal(t, "hx-trigger", attr.Name) + assert.Equal(t, trigger.ToString(), attr.Value) +} + +func TestHxTriggerClick(t *testing.T) { + attr := HxTriggerClick() // Assuming no options for simplicity + assert.Equal(t, "hx-trigger", attr.Name) + assert.Equal(t, "click", attr.Value) +} + +func TestTriggerChildren(t *testing.T) { + attr := TriggerChildren() + assert.Equal(t, "hx-ext", attr.Name) + assert.Equal(t, "trigger-children", attr.Value) +} + +func TestHxInclude(t *testing.T) { + attr := HxInclude(".include-selector") + assert.Equal(t, "hx-include", attr.(*AttributeR).Name) + assert.Equal(t, ".include-selector", attr.(*AttributeR).Value) +} + +func TestHxIndicator(t *testing.T) { + attr := HxIndicator("#my-indicator") + assert.Equal(t, "hx-indicator", attr.Name) + assert.Equal(t, "#my-indicator", attr.Value) +} + +func TestHidden(t *testing.T) { + attr := Hidden() + assert.Equal(t, "style", attr.(*AttributeR).Name) + assert.Equal(t, "display:none", attr.(*AttributeR).Value) +} + +func TestControls(t *testing.T) { + attr := Controls() + assert.Equal(t, "controls", attr.(*AttributeR).Name) + assert.Equal(t, "", attr.(*AttributeR).Value) +} + +func TestPlaceholder(t *testing.T) { + attr := Placeholder("Enter text") + assert.Equal(t, "placeholder", attr.(*AttributeR).Name) + assert.Equal(t, "Enter text", attr.(*AttributeR).Value) +} + +func TestBoost(t *testing.T) { + attr := Boost() + assert.Equal(t, "hx-boost", attr.(*AttributeR).Name) + assert.Equal(t, "true", attr.(*AttributeR).Value) +} diff --git a/framework/h/command_test.go b/framework/h/command_test.go index 11b42fa1..24425067 100644 --- a/framework/h/command_test.go +++ b/framework/h/command_test.go @@ -381,3 +381,18 @@ func TestToggleClassOnSibling(t *testing.T) { } `)) } + +func TestPreventDefault(t *testing.T) { + t.Parallel() + compareIgnoreSpaces(t, renderJs(t, PreventDefault()), "event.preventDefault();") +} + +func TestConsoleLog(t *testing.T) { + t.Parallel() + compareIgnoreSpaces(t, renderJs(t, ConsoleLog("Log Message")), "console.log('Log Message');") +} + +func TestSetValue(t *testing.T) { + t.Parallel() + compareIgnoreSpaces(t, renderJs(t, SetValue("New Value")), "this.value = 'New Value';") +} diff --git a/framework/h/header_test.go b/framework/h/header_test.go new file mode 100644 index 00000000..5715a2e1 --- /dev/null +++ b/framework/h/header_test.go @@ -0,0 +1,57 @@ +package h + +import ( + "github.com/maddalax/htmgo/framework/hx" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestReplaceUrlHeader(t *testing.T) { + headers := ReplaceUrlHeader("/new-url") + assert.Contains(t, *headers, hx.ReplaceUrlHeader) + assert.Equal(t, "/new-url", (*headers)[hx.ReplaceUrlHeader]) +} + +func TestPushUrlHeader(t *testing.T) { + headers := PushUrlHeader("/push-url") + assert.Contains(t, *headers, hx.PushUrlHeader) + assert.Equal(t, "/push-url", (*headers)[hx.PushUrlHeader]) +} + +func TestPushQsHeader(t *testing.T) { + ctx := &RequestContext{currentBrowserUrl: "https://example.com/path"} + qs := NewQs("a", "b", "c", "d") + headers := PushQsHeader(ctx, qs) + expectedURL := "/path?a=b&c=d" + assert.Contains(t, *headers, hx.ReplaceUrlHeader) + assert.Equal(t, expectedURL, (*headers)[hx.ReplaceUrlHeader]) +} + +func TestCombineHeaders(t *testing.T) { + h1 := NewHeaders("Content-Type", "application/json") + h2 := NewHeaders("Authorization", "Bearer token") + combined := CombineHeaders(h1, h2) + assert.Equal(t, "application/json", (*combined)["Content-Type"]) + assert.Equal(t, "Bearer token", (*combined)["Authorization"]) +} + +func TestCurrentPath(t *testing.T) { + req, _ := http.NewRequest("GET", "https://example.com", nil) + req.Header.Set(hx.CurrentUrlHeader, "https://example.com/current-path") + ctx := &RequestContext{Request: req} + path := CurrentPath(ctx) + assert.Equal(t, "/current-path", path) +} + +func TestNewHeaders(t *testing.T) { + headers := NewHeaders("X-Custom", "value", "X-Another", "another-value") + require.NotNil(t, headers) + assert.Equal(t, "value", (*headers)["X-Custom"]) + assert.Equal(t, "another-value", (*headers)["X-Another"]) + + invalidHeaders := NewHeaders("X-Custom") + assert.Empty(t, *invalidHeaders) // Check incorrect pair length handling +} diff --git a/framework/h/qs_test.go b/framework/h/qs_test.go index 31067fd6..22e80b16 100644 --- a/framework/h/qs_test.go +++ b/framework/h/qs_test.go @@ -2,6 +2,8 @@ package h import ( "github.com/stretchr/testify/assert" + "net/http" + "net/url" "testing" ) @@ -47,3 +49,28 @@ func TestSetQsOnUrlWithDelete(t *testing.T) { set := SetQueryParams("https://example.com/path?a=b&c=d", qs) assert.Equal(t, "https://example.com/path?a=b2", set) } + +func TestGetQueryParam(t *testing.T) { + t.Parallel() + req, _ := http.NewRequest("GET", "http://localhost/?foo=bar&baz=qux", nil) + ctx := &RequestContext{Request: req} + + result := GetQueryParam(ctx, "foo") + assert.Equal(t, "bar", result) + + result = GetQueryParam(ctx, "baz") + assert.Equal(t, "qux", result) + + result = GetQueryParam(ctx, "missing") + assert.Equal(t, "", result) + + ctx.currentBrowserUrl = "http://localhost/?current=value" + + result = GetQueryParam(ctx, "current") + assert.Equal(t, "value", result) + + // url params should override browser url + req.URL, _ = url.Parse("http://localhost/?foo=override") + result = GetQueryParam(ctx, "foo") + assert.Equal(t, "override", result) +} diff --git a/framework/h/serialize.go b/framework/h/serialize.go index 0bc2c11b..b880d410 100644 --- a/framework/h/serialize.go +++ b/framework/h/serialize.go @@ -6,6 +6,9 @@ import ( // JsonSerializeOrEmpty serializes the given data as JSON, or returns an empty string if the serialization fails. func JsonSerializeOrEmpty(data any) string { + if data == nil { + return "" + } serialized, err := json.Marshal(data) if err != nil { return "" diff --git a/framework/h/serialize_test.go b/framework/h/serialize_test.go new file mode 100644 index 00000000..a18a62f7 --- /dev/null +++ b/framework/h/serialize_test.go @@ -0,0 +1,28 @@ +package h + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSerialize(t *testing.T) { + t.Parallel() + data := map[string]any{ + "hello": "world", + "foo": "bar", + } + serialized := JsonSerializeOrEmpty(data) + assert.Equal(t, `{"foo":"bar","hello":"world"}`, serialized) +} + +func TestSerializeNil(t *testing.T) { + t.Parallel() + serialized := JsonSerializeOrEmpty(nil) + assert.Equal(t, "", serialized) +} + +func TestSerializeInvalid(t *testing.T) { + t.Parallel() + serialized := JsonSerializeOrEmpty(func() {}) + assert.Equal(t, "", serialized) +} diff --git a/framework/h/swap_test.go b/framework/h/swap_test.go new file mode 100644 index 00000000..849a2c53 --- /dev/null +++ b/framework/h/swap_test.go @@ -0,0 +1,88 @@ +package h + +import ( + "github.com/maddalax/htmgo/framework/hx" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewSwap(t *testing.T) { + content := Div() + option := SwapOption{Selector: "#myDiv", SwapType: hx.SwapTypeInnerHtml, Modifier: "test-mod"} + swapArg := NewSwap(content, option) + + assert.Equal(t, content, swapArg.Content) + assert.Equal(t, option, swapArg.Option) +} + +func TestOobSwap(t *testing.T) { + ctx := &RequestContext{isHxRequest: true} + content := Div() + + result := OobSwap(ctx, content) + assert.NotNil(t, result) + assert.Equal(t, 1, len(result.children)) + assert.Equal(t, Attribute(hx.SwapOobAttr, hx.SwapTypeTrue), result.children[0]) + + // Test with a nil context + result = OobSwap(nil, content) + assert.Equal(t, Empty(), result) + + // Test with non-HX request + ctx.isHxRequest = false + result = OobSwap(ctx, content) + assert.Equal(t, Empty(), result) +} + +func TestOobSwapWithSelector(t *testing.T) { + ctx := &RequestContext{isHxRequest: true} + content := Div() + + result := OobSwapWithSelector(ctx, "#selector", content) + assert.NotNil(t, result) + assert.Equal(t, 1, len(result.children)) + assert.Equal(t, Attribute(hx.SwapOobAttr, "#selector"), result.children[0]) +} + +func TestSwapMany(t *testing.T) { + ctx := &RequestContext{isHxRequest: true} + element1 := Div() + element2 := Span() + + result := SwapMany(ctx, element1, element2) + assert.NotNil(t, result) + assert.Equal(t, 2, len(result.children)) + assert.Equal(t, element1, result.children[0]) + assert.Equal(t, element2, result.children[1]) + assert.Equal(t, Attribute(hx.SwapOobAttr, hx.SwapTypeTrue), element1.children[0]) + assert.Equal(t, Attribute(hx.SwapOobAttr, hx.SwapTypeTrue), element2.children[0]) + + // Test with non-HX request + ctx.isHxRequest = false + result = SwapMany(ctx, element1, element2) + assert.Equal(t, Empty(), result) +} + +func TestSwapManyX(t *testing.T) { + ctx := &RequestContext{isHxRequest: true} + content1 := Div() + content2 := Span() + option := SwapOption{SwapType: hx.SwapTypeBeforeEnd} + + arg1 := NewSwap(content1) + arg2 := NewSwap(content2, option) + + result := SwapManyX(ctx, arg1, arg2) + assert.NotNil(t, result) + assert.Equal(t, 2, len(result.children)) + assert.Equal(t, content1, result.children[0]) + assert.Equal(t, content2, result.children[1]) + assert.Equal(t, Attribute(hx.SwapOobAttr, hx.SwapTypeTrue), content1.children[0]) + assert.Equal(t, Attribute(hx.SwapOobAttr, hx.SwapTypeBeforeEnd), content2.children[0]) + + // Test with non-HX request + ctx.isHxRequest = false + result = SwapManyX(ctx, arg1, arg2) + assert.Equal(t, Empty(), result) +} diff --git a/framework/h/tag_test.go b/framework/h/tag_test.go new file mode 100644 index 00000000..765c4d5a --- /dev/null +++ b/framework/h/tag_test.go @@ -0,0 +1,168 @@ +package h + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestTagFunctions(t *testing.T) { + tests := []struct { + name string + element *Element + expected string + }{ + {"Div", Div(), "div"}, + {"Span", Span(), "span"}, + {"Button", Button(), "button"}, + {"P", P(), "p"}, + {"H1", H1(), "h1"}, + {"H2", H2(), "h2"}, + {"H3", H3(), "h3"}, + {"H4", H4(), "h4"}, + {"H5", H5(), "h5"}, + {"H6", H6(), "h6"}, + {"Img", Img(), "img"}, + {"Video", Video(), "video"}, + {"Form", Form(), "form"}, + {"A", A(), "a"}, + {"Nav", Nav(), "nav"}, + {"Section", Section(), "section"}, + {"Aside", Aside(), "aside"}, + {"Header", Header(), "header"}, + {"Footer", Footer(), "footer"}, + {"Main", Main(), "main"}, + {"Ul", Ul(), "ul"}, + {"Li", Li(), "li"}, + {"Br", Br(), "br"}, + {"Hr", Hr(), "hr"}, + {"Ol", Ol(), "ol"}, + {"Table", Table(), "table"}, + {"Tr", Tr(), "tr"}, + {"Td", Td(), "td"}, + {"Th", Th(), "th"}, + {"THead", THead(), "thead"}, + {"TBody", TBody(), "tbody"}, + {"TFoot", TFoot(), "tfoot"}, + {"Abbr", Abbr(), "abbr"}, + {"Strong", Strong(), "strong"}, + {"Code", Code(), "code"}, + {"Title", Title(), "title"}, + {"Dialog", Dialog(), "dialog"}, + {"FieldSet", FieldSet(), "fieldset"}, + {"Option", Option(), "option"}, + {"Select", Select(), "select"}, + {"Template", Template(), "template"}, + {"Label", Label(), "label"}, + {"Address", Address(), "address"}, + {"Pre", Pre(), "pre"}, + {"Article", Article(), "article"}, + {"Summary", Summary(), "summary"}, + {"Details", Details(), "details"}, + {"Svg", Svg(), "svg"}, + {"Path", Path(), "path"}, + {"Html", Html(), "html"}, + {"Head", Head(), "head"}, + {"Body", Body(), "body"}, + {"Meta", Meta("description", "test"), "meta"}, + {"Link", Link("style.css", "stylesheet"), "link"}, + {"LinkWithVersion", LinkWithVersion("style.css", "stylesheet", "1.0"), "link"}, + {"Script", Script("script.js"), "script"}, + {"ScriptWithVersion", ScriptWithVersion("script.js", "1.0"), "script"}, + {"Style", Style("body {background: #000;}"), "style"}, + {"Pre", Pre(), "pre"}, + {"Article", Article(), "article"}, + {"Checkbox", Checkbox(), "input"}, + {"TextArea", TextArea(), "textarea"}, + {"TextInput", TextInput(), "input"}, + {"NumberInput", NumberInput(), "input"}, + {"FileInput", FileInput(), "input"}, + {"Radio", Radio(), "input"}, + {"Fragment", Fragment(), ""}, + {"Template", Template(), "template"}, + {"IFrame", IFrame("example.com"), "iframe"}, + {"LabelFor", LabelFor("input-id", "Input Label"), "label"}, + {"I", I(), "i"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.element.tag) + }) + } +} + +func TestList(t *testing.T) { + t.Parallel() + items := []string{"hello", "world"} + list := Ul(List(items, func(item string, index int) *Element { + return Li(Text(item)) + })) + assert.Equal(t, ``, Render(list)) +} + +func TestIterMap(t *testing.T) { + t.Parallel() + items := map[string]string{ + "hello": "world", + } + list := Ul(IterMap(items, func(key string, value string) *Element { + return Li(Text(key), Text(value)) + })) + assert.Equal(t, ``, Render(list)) +} + +func TestUnsafeRaw(t *testing.T) { + t.Parallel() + element := UnsafeRaw("
Hello World
") + assert.Equal(t, "
Hello World
", Render(element)) +} + +func TestUnsafeRawScript(t *testing.T) { + t.Parallel() + element := UnsafeRawScript("alert('Hello World')") + assert.Equal(t, "", Render(element)) +} + +func TestUnsafeRawF(t *testing.T) { + t.Parallel() + element := UnsafeRawF("Hello %s", "World") + assert.Equal(t, "Hello World", Render(element)) +} + +func TestMultiLineQuotes(t *testing.T) { + t.Parallel() + element := MultiLineQuotes("Hello World") + assert.Equal(t, "`Hello World`", element) +} + +func TestValue(t *testing.T) { + t.Parallel() + assert.Equal(t, ` value="Hello World"`, Render(Value("Hello World"))) + assert.Equal(t, ` value="1"`, Render(Value(1))) + assert.Equal(t, ` value="true"`, Render(Value(true))) +} + +func TestAppendChildren(t *testing.T) { + t.Parallel() + element := Div() + element.AppendChildren(Div(), Div()) + assert.Equal(t, "
", Render(element)) +} + +func TestTagF(t *testing.T) { + t.Parallel() + element := TagF("div", "Hello %s", "World") + assert.Equal(t, "
Hello World
", Render(element)) + + element2 := TagF("div", "Hello World", Class("my-class")) + assert.Equal(t, "
Hello World
", Render(element2)) + + element3 := TagF("div", "Value", P(Text("Hello World"))) + assert.Equal(t, "
Value

Hello World

", Render(element3)) +} + +func TestTag(t *testing.T) { + t.Parallel() + element := Tag("div") + assert.Equal(t, "
", Render(element)) +}