// Copyright 2009 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. package template import ( "bytes"; "container/vector"; "fmt"; "io"; "testing"; ) type Test struct { in, out, err string; } type T struct { item string; value string; } type S struct { header string; integer int; raw string; innerT T; innerPointerT *T; data []T; pdata []*T; empty []*T; emptystring string; null []*T; vec *vector.Vector; true bool; false bool; } var t1 = T{"ItemNumber1", "ValueNumber1"} var t2 = T{"ItemNumber2", "ValueNumber2"} func uppercase(v interface{}) string { s := v.(string); t := ""; for i := 0; i < len(s); i++ { c := s[i]; if 'a' <= c && c <= 'z' { c = c+'A'-'a'; } t += string(c); } return t; } func plus1(v interface{}) string { i := v.(int); return fmt.Sprint(i+1); } func writer(f func(interface{}) string) (func(io.Writer, interface{}, string)) { return func(w io.Writer, v interface{}, format string) { io.WriteString(w, f(v)); }; } var formatters = FormatterMap{ "uppercase": writer(uppercase), "+1": writer(plus1), } var tests = []*Test{ // Simple &Test{"", "", ""}, &Test{"abc\ndef\n", "abc\ndef\n", ""}, &Test{" {.meta-left} \n", "{", ""}, &Test{" {.meta-right} \n", "}", ""}, &Test{" {.space} \n", " ", ""}, &Test{" {.tab} \n", "\t", ""}, &Test{" {#comment} \n", "", ""}, // Variables at top level &Test{ in: "{header}={integer}\n", out: "Header=77\n", }, // Section &Test{ in: "{.section data }\n" "some text for the section\n" "{.end}\n", out: "some text for the section\n", }, &Test{ in: "{.section data }\n" "{header}={integer}\n" "{.end}\n", out: "Header=77\n", }, &Test{ in: "{.section pdata }\n" "{header}={integer}\n" "{.end}\n", out: "Header=77\n", }, &Test{ in: "{.section pdata }\n" "data present\n" "{.or}\n" "data not present\n" "{.end}\n", out: "data present\n", }, &Test{ in: "{.section empty }\n" "data present\n" "{.or}\n" "data not present\n" "{.end}\n", out: "data not present\n", }, &Test{ in: "{.section null }\n" "data present\n" "{.or}\n" "data not present\n" "{.end}\n", out: "data not present\n", }, &Test{ in: "{.section pdata }\n" "{header}={integer}\n" "{.section @ }\n" "{header}={integer}\n" "{.end}\n" "{.end}\n", out: "Header=77\n" "Header=77\n", }, &Test{ in: "{.section data}{.end} {header}\n", out: " Header\n", }, // Repeated &Test{ in: "{.section pdata }\n" "{.repeated section @ }\n" "{item}={value}\n" "{.end}\n" "{.end}\n", out: "ItemNumber1=ValueNumber1\n" "ItemNumber2=ValueNumber2\n", }, &Test{ in: "{.section pdata }\n" "{.repeated section @ }\n" "{item}={value}\n" "{.or}\n" "this should not appear\n" "{.end}\n" "{.end}\n", out: "ItemNumber1=ValueNumber1\n" "ItemNumber2=ValueNumber2\n", }, &Test{ in: "{.section @ }\n" "{.repeated section empty }\n" "{item}={value}\n" "{.or}\n" "this should appear: empty field\n" "{.end}\n" "{.end}\n", out: "this should appear: empty field\n", }, &Test{ in: "{.repeated section pdata }\n" "{item}\n" "{.alternates with}\n" "is\nover\nmultiple\nlines\n" "{.end}\n", out: "ItemNumber1\n" "is\nover\nmultiple\nlines\n" "ItemNumber2\n", }, &Test{ in: "{.section pdata }\n" "{.repeated section @ }\n" "{item}={value}\n" "{.alternates with}DIVIDER\n" "{.or}\n" "this should not appear\n" "{.end}\n" "{.end}\n", out: "ItemNumber1=ValueNumber1\n" "DIVIDER\n" "ItemNumber2=ValueNumber2\n", }, &Test{ in: "{.repeated section vec }\n" "{@}\n" "{.end}\n", out: "elt1\n" "elt2\n", }, &Test{ in: "{.repeated section integer}{.end}", err: "line 1: .repeated: cannot repeat integer (type int)", }, // Nested names &Test{ in: "{.section @ }\n" "{innerT.item}={innerT.value}\n" "{.end}", out: "ItemNumber1=ValueNumber1\n", }, &Test{ in: "{.section @ }\n" "{innerT.item}={.section innerT}{.section value}{@}{.end}{.end}\n" "{.end}", out: "ItemNumber1=ValueNumber1\n", }, // Formatters &Test{ in: "{.section pdata }\n" "{header|uppercase}={integer|+1}\n" "{header|html}={integer|str}\n" "{.end}\n", out: "HEADER=78\n" "Header=77\n", }, &Test{ in: "{raw}\n" "{raw|html}\n", out: "&<>!@ #$%^\n" "&<>!@ #$%^\n", }, &Test{ in: "{.section emptystring}emptystring{.end}\n" "{.section header}header{.end}\n", out: "\nheader\n", }, &Test{ in: "{.section true}1{.or}2{.end}\n" "{.section false}3{.or}4{.end}\n", out: "1\n4\n", }, } func TestAll(t *testing.T) { s := new(S); // initialized by hand for clarity. s.header = "Header"; s.integer = 77; s.raw = "&<>!@ #$%^"; s.innerT = t1; s.data = []T{t1, t2}; s.pdata = []*T{&t1, &t2}; s.empty = []*T{}; s.null = nil; s.vec = vector.New(0); s.vec.Push("elt1"); s.vec.Push("elt2"); s.true = true; s.false = false; var buf bytes.Buffer; for _, test := range tests { buf.Reset(); tmpl, err := Parse(test.in, formatters); if err != nil { t.Error("unexpected parse error:", err); continue; } err = tmpl.Execute(s, &buf); if test.err == "" { if err != nil { t.Error("unexpected execute error:", err); } } else { if err == nil || err.String() != test.err { t.Errorf("expected execute error %q, got %q", test.err, err.String()); } } if buf.String() != test.out { t.Errorf("for %q: expected %q got %q", test.in, test.out, buf.String()); } } } func TestStringDriverType(t *testing.T) { tmpl, err := Parse("template: {@}", nil); if err != nil { t.Error("unexpected parse error:", err); } var b bytes.Buffer; err = tmpl.Execute("hello", &b); if err != nil { t.Error("unexpected execute error:", err); } s := b.String(); if s != "template: hello" { t.Errorf("failed passing string as data: expected %q got %q", "template: hello", s); } } func TestTwice(t *testing.T) { tmpl, err := Parse("template: {@}", nil); if err != nil { t.Error("unexpected parse error:", err); } var b bytes.Buffer; err = tmpl.Execute("hello", &b); if err != nil { t.Error("unexpected parse error:", err); } s := b.String(); text := "template: hello"; if s != text { t.Errorf("failed passing string as data: expected %q got %q", text, s); } err = tmpl.Execute("hello", &b); if err != nil { t.Error("unexpected parse error:", err); } s = b.String(); text += text; if s != text { t.Errorf("failed passing string as data: expected %q got %q", text, s); } } func TestCustomDelims(t *testing.T) { // try various lengths. zero should catch error. for i := 0; i < 7; i++ { for j := 0; j < 7; j++ { tmpl := New(nil); // first two chars deliberately the same to test equal left and right delims ldelim := "$!#$%^&"[0:i]; rdelim := "$*&^%$!"[0:j]; tmpl.SetDelims(ldelim, rdelim); // if braces, this would be template: {@}{.meta-left}{.meta-right} text := "template: " + ldelim + "@" + rdelim + ldelim + ".meta-left" + rdelim + ldelim + ".meta-right" + rdelim; err := tmpl.Parse(text); if err != nil { if i == 0 || j == 0 { // expected continue; } t.Error("unexpected parse error:", err); } else if i == 0 || j == 0 { t.Errorf("expected parse error for empty delimiter: %d %d %q %q", i, j, ldelim, rdelim); continue; } var b bytes.Buffer; err = tmpl.Execute("hello", &b); s := b.String(); if s != "template: hello" + ldelim + rdelim { t.Errorf("failed delim check(%q %q) %q got %q", ldelim, rdelim, text, s); } } } } // Test that a variable evaluates to the field itself and does not further indirection func TestVarIndirection(t *testing.T) { s := new(S); // initialized by hand for clarity. s.innerPointerT = &t1; var buf bytes.Buffer; input := "{.section @}{innerPointerT}{.end}"; tmpl, err := Parse(input, nil); if err != nil { t.Fatal("unexpected parse error:", err); } err = tmpl.Execute(s, &buf); if err != nil { t.Fatal("unexpected execute error:", err); } expect := fmt.Sprintf("%v", &t1); // output should be hex address of t1 if buf.String() != expect { t.Errorf("for %q: expected %q got %q", input, expect, buf.String()); } }