about summary refs log tree commit diff stats
path: root/html
diff options
context:
space:
mode:
Diffstat (limited to 'html')
-rw-r--r--html/attributes.go161
-rw-r--r--html/attributes_test.go73
-rw-r--r--html/elements.go447
-rw-r--r--html/elements_test.go212
4 files changed, 893 insertions, 0 deletions
diff --git a/html/attributes.go b/html/attributes.go
new file mode 100644
index 0000000..3db8584
--- /dev/null
+++ b/html/attributes.go
@@ -0,0 +1,161 @@
+package html
+
+import (
+	g "github.com/maragudk/gomponents"
+)
+
+func Async() g.Node {
+	return g.Attr("async")
+}
+
+func AutoFocus() g.Node {
+	return g.Attr("autofocus")
+}
+
+func AutoPlay() g.Node {
+	return g.Attr("autoplay")
+}
+
+func Controls() g.Node {
+	return g.Attr("controls")
+}
+
+func Defer() g.Node {
+	return g.Attr("defer")
+}
+
+func Disabled() g.Node {
+	return g.Attr("disabled")
+}
+
+func Multiple() g.Node {
+	return g.Attr("multiple")
+}
+
+func ReadOnly() g.Node {
+	return g.Attr("readonly")
+}
+
+func Required() g.Node {
+	return g.Attr("required")
+}
+
+func Selected() g.Node {
+	return g.Attr("selected")
+}
+
+func Accept(v string) g.Node {
+	return g.Attr("accept", v)
+}
+
+func AutoComplete(v string) g.Node {
+	return g.Attr("autocomplete", v)
+}
+
+func Charset(v string) g.Node {
+	return g.Attr("charset", v)
+}
+
+func Class(v string) g.Node {
+	return g.Attr("class", v)
+}
+
+func Cols(v string) g.Node {
+	return g.Attr("cols", v)
+}
+
+func Content(v string) g.Node {
+	return g.Attr("content", v)
+}
+
+func FormAttr(v string) g.Node {
+	return g.Attr("form", v)
+}
+
+func Height(v string) g.Node {
+	return g.Attr("height", v)
+}
+
+func Href(v string) g.Node {
+	return g.Attr("href", v)
+}
+
+func ID(v string) g.Node {
+	return g.Attr("id", v)
+}
+
+func Lang(v string) g.Node {
+	return g.Attr("lang", v)
+}
+
+func Max(v string) g.Node {
+	return g.Attr("max", v)
+}
+
+func MaxLength(v string) g.Node {
+	return g.Attr("maxlength", v)
+}
+
+func Min(v string) g.Node {
+	return g.Attr("min", v)
+}
+
+func MinLength(v string) g.Node {
+	return g.Attr("minlength", v)
+}
+
+func Name(v string) g.Node {
+	return g.Attr("name", v)
+}
+
+func Pattern(v string) g.Node {
+	return g.Attr("pattern", v)
+}
+
+func Preload(v string) g.Node {
+	return g.Attr("preload", v)
+}
+
+func Placeholder(v string) g.Node {
+	return g.Attr("placeholder", v)
+}
+
+func Rel(v string) g.Node {
+	return g.Attr("rel", v)
+}
+
+func Rows(v string) g.Node {
+	return g.Attr("rows", v)
+}
+
+func Src(v string) g.Node {
+	return g.Attr("src", v)
+}
+
+func StyleAttr(v string) g.Node {
+	return g.Attr("style", v)
+}
+
+func TabIndex(v string) g.Node {
+	return g.Attr("tabindex", v)
+}
+
+func Target(v string) g.Node {
+	return g.Attr("target", v)
+}
+
+func TitleAttr(v string) g.Node {
+	return g.Attr("title", v)
+}
+
+func Type(v string) g.Node {
+	return g.Attr("type", v)
+}
+
+func Value(v string) g.Node {
+	return g.Attr("value", v)
+}
+
+func Width(v string) g.Node {
+	return g.Attr("width", v)
+}
diff --git a/html/attributes_test.go b/html/attributes_test.go
new file mode 100644
index 0000000..7bf45bb
--- /dev/null
+++ b/html/attributes_test.go
@@ -0,0 +1,73 @@
+package html_test
+
+import (
+	"fmt"
+	"testing"
+
+	g "github.com/maragudk/gomponents"
+	"github.com/maragudk/gomponents/assert"
+	. "github.com/maragudk/gomponents/html"
+)
+
+func TestBooleanAttributes(t *testing.T) {
+	cases := map[string]func() g.Node{
+		"async":     Async,
+		"autofocus": AutoFocus,
+		"autoplay":  AutoPlay,
+		"controls":  Controls,
+		"defer":     Defer,
+		"disabled":  Disabled,
+		"multiple":  Multiple,
+		"readonly":  ReadOnly,
+		"required":  Required,
+		"selected":  Selected,
+	}
+
+	for name, fn := range cases {
+		t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
+			n := g.El("div", fn())
+			assert.Equal(t, fmt.Sprintf(`<div %v></div>`, name), n)
+		})
+	}
+}
+
+func TestSimpleAttributes(t *testing.T) {
+	cases := map[string]func(string) g.Node{
+		"accept":       Accept,
+		"autocomplete": AutoComplete,
+		"charset":      Charset,
+		"class":        Class,
+		"cols":         Cols,
+		"content":      Content,
+		"form":         FormAttr,
+		"height":       Height,
+		"href":         Href,
+		"id":           ID,
+		"lang":         Lang,
+		"max":          Max,
+		"maxlength":    MaxLength,
+		"min":          Min,
+		"minlength":    MinLength,
+		"name":         Name,
+		"pattern":      Pattern,
+		"preload":      Preload,
+		"placeholder":  Placeholder,
+		"rel":          Rel,
+		"rows":         Rows,
+		"src":          Src,
+		"style":        StyleAttr,
+		"tabindex":     TabIndex,
+		"target":       Target,
+		"title":        TitleAttr,
+		"type":         Type,
+		"value":        Value,
+		"width":        Width,
+	}
+
+	for name, fn := range cases {
+		t.Run(fmt.Sprintf(`should output %v="hat"`, name), func(t *testing.T) {
+			n := g.El("div", fn("hat"))
+			assert.Equal(t, fmt.Sprintf(`<div %v="hat"></div>`, name), n)
+		})
+	}
+}
diff --git a/html/elements.go b/html/elements.go
new file mode 100644
index 0000000..8045561
--- /dev/null
+++ b/html/elements.go
@@ -0,0 +1,447 @@
+// Package html provides common HTML elements and attributes.
+// See https://developer.mozilla.org/en-US/docs/Web/HTML/Element for a list of elements.
+// See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes for a list of attributes.
+package html
+
+import (
+	"fmt"
+	"io"
+
+	g "github.com/maragudk/gomponents"
+)
+
+func A(href string, children ...g.Node) g.NodeFunc {
+	return g.El("a", g.Attr("href", href), g.Group(children))
+}
+
+// Document returns an special kind of Node that prefixes its child with the string "<!doctype html>".
+func Document(child g.Node) g.NodeFunc {
+	return func(w io.Writer) error {
+		if _, err := w.Write([]byte("<!doctype html>")); err != nil {
+			return err
+		}
+		return child.Render(w)
+	}
+}
+
+// FormEl returns an element with name "form", the given action and method attributes, and the given children.
+func FormEl(action, method string, children ...g.Node) g.NodeFunc {
+	return g.El("form", g.Attr("action", action), g.Attr("method", method), g.Group(children))
+}
+
+func Img(src, alt string, children ...g.Node) g.NodeFunc {
+	return g.El("img", g.Attr("src", src), g.Attr("alt", alt), g.Group(children))
+}
+
+// Input returns an element with name "input", the given type and name attributes, and the given children.
+// Note that "type" is a keyword in Go, so the parameter is called typ.
+func Input(typ, name string, children ...g.Node) g.NodeFunc {
+	return g.El("input", g.Attr("type", typ), g.Attr("name", name), g.Group(children))
+}
+
+// Label returns an element with name "label", the given for attribute, and the given children.
+// Note that "for" is a keyword in Go, so the parameter is called forr.
+func Label(forr string, children ...g.Node) g.NodeFunc {
+	return g.El("label", g.Attr("for", forr), g.Group(children))
+}
+
+// Option returns an element with name "option", the given text content and value attribute, and the given children.
+func Option(text, value string, children ...g.Node) g.NodeFunc {
+	return g.El("option", g.Attr("value", value), g.Text(text), g.Group(children))
+}
+
+// Progress returns an element with name "progress", the given value and max attributes, and the given children.
+func Progress(value, max float64, children ...g.Node) g.NodeFunc {
+	return g.El("progress",
+		g.Attr("value", fmt.Sprintf("%v", value)),
+		g.Attr("max", fmt.Sprintf("%v", max)),
+		g.Group(children))
+}
+
+// Select returns an element with name "select", the given name attribute, and the given children.
+func Select(name string, children ...g.Node) g.NodeFunc {
+	return g.El("select", g.Attr("name", name), g.Group(children))
+}
+
+// Textarea returns an element with name "textarea", the given name attribute, and the given children.
+func Textarea(name string, children ...g.Node) g.NodeFunc {
+	return g.El("textarea", g.Attr("name", name), g.Group(children))
+}
+
+func Address(children ...g.Node) g.NodeFunc {
+	return g.El("address", children...)
+}
+
+func Area(children ...g.Node) g.NodeFunc {
+	return g.El("area", children...)
+}
+
+func Article(children ...g.Node) g.NodeFunc {
+	return g.El("article", children...)
+}
+
+func Aside(children ...g.Node) g.NodeFunc {
+	return g.El("aside", children...)
+}
+
+func Audio(children ...g.Node) g.NodeFunc {
+	return g.El("audio", children...)
+}
+
+func Base(children ...g.Node) g.NodeFunc {
+	return g.El("base", children...)
+}
+
+func BlockQuote(children ...g.Node) g.NodeFunc {
+	return g.El("blockquote", children...)
+}
+
+func Body(children ...g.Node) g.NodeFunc {
+	return g.El("body", children...)
+}
+
+func Br(children ...g.Node) g.NodeFunc {
+	return g.El("br", children...)
+}
+
+func Button(children ...g.Node) g.NodeFunc {
+	return g.El("button", children...)
+}
+
+func Canvas(children ...g.Node) g.NodeFunc {
+	return g.El("canvas", children...)
+}
+
+func Cite(children ...g.Node) g.NodeFunc {
+	return g.El("cite", children...)
+}
+
+func Code(children ...g.Node) g.NodeFunc {
+	return g.El("code", children...)
+}
+
+func Col(children ...g.Node) g.NodeFunc {
+	return g.El("col", children...)
+}
+
+func ColGroup(children ...g.Node) g.NodeFunc {
+	return g.El("colgroup", children...)
+}
+
+func Data(children ...g.Node) g.NodeFunc {
+	return g.El("data", children...)
+}
+
+func DataList(children ...g.Node) g.NodeFunc {
+	return g.El("datalist", children...)
+}
+
+func Details(children ...g.Node) g.NodeFunc {
+	return g.El("details", children...)
+}
+
+func Dialog(children ...g.Node) g.NodeFunc {
+	return g.El("dialog", children...)
+}
+
+func Div(children ...g.Node) g.NodeFunc {
+	return g.El("div", children...)
+}
+
+func Dl(children ...g.Node) g.NodeFunc {
+	return g.El("dl", children...)
+}
+
+func Embed(children ...g.Node) g.NodeFunc {
+	return g.El("embed", children...)
+}
+
+func FieldSet(children ...g.Node) g.NodeFunc {
+	return g.El("fieldset", children...)
+}
+
+func Figure(children ...g.Node) g.NodeFunc {
+	return g.El("figure", children...)
+}
+
+func Footer(children ...g.Node) g.NodeFunc {
+	return g.El("footer", children...)
+}
+
+func Head(children ...g.Node) g.NodeFunc {
+	return g.El("head", children...)
+}
+
+func Header(children ...g.Node) g.NodeFunc {
+	return g.El("header", children...)
+}
+
+func HGroup(children ...g.Node) g.NodeFunc {
+	return g.El("hgroup", children...)
+}
+
+func Hr(children ...g.Node) g.NodeFunc {
+	return g.El("hr", children...)
+}
+
+func HTML(children ...g.Node) g.NodeFunc {
+	return g.El("html", children...)
+}
+
+func IFrame(children ...g.Node) g.NodeFunc {
+	return g.El("iframe", children...)
+}
+
+func Legend(children ...g.Node) g.NodeFunc {
+	return g.El("legend", children...)
+}
+
+func Li(children ...g.Node) g.NodeFunc {
+	return g.El("li", children...)
+}
+
+func Link(children ...g.Node) g.NodeFunc {
+	return g.El("link", children...)
+}
+
+func Main(children ...g.Node) g.NodeFunc {
+	return g.El("main", children...)
+}
+
+func Menu(children ...g.Node) g.NodeFunc {
+	return g.El("menu", children...)
+}
+
+func Meta(children ...g.Node) g.NodeFunc {
+	return g.El("meta", children...)
+}
+
+func Meter(children ...g.Node) g.NodeFunc {
+	return g.El("meter", children...)
+}
+
+func Nav(children ...g.Node) g.NodeFunc {
+	return g.El("nav", children...)
+}
+
+func NoScript(children ...g.Node) g.NodeFunc {
+	return g.El("noscript", children...)
+}
+
+func Object(children ...g.Node) g.NodeFunc {
+	return g.El("object", children...)
+}
+
+func Ol(children ...g.Node) g.NodeFunc {
+	return g.El("ol", children...)
+}
+
+func OptGroup(children ...g.Node) g.NodeFunc {
+	return g.El("optgroup", children...)
+}
+
+func P(children ...g.Node) g.NodeFunc {
+	return g.El("p", children...)
+}
+
+func Param(children ...g.Node) g.NodeFunc {
+	return g.El("param", children...)
+}
+
+func Picture(children ...g.Node) g.NodeFunc {
+	return g.El("picture", children...)
+}
+
+func Pre(children ...g.Node) g.NodeFunc {
+	return g.El("pre", children...)
+}
+
+func Script(children ...g.Node) g.NodeFunc {
+	return g.El("script", children...)
+}
+
+func Section(children ...g.Node) g.NodeFunc {
+	return g.El("section", children...)
+}
+
+func Source(children ...g.Node) g.NodeFunc {
+	return g.El("source", children...)
+}
+
+func Span(children ...g.Node) g.NodeFunc {
+	return g.El("span", children...)
+}
+
+func StyleEl(children ...g.Node) g.NodeFunc {
+	return g.El("style", children...)
+}
+
+func Summary(children ...g.Node) g.NodeFunc {
+	return g.El("summary", children...)
+}
+
+func SVG(children ...g.Node) g.NodeFunc {
+	return g.El("svg", children...)
+}
+
+func Table(children ...g.Node) g.NodeFunc {
+	return g.El("table", children...)
+}
+
+func TBody(children ...g.Node) g.NodeFunc {
+	return g.El("tbody", children...)
+}
+
+func Td(children ...g.Node) g.NodeFunc {
+	return g.El("td", children...)
+}
+
+func TFoot(children ...g.Node) g.NodeFunc {
+	return g.El("tfoot", children...)
+}
+
+func Th(children ...g.Node) g.NodeFunc {
+	return g.El("th", children...)
+}
+
+func THead(children ...g.Node) g.NodeFunc {
+	return g.El("thead", children...)
+}
+
+func Tr(children ...g.Node) g.NodeFunc {
+	return g.El("tr", children...)
+}
+
+func Ul(children ...g.Node) g.NodeFunc {
+	return g.El("ul", children...)
+}
+
+func Wbr(children ...g.Node) g.NodeFunc {
+	return g.El("wbr", children...)
+}
+
+func Abbr(text string, children ...g.Node) g.NodeFunc {
+	return g.El("abbr", g.Text(text), g.Group(children))
+}
+
+func B(text string, children ...g.Node) g.NodeFunc {
+	return g.El("b", g.Text(text), g.Group(children))
+}
+
+func Caption(text string, children ...g.Node) g.NodeFunc {
+	return g.El("caption", g.Text(text), g.Group(children))
+}
+
+func Dd(text string, children ...g.Node) g.NodeFunc {
+	return g.El("dd", g.Text(text), g.Group(children))
+}
+
+func Del(text string, children ...g.Node) g.NodeFunc {
+	return g.El("del", g.Text(text), g.Group(children))
+}
+
+func Dfn(text string, children ...g.Node) g.NodeFunc {
+	return g.El("dfn", g.Text(text), g.Group(children))
+}
+
+func Dt(text string, children ...g.Node) g.NodeFunc {
+	return g.El("dt", g.Text(text), g.Group(children))
+}
+
+func Em(text string, children ...g.Node) g.NodeFunc {
+	return g.El("em", g.Text(text), g.Group(children))
+}
+
+func FigCaption(text string, children ...g.Node) g.NodeFunc {
+	return g.El("figcaption", g.Text(text), g.Group(children))
+}
+
+// H1 returns an element with name "h1", the given text content, and the given children.
+func H1(text string, children ...g.Node) g.NodeFunc {
+	return g.El("h1", g.Text(text), g.Group(children))
+}
+
+// H2 returns an element with name "h2", the given text content, and the given children.
+func H2(text string, children ...g.Node) g.NodeFunc {
+	return g.El("h2", g.Text(text), g.Group(children))
+}
+
+// H3 returns an element with name "h3", the given text content, and the given children.
+func H3(text string, children ...g.Node) g.NodeFunc {
+	return g.El("h3", g.Text(text), g.Group(children))
+}
+
+// H4 returns an element with name "h4", the given text content, and the given children.
+func H4(text string, children ...g.Node) g.NodeFunc {
+	return g.El("h4", g.Text(text), g.Group(children))
+}
+
+// H5 returns an element with name "h5", the given text content, and the given children.
+func H5(text string, children ...g.Node) g.NodeFunc {
+	return g.El("h5", g.Text(text), g.Group(children))
+}
+
+// H6 returns an element with name "h6", the given text content, and the given children.
+func H6(text string, children ...g.Node) g.NodeFunc {
+	return g.El("h6", g.Text(text), g.Group(children))
+}
+
+func I(text string, children ...g.Node) g.NodeFunc {
+	return g.El("i", g.Text(text), g.Group(children))
+}
+
+func Ins(text string, children ...g.Node) g.NodeFunc {
+	return g.El("ins", g.Text(text), g.Group(children))
+}
+
+func Kbd(text string, children ...g.Node) g.NodeFunc {
+	return g.El("kbd", g.Text(text), g.Group(children))
+}
+
+func Mark(text string, children ...g.Node) g.NodeFunc {
+	return g.El("mark", g.Text(text), g.Group(children))
+}
+
+func Q(text string, children ...g.Node) g.NodeFunc {
+	return g.El("q", g.Text(text), g.Group(children))
+}
+
+func S(text string, children ...g.Node) g.NodeFunc {
+	return g.El("s", g.Text(text), g.Group(children))
+}
+
+func Samp(text string, children ...g.Node) g.NodeFunc {
+	return g.El("samp", g.Text(text), g.Group(children))
+}
+
+func Small(text string, children ...g.Node) g.NodeFunc {
+	return g.El("small", g.Text(text), g.Group(children))
+}
+
+func Strong(text string, children ...g.Node) g.NodeFunc {
+	return g.El("strong", g.Text(text), g.Group(children))
+}
+
+func Sub(text string, children ...g.Node) g.NodeFunc {
+	return g.El("sub", g.Text(text), g.Group(children))
+}
+
+func Sup(text string, children ...g.Node) g.NodeFunc {
+	return g.El("sup", g.Text(text), g.Group(children))
+}
+
+func Time(text string, children ...g.Node) g.NodeFunc {
+	return g.El("time", g.Text(text), g.Group(children))
+}
+
+func TitleEl(title string, children ...g.Node) g.NodeFunc {
+	return g.El("title", g.Text(title), g.Group(children))
+}
+
+func U(text string, children ...g.Node) g.NodeFunc {
+	return g.El("u", g.Text(text), g.Group(children))
+}
+
+func Var(text string, children ...g.Node) g.NodeFunc {
+	return g.El("var", g.Text(text), g.Group(children))
+}
diff --git a/html/elements_test.go b/html/elements_test.go
new file mode 100644
index 0000000..e550bfe
--- /dev/null
+++ b/html/elements_test.go
@@ -0,0 +1,212 @@
+package html_test
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+
+	g "github.com/maragudk/gomponents"
+	"github.com/maragudk/gomponents/assert"
+	. "github.com/maragudk/gomponents/html"
+)
+
+type erroringWriter struct{}
+
+func (w *erroringWriter) Write(p []byte) (n int, err error) {
+	return 0, errors.New("don't want to write")
+}
+
+func TestDocument(t *testing.T) {
+	t.Run("returns doctype and children", func(t *testing.T) {
+		assert.Equal(t, `<!doctype html><html></html>`, Document(g.El("html")))
+	})
+
+	t.Run("errors on write error in Render", func(t *testing.T) {
+		err := Document(g.El("html")).Render(&erroringWriter{})
+		assert.Error(t, err)
+	})
+}
+
+func TestFormEl(t *testing.T) {
+	t.Run("returns a form element with action and method attributes", func(t *testing.T) {
+		assert.Equal(t, `<form action="/" method="post"></form>`, FormEl("/", "post"))
+	})
+}
+
+func TestInput(t *testing.T) {
+	t.Run("returns an input element with attributes type and name", func(t *testing.T) {
+		assert.Equal(t, `<input type="text" name="hat">`, Input("text", "hat"))
+	})
+}
+
+func TestLabel(t *testing.T) {
+	t.Run("returns a label element with attribute for", func(t *testing.T) {
+		assert.Equal(t, `<label for="hat">Hat</label>`, Label("hat", g.Text("Hat")))
+	})
+}
+
+func TestOption(t *testing.T) {
+	t.Run("returns an option element with attribute label and content", func(t *testing.T) {
+		assert.Equal(t, `<option value="hat">Hat</option>`, Option("Hat", "hat"))
+	})
+}
+
+func TestProgress(t *testing.T) {
+	t.Run("returns a progress element with attributes value and max", func(t *testing.T) {
+		assert.Equal(t, `<progress value="5.5" max="10"></progress>`, Progress(5.5, 10))
+	})
+}
+
+func TestSelect(t *testing.T) {
+	t.Run("returns a select element with attribute name", func(t *testing.T) {
+		assert.Equal(t, `<select name="hat"><option value="partyhat">Partyhat</option></select>`,
+			Select("hat", Option("Partyhat", "partyhat")))
+	})
+}
+
+func TestTextarea(t *testing.T) {
+	t.Run("returns a textarea element with attribute name", func(t *testing.T) {
+		assert.Equal(t, `<textarea name="hat"></textarea>`, Textarea("hat"))
+	})
+}
+
+func TestA(t *testing.T) {
+	t.Run("returns an a element with a href attribute", func(t *testing.T) {
+		assert.Equal(t, `<a href="#">hat</a>`, A("#", g.Text("hat")))
+	})
+}
+
+func TestImg(t *testing.T) {
+	t.Run("returns an img element with href and alt attributes", func(t *testing.T) {
+		assert.Equal(t, `<img src="hat.png" alt="hat" id="image">`, Img("hat.png", "hat", g.Attr("id", "image")))
+	})
+}
+
+func TestSimpleElements(t *testing.T) {
+	cases := map[string]func(...g.Node) g.NodeFunc{
+		"address":    Address,
+		"article":    Article,
+		"aside":      Aside,
+		"audio":      Audio,
+		"blockquote": BlockQuote,
+		"body":       Body,
+		"button":     Button,
+		"canvas":     Canvas,
+		"cite":       Cite,
+		"code":       Code,
+		"colgroup":   ColGroup,
+		"data":       Data,
+		"datalist":   DataList,
+		"details":    Details,
+		"dialog":     Dialog,
+		"div":        Div,
+		"dl":         Dl,
+		"fieldset":   FieldSet,
+		"figure":     Figure,
+		"footer":     Footer,
+		"head":       Head,
+		"header":     Header,
+		"hgroup":     HGroup,
+		"html":       HTML,
+		"iframe":     IFrame,
+		"legend":     Legend,
+		"li":         Li,
+		"main":       Main,
+		"menu":       Menu,
+		"meter":      Meter,
+		"nav":        Nav,
+		"noscript":   NoScript,
+		"object":     Object,
+		"ol":         Ol,
+		"optgroup":   OptGroup,
+		"p":          P,
+		"picture":    Picture,
+		"pre":        Pre,
+		"script":     Script,
+		"section":    Section,
+		"span":       Span,
+		"style":      StyleEl,
+		"summary":    Summary,
+		"svg":        SVG,
+		"table":      Table,
+		"tbody":      TBody,
+		"td":         Td,
+		"tfoot":      TFoot,
+		"th":         Th,
+		"thead":      THead,
+		"tr":         Tr,
+		"ul":         Ul,
+	}
+
+	for name, fn := range cases {
+		t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
+			n := fn(g.Attr("id", "hat"))
+			assert.Equal(t, fmt.Sprintf(`<%v id="hat"></%v>`, name, name), n)
+		})
+	}
+}
+
+func TestSimpleVoidKindElements(t *testing.T) {
+	cases := map[string]func(...g.Node) g.NodeFunc{
+		"area":   Area,
+		"base":   Base,
+		"br":     Br,
+		"col":    Col,
+		"embed":  Embed,
+		"hr":     Hr,
+		"link":   Link,
+		"meta":   Meta,
+		"param":  Param,
+		"source": Source,
+		"wbr":    Wbr,
+	}
+
+	for name, fn := range cases {
+		t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
+			n := fn(g.Attr("id", "hat"))
+			assert.Equal(t, fmt.Sprintf(`<%v id="hat">`, name), n)
+		})
+	}
+}
+
+func TestTextElements(t *testing.T) {
+	cases := map[string]func(string, ...g.Node) g.NodeFunc{
+		"abbr":       Abbr,
+		"b":          B,
+		"caption":    Caption,
+		"dd":         Dd,
+		"del":        Del,
+		"dfn":        Dfn,
+		"dt":         Dt,
+		"em":         Em,
+		"figcaption": FigCaption,
+		"h1":         H1,
+		"h2":         H2,
+		"h3":         H3,
+		"h4":         H4,
+		"h5":         H5,
+		"h6":         H6,
+		"i":          I,
+		"ins":        Ins,
+		"kbd":        Kbd,
+		"mark":       Mark,
+		"q":          Q,
+		"s":          S,
+		"samp":       Samp,
+		"small":      Small,
+		"strong":     Strong,
+		"sub":        Sub,
+		"sup":        Sup,
+		"time":       Time,
+		"title":      TitleEl,
+		"u":          U,
+		"var":        Var,
+	}
+
+	for name, fn := range cases {
+		t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
+			n := fn("hat", g.Attr("id", "hat"))
+			assert.Equal(t, fmt.Sprintf(`<%v id="hat">hat</%v>`, name, name), n)
+		})
+	}
+}