commit 1848cd2f09d35f76e70a15b0dffbb84b615961a7
parent 8664ce4f9243f1278df70f02098658b8af015ba3
Author: Yongbin Kim <iam@yongbin.kim>
Date: Wed, 26 Jan 2022 04:08:41 +0900
Added tests for built-in tags
Diffstat:
9 files changed, 708 insertions(+), 0 deletions(-)
diff --git a/tplc/builtin_fallback_test.go b/tplc/builtin_fallback_test.go
@@ -0,0 +1,63 @@
+package tplc
+
+import (
+ "strings"
+ "testing"
+
+ "go.lair.cx/go-core/net/htmlx"
+)
+
+func Test_TagFallback(t *testing.T) {
+ tests := []struct {
+ name string
+ src string
+ wantErr bool
+ }{
+ {
+ "self-closing tag",
+ "<test />",
+ false,
+ },
+ {
+ "empty tag",
+ "<test></test>",
+ false,
+ },
+ {
+ "with contents",
+ "<test><div>hi, this is dummy. <a>Aaa-</a></div></test>",
+ false,
+ },
+ {
+ "with plain texts",
+ "<test>Hello, World!</test>",
+ false,
+ },
+ {
+ "with incomplete content",
+ "<test><br></test>",
+ false,
+ },
+ {
+ "incomplete",
+ "<test>dummy.",
+ true,
+ },
+ }
+ tagHandler := TagFallback("test")
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var (
+ writer = NewTestWriter()
+ tokenizer = htmlx.NewTokenizer(strings.NewReader(tt.src))
+ )
+ if kind := tokenizer.Next(); kind != htmlx.SelfClosingTagToken && kind != htmlx.StartTagToken {
+ t.Errorf("invalid test: unexpected token: %s", kind.String())
+ return
+ }
+ if err := tagHandler(writer.builder, writer, tokenizer); (err != nil) != tt.wantErr {
+ t.Errorf("TagFallback.func() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
diff --git a/tplc/builtin_for_test.go b/tplc/builtin_for_test.go
@@ -0,0 +1,72 @@
+package tplc
+
+import (
+ "strconv"
+ "strings"
+ "testing"
+
+ "go.lair.cx/go-core/net/htmlx"
+)
+
+func Test_TagFor(t *testing.T) {
+ tests := []struct {
+ name string
+ src string
+ wantErr bool
+ want string
+ }{
+ {
+ "self-closing tag",
+ `<for />`,
+ false,
+ "for {\n}\n",
+ },
+ {
+ "empty tag",
+ `<for></for>`,
+ false,
+ "for {\n}\n",
+ },
+ {
+ "infinity loop",
+ `<for>Hello, World!</for>`,
+ false,
+ "for {\nw.WriteRaw(`Hello, World!`)\n}\n",
+ },
+ {
+ "for range",
+ `<for class="a := range []int{1, 2, 3}"><integer>a</integer></for>`,
+ false,
+ "for a := range []int{1, 2, 3} {\nw.WriteInteger(a)\n}\n",
+ },
+ {
+ "incomplete for",
+ `<for class="true">for`,
+ true,
+ "",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var (
+ writer = NewTestWriter()
+ tokenizer = htmlx.NewTokenizer(strings.NewReader(tt.src))
+ )
+ if kind := tokenizer.Next(); kind != htmlx.SelfClosingTagToken && kind != htmlx.StartTagToken {
+ t.Errorf("invalid test: unexpected token: %s", kind.String())
+ return
+ }
+ err := TagFor(writer.builder, writer, tokenizer)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("TagIf() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if err != nil {
+ return
+ }
+ if got := writer.String(); got != tt.want {
+ t.Errorf("renderTag() got = %s, want = %s", strconv.Quote(got), strconv.Quote(tt.want))
+ }
+ })
+ }
+}
diff --git a/tplc/builtin_if_test.go b/tplc/builtin_if_test.go
@@ -0,0 +1,108 @@
+package tplc
+
+import (
+ "strconv"
+ "strings"
+ "testing"
+
+ "go.lair.cx/go-core/net/htmlx"
+)
+
+func Test_TagIf(t *testing.T) {
+ tests := []struct {
+ name string
+ src string
+ wantErr bool
+ want string
+ }{
+ {
+ "self-closing tag",
+ `<if class="true" />`,
+ false,
+ "if true {\n}\n",
+ },
+ {
+ "self-closing then",
+ `<if class="true"><then /></if>`,
+ false,
+ "if true {\n}\n",
+ },
+ {
+ "self-closing else",
+ `<if class="true"><then>Hello, World!</then><else /></if>`,
+ false,
+ "if true {\nw.WriteRaw(`Hello, World!`)\n} else {\n}\n",
+ },
+ {
+ "empty tag",
+ `<if class="true"></if>`,
+ false,
+ "if true {\n}\n",
+ },
+ {
+ "if then",
+ `<if class="true"><then>Hello, World!</then></if>`,
+ false,
+ "if true {\nw.WriteRaw(`Hello, World!`)\n}\n",
+ },
+ {
+ "if then else",
+ `<if class="true"><then>Hello, World!</then><else>true is !true</else></if>`,
+ false,
+ "if true {\nw.WriteRaw(`Hello, World!`)\n} else {\nw.WriteRaw(`true is !true`)\n}\n",
+ },
+ {
+ "if then else-if else",
+ `<if class="true"><then>Hello, World!</then><else class="1 == 1">true is !true but 1 == 1</else><else>1 != 1</else></if>`,
+ false,
+ "if true {\nw.WriteRaw(`Hello, World!`)\n} else if 1 == 1 {\nw.WriteRaw(`true is !true but 1 == 1`)\n} else {\nw.WriteRaw(`1 != 1`)\n}\n",
+ },
+ {
+ "incomplete if",
+ `<if class="true"><then>Hello, World!</then>`,
+ true,
+ "",
+ },
+ {
+ "incomplete then",
+ `<if class="true"><then>Hello, World!</if>`,
+ true,
+ "",
+ },
+ {
+ "incomplete else",
+ `<if class="true"><then>Hello, World!</then><else>Else</if>`,
+ true,
+ "",
+ },
+ {
+ "plain text in if",
+ `<if class="true">Hello, World!</if>`,
+ true,
+ "",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var (
+ writer = NewTestWriter()
+ tokenizer = htmlx.NewTokenizer(strings.NewReader(tt.src))
+ )
+ if kind := tokenizer.Next(); kind != htmlx.SelfClosingTagToken && kind != htmlx.StartTagToken {
+ t.Errorf("invalid test: unexpected token: %s", kind.String())
+ return
+ }
+ err := TagIf(writer.builder, writer, tokenizer)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("TagIf() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if err != nil {
+ return
+ }
+ if got := writer.String(); got != tt.want {
+ t.Errorf("renderTag() got = %s, want = %s", strconv.Quote(got), strconv.Quote(tt.want))
+ }
+ })
+ }
+}
diff --git a/tplc/builtin_import_test.go b/tplc/builtin_import_test.go
@@ -0,0 +1,66 @@
+package tplc
+
+import (
+ "strconv"
+ "strings"
+ "testing"
+
+ "go.lair.cx/go-core/net/htmlx"
+)
+
+func Test_TagImport(t *testing.T) {
+ tests := []struct {
+ name string
+ src string
+ wantErr bool
+ want string
+ }{
+ {
+ "self-closing tag",
+ "<import />",
+ false,
+ "",
+ },
+ {
+ "empty tag",
+ "<import></import>",
+ false,
+ "",
+ },
+ {
+ "import pkg",
+ "<import>\"pkg\"</import>",
+ false,
+ "import (\"pkg\")\n\n",
+ },
+ {
+ "incomplete",
+ "<import>\"pkg\"",
+ true,
+ "",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var (
+ writer = NewTestWriter()
+ tokenizer = htmlx.NewTokenizer(strings.NewReader(tt.src))
+ )
+ if kind := tokenizer.Next(); kind != htmlx.SelfClosingTagToken && kind != htmlx.StartTagToken {
+ t.Errorf("invalid test: unexpected token: %s", kind.String())
+ return
+ }
+ err := TagImport(writer.builder, writer, tokenizer)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("TagImport() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if err != nil {
+ return
+ }
+ if got := writer.String(); got != tt.want {
+ t.Errorf("renderTag() got = %s, want = %s", strconv.Quote(got), strconv.Quote(tt.want))
+ }
+ })
+ }
+}
diff --git a/tplc/builtin_resources_test.go b/tplc/builtin_resources_test.go
@@ -0,0 +1,69 @@
+package tplc
+
+import (
+ "strconv"
+ "strings"
+ "testing"
+
+ "go.lair.cx/go-core/net/htmlx"
+)
+
+func Test_handleResourceTag(t *testing.T) {
+ tests := []struct {
+ name string
+ src string
+ wantErr bool
+ want string
+ }{
+ {
+ "self-closing tag",
+ "<test />",
+ true,
+ "",
+ },
+ {
+ "empty tag",
+ "<test></test>",
+ true,
+ "",
+ },
+ {
+ "hello world",
+ "<test>Hello, World!</test>",
+ false,
+ "Hello, World!",
+ },
+ {
+ "incomplete",
+ "<test>Hello, World!",
+ true,
+ "",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var (
+ writer = NewTestWriter()
+ tokenizer = htmlx.NewTokenizer(strings.NewReader(tt.src))
+ )
+ testResourceHandler := func(p []byte) {
+ writer.WriteGoCodeBytes(p)
+ }
+ if kind := tokenizer.Next(); kind != htmlx.SelfClosingTagToken && kind != htmlx.StartTagToken {
+ t.Errorf("invalid test: unexpected token: %s", kind.String())
+ return
+ }
+ err := handleResourceTag(tokenizer, "test", testResourceHandler)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("handleResourceTag() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if err != nil {
+ return
+ }
+ if got := writer.String(); got != tt.want {
+ t.Errorf("renderTag() got = %s, want = %s", strconv.Quote(got), strconv.Quote(tt.want))
+ }
+ })
+ }
+}
diff --git a/tplc/builtin_template_test.go b/tplc/builtin_template_test.go
@@ -0,0 +1,100 @@
+package tplc
+
+import (
+ "strconv"
+ "strings"
+ "testing"
+
+ "go.lair.cx/go-core/net/htmlx"
+)
+
+func Test_renderTag(t *testing.T) {
+ tests := []struct {
+ name string
+ src string
+ tagName string
+ customHandlers map[string]TagHandler
+ useCustomOnly bool
+ wantErr bool
+ want string
+ }{
+ {
+ "root document",
+ `<raw>fmt.Println("Hello, World!")</raw>`,
+ "",
+ nil,
+ false,
+ false,
+ "fmt.Println(\"Hello, World!\")\n",
+ },
+ {
+ "with wrapper",
+ // When renderTags is called with tagName, starting tag is
+ // already parsed.
+ `<raw>fmt.Println("Hello, World!")</raw></wrapper>`,
+ "wrapper",
+ nil,
+ false,
+ false,
+ "fmt.Println(\"Hello, World!\")\n",
+ },
+ {
+ "unknown tag with custom handler only flag",
+ `<div>Hello!</div></test>`,
+ "test",
+ map[string]TagHandler{},
+ true,
+ true,
+ "",
+ },
+ {
+ "custom tag with standard tag",
+ `<raw>fmt.Println("Hello, World!")</raw>
+ <custom>Hi!</custom></test>`,
+ "test",
+ map[string]TagHandler{
+ "custom": TagTestTransparent("custom"),
+ },
+ false,
+ false,
+ "fmt.Println(\"Hello, World!\")\nHi!",
+ },
+ {
+ "with self-closing content",
+ `<custom /></test>`,
+ "test",
+ map[string]TagHandler{
+ "custom": func(b *Builder, w *Writer, t *htmlx.Tokenizer) error {
+ if t.CurrentType() != htmlx.SelfClosingTagToken {
+ w.WriteGoCode("Not Good")
+ return TagTestTransparent("custom")(b, w, t)
+ }
+ w.WriteGoCode("Good")
+ return nil
+ },
+ },
+ true,
+ false,
+ "Good",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var (
+ writer = NewTestWriter()
+ tokenizer = htmlx.NewTokenizer(strings.NewReader(tt.src))
+ )
+ err := renderTag(writer.builder, writer, tokenizer, tt.tagName, tt.customHandlers, tt.useCustomOnly)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("renderTag() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if err != nil {
+ return
+ }
+ if got := writer.String(); got != tt.want {
+ t.Errorf("renderTag() got = %s, want = %s", strconv.Quote(got), strconv.Quote(tt.want))
+ }
+ })
+ }
+}
diff --git a/tplc/builtin_test.go b/tplc/builtin_test.go
@@ -0,0 +1,72 @@
+package tplc
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+
+ "go.lair.cx/go-core/net/htmlx"
+)
+
+func Test_getBodyFromTextOnlyTag(t *testing.T) {
+ tests := []struct {
+ name string
+ src string
+ allowEmpty bool
+ want []byte
+ wantErr bool
+ }{
+ {
+ "with content",
+ `<test>ok</test>`,
+ false,
+ []byte("ok"),
+ false,
+ },
+ {
+ "empty body but not allowed",
+ `<test></test>`,
+ false,
+ nil,
+ true,
+ },
+ {
+ "allow empty",
+ `<test></test>`,
+ true,
+ nil,
+ false,
+ },
+ {
+ "self-closing with allow empty",
+ `<test />`,
+ true,
+ nil,
+ false,
+ },
+ {
+ "self-closing without allow empty",
+ `<test />`,
+ false,
+ nil,
+ true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var tokenizer = htmlx.NewTokenizer(strings.NewReader(tt.src))
+ if kind := tokenizer.Next(); kind != htmlx.SelfClosingTagToken && kind != htmlx.StartTagToken {
+ t.Errorf("invalid test: unexpected token: %s", kind.String())
+ return
+ }
+ got, err := getBodyFromTextOnlyTag(tokenizer, "test", tt.allowEmpty)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("getBodyFromTextOnlyTag() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !bytes.Equal(got, tt.want) {
+ t.Errorf("getBodyFromTextOnlyTag() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/tplc/builtin_typedef_test.go b/tplc/builtin_typedef_test.go
@@ -0,0 +1,66 @@
+package tplc
+
+import (
+ "strconv"
+ "strings"
+ "testing"
+
+ "go.lair.cx/go-core/net/htmlx"
+)
+
+func Test_handleTypeTag(t *testing.T) {
+ tests := []struct {
+ name string
+ src string
+ wantErr bool
+ want string
+ }{
+ {
+ "self-closing tag",
+ "<interface class=\"test\" />",
+ false,
+ "type test interface {}\n\n",
+ },
+ {
+ "empty tag",
+ "<interface class=\"test\"></interface>",
+ false,
+ "type test interface {}\n\n",
+ },
+ {
+ "without class name",
+ "<interface>A()</interface>",
+ true,
+ "",
+ },
+ {
+ "with content",
+ "<interface class=\"test\">A()</interface>",
+ false,
+ "type test interface {A()}\n\n",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var (
+ writer = NewTestWriter()
+ tokenizer = htmlx.NewTokenizer(strings.NewReader(tt.src))
+ )
+ if kind := tokenizer.Next(); kind != htmlx.SelfClosingTagToken && kind != htmlx.StartTagToken {
+ t.Errorf("invalid test: unexpected token: %s", kind.String())
+ return
+ }
+ err := handleTypeTag(writer, tokenizer, "interface")
+ if (err != nil) != tt.wantErr {
+ t.Errorf("handleTypeTag() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if err != nil {
+ return
+ }
+ if got := writer.String(); got != tt.want {
+ t.Errorf("handleTypeTag() got = %s, want = %s", strconv.Quote(got), strconv.Quote(tt.want))
+ }
+ })
+ }
+}
diff --git a/tplc/tplc_commons_test.go b/tplc/tplc_commons_test.go
@@ -0,0 +1,92 @@
+package tplc
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "strconv"
+
+ "github.com/valyala/bytebufferpool"
+
+ "go.lair.cx/go-core/net/htmlx"
+ "go.lair.cx/tpls/internal/stacks"
+)
+
+func NewTestWriter() *Writer {
+ return &Writer{
+ builder: New().UseBuiltin(),
+ }
+}
+
+func NewTestBuilder(useBuiltin bool, tags map[string]TagHandler) *Builder {
+ b := New()
+ if useBuiltin {
+ b = b.UseBuiltin()
+ }
+ for key, tag := range tags {
+ b = b.AddTag(key, tag)
+ }
+ return b
+}
+
+func TagTestTransparent(name string) TagHandler {
+ return func(b *Builder, w *Writer, t *htmlx.Tokenizer) error {
+ if t.CurrentType() == htmlx.SelfClosingTagToken {
+ return nil
+ }
+
+ buf := bytebufferpool.Get()
+ defer bytebufferpool.Put(buf)
+
+ stackBuf := bytebufferpool.Get()
+ defer bytebufferpool.Put(stackBuf)
+
+ stack := stacks.NewByteStack(stackBuf.B)
+
+ loop:
+ for {
+ switch t.Next() {
+ case htmlx.StartTagToken:
+ tagName, _ := t.TagName()
+ stack.Put(tagName)
+
+ case htmlx.EndTagToken:
+ tagName, _ := t.TagName()
+
+ // Remove items in stack until the matched starting tag is found.
+ // If the tag is not exists in stack,
+ // - Root tag's closing.
+ // - Unexpected closing tag.
+ for {
+ item, ok := stack.Pop()
+ if !ok { // Tag is not found:
+ if string(tagName) == name {
+ break loop
+ }
+ return fmt.Errorf(
+ "unexpected closing tag: %s",
+ strconv.Quote(string(tagName)),
+ )
+ }
+ if bytes.Equal(item, tagName) {
+ break
+ }
+ }
+
+ case htmlx.ErrorToken:
+ err := t.Err()
+ if err == io.EOF {
+ if len(name) == 0 && stack.Len() == 0 {
+ return nil
+ }
+ return io.ErrUnexpectedEOF
+ }
+ return err
+ }
+
+ w.WriteGoCodeBytes(t.Raw())
+ }
+
+ return nil
+ }
+}