github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/tpl/internal/go_templates/htmltemplate/template_test.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build go1.13
     6  // +build go1.13
     7  
     8  package template_test
     9  
    10  import (
    11  	"bytes"
    12  	"encoding/json"
    13  	"strings"
    14  	"testing"
    15  
    16  	. "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
    17  	"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse" // https://golang.org/issue/12996
    18  )
    19  
    20  func TestTemplateClone(t *testing.T) {
    21  
    22  	orig := New("name")
    23  	clone, err := orig.Clone()
    24  	if err != nil {
    25  		t.Fatal(err)
    26  	}
    27  	if len(clone.Templates()) != len(orig.Templates()) {
    28  		t.Fatalf("Invalid length of t.Clone().Templates()")
    29  	}
    30  
    31  	const want = "stuff"
    32  	parsed := Must(clone.Parse(want))
    33  	var buf strings.Builder
    34  	err = parsed.Execute(&buf, nil)
    35  	if err != nil {
    36  		t.Fatal(err)
    37  	}
    38  	if got := buf.String(); got != want {
    39  		t.Fatalf("got %q; want %q", got, want)
    40  	}
    41  }
    42  
    43  func TestRedefineNonEmptyAfterExecution(t *testing.T) {
    44  	c := newTestCase(t)
    45  	c.mustParse(c.root, `foo`)
    46  	c.mustExecute(c.root, nil, "foo")
    47  	c.mustNotParse(c.root, `bar`)
    48  }
    49  
    50  func TestRedefineEmptyAfterExecution(t *testing.T) {
    51  	c := newTestCase(t)
    52  	c.mustParse(c.root, ``)
    53  	c.mustExecute(c.root, nil, "")
    54  	c.mustNotParse(c.root, `foo`)
    55  	c.mustExecute(c.root, nil, "")
    56  }
    57  
    58  func TestRedefineAfterNonExecution(t *testing.T) {
    59  	c := newTestCase(t)
    60  	c.mustParse(c.root, `{{if .}}<{{template "X"}}>{{end}}{{define "X"}}foo{{end}}`)
    61  	c.mustExecute(c.root, 0, "")
    62  	c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
    63  	c.mustExecute(c.root, 1, "&lt;foo>")
    64  }
    65  
    66  func TestRedefineAfterNamedExecution(t *testing.T) {
    67  	c := newTestCase(t)
    68  	c.mustParse(c.root, `<{{template "X" .}}>{{define "X"}}foo{{end}}`)
    69  	c.mustExecute(c.root, nil, "&lt;foo>")
    70  	c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
    71  	c.mustExecute(c.root, nil, "&lt;foo>")
    72  }
    73  
    74  func TestRedefineNestedByNameAfterExecution(t *testing.T) {
    75  	c := newTestCase(t)
    76  	c.mustParse(c.root, `{{define "X"}}foo{{end}}`)
    77  	c.mustExecute(c.lookup("X"), nil, "foo")
    78  	c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
    79  	c.mustExecute(c.lookup("X"), nil, "foo")
    80  }
    81  
    82  func TestRedefineNestedByTemplateAfterExecution(t *testing.T) {
    83  	c := newTestCase(t)
    84  	c.mustParse(c.root, `{{define "X"}}foo{{end}}`)
    85  	c.mustExecute(c.lookup("X"), nil, "foo")
    86  	c.mustNotParse(c.lookup("X"), `bar`)
    87  	c.mustExecute(c.lookup("X"), nil, "foo")
    88  }
    89  
    90  func TestRedefineSafety(t *testing.T) {
    91  	c := newTestCase(t)
    92  	c.mustParse(c.root, `<html><a href="{{template "X"}}">{{define "X"}}{{end}}`)
    93  	c.mustExecute(c.root, nil, `<html><a href="">`)
    94  	// Note: Every version of Go prior to Go 1.8 accepted the redefinition of "X"
    95  	// on the next line, but luckily kept it from being used in the outer template.
    96  	// Now we reject it, which makes clearer that we're not going to use it.
    97  	c.mustNotParse(c.root, `{{define "X"}}" bar="baz{{end}}`)
    98  	c.mustExecute(c.root, nil, `<html><a href="">`)
    99  }
   100  
   101  func TestRedefineTopUse(t *testing.T) {
   102  	c := newTestCase(t)
   103  	c.mustParse(c.root, `{{template "X"}}{{.}}{{define "X"}}{{end}}`)
   104  	c.mustExecute(c.root, 42, `42`)
   105  	c.mustNotParse(c.root, `{{define "X"}}<script>{{end}}`)
   106  	c.mustExecute(c.root, 42, `42`)
   107  }
   108  
   109  func TestRedefineOtherParsers(t *testing.T) {
   110  	c := newTestCase(t)
   111  	c.mustParse(c.root, ``)
   112  	c.mustExecute(c.root, nil, ``)
   113  	if _, err := c.root.ParseFiles("no.template"); err == nil || !strings.Contains(err.Error(), "Execute") {
   114  		t.Errorf("ParseFiles: %v\nwanted error about already having Executed", err)
   115  	}
   116  	if _, err := c.root.ParseGlob("*.no.template"); err == nil || !strings.Contains(err.Error(), "Execute") {
   117  		t.Errorf("ParseGlob: %v\nwanted error about already having Executed", err)
   118  	}
   119  	if _, err := c.root.AddParseTree("t1", c.root.Tree); err == nil || !strings.Contains(err.Error(), "Execute") {
   120  		t.Errorf("AddParseTree: %v\nwanted error about already having Executed", err)
   121  	}
   122  }
   123  
   124  func TestNumbers(t *testing.T) {
   125  	c := newTestCase(t)
   126  	c.mustParse(c.root, `{{print 1_2.3_4}} {{print 0x0_1.e_0p+02}}`)
   127  	c.mustExecute(c.root, nil, "12.34 7.5")
   128  }
   129  
   130  func TestStringsInScriptsWithJsonContentTypeAreCorrectlyEscaped(t *testing.T) {
   131  	// See #33671 and #37634 for more context on this.
   132  	tests := []struct{ name, in string }{
   133  		{"empty", ""},
   134  		{"invalid", string(rune(-1))},
   135  		{"null", "\u0000"},
   136  		{"unit separator", "\u001F"},
   137  		{"tab", "\t"},
   138  		{"gt and lt", "<>"},
   139  		{"quotes", `'"`},
   140  		{"ASCII letters", "ASCII letters"},
   141  		{"Unicode", "ʕ⊙ϖ⊙ʔ"},
   142  		{"Pizza", "🍕"},
   143  	}
   144  	const (
   145  		prefix = `<script type="application/ld+json">`
   146  		suffix = `</script>`
   147  		templ  = prefix + `"{{.}}"` + suffix
   148  	)
   149  	tpl := Must(New("JS string is JSON string").Parse(templ))
   150  	for _, tt := range tests {
   151  		t.Run(tt.name, func(t *testing.T) {
   152  			var buf bytes.Buffer
   153  			if err := tpl.Execute(&buf, tt.in); err != nil {
   154  				t.Fatalf("Cannot render template: %v", err)
   155  			}
   156  			trimmed := bytes.TrimSuffix(bytes.TrimPrefix(buf.Bytes(), []byte(prefix)), []byte(suffix))
   157  			var got string
   158  			if err := json.Unmarshal(trimmed, &got); err != nil {
   159  				t.Fatalf("Cannot parse JS string %q as JSON: %v", trimmed[1:len(trimmed)-1], err)
   160  			}
   161  			if got != tt.in {
   162  				t.Errorf("Serialization changed the string value: got %q want %q", got, tt.in)
   163  			}
   164  		})
   165  	}
   166  }
   167  
   168  func TestSkipEscapeComments(t *testing.T) {
   169  	c := newTestCase(t)
   170  	tr := parse.New("root")
   171  	tr.Mode = parse.ParseComments
   172  	newT, err := tr.Parse("{{/* A comment */}}{{ 1 }}{{/* Another comment */}}", "", "", make(map[string]*parse.Tree))
   173  	if err != nil {
   174  		t.Fatalf("Cannot parse template text: %v", err)
   175  	}
   176  	c.root, err = c.root.AddParseTree("root", newT)
   177  	if err != nil {
   178  		t.Fatalf("Cannot add parse tree to template: %v", err)
   179  	}
   180  	c.mustExecute(c.root, nil, "1")
   181  }
   182  
   183  type testCase struct {
   184  	t    *testing.T
   185  	root *Template
   186  }
   187  
   188  func newTestCase(t *testing.T) *testCase {
   189  	return &testCase{
   190  		t:    t,
   191  		root: New("root"),
   192  	}
   193  }
   194  
   195  func (c *testCase) lookup(name string) *Template {
   196  	return c.root.Lookup(name)
   197  }
   198  
   199  func (c *testCase) mustParse(t *Template, text string) {
   200  	_, err := t.Parse(text)
   201  	if err != nil {
   202  		c.t.Fatalf("parse: %v", err)
   203  	}
   204  }
   205  
   206  func (c *testCase) mustNotParse(t *Template, text string) {
   207  	_, err := t.Parse(text)
   208  	if err == nil {
   209  		c.t.Fatalf("parse: unexpected success")
   210  	}
   211  }
   212  
   213  func (c *testCase) mustExecute(t *Template, val any, want string) {
   214  	var buf strings.Builder
   215  	err := t.Execute(&buf, val)
   216  	if err != nil {
   217  		c.t.Fatalf("execute: %v", err)
   218  	}
   219  	if buf.String() != want {
   220  		c.t.Fatalf("template output:\n%s\nwant:\n%s", buf.String(), want)
   221  	}
   222  }