1
0
html/lib_test.go

658 lines
15 KiB
Go

package html_test
import (
"bytes"
"code.squareroundforest.org/arpio/html"
. "code.squareroundforest.org/arpio/html/tag"
"code.squareroundforest.org/arpio/notation"
"fmt"
"testing"
)
func TestLib(t *testing.T) {
t.Run("escape", func(t *testing.T) {
t.Run("html", func(t *testing.T) {
if html.Escape("<div>foo&bar</div>") != "&lt;div&gt;foo&amp;bar&lt;/div&gt;" {
t.Fatal(html.Escape("<div>foo&bar</div>"))
}
})
t.Run("attribute", func(t *testing.T) {
if html.EscapeAttribute("\"foo&bar\"") != "&quot;foo&amp;bar&quot;" {
t.Fatal(html.EscapeAttribute("\"foo&bar\""))
}
})
})
t.Run("tags", func(t *testing.T) {
t.Run("tag name", func(t *testing.T) {
if html.Name(Div) != "div" {
t.Fatal(html.Name(Div))
}
})
t.Run("custom tag", func(t *testing.T) {
foo := html.Define("foo")
if html.Name(foo) != "foo" {
t.Fatal(html.Name(foo))
}
})
t.Run("invalid tag name", func(t *testing.T) {
foo := html.Define("foo+bar")
var b bytes.Buffer
if err := html.WriteRaw(&b, foo); err == nil {
t.Fatal("failed to fail")
}
})
t.Run("string", func(t *testing.T) {
s := fmt.Sprint(Div)
if s != "<div></div>" {
t.Fatal(s)
}
})
})
t.Run("attributes", func(t *testing.T) {
t.Run("empty attributes", func(t *testing.T) {
div := Div(Attr())
if len(html.AllAttributes(div).Names()) != 0 {
t.Fatal(html.AllAttributes(div).Names())
}
})
t.Run("set default value", func(t *testing.T) {
input := Input(Attr("disabled"))
if html.Attribute(input, "disabled") != true {
t.Fatal(html.Attribute(input, "disabled"))
}
})
t.Run("set attribute standard way", func(t *testing.T) {
div := Div(Attr("foo", "bar"))
if html.Attribute(div, "foo") != "bar" {
t.Fatal(html.Attribute(div, "foo"))
}
})
t.Run("set attribute function", func(t *testing.T) {
div := html.SetAttribute(Div, "foo", "bar")
if html.Attribute(div, "foo") != "bar" {
t.Fatal(html.Attribute(div, "foo"))
}
})
t.Run("set multiple attributes", func(t *testing.T) {
div := Div(Attr("foo", "bar", "baz", "qux"))
if html.Attribute(div, "foo") != "bar" || html.Attribute(div, "baz") != "qux" {
t.Fatal(html.Attribute(div, "foo"), html.Attribute(div, "baz"))
}
})
t.Run("all attributes", func(t *testing.T) {
div := Div(Attr("foo", "bar", "baz", "qux"))
a := html.AllAttributes(div)
if len(a.Names()) != 2 {
t.Fatal(len(a.Names()))
}
if !a.Has("foo") || !a.Has("baz") {
t.Fatal(a.Has("foo"), a.Has("baz"))
}
if a.Names()[0] != "foo" || a.Names()[1] != "baz" {
t.Fatal(a.Names()[0], a.Names()[1])
}
if a.Value("foo") != "bar" || a.Value("baz") != "qux" {
t.Fatal(a.Value("foo"), a.Value("baz"))
}
})
t.Run("delete attribute", func(t *testing.T) {
div := Div(Attr("foo", "bar", "baz", "qux"))
div = html.DeleteAttribute(div, "foo")
a := html.AllAttributes(div)
if a.Has("foo") || !a.Has("baz") || a.Value("baz") != "qux" {
t.Fatal(a.Has("foo"), a.Has("baz"), a.Value("baz"))
}
})
t.Run("delete multiple attributes", func(t *testing.T) {
div := Div(Attr("foo", "bar", "baz", "qux", "quux", "corge"))
div = html.DeleteAttribute(div, "foo", "quux")
a := html.AllAttributes(div)
if a.Has("foo") || a.Has("quux") || !a.Has("baz") || a.Value("baz") != "qux" {
t.Fatal(a.Has("foo"), a.Has("quux"), a.Has("baz"), a.Value("baz"))
}
})
t.Run("setting attribute immutable", func(t *testing.T) {
div0 := Div(Attr("foo", "bar"))
div1 := div0(Attr("baz", "qux"))
if html.Attribute(div0, "foo") != "bar" || html.Attribute(div0, "baz") != nil {
t.Fatal(html.Attribute(div0, "foo"), html.Attribute(div0, "baz"))
}
if html.Attribute(div1, "foo") != "bar" || html.Attribute(div1, "baz") != "qux" {
t.Fatal(html.Attribute(div1, "foo"), html.Attribute(div1, "baz"))
}
})
t.Run("invalid attribute name", func(t *testing.T) {
var b bytes.Buffer
if err := html.WriteRaw(&b, Div(Attr("foo+bar", "baz"))); err == nil {
t.Fatal()
}
})
})
t.Run("classes", func(t *testing.T) {
t.Run("set class", func(t *testing.T) {
div := html.SetClass(Div, "foo bar")
if html.Class(div) != "foo bar" {
t.Fatal(html.Class(div))
}
})
t.Run("add class", func(t *testing.T) {
div := html.AddClass(Div, "foo")
div = html.AddClass(div, "bar")
if html.Class(div) != "foo bar" {
t.Fatal(html.Class(div))
}
})
t.Run("delete class", func(t *testing.T) {
div := html.SetClass(Div, "foo")
div = html.DeleteClass(div, "foo")
if html.Class(div) != "" {
t.Fatal(html.Class(div))
}
})
t.Run("delete class from multiple", func(t *testing.T) {
div := html.SetClass(Div, "foo bar")
div = html.DeleteClass(div, "foo")
if html.Class(div) != "bar" {
t.Fatal(html.Class(div))
}
})
t.Run("delete multiple of the same class from multiple classes", func(t *testing.T) {
div := html.SetClass(Div, "foo bar baz bar foo")
div = html.DeleteClass(div, "bar")
if html.Class(div) != "foo baz foo" {
t.Fatal(html.Class(div))
}
})
t.Run("setting class immutable", func(t *testing.T) {
div0 := html.SetClass(Div, "foo")
div1 := html.SetClass(div0, "bar")
if html.Class(div0) != "foo" || html.Class(div1) != "bar" {
t.Fatal(html.Class(div0), html.Class(div1))
}
})
})
t.Run("children", func(t *testing.T) {
t.Run("no children", func(t *testing.T) {
if len(html.Children(Div)) != 0 {
t.Fatal(len(html.Children(Div)))
}
})
t.Run("get children", func(t *testing.T) {
div := Div("foo", "bar", "baz")
c := html.Children(div)
if len(c) != 3 {
t.Fatal(len(c))
}
if c[0] != "foo" || c[1] != "bar" || c[2] != "baz" {
t.Fatal(c[0], c[1], c[2])
}
})
t.Run("map", func(t *testing.T) {
tags := html.Map([]string{"foo", "bar", "baz"}, Div)
tagChildren := make([]any, len(tags))
for i := range tags {
tagChildren[i] = tags[i]
}
var b bytes.Buffer
if err := html.WriteRaw(&b, Div(tagChildren...)); err != nil {
t.Fatal(err)
}
if b.String() != "<div><div>foo</div><div>bar</div><div>baz</div></div>" {
t.Fatal(b.String())
}
})
t.Run("map children", func(t *testing.T) {
tags := html.MapChildren([]string{"foo", "bar", "baz"}, Div)
var b bytes.Buffer
if err := html.WriteRaw(&b, Div(tags...)); err != nil {
t.Fatal(err)
}
if b.String() != "<div><div>foo</div><div>bar</div><div>baz</div></div>" {
t.Fatal(b.String())
}
})
t.Run("adding children immutable", func(t *testing.T) {
div0 := Div("foo")
div1 := div0("bar")
var b bytes.Buffer
if err := html.WriteRaw(&b, Div(div0, div1)); err != nil {
t.Fatal(err)
}
if b.String() != "<div><div>foo</div><div>foobar</div></div>" {
t.Fatal(b.String())
}
})
})
t.Run("render", func(t *testing.T) {
t.Run("block", func(t *testing.T) {
var b bytes.Buffer
if err := html.Write(&b, Div("foo")); err != nil {
t.Fatal(err)
}
if b.String() != "<div>\n\tfoo\n</div>" {
t.Fatal(b.String())
}
})
t.Run("inline", func(t *testing.T) {
var b bytes.Buffer
if err := html.Write(&b, Span("foo")); err != nil {
t.Fatal(err)
}
if b.String() != "<span>foo</span>" {
t.Fatal(b.String())
}
})
t.Run("inline children", func(t *testing.T) {
var b bytes.Buffer
if err := html.Write(&b, Div(P("foo"), P("bar"))); err != nil {
t.Fatal(err)
}
if b.String() != "<div>\n\t<p>foo</p>\n\t<p>bar</p>\n</div>" {
t.Fatal(b.String())
}
})
t.Run("inline overrides inline children", func(t *testing.T) {
inlineP := html.Inline(P)
var b bytes.Buffer
if err := html.Write(&b, Div(inlineP("foo"), inlineP("bar"))); err != nil {
t.Fatal(err)
}
if b.String() != "<div>\n\t<p>foo</p><p>bar</p>\n</div>" {
t.Fatal(b.String())
}
})
t.Run("void", func(t *testing.T) {
var b bytes.Buffer
if err := html.WriteRaw(&b, Br); err != nil {
t.Fatal(err)
}
if b.String() != "<br>" {
t.Fatal(b.String())
}
})
t.Run("void with children", func(t *testing.T) {
var b bytes.Buffer
if err := html.WriteRaw(&b, Br("foo")); err == nil {
t.Fatal()
}
})
t.Run("verbatim unindented", func(t *testing.T) {
div := html.Verbatim(Div("foo &\nbar"))
var b bytes.Buffer
if err := html.WriteRaw(&b, div); err != nil {
t.Fatal(err)
}
if b.String() != "<div>foo &\nbar</div>" {
t.Fatal(b.String())
}
})
t.Run("verbatim indented", func(t *testing.T) {
div := html.Verbatim(Div("foo &\nbar"))
var b bytes.Buffer
if err := html.Write(&b, div); err != nil {
t.Fatal(err)
}
if b.String() != "<div>\n\tfoo &\n\tbar\n</div>" {
t.Fatal(b.String())
}
})
t.Run("script unindented", func(t *testing.T) {
script := Script("let foo = 42\nlet bar = 84")
var b bytes.Buffer
if err := html.WriteRaw(&b, script); err != nil {
t.Fatal(err)
}
if b.String() != "<script>let foo = 42\nlet bar = 84</script>" {
t.Fatal(b.String())
}
})
t.Run("script indented", func(t *testing.T) {
script := Script("let foo = 42\nlet bar = 84")
var b bytes.Buffer
if err := html.Write(&b, script); err != nil {
t.Fatal(err)
}
if b.String() != "<script>\nlet foo = 42\nlet bar = 84\n</script>" {
t.Fatal(b.String())
}
})
t.Run("script overrides verbatim", func(t *testing.T) {
script := html.Verbatim(Script("let foo = 42\nlet bar = 84"))
var b bytes.Buffer
if err := html.Write(&b, script); err != nil {
t.Fatal(err)
}
if b.String() != "<script>\nlet foo = 42\nlet bar = 84\n</script>" {
t.Fatal(b.String())
}
})
t.Run("setting render guides immutable", func(t *testing.T) {
div0 := Div("foo")
div1 := html.Inline(div0)
div := Div(div0, div1)
var b bytes.Buffer
if err := html.Write(&b, div); err != nil {
t.Fatal(err)
}
if b.String() != "<div>\n\t<div>\n\t\tfoo\n\t</div>\n\t<div>foo</div>\n</div>" {
t.Fatal(b.String())
}
})
t.Run("verify min pwidth", func(t *testing.T) {
p := P("foo bar baz")
var b bytes.Buffer
if err := html.WriteIndent(&b, html.Indentation{PWidth: 8, MinPWidth: 12}, p); err != nil {
t.Fatal(err)
}
if b.String() != "<p>foo bar\nbaz</p>" {
t.Fatal(b.String())
}
})
t.Run("doc unindented", func(t *testing.T) {
doc := Html(Body(P("foo bar baz")))
var b bytes.Buffer
if err := html.WriteRaw(&b, Doctype("html"), doc); err != nil {
t.Fatal(err)
}
if b.String() != "<!doctype html>\n<html><body><p>foo bar baz</p></body></html>" {
t.Fatal(b.String())
}
})
t.Run("doc indented", func(t *testing.T) {
doc := Html(Body(P("foo bar baz")))
var b bytes.Buffer
if err := html.Write(&b, Doctype("html"), doc); err != nil {
t.Fatal(err)
}
if b.String() != "<!doctype html>\n<html>\n\t<body>\n\t\t<p>foo bar baz</p>\n\t</body>\n</html>" {
t.Fatal(b.String())
}
})
})
t.Run("templates", func(t *testing.T) {
t.Run("template function", func(t *testing.T) {
double := func(i int) html.Tag { return Div(2 * i) }
var b bytes.Buffer
if err := html.WriteRaw(&b, double(42)); err != nil {
t.Fatal(err)
}
if b.String() != "<div>84</div>" {
t.Fatal(b.String())
}
})
t.Run("templated tag", func(t *testing.T) {
type (
member struct {
name string
level int
}
team struct {
name string
rank int
members []member
}
)
memberHTML := html.FromTemplate(
func(m member) html.Tag {
return Li(
Div("Name: ", m.name),
Div("Level: ", m.level),
)
},
)
teamHTML := html.FromTemplate(
func(t team) html.Tag {
return Div(
H3(t.name),
P("Rank: ", t.rank),
Ul(html.MapChildren(t.members, memberHTML)...),
)
},
)
myTeam := team{
name: "Foo",
rank: 3,
members: []member{{
name: "Bar",
level: 4,
}, {
name: "Baz",
level: 1,
}, {
name: "Qux",
level: 4,
}},
}
var b bytes.Buffer
if err := html.Write(&b, teamHTML(myTeam)); err != nil {
t.Fatal(err)
}
const expect = `<div>
<h3>Foo</h3>
<p>Rank: 3</p>
<ul>
<li>
<div>
Name: Bar
</div>
<div>
Level: 4
</div>
</li>
<li>
<div>
Name: Baz
</div>
<div>
Level: 1
</div>
</li>
<li>
<div>
Name: Qux
</div>
<div>
Level: 4
</div>
</li>
</ul>
</div>`
if b.String() != expect {
notation.Println([]byte(expect)[48:96])
notation.Println(b.Bytes()[48:96])
t.Fatal(b.String())
}
})
t.Run("templated tag additional children", func(t *testing.T) {
double := html.FromTemplate(func(i int) html.Tag { return Div(2 * i) })
double = double(42)
double = double("foo")
var b bytes.Buffer
if err := html.WriteRaw(&b, double); err != nil {
t.Fatal(err)
}
if b.String() != "<div>84foo</div>" {
t.Fatal(b.String())
}
})
t.Run("templated tag binding with multiple data items", func(t *testing.T) {
double := html.FromTemplate(func(i int) html.Tag { return Div(2 * i) })
double = double(42, 84)
var b bytes.Buffer
if err := html.WriteRaw(&b, double); err != nil {
t.Fatal(err)
}
if b.String() != "<div>8484</div>" {
t.Fatal(b.String())
}
})
t.Run("templated tag immutable", func(t *testing.T) {
double := html.FromTemplate(func(i int) html.Tag { return Div(2 * i) })
double0 := double(42)
double1 := double0("foo")
var b bytes.Buffer
if err := html.WriteRaw(&b, Div(double0, double1)); err != nil {
t.Fatal(err)
}
if b.String() != "<div><div>84</div><div>84foo</div></div>" {
t.Fatal(b.String())
}
})
})
t.Run("declarations", func(t *testing.T) {
t.Run("comments", func(t *testing.T) {
comment := Comment("foo &\nbar")
var b bytes.Buffer
if err := html.Write(&b, comment); err != nil {
t.Fatal(err)
}
if b.String() != "<!-- foo &\nbar -->" {
t.Fatal(b.String())
}
})
t.Run("doctype", func(t *testing.T) {
doctype := Doctype("html")
var b bytes.Buffer
if err := html.Write(&b, doctype); err != nil {
t.Fatal(err)
}
if b.String() != "<!doctype html>" {
t.Fatal(b.String())
}
})
t.Run("doctype complex", func(t *testing.T) {
doctype := Doctype("html", "public", "-//W3C//DTD HTML 4.01//EN", "http://www.w3.org/TR/html4/strict.dtd")
var b bytes.Buffer
if err := html.Write(&b, doctype); err != nil {
t.Fatal(err)
}
if b.String() != "<!doctype html public \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" {
t.Fatal(b.String())
}
})
t.Run("custom declaration", func(t *testing.T) {
cdataTemplate := func(d any) html.Tag {
return html.Declaration("[CDATA[", d, "]]")
}
cdata := html.FromTemplate(cdataTemplate)
var b bytes.Buffer
if err := html.WriteRaw(&b, cdata("foo &\nbar")); err != nil {
t.Fatal(err)
}
if b.String() != "<![CDATA[ foo &\nbar ]]>" {
t.Fatal(b.String())
}
})
})
}