shithub: hugo

ref: 3ebe83aea7c0eeb309b9ff7827075cc682c86c00
dir: /tpl/internal/go_templates/htmltemplate/template_test.go/

View raw version
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build go1.13

package template_test

import (
	"bytes"
	"encoding/json"
	"strings"
	"testing"

	. "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
)

func TestTemplateClone(t *testing.T) {
	// https://golang.org/issue/12996
	orig := New("name")
	clone, err := orig.Clone()
	if err != nil {
		t.Fatal(err)
	}
	if len(clone.Templates()) != len(orig.Templates()) {
		t.Fatalf("Invalid length of t.Clone().Templates()")
	}

	const want = "stuff"
	parsed := Must(clone.Parse(want))
	var buf bytes.Buffer
	err = parsed.Execute(&buf, nil)
	if err != nil {
		t.Fatal(err)
	}
	if got := buf.String(); got != want {
		t.Fatalf("got %q; want %q", got, want)
	}
}

func TestRedefineNonEmptyAfterExecution(t *testing.T) {
	c := newTestCase(t)
	c.mustParse(c.root, `foo`)
	c.mustExecute(c.root, nil, "foo")
	c.mustNotParse(c.root, `bar`)
}

func TestRedefineEmptyAfterExecution(t *testing.T) {
	c := newTestCase(t)
	c.mustParse(c.root, ``)
	c.mustExecute(c.root, nil, "")
	c.mustNotParse(c.root, `foo`)
	c.mustExecute(c.root, nil, "")
}

func TestRedefineAfterNonExecution(t *testing.T) {
	c := newTestCase(t)
	c.mustParse(c.root, `{{if .}}<{{template "X"}}>{{end}}{{define "X"}}foo{{end}}`)
	c.mustExecute(c.root, 0, "")
	c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
	c.mustExecute(c.root, 1, "&lt;foo>")
}

func TestRedefineAfterNamedExecution(t *testing.T) {
	c := newTestCase(t)
	c.mustParse(c.root, `<{{template "X" .}}>{{define "X"}}foo{{end}}`)
	c.mustExecute(c.root, nil, "&lt;foo>")
	c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
	c.mustExecute(c.root, nil, "&lt;foo>")
}

func TestRedefineNestedByNameAfterExecution(t *testing.T) {
	c := newTestCase(t)
	c.mustParse(c.root, `{{define "X"}}foo{{end}}`)
	c.mustExecute(c.lookup("X"), nil, "foo")
	c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
	c.mustExecute(c.lookup("X"), nil, "foo")
}

func TestRedefineNestedByTemplateAfterExecution(t *testing.T) {
	c := newTestCase(t)
	c.mustParse(c.root, `{{define "X"}}foo{{end}}`)
	c.mustExecute(c.lookup("X"), nil, "foo")
	c.mustNotParse(c.lookup("X"), `bar`)
	c.mustExecute(c.lookup("X"), nil, "foo")
}

func TestRedefineSafety(t *testing.T) {
	c := newTestCase(t)
	c.mustParse(c.root, `<html><a href="{{template "X"}}">{{define "X"}}{{end}}`)
	c.mustExecute(c.root, nil, `<html><a href="">`)
	// Note: Every version of Go prior to Go 1.8 accepted the redefinition of "X"
	// on the next line, but luckily kept it from being used in the outer template.
	// Now we reject it, which makes clearer that we're not going to use it.
	c.mustNotParse(c.root, `{{define "X"}}" bar="baz{{end}}`)
	c.mustExecute(c.root, nil, `<html><a href="">`)
}

func TestRedefineTopUse(t *testing.T) {
	c := newTestCase(t)
	c.mustParse(c.root, `{{template "X"}}{{.}}{{define "X"}}{{end}}`)
	c.mustExecute(c.root, 42, `42`)
	c.mustNotParse(c.root, `{{define "X"}}<script>{{end}}`)
	c.mustExecute(c.root, 42, `42`)
}

func TestRedefineOtherParsers(t *testing.T) {
	c := newTestCase(t)
	c.mustParse(c.root, ``)
	c.mustExecute(c.root, nil, ``)
	if _, err := c.root.ParseFiles("no.template"); err == nil || !strings.Contains(err.Error(), "Execute") {
		t.Errorf("ParseFiles: %v\nwanted error about already having Executed", err)
	}
	if _, err := c.root.ParseGlob("*.no.template"); err == nil || !strings.Contains(err.Error(), "Execute") {
		t.Errorf("ParseGlob: %v\nwanted error about already having Executed", err)
	}
	if _, err := c.root.AddParseTree("t1", c.root.Tree); err == nil || !strings.Contains(err.Error(), "Execute") {
		t.Errorf("AddParseTree: %v\nwanted error about already having Executed", err)
	}
}

func TestNumbers(t *testing.T) {
	c := newTestCase(t)
	c.mustParse(c.root, `{{print 1_2.3_4}} {{print 0x0_1.e_0p+02}}`)
	c.mustExecute(c.root, nil, "12.34 7.5")
}

func TestStringsInScriptsWithJsonContentTypeAreCorrectlyEscaped(t *testing.T) {
	// See #33671 and #37634 for more context on this.
	tests := []struct{ name, in string }{
		{"empty", ""},
		{"invalid", string(rune(-1))},
		{"null", "\u0000"},
		{"unit separator", "\u001F"},
		{"tab", "\t"},
		{"gt and lt", "<>"},
		{"quotes", `'"`},
		{"ASCII letters", "ASCII letters"},
		{"Unicode", "ʕ⊙ϖ⊙ʔ"},
		{"Pizza", "🍕"},
	}
	const (
		prefix = `<script type="application/ld+json">`
		suffix = `</script>`
		templ  = prefix + `"{{.}}"` + suffix
	)
	tpl := Must(New("JS string is JSON string").Parse(templ))
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var buf bytes.Buffer
			if err := tpl.Execute(&buf, tt.in); err != nil {
				t.Fatalf("Cannot render template: %v", err)
			}
			trimmed := bytes.TrimSuffix(bytes.TrimPrefix(buf.Bytes(), []byte(prefix)), []byte(suffix))
			var got string
			if err := json.Unmarshal(trimmed, &got); err != nil {
				t.Fatalf("Cannot parse JS string %q as JSON: %v", trimmed[1:len(trimmed)-1], err)
			}
			if got != tt.in {
				t.Errorf("Serialization changed the string value: got %q want %q", got, tt.in)
			}
		})
	}
}

type testCase struct {
	t    *testing.T
	root *Template
}

func newTestCase(t *testing.T) *testCase {
	return &testCase{
		t:    t,
		root: New("root"),
	}
}

func (c *testCase) lookup(name string) *Template {
	return c.root.Lookup(name)
}

func (c *testCase) mustParse(t *Template, text string) {
	_, err := t.Parse(text)
	if err != nil {
		c.t.Fatalf("parse: %v", err)
	}
}

func (c *testCase) mustNotParse(t *Template, text string) {
	_, err := t.Parse(text)
	if err == nil {
		c.t.Fatalf("parse: unexpected success")
	}
}

func (c *testCase) mustExecute(t *Template, val interface{}, want string) {
	var buf bytes.Buffer
	err := t.Execute(&buf, val)
	if err != nil {
		c.t.Fatalf("execute: %v", err)
	}
	if buf.String() != want {
		c.t.Fatalf("template output:\n%s\nwant:\n%s", buf.String(), want)
	}
}