Allow callers to seed Locale directly when constructing a rendering context, while preserving the existing no-argument API. Co-Authored-By: Virgil <virgil@lethean.io>
339 lines
8.8 KiB
Go
339 lines
8.8 KiB
Go
package html
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
i18n "dappco.re/go/core/i18n"
|
|
)
|
|
|
|
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 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_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 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 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 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="false"`) {
|
|
t.Errorf("AriaHidden(false) = %q, want aria-hidden=\"false\"", gotVisible)
|
|
}
|
|
}
|
|
|
|
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 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 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)
|
|
}
|
|
}
|