Source file src/html/template/template_test.go

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

View as plain text