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("
foo&bar
") != "<div>foo&bar</div>" { t.Fatal(html.Escape("
foo&bar
")) } }) t.Run("attribute", func(t *testing.T) { if html.EscapeAttribute("\"foo&bar\"") != ""foo&bar"" { 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.Render(&b, foo); err == nil { t.Fatal("failed to fail") } }) t.Run("string", func(t *testing.T) { s := fmt.Sprint(Div) if s != "
" { 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("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.Render(&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.Render(&b, Div(tagChildren...)); err != nil { t.Fatal(err) } if b.String() != "
foo
bar
baz
" { 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.Render(&b, Div(tags...)); err != nil { t.Fatal(err) } if b.String() != "
foo
bar
baz
" { 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.Render(&b, Div(div0, div1)); err != nil { t.Fatal(err) } if b.String() != "
foo
foobar
" { 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.RenderIndent(&b, html.Indent(), Div("foo")); err != nil { t.Fatal(err) } if b.String() != "
\n\tfoo\n
" { t.Fatal(b.String()) } }) t.Run("inline", func(t *testing.T) { var b bytes.Buffer if err := html.RenderIndent(&b, html.Indent(), Span("foo")); err != nil { t.Fatal(err) } if b.String() != "foo" { t.Fatal(b.String()) } }) t.Run("inline children", func(t *testing.T) { var b bytes.Buffer if err := html.RenderIndent(&b, html.Indent(), Div(P("foo"), P("bar"))); err != nil { t.Fatal(err) } if b.String() != "
\n\t

foo

\n\t

bar

\n
" { 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.RenderIndent(&b, html.Indent(), Div(inlineP("foo"), inlineP("bar"))); err != nil { t.Fatal(err) } if b.String() != "
\n\t

foo

bar

\n
" { t.Fatal(b.String()) } }) t.Run("void", func(t *testing.T) { var b bytes.Buffer if err := html.Render(&b, Br); err != nil { t.Fatal(err) } if b.String() != "
" { t.Fatal(b.String()) } }) t.Run("void with children", func(t *testing.T) { var b bytes.Buffer if err := html.Render(&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.Render(&b, div); err != nil { t.Fatal(err) } if b.String() != "
foo &\nbar
" { 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.RenderIndent(&b, html.Indent(), div); err != nil { t.Fatal(err) } if b.String() != "
\n\tfoo &\n\tbar\n
" { 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.Render(&b, script); err != nil { t.Fatal(err) } if b.String() != "" { 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.RenderIndent(&b, html.Indent(), script); err != nil { t.Fatal(err) } if b.String() != "" { 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.RenderIndent(&b, html.Indent(), script); err != nil { t.Fatal(err) } if b.String() != "" { 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.RenderIndent(&b, html.Indent(), div); err != nil { t.Fatal(err) } if b.String() != "
\n\t
\n\t\tfoo\n\t
\n\t
foo
\n
" { 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.RenderIndent(&b, html.Indentation{PWidth: 8, MinPWidth: 12}, p); err != nil { t.Fatal(err) } if b.String() != "

foo bar\nbaz

" { 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.Render(&b, double(42)); err != nil { t.Fatal(err) } if b.String() != "
84
" { 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.RenderIndent(&b, html.Indent(), teamHTML(myTeam)); err != nil { t.Fatal(err) } const expect = `

Foo

Rank: 3

` 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.Render(&b, double); err != nil { t.Fatal(err) } if b.String() != "
84foo
" { 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.Render(&b, double); err != nil { t.Fatal(err) } if b.String() != "
8484
" { 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.Render(&b, Div(double0, double1)); err != nil { t.Fatal(err) } if b.String() != "
84
84foo
" { 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.RenderIndent(&b, html.Indent(), comment); err != nil { t.Fatal(err) } if b.String() != "" { t.Fatal(b.String()) } }) t.Run("doctype", func(t *testing.T) { doctype := Doctype("html") var b bytes.Buffer if err := html.RenderIndent(&b, html.Indent(), doctype); err != nil { t.Fatal(err) } if b.String() != "" { 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.RenderIndent(&b, html.Indent(), doctype); err != nil { t.Fatal(err) } if b.String() != "" { 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.Render(&b, cdata("foo &\nbar")); err != nil { t.Fatal(err) } if b.String() != "" { t.Fatal(b.String()) } }) }) }