diff options
author | Markus Wüstenberg | 2024-09-24 09:46:45 +0200 |
---|---|---|
committer | GitHub | 2024-09-24 09:46:45 +0200 |
commit | f58a0660823bc88c901eba4a130f1d4996729c73 (patch) | |
tree | fcf96b76fe265e332c7f442c8052014c77c0d720 | |
parent | 3a3de2932c6ec72c23c63608b21df93f61d88df5 (diff) | |
download | gomponents-f58a0660823bc88c901eba4a130f1d4996729c73.tar.lz gomponents-f58a0660823bc88c901eba4a130f1d4996729c73.tar.zst gomponents-f58a0660823bc88c901eba4a130f1d4996729c73.zip |
Make `Group` a type (#202)
This changes `Group` to be a type instead of a function, which means it can support both grouping `[]Node` as well as doing variadic-ish slice things like `Group{n1, n2}`. This also means that `Map` can just return a `Group`. Special thanks to @deitrix for this simple and elegant solution that, for some reason or another, has had me perplexed for a long time. Fixes #201.
-rw-r--r-- | gomponents.go | 28 | ||||
-rw-r--r-- | gomponents_test.go | 39 |
2 files changed, 37 insertions, 30 deletions
diff --git a/gomponents.go b/gomponents.go index 1a3b4ee..a17e1da 100644 --- a/gomponents.go +++ b/gomponents.go @@ -113,8 +113,8 @@ func renderChild(w *statefulWriter, c Node, t NodeType) { // Rendering groups like this is still important even though a group can render itself, // since otherwise attributes will sometimes be ignored. - if g, ok := c.(group); ok { - for _, groupC := range g.children { + if g, ok := c.(Group); ok { + for _, groupC := range g { renderChild(w, groupC, t) } return @@ -246,8 +246,8 @@ func Rawf(format string, a ...interface{}) Node { }) } -// Map a slice of anything to a slice of Nodes. -func Map[T any](ts []T, cb func(T) Node) []Node { +// Map a slice of anything to a [Group] (which is just a slice of [Node]s). +func Map[T any](ts []T, cb func(T) Node) Group { var nodes []Node for _, t := range ts { nodes = append(nodes, cb(t)) @@ -255,27 +255,21 @@ func Map[T any](ts []T, cb func(T) Node) []Node { return nodes } -type group struct { - children []Node -} +// Group a slice of [Node]s into one Node, while still being usable like a regular slice of [Node]s. +// A [Group] can render directly, but if any of the direct children are [AttributeType], they will be ignored, +// to not produce invalid HTML. +type Group []Node // String satisfies [fmt.Stringer]. -func (g group) String() string { +func (g Group) String() string { var b strings.Builder _ = g.Render(&b) return b.String() } // Render satisfies [Node]. -func (g group) Render(w io.Writer) error { - return render(w, nil, g.children...) -} - -// Group a slice of Nodes into one Node. Useful for grouping the result of [Map] into one [Node]. -// A [Group] can render directly, but if any of the direct children are [AttributeType], they will be ignored, -// to not produce invalid HTML. -func Group(children []Node) Node { - return group{children: children} +func (g Group) Render(w io.Writer) error { + return render(w, nil, g...) } // If condition is true, return the given [Node]. Otherwise, return nil. diff --git a/gomponents_test.go b/gomponents_test.go index 27fb534..c8dab1b 100644 --- a/gomponents_test.go +++ b/gomponents_test.go @@ -228,7 +228,7 @@ func ExampleRawf() { } func TestMap(t *testing.T) { - t.Run("maps slices to nodes", func(t *testing.T) { + t.Run("maps a slice to a group", func(t *testing.T) { items := []string{"hat", "partyhat", "turtlehat"} lis := g.Map(items, func(i string) g.Node { return g.El("li", g.Text(i)) @@ -237,14 +237,20 @@ func TestMap(t *testing.T) { list := g.El("ul", lis...) assert.Equal(t, `<ul><li>hat</li><li>partyhat</li><li>turtlehat</li></ul>`, list) + if len(lis) != 3 { + t.FailNow() + } + assert.Equal(t, `<li>hat</li>`, lis[0]) + assert.Equal(t, `<li>partyhat</li>`, lis[1]) + assert.Equal(t, `<li>turtlehat</li>`, lis[2]) }) } func ExampleMap() { items := []string{"party hat", "super hat"} - e := g.El("ul", g.Group(g.Map(items, func(i string) g.Node { + e := g.El("ul", g.Map(items, func(i string) g.Node { return g.El("li", g.Text(i)) - }))) + })) _ = e.Render(os.Stdout) // Output: <ul><li>party hat</li><li>super hat</li></ul> } @@ -262,25 +268,26 @@ func TestGroup(t *testing.T) { assert.Equal(t, "<div></div><span></span>", e) }) - t.Run("does not ignore attributes at the second level", func(t *testing.T) { - children := []g.Node{g.El("div", g.Attr("class", "hat")), g.El("span")} + t.Run("does not ignore attributes at the second level and below", func(t *testing.T) { + children := []g.Node{g.El("div", g.Attr("class", "hat"), g.El("hr", g.Attr("id", "partyhat"))), g.El("span")} e := g.Group(children) - assert.Equal(t, `<div class="hat"></div><span></span>`, e) - }) - - t.Run("can render a group child node including attributes", func(t *testing.T) { - children := []g.Node{g.Attr("id", "hat"), g.El("div"), g.El("span")} - e := g.El("div", g.Group(children)) - assert.Equal(t, `<div id="hat"><div></div><span></span></div>`, e) + assert.Equal(t, `<div class="hat"><hr id="partyhat"></div><span></span>`, e) }) t.Run("implements fmt.Stringer", func(t *testing.T) { children := []g.Node{g.El("div"), g.El("span")} e := g.Group(children) - if e, ok := e.(fmt.Stringer); !ok || e.String() != "<div></div><span></span>" { + if e, ok := any(e).(fmt.Stringer); !ok || e.String() != "<div></div><span></span>" { t.FailNow() } }) + + t.Run("can be used like a regular slice", func(t *testing.T) { + e := g.Group{g.El("div"), g.El("span")} + assert.Equal(t, "<div></div><span></span>", e) + assert.Equal(t, "<div></div>", e[0]) + assert.Equal(t, "<span></span>", e[1]) + }) } func ExampleGroup() { @@ -290,6 +297,12 @@ func ExampleGroup() { // Output: <div></div><span></span> } +func ExampleGroup_slice() { + e := g.Group{g.El("div"), g.El("span")} + _ = e.Render(os.Stdout) + // Output: <div></div><span></span> +} + func TestIf(t *testing.T) { t.Run("returns node if condition is true", func(t *testing.T) { n := g.El("div", g.If(true, g.El("span"))) |