1246 lines
35 KiB
Go
1246 lines
35 KiB
Go
package html
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
i18n "dappco.re/go/core/i18n"
|
|
"slices"
|
|
)
|
|
|
|
func TestRawNode_Render(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Raw("hello")
|
|
got := node.Render(ctx)
|
|
if got != "hello" {
|
|
t.Errorf("Raw(\"hello\").Render() = %q, want %q", got, "hello")
|
|
}
|
|
}
|
|
|
|
func TestNewContext_Defaults(t *testing.T) {
|
|
ctx := NewContext()
|
|
if ctx == nil {
|
|
t.Fatal("NewContext() returned nil")
|
|
}
|
|
if ctx.Locale != "" {
|
|
t.Errorf("NewContext() Locale = %q, want empty string", ctx.Locale)
|
|
}
|
|
if ctx.Data == nil {
|
|
t.Fatal("NewContext() should initialise Data map")
|
|
}
|
|
}
|
|
|
|
func TestNewContext_Locale(t *testing.T) {
|
|
ctx := NewContext("en-GB")
|
|
if ctx.Locale != "en-GB" {
|
|
t.Errorf("NewContext(locale) Locale = %q, want %q", ctx.Locale, "en-GB")
|
|
}
|
|
}
|
|
|
|
func TestNewContextWithService_Locale(t *testing.T) {
|
|
svc, _ := i18n.New()
|
|
ctx := NewContextWithService(svc, "fr-FR")
|
|
if ctx.Locale != "fr-FR" {
|
|
t.Errorf("NewContextWithService(locale) Locale = %q, want %q", ctx.Locale, "fr-FR")
|
|
}
|
|
if ctx.service != svc {
|
|
t.Error("NewContextWithService should retain the provided service")
|
|
}
|
|
}
|
|
|
|
func TestRender_NormalisesZeroValueContext(t *testing.T) {
|
|
ctx := &Context{}
|
|
got := Text("hello").Render(ctx)
|
|
|
|
if got != "hello" {
|
|
t.Fatalf("Text(\"hello\").Render(&Context{}) = %q, want %q", got, "hello")
|
|
}
|
|
if ctx.Data == nil {
|
|
t.Fatal("Render should initialise Data on a zero-value context")
|
|
}
|
|
}
|
|
|
|
func TestElNode_Render(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := El("div", Raw("content"))
|
|
got := node.Render(ctx)
|
|
want := "<div>content</div>"
|
|
if got != want {
|
|
t.Errorf("El(\"div\", Raw(\"content\")).Render() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestElNode_Nested(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := El("div", El("span", Raw("inner")))
|
|
got := node.Render(ctx)
|
|
want := "<div><span>inner</span></div>"
|
|
if got != want {
|
|
t.Errorf("nested El().Render() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestElNode_MultipleChildren(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := El("div", Raw("a"), Raw("b"))
|
|
got := node.Render(ctx)
|
|
want := "<div>ab</div>"
|
|
if got != want {
|
|
t.Errorf("El with multiple children = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestElNode_NilChild(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := El("div", nil, Raw("content"))
|
|
got := node.Render(ctx)
|
|
want := "<div>content</div>"
|
|
if got != want {
|
|
t.Errorf("El with nil child = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestElNode_VoidElement(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := El("br")
|
|
got := node.Render(ctx)
|
|
want := "<br>"
|
|
if got != want {
|
|
t.Errorf("El(\"br\").Render() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestTextNode_Render(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Text("hello")
|
|
got := node.Render(ctx)
|
|
if got != "hello" {
|
|
t.Errorf("Text(\"hello\").Render() = %q, want %q", got, "hello")
|
|
}
|
|
}
|
|
|
|
func TestTextNode_Escapes(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Text("<script>alert('xss')</script>")
|
|
got := node.Render(ctx)
|
|
if strings.Contains(got, "<script>") {
|
|
t.Errorf("Text node must HTML-escape output, got %q", got)
|
|
}
|
|
if !strings.Contains(got, "<script>") {
|
|
t.Errorf("Text node should contain escaped script tag, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestIfNode_True(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := If(func(*Context) bool { return true }, Raw("visible"))
|
|
got := node.Render(ctx)
|
|
if got != "visible" {
|
|
t.Errorf("If(true) = %q, want %q", got, "visible")
|
|
}
|
|
}
|
|
|
|
func TestIfNode_False(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := If(func(*Context) bool { return false }, Raw("hidden"))
|
|
got := node.Render(ctx)
|
|
if got != "" {
|
|
t.Errorf("If(false) = %q, want %q", got, "")
|
|
}
|
|
}
|
|
|
|
func TestUnlessNode(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Unless(func(*Context) bool { return false }, Raw("visible"))
|
|
got := node.Render(ctx)
|
|
if got != "visible" {
|
|
t.Errorf("Unless(false) = %q, want %q", got, "visible")
|
|
}
|
|
}
|
|
|
|
func TestEntitledNode_Granted(t *testing.T) {
|
|
ctx := NewContext()
|
|
ctx.Entitlements = func(feature string) bool { return feature == "premium" }
|
|
node := Entitled("premium", Raw("premium content"))
|
|
got := node.Render(ctx)
|
|
if got != "premium content" {
|
|
t.Errorf("Entitled(granted) = %q, want %q", got, "premium content")
|
|
}
|
|
}
|
|
|
|
func TestEntitledNode_Denied(t *testing.T) {
|
|
ctx := NewContext()
|
|
ctx.Entitlements = func(feature string) bool { return false }
|
|
node := Entitled("premium", Raw("premium content"))
|
|
got := node.Render(ctx)
|
|
if got != "" {
|
|
t.Errorf("Entitled(denied) = %q, want %q", got, "")
|
|
}
|
|
}
|
|
|
|
func TestEntitledNode_NoFunc(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Entitled("premium", Raw("premium content"))
|
|
got := node.Render(ctx)
|
|
if got != "" {
|
|
t.Errorf("Entitled(no func) = %q, want %q (deny by default)", got, "")
|
|
}
|
|
}
|
|
|
|
func TestEachNode(t *testing.T) {
|
|
ctx := NewContext()
|
|
items := []string{"a", "b", "c"}
|
|
node := Each(items, func(item string) Node {
|
|
return El("li", Raw(item))
|
|
})
|
|
got := node.Render(ctx)
|
|
want := "<li>a</li><li>b</li><li>c</li>"
|
|
if got != want {
|
|
t.Errorf("Each([a,b,c]) = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestEachNode_Empty(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Each([]string{}, func(item string) Node {
|
|
return El("li", Raw(item))
|
|
})
|
|
got := node.Render(ctx)
|
|
if got != "" {
|
|
t.Errorf("Each([]) = %q, want %q", got, "")
|
|
}
|
|
}
|
|
|
|
func TestEachNode_NilReturn(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Each([]string{"a", "b"}, func(item string) Node {
|
|
if item == "a" {
|
|
return nil
|
|
}
|
|
return El("span", Raw(item))
|
|
})
|
|
got := node.Render(ctx)
|
|
want := "<span>b</span>"
|
|
if got != want {
|
|
t.Errorf("Each with nil return = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestElNode_Attr(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Attr(El("div", Raw("content")), "class", "container")
|
|
got := node.Render(ctx)
|
|
want := `<div class="container">content</div>`
|
|
if got != want {
|
|
t.Errorf("Attr() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestElNode_AttrEscaping(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Attr(El("img"), "alt", `he said "hello"`)
|
|
got := node.Render(ctx)
|
|
if !strings.Contains(got, `alt="he said "hello""`) {
|
|
t.Errorf("Attr should escape attribute values, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestAriaLabelHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaLabel(El("button", Raw("menu")), "Open navigation")
|
|
got := node.Render(ctx)
|
|
want := `<button aria-label="Open navigation">menu</button>`
|
|
if got != want {
|
|
t.Errorf("AriaLabel() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaLabelHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaLabel(El("button", Raw("menu")), " ")
|
|
got := node.Render(ctx)
|
|
want := `<button>menu</button>`
|
|
if got != want {
|
|
t.Errorf("AriaLabel() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaDescribedByHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaDescribedBy(El("input"), "hint-1", "hint-2")
|
|
got := node.Render(ctx)
|
|
want := `<input aria-describedby="hint-1 hint-2">`
|
|
if got != want {
|
|
t.Errorf("AriaDescribedBy() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaDescribedByHelper_IgnoresEmptyIDs(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaDescribedBy(El("input"), "", "hint-1", "", "hint-2")
|
|
got := node.Render(ctx)
|
|
want := `<input aria-describedby="hint-1 hint-2">`
|
|
if got != want {
|
|
t.Errorf("AriaDescribedBy() with empty IDs = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaDescribedByHelper_IgnoresWhitespaceIDs(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaDescribedBy(El("input"), " ", "hint-1", "\t", "hint-2")
|
|
got := node.Render(ctx)
|
|
want := `<input aria-describedby="hint-1 hint-2">`
|
|
if got != want {
|
|
t.Errorf("AriaDescribedBy() with whitespace IDs = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaLabelledByHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaLabelledBy(El("input"), "label-1", "label-2")
|
|
got := node.Render(ctx)
|
|
want := `<input aria-labelledby="label-1 label-2">`
|
|
if got != want {
|
|
t.Errorf("AriaLabelledBy() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaLabelledByHelper_IgnoresEmptyIDs(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaLabelledBy(El("input"), "", "label-1", "", "label-2")
|
|
got := node.Render(ctx)
|
|
want := `<input aria-labelledby="label-1 label-2">`
|
|
if got != want {
|
|
t.Errorf("AriaLabelledBy() with empty IDs = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaControlsHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaControls(El("button", Raw("menu")), "menu-panel", "shortcut-hints")
|
|
got := node.Render(ctx)
|
|
want := `<button aria-controls="menu-panel shortcut-hints">menu</button>`
|
|
if got != want {
|
|
t.Errorf("AriaControls() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaControlsHelper_IgnoresEmptyIDs(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaControls(El("button", Raw("menu")), "", "menu-panel", "")
|
|
got := node.Render(ctx)
|
|
want := `<button aria-controls="menu-panel">menu</button>`
|
|
if got != want {
|
|
t.Errorf("AriaControls() with empty IDs = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaHasPopupHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaHasPopup(El("button", Raw("menu")), "menu")
|
|
got := node.Render(ctx)
|
|
want := `<button aria-haspopup="menu">menu</button>`
|
|
if got != want {
|
|
t.Errorf("AriaHasPopup() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaHasPopupHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaHasPopup(El("button", Raw("menu")), " \t ")
|
|
got := node.Render(ctx)
|
|
want := `<button>menu</button>`
|
|
if got != want {
|
|
t.Errorf("AriaHasPopup() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaOwnsHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaOwns(El("div", Raw("panel")), "menu-panel", "shortcut-hints")
|
|
got := node.Render(ctx)
|
|
want := `<div aria-owns="menu-panel shortcut-hints">panel</div>`
|
|
if got != want {
|
|
t.Errorf("AriaOwns() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaOwnsHelper_IgnoresEmptyIDs(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaOwns(El("div", Raw("panel")), "", "menu-panel", "")
|
|
got := node.Render(ctx)
|
|
want := `<div aria-owns="menu-panel">panel</div>`
|
|
if got != want {
|
|
t.Errorf("AriaOwns() with empty IDs = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaKeyShortcutsHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaKeyShortcuts(El("button", Raw("save")), "Ctrl+S", "Meta+S")
|
|
got := node.Render(ctx)
|
|
want := `<button aria-keyshortcuts="Ctrl+S Meta+S">save</button>`
|
|
if got != want {
|
|
t.Errorf("AriaKeyShortcuts() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaKeyShortcutsHelper_IgnoresEmptyShortcuts(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaKeyShortcuts(El("button", Raw("save")), "", "Ctrl+S", " ", "Meta+S")
|
|
got := node.Render(ctx)
|
|
want := `<button aria-keyshortcuts="Ctrl+S Meta+S">save</button>`
|
|
if got != want {
|
|
t.Errorf("AriaKeyShortcuts() with empty shortcuts = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaCurrentHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaCurrent(El("a", Raw("Home")), "page")
|
|
got := node.Render(ctx)
|
|
want := `<a aria-current="page">Home</a>`
|
|
if got != want {
|
|
t.Errorf("AriaCurrent() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaCurrentHelper_Empty(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaCurrent(El("a", Raw("Home")), "")
|
|
got := node.Render(ctx)
|
|
want := `<a>Home</a>`
|
|
if got != want {
|
|
t.Errorf("AriaCurrent(\"\") = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaCurrentHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaCurrent(El("a", Raw("Home")), " \t ")
|
|
got := node.Render(ctx)
|
|
want := `<a>Home</a>`
|
|
if got != want {
|
|
t.Errorf("AriaCurrent() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaBusyHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
busy := AriaBusy(El("section", Raw("loading")), true)
|
|
gotBusy := busy.Render(ctx)
|
|
if !strings.Contains(gotBusy, `aria-busy="true"`) {
|
|
t.Errorf("AriaBusy(true) = %q, want aria-busy=\"true\"", gotBusy)
|
|
}
|
|
|
|
idle := AriaBusy(El("section", Raw("loading")), false)
|
|
gotIdle := idle.Render(ctx)
|
|
if !strings.Contains(gotIdle, `aria-busy="false"`) {
|
|
t.Errorf("AriaBusy(false) = %q, want aria-busy=\"false\"", gotIdle)
|
|
}
|
|
}
|
|
|
|
func TestAriaLiveHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
polite := AriaLive(El("div", Raw("updates")), "polite")
|
|
gotPolite := polite.Render(ctx)
|
|
if !strings.Contains(gotPolite, `aria-live="polite"`) {
|
|
t.Errorf("AriaLive(\"polite\") = %q, want aria-live=\"polite\"", gotPolite)
|
|
}
|
|
|
|
silent := AriaLive(El("div", Raw("updates")), "")
|
|
gotSilent := silent.Render(ctx)
|
|
if strings.Contains(gotSilent, `aria-live=`) {
|
|
t.Errorf("AriaLive(\"\") = %q, want no aria-live attribute", gotSilent)
|
|
}
|
|
}
|
|
|
|
func TestAriaLiveHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaLive(El("div", Raw("updates")), "\n ")
|
|
got := node.Render(ctx)
|
|
want := `<div>updates</div>`
|
|
if got != want {
|
|
t.Errorf("AriaLive() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaAtomicHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaAtomic(El("div", Raw("updates")), true)
|
|
got := node.Render(ctx)
|
|
want := `<div aria-atomic="true">updates</div>`
|
|
if got != want {
|
|
t.Errorf("AriaAtomic(true) = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaAtomicHelper_False(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaAtomic(El("div", Raw("updates")), false)
|
|
got := node.Render(ctx)
|
|
want := `<div aria-atomic="false">updates</div>`
|
|
if got != want {
|
|
t.Errorf("AriaAtomic(false) = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaDescriptionHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
described := AriaDescription(El("button", Raw("menu")), "Opens the navigation menu")
|
|
gotDescribed := described.Render(ctx)
|
|
wantDescribed := `<button aria-description="Opens the navigation menu">menu</button>`
|
|
if gotDescribed != wantDescribed {
|
|
t.Errorf("AriaDescription() = %q, want %q", gotDescribed, wantDescribed)
|
|
}
|
|
|
|
silent := AriaDescription(El("button", Raw("menu")), "")
|
|
gotSilent := silent.Render(ctx)
|
|
if strings.Contains(gotSilent, `aria-description=`) {
|
|
t.Errorf("AriaDescription(\"\") = %q, want no aria-description attribute", gotSilent)
|
|
}
|
|
}
|
|
|
|
func TestAriaDescriptionHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaDescription(El("button", Raw("menu")), " \t ")
|
|
got := node.Render(ctx)
|
|
want := `<button>menu</button>`
|
|
if got != want {
|
|
t.Errorf("AriaDescription() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaDetailsHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaDetails(El("input"), "field-help", "usage-help")
|
|
got := node.Render(ctx)
|
|
want := `<input aria-details="field-help usage-help">`
|
|
if got != want {
|
|
t.Errorf("AriaDetails() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaDetailsHelper_IgnoresWhitespaceIDs(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaDetails(El("input"), "", "field-help", " ", "usage-help")
|
|
got := node.Render(ctx)
|
|
want := `<input aria-details="field-help usage-help">`
|
|
if got != want {
|
|
t.Errorf("AriaDetails() with whitespace IDs = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaErrorMessageHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaErrorMessage(El("input"), "field-error")
|
|
got := node.Render(ctx)
|
|
want := `<input aria-errormessage="field-error">`
|
|
if got != want {
|
|
t.Errorf("AriaErrorMessage() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaErrorMessageHelper_IgnoresWhitespaceIDs(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaErrorMessage(El("input"), " ", "field-error", "")
|
|
got := node.Render(ctx)
|
|
want := `<input aria-errormessage="field-error">`
|
|
if got != want {
|
|
t.Errorf("AriaErrorMessage() with whitespace IDs = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaRoleDescriptionHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaRoleDescription(El("section", Raw("content")), "carousel")
|
|
got := node.Render(ctx)
|
|
want := `<section aria-roledescription="carousel">content</section>`
|
|
if got != want {
|
|
t.Errorf("AriaRoleDescription() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaRoleDescriptionHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AriaRoleDescription(El("section", Raw("content")), " \n ")
|
|
got := node.Render(ctx)
|
|
want := `<section>content</section>`
|
|
if got != want {
|
|
t.Errorf("AriaRoleDescription() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRoleHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Role(El("button", Raw("menu")), "navigation")
|
|
got := node.Render(ctx)
|
|
want := `<button role="navigation">menu</button>`
|
|
if got != want {
|
|
t.Errorf("Role() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRoleHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Role(El("button", Raw("menu")), " \n ")
|
|
got := node.Render(ctx)
|
|
want := `<button>menu</button>`
|
|
if got != want {
|
|
t.Errorf("Role() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestLangHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Lang(El("html", Raw("content")), "en-GB")
|
|
got := node.Render(ctx)
|
|
want := `<html lang="en-GB">content</html>`
|
|
if got != want {
|
|
t.Errorf("Lang() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestLangHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Lang(El("html", Raw("content")), " ")
|
|
got := node.Render(ctx)
|
|
want := `<html>content</html>`
|
|
if got != want {
|
|
t.Errorf("Lang() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDirHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Dir(El("p", Raw("مرحبا")), "rtl")
|
|
got := node.Render(ctx)
|
|
want := `<p dir="rtl">مرحبا</p>`
|
|
if got != want {
|
|
t.Errorf("Dir() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAltHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Alt(El("img"), `A "quoted" caption`)
|
|
got := node.Render(ctx)
|
|
if !strings.Contains(got, `alt="A "quoted" caption"`) {
|
|
t.Errorf("Alt should escape attribute values, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestAltHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Alt(El("img"), " \t ")
|
|
got := node.Render(ctx)
|
|
want := `<img>`
|
|
if got != want {
|
|
t.Errorf("Alt() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestTitleHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Title(El("abbr", Raw("HTML")), "HyperText Markup Language")
|
|
got := node.Render(ctx)
|
|
want := `<abbr title="HyperText Markup Language">HTML</abbr>`
|
|
if got != want {
|
|
t.Errorf("Title() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestTitleHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Title(El("abbr", Raw("HTML")), " \t ")
|
|
got := node.Render(ctx)
|
|
want := `<abbr>HTML</abbr>`
|
|
if got != want {
|
|
t.Errorf("Title() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestPlaceholderHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Placeholder(El("input"), "Search by keyword")
|
|
got := node.Render(ctx)
|
|
want := `<input placeholder="Search by keyword">`
|
|
if got != want {
|
|
t.Errorf("Placeholder() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestPlaceholderHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Placeholder(El("input"), " \t ")
|
|
got := node.Render(ctx)
|
|
want := `<input>`
|
|
if got != want {
|
|
t.Errorf("Placeholder() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAltTextHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AltText(El("img"), `A "quoted" caption`)
|
|
got := node.Render(ctx)
|
|
if !strings.Contains(got, `alt="A "quoted" caption"`) {
|
|
t.Errorf("AltText should escape attribute values, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestClassHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Class(El("div", Raw("content")), "card", "card--primary")
|
|
got := node.Render(ctx)
|
|
want := `<div class="card card--primary">content</div>`
|
|
if got != want {
|
|
t.Errorf("Class() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestClassHelper_IgnoresEmptyClasses(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Class(El("div", Raw("content")), "", "card", "", "card--primary")
|
|
got := node.Render(ctx)
|
|
want := `<div class="card card--primary">content</div>`
|
|
if got != want {
|
|
t.Errorf("Class() with empty classes = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestClassHelper_IgnoresWhitespaceClasses(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Class(El("div", Raw("content")), " ", "card", "\t", "card--primary")
|
|
got := node.Render(ctx)
|
|
want := `<div class="card card--primary">content</div>`
|
|
if got != want {
|
|
t.Errorf("Class() with whitespace classes = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestIDHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := ID(El("section", Raw("content")), "main-content")
|
|
got := node.Render(ctx)
|
|
want := `<section id="main-content">content</section>`
|
|
if got != want {
|
|
t.Errorf("ID() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestIDHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := ID(El("section", Raw("content")), " \t ")
|
|
got := node.Render(ctx)
|
|
want := `<section>content</section>`
|
|
if got != want {
|
|
t.Errorf("ID() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestForHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := For(El("label", Raw("Email")), "email-input")
|
|
got := node.Render(ctx)
|
|
want := `<label for="email-input">Email</label>`
|
|
if got != want {
|
|
t.Errorf("For() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestForHelper_IgnoresWhitespace(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := For(El("label", Raw("Email")), " ")
|
|
got := node.Render(ctx)
|
|
want := `<label>Email</label>`
|
|
if got != want {
|
|
t.Errorf("For() with whitespace = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAriaHiddenHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
hidden := AriaHidden(El("span", Raw("decorative")), true)
|
|
gotHidden := hidden.Render(ctx)
|
|
if !strings.Contains(gotHidden, `aria-hidden="true"`) {
|
|
t.Errorf("AriaHidden(true) = %q, want aria-hidden=\"true\"", gotHidden)
|
|
}
|
|
|
|
visible := AriaHidden(El("span", Raw("decorative")), false)
|
|
gotVisible := visible.Render(ctx)
|
|
if strings.Contains(gotVisible, `aria-hidden=`) {
|
|
t.Errorf("AriaHidden(false) = %q, want no aria-hidden attribute", gotVisible)
|
|
}
|
|
}
|
|
|
|
func TestHiddenHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
hidden := Hidden(El("section", Raw("content")), true)
|
|
gotHidden := hidden.Render(ctx)
|
|
if !strings.Contains(gotHidden, `hidden="hidden"`) {
|
|
t.Errorf("Hidden(true) = %q, want hidden=\"hidden\"", gotHidden)
|
|
}
|
|
|
|
visible := Hidden(El("section", Raw("content")), false)
|
|
gotVisible := visible.Render(ctx)
|
|
if strings.Contains(gotVisible, `hidden=`) {
|
|
t.Errorf("Hidden(false) = %q, want no hidden attribute", gotVisible)
|
|
}
|
|
}
|
|
|
|
func TestDisabledHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
disabled := Disabled(El("button", Raw("menu")), true)
|
|
gotDisabled := disabled.Render(ctx)
|
|
if !strings.Contains(gotDisabled, `disabled="disabled"`) {
|
|
t.Errorf("Disabled(true) = %q, want disabled=\"disabled\"", gotDisabled)
|
|
}
|
|
|
|
enabled := Disabled(El("button", Raw("menu")), false)
|
|
gotEnabled := enabled.Render(ctx)
|
|
if strings.Contains(gotEnabled, `disabled=`) {
|
|
t.Errorf("Disabled(false) = %q, want no disabled attribute", gotEnabled)
|
|
}
|
|
}
|
|
|
|
func TestCheckedHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
checked := Checked(El("input"), true)
|
|
gotChecked := checked.Render(ctx)
|
|
if !strings.Contains(gotChecked, `checked="checked"`) {
|
|
t.Errorf("Checked(true) = %q, want checked=\"checked\"", gotChecked)
|
|
}
|
|
|
|
unchecked := Checked(El("input"), false)
|
|
gotUnchecked := unchecked.Render(ctx)
|
|
if strings.Contains(gotUnchecked, `checked=`) {
|
|
t.Errorf("Checked(false) = %q, want no checked attribute", gotUnchecked)
|
|
}
|
|
}
|
|
|
|
func TestRequiredHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
required := Required(El("input"), true)
|
|
gotRequired := required.Render(ctx)
|
|
if !strings.Contains(gotRequired, `required="required"`) {
|
|
t.Errorf("Required(true) = %q, want required=\"required\"", gotRequired)
|
|
}
|
|
|
|
optional := Required(El("input"), false)
|
|
gotOptional := optional.Render(ctx)
|
|
if strings.Contains(gotOptional, `required=`) {
|
|
t.Errorf("Required(false) = %q, want no required attribute", gotOptional)
|
|
}
|
|
}
|
|
|
|
func TestReadOnlyHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
readonly := ReadOnly(El("input"), true)
|
|
gotReadonly := readonly.Render(ctx)
|
|
if !strings.Contains(gotReadonly, `readonly="readonly"`) {
|
|
t.Errorf("ReadOnly(true) = %q, want readonly=\"readonly\"", gotReadonly)
|
|
}
|
|
|
|
editable := ReadOnly(El("input"), false)
|
|
gotEditable := editable.Render(ctx)
|
|
if strings.Contains(gotEditable, `readonly=`) {
|
|
t.Errorf("ReadOnly(false) = %q, want no readonly attribute", gotEditable)
|
|
}
|
|
}
|
|
|
|
func TestSelectedHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
selected := Selected(El("option", Raw("menu")), true)
|
|
gotSelected := selected.Render(ctx)
|
|
if !strings.Contains(gotSelected, `selected="selected"`) {
|
|
t.Errorf("Selected(true) = %q, want selected=\"selected\"", gotSelected)
|
|
}
|
|
|
|
unselected := Selected(El("option", Raw("menu")), false)
|
|
gotUnselected := unselected.Render(ctx)
|
|
if strings.Contains(gotUnselected, `selected=`) {
|
|
t.Errorf("Selected(false) = %q, want no selected attribute", gotUnselected)
|
|
}
|
|
}
|
|
|
|
func TestAriaExpandedHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
expanded := AriaExpanded(El("button", Raw("menu")), true)
|
|
gotExpanded := expanded.Render(ctx)
|
|
if !strings.Contains(gotExpanded, `aria-expanded="true"`) {
|
|
t.Errorf("AriaExpanded(true) = %q, want aria-expanded=\"true\"", gotExpanded)
|
|
}
|
|
|
|
collapsed := AriaExpanded(El("button", Raw("menu")), false)
|
|
gotCollapsed := collapsed.Render(ctx)
|
|
if !strings.Contains(gotCollapsed, `aria-expanded="false"`) {
|
|
t.Errorf("AriaExpanded(false) = %q, want aria-expanded=\"false\"", gotCollapsed)
|
|
}
|
|
}
|
|
|
|
func TestAriaDisabledHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
disabled := AriaDisabled(El("button", Raw("menu")), true)
|
|
gotDisabled := disabled.Render(ctx)
|
|
if !strings.Contains(gotDisabled, `aria-disabled="true"`) {
|
|
t.Errorf("AriaDisabled(true) = %q, want aria-disabled=\"true\"", gotDisabled)
|
|
}
|
|
|
|
enabled := AriaDisabled(El("button", Raw("menu")), false)
|
|
gotEnabled := enabled.Render(ctx)
|
|
if !strings.Contains(gotEnabled, `aria-disabled="false"`) {
|
|
t.Errorf("AriaDisabled(false) = %q, want aria-disabled=\"false\"", gotEnabled)
|
|
}
|
|
}
|
|
|
|
func TestAriaModalHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
modal := AriaModal(El("dialog", Raw("content")), true)
|
|
gotModal := modal.Render(ctx)
|
|
if !strings.Contains(gotModal, `aria-modal="true"`) {
|
|
t.Errorf("AriaModal(true) = %q, want aria-modal=\"true\"", gotModal)
|
|
}
|
|
|
|
nonModal := AriaModal(El("dialog", Raw("content")), false)
|
|
gotNonModal := nonModal.Render(ctx)
|
|
if !strings.Contains(gotNonModal, `aria-modal="false"`) {
|
|
t.Errorf("AriaModal(false) = %q, want aria-modal=\"false\"", gotNonModal)
|
|
}
|
|
}
|
|
|
|
func TestAriaCheckedHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
checked := AriaChecked(El("input"), true)
|
|
gotChecked := checked.Render(ctx)
|
|
if !strings.Contains(gotChecked, `aria-checked="true"`) {
|
|
t.Errorf("AriaChecked(true) = %q, want aria-checked=\"true\"", gotChecked)
|
|
}
|
|
|
|
unchecked := AriaChecked(El("input"), false)
|
|
gotUnchecked := unchecked.Render(ctx)
|
|
if !strings.Contains(gotUnchecked, `aria-checked="false"`) {
|
|
t.Errorf("AriaChecked(false) = %q, want aria-checked=\"false\"", gotUnchecked)
|
|
}
|
|
}
|
|
|
|
func TestAriaInvalidHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
invalid := AriaInvalid(El("input"), true)
|
|
gotInvalid := invalid.Render(ctx)
|
|
if !strings.Contains(gotInvalid, `aria-invalid="true"`) {
|
|
t.Errorf("AriaInvalid(true) = %q, want aria-invalid=\"true\"", gotInvalid)
|
|
}
|
|
|
|
valid := AriaInvalid(El("input"), false)
|
|
gotValid := valid.Render(ctx)
|
|
if !strings.Contains(gotValid, `aria-invalid="false"`) {
|
|
t.Errorf("AriaInvalid(false) = %q, want aria-invalid=\"false\"", gotValid)
|
|
}
|
|
}
|
|
|
|
func TestAriaRequiredHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
required := AriaRequired(El("input"), true)
|
|
gotRequired := required.Render(ctx)
|
|
if !strings.Contains(gotRequired, `aria-required="true"`) {
|
|
t.Errorf("AriaRequired(true) = %q, want aria-required=\"true\"", gotRequired)
|
|
}
|
|
|
|
optional := AriaRequired(El("input"), false)
|
|
gotOptional := optional.Render(ctx)
|
|
if !strings.Contains(gotOptional, `aria-required="false"`) {
|
|
t.Errorf("AriaRequired(false) = %q, want aria-required=\"false\"", gotOptional)
|
|
}
|
|
}
|
|
|
|
func TestAriaReadOnlyHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
readonly := AriaReadOnly(El("input"), true)
|
|
gotReadonly := readonly.Render(ctx)
|
|
if !strings.Contains(gotReadonly, `aria-readonly="true"`) {
|
|
t.Errorf("AriaReadOnly(true) = %q, want aria-readonly=\"true\"", gotReadonly)
|
|
}
|
|
|
|
editable := AriaReadOnly(El("input"), false)
|
|
gotEditable := editable.Render(ctx)
|
|
if !strings.Contains(gotEditable, `aria-readonly="false"`) {
|
|
t.Errorf("AriaReadOnly(false) = %q, want aria-readonly=\"false\"", gotEditable)
|
|
}
|
|
}
|
|
|
|
func TestAriaPressedHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
pressed := AriaPressed(El("button", Raw("menu")), true)
|
|
gotPressed := pressed.Render(ctx)
|
|
if !strings.Contains(gotPressed, `aria-pressed="true"`) {
|
|
t.Errorf("AriaPressed(true) = %q, want aria-pressed=\"true\"", gotPressed)
|
|
}
|
|
|
|
released := AriaPressed(El("button", Raw("menu")), false)
|
|
gotReleased := released.Render(ctx)
|
|
if !strings.Contains(gotReleased, `aria-pressed="false"`) {
|
|
t.Errorf("AriaPressed(false) = %q, want aria-pressed=\"false\"", gotReleased)
|
|
}
|
|
}
|
|
|
|
func TestAriaSelectedHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
|
|
selected := AriaSelected(El("option", Raw("menu")), true)
|
|
gotSelected := selected.Render(ctx)
|
|
if !strings.Contains(gotSelected, `aria-selected="true"`) {
|
|
t.Errorf("AriaSelected(true) = %q, want aria-selected=\"true\"", gotSelected)
|
|
}
|
|
|
|
unselected := AriaSelected(El("option", Raw("menu")), false)
|
|
gotUnselected := unselected.Render(ctx)
|
|
if !strings.Contains(gotUnselected, `aria-selected="false"`) {
|
|
t.Errorf("AriaSelected(false) = %q, want aria-selected=\"false\"", gotUnselected)
|
|
}
|
|
}
|
|
|
|
func TestTabIndexHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := TabIndex(El("button", Raw("action")), -1)
|
|
got := node.Render(ctx)
|
|
if !strings.Contains(got, `tabindex="-1"`) {
|
|
t.Errorf("TabIndex() = %q, want tabindex=\"-1\"", got)
|
|
}
|
|
}
|
|
|
|
func TestAutoFocusHelper(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := AutoFocus(El("input"))
|
|
got := node.Render(ctx)
|
|
if !strings.Contains(got, `autofocus="autofocus"`) {
|
|
t.Errorf("AutoFocus() = %q, want autofocus=\"autofocus\"", got)
|
|
}
|
|
}
|
|
|
|
func TestElNode_MultipleAttrs(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Attr(Attr(El("a", Raw("link")), "href", "/home"), "class", "nav")
|
|
got := node.Render(ctx)
|
|
if !strings.Contains(got, `class="nav"`) || !strings.Contains(got, `href="/home"`) {
|
|
t.Errorf("multiple Attr() calls should stack, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestAttr_NonElement(t *testing.T) {
|
|
node := Attr(Raw("text"), "class", "x")
|
|
got := node.Render(NewContext())
|
|
if got != "text" {
|
|
t.Errorf("Attr on non-element should return unchanged, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestUnlessNode_True(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Unless(func(*Context) bool { return true }, Raw("hidden"))
|
|
got := node.Render(ctx)
|
|
if got != "" {
|
|
t.Errorf("Unless(true) = %q, want %q", got, "")
|
|
}
|
|
}
|
|
|
|
func TestAttr_ThroughIfNode(t *testing.T) {
|
|
ctx := NewContext()
|
|
inner := El("div", Raw("content"))
|
|
node := If(func(*Context) bool { return true }, inner)
|
|
Attr(node, "class", "wrapped")
|
|
got := node.Render(ctx)
|
|
want := `<div class="wrapped">content</div>`
|
|
if got != want {
|
|
t.Errorf("Attr through If = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAttr_ThroughUnlessNode(t *testing.T) {
|
|
ctx := NewContext()
|
|
inner := El("div", Raw("content"))
|
|
node := Unless(func(*Context) bool { return false }, inner)
|
|
Attr(node, "id", "test")
|
|
got := node.Render(ctx)
|
|
want := `<div id="test">content</div>`
|
|
if got != want {
|
|
t.Errorf("Attr through Unless = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAttr_ThroughEntitledNode(t *testing.T) {
|
|
ctx := NewContext()
|
|
ctx.Entitlements = func(string) bool { return true }
|
|
inner := El("div", Raw("content"))
|
|
node := Entitled("feature", inner)
|
|
Attr(node, "data-feat", "on")
|
|
got := node.Render(ctx)
|
|
want := `<div data-feat="on">content</div>`
|
|
if got != want {
|
|
t.Errorf("Attr through Entitled = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAttr_ThroughSwitchNode(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Switch(func(*Context) string { return "dark" }, map[string]Node{
|
|
"dark": El("div", Raw("content")),
|
|
"light": El("div", Raw("other")),
|
|
})
|
|
|
|
Attr(node, "class", "theme")
|
|
|
|
got := node.Render(ctx)
|
|
want := `<div class="theme">content</div>`
|
|
if got != want {
|
|
t.Errorf("Attr through Switch = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAttr_ThroughSwitchNode_IsolatedFromSharedCases(t *testing.T) {
|
|
ctx := NewContext()
|
|
shared := El("div", Raw("content"))
|
|
cases := map[string]Node{
|
|
"dark": shared,
|
|
"light": shared,
|
|
}
|
|
|
|
first := Switch(func(*Context) string { return "dark" }, cases)
|
|
second := Switch(func(*Context) string { return "light" }, cases)
|
|
|
|
Attr(first, "class", "theme")
|
|
|
|
if got := first.Render(ctx); got != `<div class="theme">content</div>` {
|
|
t.Fatalf("first switch render = %q, want %q", got, `<div class="theme">content</div>`)
|
|
}
|
|
|
|
if got := second.Render(ctx); got != `<div>content</div>` {
|
|
t.Fatalf("shared switch cases should not leak attrs, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestAttr_ThroughEachNode(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Each([]string{"a", "b"}, func(item string) Node {
|
|
return El("span", Raw(item))
|
|
})
|
|
|
|
Attr(node, "class", "item")
|
|
|
|
got := node.Render(ctx)
|
|
want := `<span class="item">a</span><span class="item">b</span>`
|
|
if got != want {
|
|
t.Errorf("Attr through Each = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAttr_ThroughEachNode_IsolatedFromSharedItems(t *testing.T) {
|
|
ctx := NewContext()
|
|
shared := El("span", Raw("item"))
|
|
node := Each([]int{1, 2}, func(int) Node {
|
|
return shared
|
|
})
|
|
|
|
Attr(node, "class", "shared")
|
|
|
|
got := node.Render(ctx)
|
|
want := `<span class="shared">item</span><span class="shared">item</span>`
|
|
if got != want {
|
|
t.Errorf("Attr through Each should clone shared items, got %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAttr_ThroughEachSeqNode(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := EachSeq(slices.Values([]string{"a", "b"}), func(item string) Node {
|
|
return El("span", Raw(item))
|
|
})
|
|
|
|
Attr(node, "class", "item")
|
|
|
|
got := node.Render(ctx)
|
|
want := `<span class="item">a</span><span class="item">b</span>`
|
|
if got != want {
|
|
t.Errorf("Attr through EachSeq = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAttr_ThroughLayoutNode(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := NewLayout("C").C(Raw("content"))
|
|
|
|
Attr(node, "class", "page")
|
|
|
|
got := node.Render(ctx)
|
|
want := `<main class="page" role="main" data-block="C-0">content</main>`
|
|
if got != want {
|
|
t.Errorf("Attr through Layout = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestAttr_ThroughIfNode_WithLayout(t *testing.T) {
|
|
ctx := NewContext()
|
|
inner := NewLayout("C").C(Raw("content"))
|
|
node := If(func(*Context) bool { return true }, inner)
|
|
|
|
Attr(node, "data-variant", "primary")
|
|
|
|
got := node.Render(ctx)
|
|
want := `<main data-variant="primary" role="main" data-block="C-0">content</main>`
|
|
if got != want {
|
|
t.Errorf("Attr through If/Layout = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestTextNode_WithService(t *testing.T) {
|
|
svc, _ := i18n.New()
|
|
ctx := NewContextWithService(svc)
|
|
node := Text("hello")
|
|
got := node.Render(ctx)
|
|
if got != "hello" {
|
|
t.Errorf("Text with service context = %q, want %q", got, "hello")
|
|
}
|
|
}
|
|
|
|
func TestSwitchNode(t *testing.T) {
|
|
ctx := NewContext()
|
|
cases := map[string]Node{
|
|
"dark": Raw("dark theme"),
|
|
"light": Raw("light theme"),
|
|
}
|
|
node := Switch(func(*Context) string { return "dark" }, cases)
|
|
got := node.Render(ctx)
|
|
want := "dark theme"
|
|
if got != want {
|
|
t.Errorf("Switch(\"dark\") = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestSwitchNode_NilCase(t *testing.T) {
|
|
ctx := NewContext()
|
|
node := Switch(func(*Context) string { return "dark" }, map[string]Node{
|
|
"dark": nil,
|
|
})
|
|
got := node.Render(ctx)
|
|
if got != "" {
|
|
t.Errorf("Switch with nil case = %q, want empty string", got)
|
|
}
|
|
}
|