Repository: maragudk/gomponents
Pull request #34 (2020-10-27 10:40:33)

Add element helpers and refactor
This change adds a lot of element helpers, and refactors:
- helpers into simple, text, and other helpers
- most tests into table-driven tests, so they're easier to read

Thanks to @oderwat for pushing me to improve the tests. 😉

codecov[bot]: # [Codecov](https://codecov.io/gh/maragudk/gomponents/pull/34?src=pr&el=h1) Report
> Merging [#34](https://codecov.io/gh/maragudk/gomponents/pull/34?src=pr&el=desc) into [master](https://codecov.io/gh/maragudk/gomponents/commit/8f17dba6f1268ab8c8810d7940d1484315cdb825?el=desc) will **decrease** coverage by `32.96%`.
> The diff coverage is `10.60%`.

[![Impacted file tree graph](https://codecov.io/gh/maragudk/gomponents/pull/34/graphs/tree.svg?width=650&height=150&src=pr&token=JYN032TYAC)](https://codecov.io/gh/maragudk/gomponents/pull/34?src=pr&el=tree)

```diff
@@             Coverage Diff              @@
##            master      #34       +/-   ##
============================================
- Coverage   100.00%   67.03%   -32.97%     
============================================
  Files            9       14        +5     
  Lines          120      179       +59     
============================================
  Hits           120      120               
- Misses           0       59       +59     
```


| [Impacted Files](https://codecov.io/gh/maragudk/gomponents/pull/34?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [el/edits.go](https://codecov.io/gh/maragudk/gomponents/pull/34/diff?src=pr&el=tree#diff-ZWwvZWRpdHMuZ28=) | `0.00% <0.00%> (ø)` | |
| [el/embed.go](https://codecov.io/gh/maragudk/gomponents/pull/34/diff?src=pr&el=tree#diff-ZWwvZW1iZWQuZ28=) | `0.00% <0.00%> (ø)` | |
| [el/form.go](https://codecov.io/gh/maragudk/gomponents/pull/34/diff?src=pr&el=tree#diff-ZWwvZm9ybS5nbw==) | `68.75% <0.00%> (-31.25%)` | :arrow_down: |
| [el/interactive.go](https://codecov.io/gh/maragudk/gomponents/pull/34/diff?src=pr&el=tree#diff-ZWwvaW50ZXJhY3RpdmUuZ28=) | `0.00% <0.00%> (ø)` | |
| [el/media.go](https://codecov.io/gh/maragudk/gomponents/pull/34/diff?src=pr&el=tree#diff-ZWwvbWVkaWEuZ28=) | `16.66% <0.00%> (-83.34%)` | :arrow_down: |
| [el/script.go](https://codecov.io/gh/maragudk/gomponents/pull/34/diff?src=pr&el=tree#diff-ZWwvc2NyaXB0Lmdv) | `0.00% <0.00%> (ø)` | |
| [el/table.go](https://codecov.io/gh/maragudk/gomponents/pull/34/diff?src=pr&el=tree#diff-ZWwvdGFibGUuZ28=) | `0.00% <0.00%> (ø)` | |
| [el/inline.go](https://codecov.io/gh/maragudk/gomponents/pull/34/diff?src=pr&el=tree#diff-ZWwvaW5saW5lLmdv) | `29.16% <19.04%> (-70.84%)` | :arrow_down: |
| [el/content.go](https://codecov.io/gh/maragudk/gomponents/pull/34/diff?src=pr&el=tree#diff-ZWwvY29udGVudC5nbw==) | `46.15% <30.00%> (-53.85%)` | :arrow_down: |
| ... and [4 more](https://codecov.io/gh/maragudk/gomponents/pull/34/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/maragudk/gomponents/pull/34?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/maragudk/gomponents/pull/34?src=pr&el=footer). Last update [8f17dba...1ae25ed](https://codecov.io/gh/maragudk/gomponents/pull/34?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

oderwat: Just an idea as adding all individual elements as tests seem trivial and bloated for what they do: Write one huge test which has all of the elements in one "test page" using a lot of linefeeds to make them on separate lines. So one can simply add them all into that test. When the test fails it could show the line number of the first non-matching line, which would lead to the problem quite reliably.
markuswustenberg: > Just an idea as adding all individual elements as tests seem trivial and bloated for what they do: Write one huge test which has all of the elements in one "test page" using a lot of linefeeds to make them on separate lines. So one can simply add them all into that test. When the test fails it could show the line number of the first non-matching line, which would lead to the problem quite reliably. Probably too late :)

Yeah, you're probably right. Or something like table-driven tests. I'm usually not a fan of those, but here they might make sense.

That, or auto-generated tests. ;)

Patch:
diff --git a/el/content.go b/el/content.go
deleted file mode 100644
index a4bcd61..0000000
--- a/el/content.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package el
-
-import (
-	g "github.com/maragudk/gomponents"
-)
-
-func Div(children ...g.Node) g.NodeFunc {
-	return g.El("div", children...)
-}
-
-func Ol(children ...g.Node) g.NodeFunc {
-	return g.El("ol", children...)
-}
-
-func Ul(children ...g.Node) g.NodeFunc {
-	return g.El("ul", children...)
-}
-
-func Li(children ...g.Node) g.NodeFunc {
-	return g.El("li", children...)
-}
-
-func P(children ...g.Node) g.NodeFunc {
-	return g.El("p", children...)
-}
-
-func Br(children ...g.Node) g.NodeFunc {
-	return g.El("br", children...)
-}
-
-func Hr(children ...g.Node) g.NodeFunc {
-	return g.El("hr", children...)
-}
diff --git a/el/document.go b/el/document.go
deleted file mode 100644
index 370a124..0000000
--- a/el/document.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// Package el provides shortcuts and helpers to common HTML elements.
-// See https://developer.mozilla.org/en-US/docs/Web/HTML/Element for a list of elements.
-package el
-
-import (
-	"strings"
-
-	g "github.com/maragudk/gomponents"
-)
-
-// Document returns an special kind of Node that prefixes its children with the string "<!doctype html>".
-func Document(children ...g.Node) g.NodeFunc {
-	return func() string {
-		var b strings.Builder
-		b.WriteString("<!doctype html>")
-		for _, c := range children {
-			b.WriteString(c.Render())
-		}
-		return b.String()
-	}
-}
-
-// HTML returns an element with name "html" and the given children.
-func HTML(children ...g.Node) g.NodeFunc {
-	return g.El("html", children...)
-}
-
-// Head returns an element with name "head" and the given children.
-func Head(children ...g.Node) g.NodeFunc {
-	return g.El("head", children...)
-}
-
-// Body returns an element with name "body" and the given children.
-func Body(children ...g.Node) g.NodeFunc {
-	return g.El("body", children...)
-}
-
-// Title returns an element with name "title" and a single Text child.
-func Title(title string) g.NodeFunc {
-	return g.El("title", g.Text(title))
-}
-
-func Meta(children ...g.Node) g.NodeFunc {
-	return g.El("meta", children...)
-}
-
-func Link(children ...g.Node) g.NodeFunc {
-	return g.El("link", children...)
-}
-
-func Style(children ...g.Node) g.NodeFunc {
-	return g.El("style", children...)
-}
-
-func Base(children ...g.Node) g.NodeFunc {
-	return g.El("base", children...)
-}
diff --git a/el/form.go b/el/elements.go
similarity index 72%
rename from el/form.go
rename to el/elements.go
index 25f89c7..4ca001f 100644
--- a/el/form.go
+++ b/el/elements.go
@@ -1,14 +1,28 @@
+// Package el provides shortcuts and helpers to common HTML elements.
+// See https://developer.mozilla.org/en-US/docs/Web/HTML/Element for a list of elements.
 package el
 
 import (
 	"fmt"
+	"strings"
 
 	g "github.com/maragudk/gomponents"
 )
 
-// Button returns an element with name "button" and the given children.
-func Button(children ...g.Node) g.NodeFunc {
-	return g.El("button", children...)
+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 children with the string "<!doctype html>".
+func Document(children ...g.Node) g.NodeFunc {
+	return func() string {
+		var b strings.Builder
+		b.WriteString("<!doctype html>")
+		for _, c := range children {
+			b.WriteString(c.Render())
+		}
+		return b.String()
+	}
 }
 
 // Form returns an element with name "form", the given action and method attributes, and the given children.
@@ -16,6 +30,10 @@ func Form(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 {
diff --git a/el/inline.go b/el/inline.go
deleted file mode 100644
index e0530d3..0000000
--- a/el/inline.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package el
-
-import (
-	g "github.com/maragudk/gomponents"
-)
-
-func Span(children ...g.Node) g.NodeFunc {
-	return g.El("span", children...)
-}
-
-func A(href string, children ...g.Node) g.NodeFunc {
-	return g.El("a", g.Attr("href", href), g.Group(children))
-}
-
-func B(text string, children ...g.Node) g.NodeFunc {
-	return g.El("b", 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 I(text string, children ...g.Node) g.NodeFunc {
-	return g.El("i", 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))
-}
diff --git a/el/media.go b/el/media.go
deleted file mode 100644
index 89787da..0000000
--- a/el/media.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package el
-
-import (
-	g "github.com/maragudk/gomponents"
-)
-
-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))
-}
diff --git a/el/section.go b/el/section.go
deleted file mode 100644
index f49a39c..0000000
--- a/el/section.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package el
-
-import (
-	g "github.com/maragudk/gomponents"
-)
-
-// Address returns an element with name "address" and the given children.
-func Address(children ...g.Node) g.NodeFunc {
-	return g.El("address", children...)
-}
-
-// Article returns an element with name "article" and the given children.
-func Article(children ...g.Node) g.NodeFunc {
-	return g.El("article", children...)
-}
-
-// Aside returns an element with name "aside" and the given children.
-func Aside(children ...g.Node) g.NodeFunc {
-	return g.El("aside", children...)
-}
-
-// Footer returns an element with name "footer" and the given children.
-func Footer(children ...g.Node) g.NodeFunc {
-	return g.El("footer", children...)
-}
-
-// Header returns an element with name "header" and the given children.
-func Header(children ...g.Node) g.NodeFunc {
-	return g.El("header", 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))
-}
-
-// HGroup returns an element with name "hgroup" and the given children.
-func HGroup(children ...g.Node) g.NodeFunc {
-	return g.El("hgroup", children...)
-}
-
-// Main returns an element with name "main" and the given children.
-func Main(children ...g.Node) g.NodeFunc {
-	return g.El("main", children...)
-}
-
-// Nav returns an element with name "nav" and the given children.
-func Nav(children ...g.Node) g.NodeFunc {
-	return g.El("nav", children...)
-}
-
-// Section returns an element with name "section" and the given children.
-func Section(children ...g.Node) g.NodeFunc {
-	return g.El("section", children...)
-}
diff --git a/el/simple.go b/el/simple.go
new file mode 100644
index 0000000..2fb4c3e
--- /dev/null
+++ b/el/simple.go
@@ -0,0 +1,253 @@
+package el
+
+import (
+	g "github.com/maragudk/gomponents"
+)
+
+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 Style(children ...g.Node) g.NodeFunc {
+	return g.El("style", children...)
+}
+
+func Summary(children ...g.Node) g.NodeFunc {
+	return g.El("summary", 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...)
+}
diff --git a/el/text.go b/el/text.go
new file mode 100644
index 0000000..6ab88db
--- /dev/null
+++ b/el/text.go
@@ -0,0 +1,131 @@
+package el
+
+import (
+	g "github.com/maragudk/gomponents"
+)
+
+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 Title(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))
+}


Test patch:
diff --git a/el/content_test.go b/el/content_test.go
deleted file mode 100644
index 9cb1d52..0000000
--- a/el/content_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package el_test
-
-import (
-	"testing"
-
-	g "github.com/maragudk/gomponents"
-	"github.com/maragudk/gomponents/assert"
-	"github.com/maragudk/gomponents/el"
-)
-
-func TestDiv(t *testing.T) {
-	t.Run("returns a div element", func(t *testing.T) {
-		assert.Equal(t, `<div><span /></div>`, el.Div(el.Span()))
-	})
-}
-
-func TestOl(t *testing.T) {
-	t.Run("returns an ol element", func(t *testing.T) {
-		assert.Equal(t, `<ol><li /></ol>`, el.Ol(el.Li()))
-	})
-}
-
-func TestUl(t *testing.T) {
-	t.Run("returns a ul element", func(t *testing.T) {
-		assert.Equal(t, `<ul><li /></ul>`, el.Ul(el.Li()))
-	})
-}
-
-func TestLi(t *testing.T) {
-	t.Run("returns an li element", func(t *testing.T) {
-		assert.Equal(t, `<li>hat</li>`, el.Li(g.Text("hat")))
-	})
-}
-
-func TestP(t *testing.T) {
-	t.Run("returns a p element", func(t *testing.T) {
-		assert.Equal(t, `<p>hat</p>`, el.P(g.Text("hat")))
-	})
-}
-
-func TestBr(t *testing.T) {
-	t.Run("returns a br element in context", func(t *testing.T) {
-		assert.Equal(t, `<p>Test<br />Me</p>`, el.P(g.Text("Test"), el.Br(), g.Text("Me")))
-	})
-}
-
-func TestHr(t *testing.T) {
-	t.Run("returns a hr element with class", func(t *testing.T) {
-		assert.Equal(t, `<hr class="test" />`, el.Hr(g.Attr("class", "test")))
-	})
-}
diff --git a/el/document_test.go b/el/document_test.go
deleted file mode 100644
index c525f30..0000000
--- a/el/document_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package el_test
-
-import (
-	"testing"
-
-	g "github.com/maragudk/gomponents"
-	"github.com/maragudk/gomponents/assert"
-	"github.com/maragudk/gomponents/el"
-)
-
-func TestDocument(t *testing.T) {
-	t.Run("returns doctype and children", func(t *testing.T) {
-		assert.Equal(t, `<!doctype html><html />`, el.Document(g.El("html")))
-	})
-}
-
-func TestHTML(t *testing.T) {
-	t.Run("returns an html element", func(t *testing.T) {
-		assert.Equal(t, "<html><div /><span /></html>", el.HTML(g.El("div"), g.El("span")))
-	})
-}
-
-func TestHead(t *testing.T) {
-	t.Run("returns a head element", func(t *testing.T) {
-		assert.Equal(t, "<head><title /><link /></head>", el.Head(g.El("title"), g.El("link")))
-	})
-}
-
-func TestBody(t *testing.T) {
-	t.Run("returns a body element", func(t *testing.T) {
-		assert.Equal(t, "<body><div /><span /></body>", el.Body(g.El("div"), g.El("span")))
-	})
-}
-
-func TestTitle(t *testing.T) {
-	t.Run("returns a title element with text content", func(t *testing.T) {
-		assert.Equal(t, "<title>hat</title>", el.Title("hat"))
-	})
-}
-
-func TestMeta(t *testing.T) {
-	t.Run("returns a meta element", func(t *testing.T) {
-		assert.Equal(t, `<meta charset="utf-8" />`, el.Meta(g.Attr("charset", "utf-8")))
-	})
-}
-
-func TestLink(t *testing.T) {
-	t.Run("returns a link element", func(t *testing.T) {
-		assert.Equal(t, `<link rel="stylesheet" />`, el.Link(g.Attr("rel", "stylesheet")))
-	})
-}
-
-func TestStyle(t *testing.T) {
-	t.Run("returns a style element", func(t *testing.T) {
-		assert.Equal(t, `<style type="text/css" />`, el.Style(g.Attr("type", "text/css")))
-	})
-}
-
-func TestBase(t *testing.T) {
-	t.Run("returns a base element", func(t *testing.T) {
-		assert.Equal(t, `<base href="/hat/" />`, el.Base(g.Attr("href", "/hat/")))
-	})
-}
diff --git a/el/form_test.go b/el/elements_test.go
similarity index 73%
rename from el/form_test.go
rename to el/elements_test.go
index 2f43036..6c73188 100644
--- a/el/form_test.go
+++ b/el/elements_test.go
@@ -8,9 +8,9 @@ import (
 	"github.com/maragudk/gomponents/el"
 )
 
-func TestButton(t *testing.T) {
-	t.Run("returns a button element", func(t *testing.T) {
-		assert.Equal(t, `<button />`, el.Button())
+func TestDocument(t *testing.T) {
+	t.Run("returns doctype and children", func(t *testing.T) {
+		assert.Equal(t, `<!doctype html><html />`, el.Document(g.El("html")))
 	})
 }
 
@@ -56,3 +56,15 @@ func TestTextarea(t *testing.T) {
 		assert.Equal(t, `<textarea name="hat" />`, el.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>`, el.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" />`, el.Img("hat.png", "hat", g.Attr("id", "image")))
+	})
+}
diff --git a/el/inline_test.go b/el/inline_test.go
deleted file mode 100644
index 0873bc2..0000000
--- a/el/inline_test.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package el_test
-
-import (
-	"testing"
-
-	g "github.com/maragudk/gomponents"
-	"github.com/maragudk/gomponents/assert"
-	"github.com/maragudk/gomponents/el"
-)
-
-func TestSpan(t *testing.T) {
-	t.Run("returns a span element", func(t *testing.T) {
-		assert.Equal(t, `<span>hat</span>`, el.Span(g.Text("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>`, el.A("#", g.Text("hat")))
-	})
-}
-
-func TestB(t *testing.T) {
-	t.Run("returns a b element", func(t *testing.T) {
-		assert.Equal(t, `<b id="text">hat</b>`, el.B("hat", g.Attr("id", "text")))
-	})
-}
-
-func TestStrong(t *testing.T) {
-	t.Run("returns a strong element", func(t *testing.T) {
-		assert.Equal(t, `<strong id="text">hat</strong>`, el.Strong("hat", g.Attr("id", "text")))
-	})
-}
-
-func TestI(t *testing.T) {
-	t.Run("returns an i element", func(t *testing.T) {
-		assert.Equal(t, `<i id="text">hat</i>`, el.I("hat", g.Attr("id", "text")))
-	})
-}
-
-func TestEm(t *testing.T) {
-	t.Run("returns an em element", func(t *testing.T) {
-		assert.Equal(t, `<em id="text">hat</em>`, el.Em("hat", g.Attr("id", "text")))
-	})
-}
diff --git a/el/media_test.go b/el/media_test.go
deleted file mode 100644
index 0c078dd..0000000
--- a/el/media_test.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package el_test
-
-import (
-	"testing"
-
-	g "github.com/maragudk/gomponents"
-	"github.com/maragudk/gomponents/assert"
-	"github.com/maragudk/gomponents/el"
-)
-
-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" />`, el.Img("hat.png", "hat", g.Attr("id", "image")))
-	})
-}
diff --git a/el/section_test.go b/el/section_test.go
deleted file mode 100644
index 11c60fb..0000000
--- a/el/section_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package el_test
-
-import (
-	"testing"
-
-	g "github.com/maragudk/gomponents"
-	"github.com/maragudk/gomponents/assert"
-	"github.com/maragudk/gomponents/el"
-)
-
-func TestAddress(t *testing.T) {
-	t.Run("returns an address element", func(t *testing.T) {
-		assert.Equal(t, `<address />`, el.Address())
-	})
-}
-
-func TestArticle(t *testing.T) {
-	t.Run("returns an article element", func(t *testing.T) {
-		assert.Equal(t, `<article />`, el.Article())
-	})
-}
-
-func TestAside(t *testing.T) {
-	t.Run("returns an aside element", func(t *testing.T) {
-		assert.Equal(t, `<aside />`, el.Aside())
-	})
-}
-
-func TestFooter(t *testing.T) {
-	t.Run("returns a footer element", func(t *testing.T) {
-		assert.Equal(t, `<footer />`, el.Footer())
-	})
-}
-
-func TestHeader(t *testing.T) {
-	t.Run("returns a header element", func(t *testing.T) {
-		assert.Equal(t, `<header />`, el.Header())
-	})
-}
-
-func TestH1(t *testing.T) {
-	t.Run("returns an h1 element", func(t *testing.T) {
-		assert.Equal(t, `<h1 id="headline">hat</h1>`, el.H1("hat", g.Attr("id", "headline")))
-	})
-}
-
-func TestH2(t *testing.T) {
-	t.Run("returns an h2 element", func(t *testing.T) {
-		assert.Equal(t, `<h2 id="headline">hat</h2>`, el.H2("hat", g.Attr("id", "headline")))
-	})
-}
-
-func TestH3(t *testing.T) {
-	t.Run("returns an h3 element", func(t *testing.T) {
-		assert.Equal(t, `<h3 id="headline">hat</h3>`, el.H3("hat", g.Attr("id", "headline")))
-	})
-}
-
-func TestH4(t *testing.T) {
-	t.Run("returns an h4 element", func(t *testing.T) {
-		assert.Equal(t, `<h4 id="headline">hat</h4>`, el.H4("hat", g.Attr("id", "headline")))
-	})
-}
-
-func TestH5(t *testing.T) {
-	t.Run("returns an h5 element", func(t *testing.T) {
-		assert.Equal(t, `<h5 id="headline">hat</h5>`, el.H5("hat", g.Attr("id", "headline")))
-	})
-}
-
-func TestH6(t *testing.T) {
-	t.Run("returns an h6 element", func(t *testing.T) {
-		assert.Equal(t, `<h6 id="headline">hat</h6>`, el.H6("hat", g.Attr("id", "headline")))
-	})
-}
-
-func TestHGroup(t *testing.T) {
-	t.Run("returns an hgroup element", func(t *testing.T) {
-		assert.Equal(t, `<hgroup />`, el.HGroup())
-	})
-}
-
-func TestMainEl(t *testing.T) {
-	t.Run("returns a main element", func(t *testing.T) {
-		assert.Equal(t, `<main />`, el.Main())
-	})
-}
-
-func TestNav(t *testing.T) {
-	t.Run("returns a nav element", func(t *testing.T) {
-		assert.Equal(t, `<nav />`, el.Nav())
-	})
-}
-
-func TestSection(t *testing.T) {
-	t.Run("returns a section element", func(t *testing.T) {
-		assert.Equal(t, `<section />`, el.Section())
-	})
-}
diff --git a/el/simple_test.go b/el/simple_test.go
new file mode 100644
index 0000000..d388e1e
--- /dev/null
+++ b/el/simple_test.go
@@ -0,0 +1,84 @@
+package el_test
+
+import (
+	"fmt"
+	"testing"
+
+	g "github.com/maragudk/gomponents"
+	"github.com/maragudk/gomponents/assert"
+	"github.com/maragudk/gomponents/el"
+)
+
+func TestSimpleElements(t *testing.T) {
+	cases := map[string]func(...g.Node) g.NodeFunc{
+		"address":    el.Address,
+		"area":       el.Area,
+		"article":    el.Article,
+		"aside":      el.Aside,
+		"audio":      el.Audio,
+		"base":       el.Base,
+		"blockquote": el.BlockQuote,
+		"body":       el.Body,
+		"br":         el.Br,
+		"button":     el.Button,
+		"canvas":     el.Canvas,
+		"cite":       el.Cite,
+		"code":       el.Code,
+		"col":        el.Col,
+		"colgroup":   el.ColGroup,
+		"data":       el.Data,
+		"datalist":   el.DataList,
+		"details":    el.Details,
+		"dialog":     el.Dialog,
+		"div":        el.Div,
+		"dl":         el.Dl,
+		"embed":      el.Embed,
+		"fieldset":   el.FieldSet,
+		"figure":     el.Figure,
+		"footer":     el.Footer,
+		"head":       el.Head,
+		"header":     el.Header,
+		"hgroup":     el.HGroup,
+		"hr":         el.Hr,
+		"html":       el.HTML,
+		"iframe":     el.IFrame,
+		"legend":     el.Legend,
+		"li":         el.Li,
+		"link":       el.Link,
+		"main":       el.Main,
+		"menu":       el.Menu,
+		"meta":       el.Meta,
+		"meter":      el.Meter,
+		"nav":        el.Nav,
+		"noscript":   el.NoScript,
+		"object":     el.Object,
+		"ol":         el.Ol,
+		"optgroup":   el.OptGroup,
+		"p":          el.P,
+		"param":      el.Param,
+		"picture":    el.Picture,
+		"pre":        el.Pre,
+		"script":     el.Script,
+		"section":    el.Section,
+		"source":     el.Source,
+		"span":       el.Span,
+		"style":      el.Style,
+		"summary":    el.Summary,
+		"table":      el.Table,
+		"tbody":      el.TBody,
+		"td":         el.Td,
+		"tfoot":      el.TFoot,
+		"th":         el.Th,
+		"thead":      el.THead,
+		"tr":         el.Tr,
+		"ul":         el.Ul,
+		"wbr":        el.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)
+		})
+	}
+}
diff --git a/el/text_test.go b/el/text_test.go
new file mode 100644
index 0000000..a751e19
--- /dev/null
+++ b/el/text_test.go
@@ -0,0 +1,52 @@
+package el_test
+
+import (
+	"fmt"
+	"testing"
+
+	g "github.com/maragudk/gomponents"
+	"github.com/maragudk/gomponents/assert"
+	"github.com/maragudk/gomponents/el"
+)
+
+func TestTextElements(t *testing.T) {
+	cases := map[string]func(string, ...g.Node) g.NodeFunc{
+		"abbr":       el.Abbr,
+		"b":          el.B,
+		"caption":    el.Caption,
+		"dd":         el.Dd,
+		"del":        el.Del,
+		"dfn":        el.Dfn,
+		"dt":         el.Dt,
+		"em":         el.Em,
+		"figcaption": el.FigCaption,
+		"h1":         el.H1,
+		"h2":         el.H2,
+		"h3":         el.H3,
+		"h4":         el.H4,
+		"h5":         el.H5,
+		"h6":         el.H6,
+		"i":          el.I,
+		"ins":        el.Ins,
+		"kbd":        el.Kbd,
+		"mark":       el.Mark,
+		"q":          el.Q,
+		"s":          el.S,
+		"samp":       el.Samp,
+		"small":      el.Small,
+		"strong":     el.Strong,
+		"sub":        el.Sub,
+		"sup":        el.Sup,
+		"time":       el.Time,
+		"title":      el.Title,
+		"u":          el.U,
+		"var":        el.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)
+		})
+	}
+}