298 lines
6.3 KiB
Go
298 lines
6.3 KiB
Go
package handlebars
|
|
|
|
import "testing"
|
|
|
|
var evalTests = []Test{
|
|
{
|
|
"only content",
|
|
"this is content",
|
|
nil, nil, nil, nil,
|
|
"this is content",
|
|
},
|
|
{
|
|
"checks path in parent contexts",
|
|
"{{#a}}{{one}}{{#b}}{{one}}{{two}}{{one}}{{/b}}{{/a}}",
|
|
map[string]interface{}{"a": map[string]int{"one": 1}, "b": map[string]int{"two": 2}},
|
|
nil, nil, nil,
|
|
"1121",
|
|
},
|
|
{
|
|
"block params",
|
|
"{{#foo as |bar|}}{{bar}}{{/foo}}{{bar}}",
|
|
map[string]string{"foo": "baz", "bar": "bat"},
|
|
nil, nil, nil,
|
|
"bazbat",
|
|
},
|
|
{
|
|
"block params on array",
|
|
"{{#foo as |bar i|}}{{i}}.{{bar}} {{/foo}}",
|
|
map[string][]string{"foo": {"baz", "bar", "bat"}},
|
|
nil, nil, nil,
|
|
"0.baz 1.bar 2.bat ",
|
|
},
|
|
{
|
|
"nested block params",
|
|
"{{#foos as |foo iFoo|}}{{#wats as |wat iWat|}}{{iFoo}}.{{iWat}}.{{foo}}-{{wat}} {{/wats}}{{/foos}}",
|
|
map[string][]string{"foos": {"baz", "bar"}, "wats": {"the", "phoque"}},
|
|
nil, nil, nil,
|
|
"0.0.baz-the 0.1.baz-phoque 1.0.bar-the 1.1.bar-phoque ",
|
|
},
|
|
{
|
|
"block params with path reference",
|
|
"{{#foo as |bar|}}{{bar.baz}}{{/foo}}",
|
|
map[string]map[string]string{"foo": {"baz": "bat"}},
|
|
nil, nil, nil,
|
|
"bat",
|
|
},
|
|
{
|
|
"falsy block evaluation",
|
|
"{{#foo}}bar{{/foo}} baz",
|
|
map[string]interface{}{"foo": false},
|
|
nil, nil, nil,
|
|
" baz",
|
|
},
|
|
{
|
|
"block helper returns a SafeString",
|
|
"{{title}} - {{#bold}}{{body}}{{/bold}}",
|
|
map[string]string{
|
|
"title": "My new blog post",
|
|
"body": "I have so many things to say!",
|
|
},
|
|
nil,
|
|
map[string]interface{}{"bold": func(options *Options) SafeString {
|
|
return SafeString(`<div class="mybold">` + options.Fn() + "</div>")
|
|
}},
|
|
nil,
|
|
`My new blog post - <div class="mybold">I have so many things to say!</div>`,
|
|
},
|
|
{
|
|
"chained blocks",
|
|
"{{#if a}}A{{else if b}}B{{else}}C{{/if}}",
|
|
map[string]interface{}{"b": false},
|
|
nil, nil, nil,
|
|
"C",
|
|
},
|
|
{
|
|
"virtual length method on a map",
|
|
"Length: {{map.length}}",
|
|
map[string]interface{}{"map": map[string]string{"a": "a", "b": "b"}},
|
|
nil, nil, nil,
|
|
`Length: 2`,
|
|
},
|
|
{
|
|
"virtual length method on a slice",
|
|
"Length: {{arr.length}}",
|
|
map[string]interface{}{"arr": []int{0, 1, 2}},
|
|
nil, nil, nil,
|
|
`Length: 3`,
|
|
},
|
|
{
|
|
"virtual length method on an array",
|
|
"Length: {{arr.length}}",
|
|
map[string]interface{}{"arr": [...]int{0, 1, 2, 3}},
|
|
nil, nil, nil,
|
|
`Length: 4`,
|
|
},
|
|
{
|
|
"virtual length method on a string",
|
|
"Length: {{str.length}}",
|
|
map[string]interface{}{"str": "abcde"},
|
|
nil, nil, nil,
|
|
`Length: 5`,
|
|
},
|
|
// @todo Test with a "../../path" (depth 2 path) while context is only depth 1
|
|
}
|
|
|
|
func TestEval(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
launchTests(t, evalTests)
|
|
}
|
|
|
|
var evalErrors = []Test{
|
|
{
|
|
"functions with wrong number of arguments",
|
|
`{{foo "bar"}}`,
|
|
map[string]interface{}{"foo": func(a string, b string) string { return "foo" }},
|
|
nil, nil, nil,
|
|
"Helper 'foo' called with wrong number of arguments, needed 2 but got 1",
|
|
},
|
|
{
|
|
"functions with wrong number of returned values (1)",
|
|
"{{foo}}",
|
|
map[string]interface{}{"foo": func() {}},
|
|
nil, nil, nil,
|
|
"Helper function must return a string or a SafeString",
|
|
},
|
|
{
|
|
"functions with wrong number of returned values (2)",
|
|
"{{foo}}",
|
|
map[string]interface{}{"foo": func() (string, bool, string) { return "foo", true, "bar" }},
|
|
nil, nil, nil,
|
|
"Helper function must return a string or a SafeString",
|
|
},
|
|
}
|
|
|
|
func TestEvalErrors(t *testing.T) {
|
|
launchErrorTests(t, evalErrors)
|
|
}
|
|
|
|
func TestEvalStruct(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
source := `<div class="post">
|
|
<h1>By {{author.FirstName}} {{Author.lastName}}</h1>
|
|
<div class="body">{{Body}}</div>
|
|
|
|
<h1>Comments</h1>
|
|
|
|
{{#each comments}}
|
|
<h2>By {{Author.FirstName}} {{author.LastName}}</h2>
|
|
<div class="body">{{body}}</div>
|
|
{{/each}}
|
|
</div>`
|
|
|
|
expected := `<div class="post">
|
|
<h1>By Jean Valjean</h1>
|
|
<div class="body">Life is difficult</div>
|
|
|
|
<h1>Comments</h1>
|
|
|
|
<h2>By Marcel Beliveau</h2>
|
|
<div class="body">LOL!</div>
|
|
</div>`
|
|
|
|
type Person struct {
|
|
FirstName string
|
|
LastName string
|
|
}
|
|
|
|
type Comment struct {
|
|
Author Person
|
|
Body string
|
|
}
|
|
|
|
type Post struct {
|
|
Author Person
|
|
Body string
|
|
Comments []Comment
|
|
}
|
|
|
|
ctx := Post{
|
|
Person{"Jean", "Valjean"},
|
|
"Life is difficult",
|
|
[]Comment{
|
|
Comment{
|
|
Person{"Marcel", "Beliveau"},
|
|
"LOL!",
|
|
},
|
|
},
|
|
}
|
|
|
|
output := MustRender(source, ctx)
|
|
if output != expected {
|
|
t.Errorf("Failed to evaluate with struct context")
|
|
}
|
|
}
|
|
|
|
func TestEvalStructTag(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
source := `<div class="person">
|
|
<h1>{{real-name}}</h1>
|
|
<ul>
|
|
<li>City: {{info.location}}</li>
|
|
<li>Rug: {{info.[r.u.g]}}</li>
|
|
<li>Activity: {{info.activity}}</li>
|
|
</ul>
|
|
{{#each other-names}}
|
|
<p>{{alias-name}}</p>
|
|
{{/each}}
|
|
</div>`
|
|
|
|
expected := `<div class="person">
|
|
<h1>Lebowski</h1>
|
|
<ul>
|
|
<li>City: Venice</li>
|
|
<li>Rug: Tied The Room Together</li>
|
|
<li>Activity: Bowling</li>
|
|
</ul>
|
|
<p>his dudeness</p>
|
|
<p>el duderino</p>
|
|
</div>`
|
|
|
|
type Alias struct {
|
|
Name string `handlebars:"alias-name"`
|
|
}
|
|
|
|
type CharacterInfo struct {
|
|
City string `handlebars:"location"`
|
|
Rug string `handlebars:"r.u.g"`
|
|
Activity string `handlebars:"not-activity"`
|
|
}
|
|
|
|
type Character struct {
|
|
RealName string `handlebars:"real-name"`
|
|
Info CharacterInfo
|
|
Aliases []Alias `handlebars:"other-names"`
|
|
}
|
|
|
|
ctx := Character{
|
|
"Lebowski",
|
|
CharacterInfo{"Venice", "Tied The Room Together", "Bowling"},
|
|
[]Alias{
|
|
{"his dudeness"},
|
|
{"el duderino"},
|
|
},
|
|
}
|
|
|
|
output := MustRender(source, ctx)
|
|
if output != expected {
|
|
t.Errorf("Failed to evaluate with struct tag context")
|
|
}
|
|
}
|
|
|
|
type TestFoo struct{}
|
|
|
|
func (t *TestFoo) Subject() string {
|
|
return "foo"
|
|
}
|
|
|
|
func TestEvalMethod(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
source := `Subject is {{subject}}! YES I SAID {{Subject}}!`
|
|
expected := `Subject is foo! YES I SAID foo!`
|
|
|
|
ctx := &TestFoo{}
|
|
|
|
output := MustRender(source, ctx)
|
|
if output != expected {
|
|
t.Errorf("Failed to evaluate struct method: %s", output)
|
|
}
|
|
}
|
|
|
|
type TestBar struct{}
|
|
|
|
func (t *TestBar) Subject() interface{} {
|
|
return testBar
|
|
}
|
|
|
|
func testBar() string {
|
|
return "bar"
|
|
}
|
|
|
|
func TestEvalMethodReturningFunc(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
source := `Subject is {{subject}}! YES I SAID {{Subject}}!`
|
|
expected := `Subject is bar! YES I SAID bar!`
|
|
|
|
ctx := &TestBar{}
|
|
|
|
output := MustRender(source, ctx)
|
|
if output != expected {
|
|
t.Errorf("Failed to evaluate struct method: %s", output)
|
|
}
|
|
}
|