1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-26 23:24:38 +01:00
This commit is contained in:
Oliver Steele 2017-07-14 14:28:02 -04:00
parent 1c94b61bcc
commit a2a4a1a5ec
6 changed files with 110 additions and 37 deletions

View File

@ -35,15 +35,17 @@ func conversionError(modifier string, value interface{}, typ reflect.Type) error
func Convert(value interface{}, typ reflect.Type) (interface{}, error) { // nolint: gocyclo
value = ToLiquid(value)
r := reflect.ValueOf(value)
switch {
case typ.Kind() != reflect.String && value != nil && r.Type().ConvertibleTo(typ):
// convert int.Convert(string) yields "\x01" not "1"
// int.Convert(string) returns "\x01" not "1", so guard against that in the following test
if typ.Kind() != reflect.String && value != nil && r.Type().ConvertibleTo(typ) {
return r.Convert(typ).Interface(), nil
case typ == timeType && r.Kind() == reflect.String:
}
if typ == timeType && r.Kind() == reflect.String {
return ParseDate(value.(string))
}
// currently unused:
// case reflect.PtrTo(r.Type()) == typ:
// return &value, nil
}
// }
switch typ.Kind() {
case reflect.Bool:
return !(value == nil || value == false), nil
@ -59,8 +61,7 @@ func Convert(value interface{}, typ reflect.Type) (interface{}, error) { // noli
}
case reflect.Float32, reflect.Float64:
switch value := value.(type) {
case int:
return float64(value), nil
// case int is handled by r.Convert(type) above
case string:
return strconv.ParseFloat(value, 64)
}

View File

@ -43,6 +43,13 @@ var convertTests = []struct {
// {"March 14, 2016", time.Now(), timeMustParse("2016-03-14T00:00:00Z")},
{redConvertible{}, "", "red"},
}
var convertErrorTests = []struct {
value, proto, expected interface{}
}{
{map[string]bool{"k": true}, map[int]bool{}, "map key"},
{map[string]string{"k": "v"}, map[string]int{}, "map value"},
{map[interface{}]interface{}{"k": "v"}, map[string]int{}, "map value"},
}
func TestConvert(t *testing.T) {
for i, test := range convertTests {
@ -55,6 +62,19 @@ func TestConvert(t *testing.T) {
})
}
}
func TestConvert_errors(t *testing.T) {
for i, test := range convertErrorTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
typ := reflect.TypeOf(test.proto)
name := fmt.Sprintf("Convert %#v -> %v", test.value, typ)
_, err := Convert(test.value, typ)
require.Errorf(t, err, name)
require.Containsf(t, err.Error(), test.expected, name)
})
}
}
func TestConvert_map(t *testing.T) {
typ := reflect.TypeOf(map[string]string{})
v, err := Convert(map[interface{}]interface{}{"key": "value"}, typ)
@ -64,12 +84,6 @@ func TestConvert_map(t *testing.T) {
require.Equal(t, "value", m["key"])
}
func TestConvert_map_key_error(t *testing.T) {
typ := reflect.TypeOf(map[string]int{})
_, err := Convert(map[interface{}]interface{}{"key": "value"}, typ)
require.Error(t, err)
}
func TestConvert_map_synonym(t *testing.T) {
type VariableMap map[interface{}]interface{}
typ := reflect.TypeOf(map[string]string{})

View File

@ -7,7 +7,7 @@ import (
func init() {
// This allows adding and removing references to fmt in the rules below,
// without having to edit the import statement to avoid erorrs each time.
// without having to comment and un-comment the import statement above.
_ = fmt.Sprint("")
}

View File

@ -12,23 +12,27 @@ import (
)
func addRenderTestTags(s Config) {
s.AddBlock("err2").Compiler(func(c BlockNode) (func(io.Writer, Context) error, error) {
s.AddBlock("errblock").Compiler(func(c BlockNode) (func(io.Writer, Context) error, error) {
return func(w io.Writer, c Context) error {
return fmt.Errorf("stage 2 error")
return fmt.Errorf("errblock error")
}, nil
})
}
var renderTests = []struct{ in, out string }{
{`{{ nil }}`, ""},
{`{{ true }}`, "true"},
{`{{ false }}`, "false"},
{`{{ 12 }}`, "12"},
{`{{ 12.3 }}`, "12.3"},
{`{{ "abc" }}`, "abc"},
{`{{ x }}`, "123"},
{`{{ page.title }}`, "Introduction"},
{`{{ array[1] }}`, "second"},
}
var renderErrorTests = []struct{ in, out string }{
// {"{%if syntax error%}{%endif%}", "parse error"},
{`{% err2 %}{% enderr2 %}`, "stage 2 error"},
{`{% errblock %}{% enderrblock %}`, "errblock error"},
}
var renderTestBindings = map[string]interface{}{

View File

@ -3,6 +3,8 @@ package tags
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"testing"
"github.com/osteele/liquid/parser"
@ -43,17 +45,53 @@ var cfTagTests = []struct{ in, expected string }{
{`{% unless false %}true{% endunless %}`, "true"},
}
var cfTagCompilationErrorTests = []struct{ in, expected string }{
{`{% case syntax error %}{% when 1 %}{% endcase %}`, "syntax error"},
}
var cfTagErrorTests = []struct{ in, expected string }{
{`{% case 1 %}{% when 1 %}{% error %}{% endcase %}`, "tag render error"},
}
func TestControlFlowTags(t *testing.T) {
config := render.NewConfig()
AddStandardTags(config)
cfg := render.NewConfig()
AddStandardTags(cfg)
for i, test := range cfTagTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
ast, err := config.Compile(test.in, parser.SourceLoc{})
root, err := cfg.Compile(test.in, parser.SourceLoc{})
require.NoErrorf(t, err, test.in)
buf := new(bytes.Buffer)
err = render.Render(ast, buf, tagTestBindings, config)
err = render.Render(root, buf, tagTestBindings, cfg)
require.NoErrorf(t, err, test.in)
require.Equalf(t, test.expected, buf.String(), test.in)
})
}
}
func TestControlFlowTags_errors(t *testing.T) {
cfg := render.NewConfig()
AddStandardTags(cfg)
cfg.AddTag("error", func(string) (func(io.Writer, render.Context) error, error) {
return func(io.Writer, render.Context) error {
return fmt.Errorf("tag render error")
}, nil
})
for i, test := range cfTagCompilationErrorTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
_, err := cfg.Compile(test.in, parser.SourceLoc{})
require.Errorf(t, err, test.in)
require.Contains(t, err.Error(), test.expected, test.in)
})
}
for i, test := range cfTagErrorTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
root, err := cfg.Compile(test.in, parser.SourceLoc{})
require.NoErrorf(t, err, test.in)
err = render.Render(root, ioutil.Discard, tagTestBindings, cfg)
require.Errorf(t, err, test.in)
require.Contains(t, err.Error(), test.expected, test.in)
})
}
}

View File

@ -3,6 +3,7 @@ package tags
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/osteele/liquid/parser"
@ -10,7 +11,7 @@ import (
"github.com/stretchr/testify/require"
)
var loopTests = []struct{ in, expected string }{
var iterationTests = []struct{ in, expected string }{
{`{% for a in array %}{{ a }} {% endfor %}`, "first second third "},
// loop modifiers
@ -69,12 +70,19 @@ var loopTests = []struct{ in, expected string }{
{`{% for i in (3..5) %}{{i}}.{% endfor %}`, "3.4.5."},
}
var loopErrorTests = []struct{ in, expected string }{
{`{% break %}`, "break outside a loop"},
{`{% continue %}`, "continue outside a loop"},
var iterationSyntaxErrorTests = []struct{ in, expected string }{
{`{% for a b c %}{% endfor %}`, "parse error"},
{`{% for a in array offset %}{% endfor %}`, "undefined loop modifier"},
{`{% cycle %}`, "parse error"},
}
var loopTestBindings = map[string]interface{}{
var iterationErrorTests = []struct{ in, expected string }{
{`{% break %}`, "break outside a loop"},
{`{% continue %}`, "continue outside a loop"},
{`{% cycle 'a', 'b' %}`, "cycle must be within a forloop"},
}
var iterationTestBindings = map[string]interface{}{
"array": []string{"first", "second", "third"},
"hash": map[string]interface{}{"a": 1},
}
@ -82,12 +90,12 @@ var loopTestBindings = map[string]interface{}{
func TestLoopTag(t *testing.T) {
config := render.NewConfig()
AddStandardTags(config)
for i, test := range loopTests {
for i, test := range iterationTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
ast, err := config.Compile(test.in, parser.SourceLoc{})
root, err := config.Compile(test.in, parser.SourceLoc{})
require.NoErrorf(t, err, test.in)
buf := new(bytes.Buffer)
err = render.Render(ast, buf, loopTestBindings, config)
err = render.Render(root, buf, iterationTestBindings, config)
require.NoErrorf(t, err, test.in)
require.Equalf(t, test.expected, buf.String(), test.in)
})
@ -97,12 +105,20 @@ func TestLoopTag(t *testing.T) {
func TestLoopTag_errors(t *testing.T) {
config := render.NewConfig()
AddStandardTags(config)
for i, test := range loopErrorTests {
for i, test := range iterationSyntaxErrorTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
ast, err := config.Compile(test.in, parser.SourceLoc{})
_, err := config.Compile(test.in, parser.SourceLoc{})
require.Errorf(t, err, test.in)
require.Containsf(t, err.Error(), test.expected, test.in)
})
}
for i, test := range iterationErrorTests {
t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) {
root, err := config.Compile(test.in, parser.SourceLoc{})
require.NoErrorf(t, err, test.in)
buf := new(bytes.Buffer)
err = render.Render(ast, buf, loopTestBindings, config)
err = render.Render(root, ioutil.Discard, iterationTestBindings, config)
require.Errorf(t, err, test.in)
require.Containsf(t, err.Error(), test.expected, test.in)
})