From f244df62936eea307a5fc3f27fb9968527d402ac Mon Sep 17 00:00:00 2001 From: Frédéric Guillot Date: Thu, 20 Sep 2018 19:45:56 -0700 Subject: Add more unit tests for template functions --- template/dict.go | 22 --------- template/dict_test.go | 42 ---------------- template/engine.go | 2 + template/functions.go | 71 +++++++++++++++++---------- template/functions_test.go | 116 +++++++++++++++++++++++++++++++-------------- 5 files changed, 130 insertions(+), 123 deletions(-) delete mode 100644 template/dict.go delete mode 100644 template/dict_test.go (limited to 'template') diff --git a/template/dict.go b/template/dict.go deleted file mode 100644 index 1251bb6..0000000 --- a/template/dict.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package template // import "miniflux.app/template" - -import "fmt" - -func dict(values ...interface{}) (map[string]interface{}, error) { - if len(values)%2 != 0 { - return nil, fmt.Errorf("Dict expects an even number of arguments") - } - dict := make(map[string]interface{}, len(values)/2) - for i := 0; i < len(values); i += 2 { - key, ok := values[i].(string) - if !ok { - return nil, fmt.Errorf("Dict keys must be strings") - } - dict[key] = values[i+1] - } - return dict, nil -} diff --git a/template/dict_test.go b/template/dict_test.go deleted file mode 100644 index ef4b17c..0000000 --- a/template/dict_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package template // import "miniflux.app/template" - -import ( - "testing" -) - -func TestDict(t *testing.T) { - d, err := dict("k1", "v1", "k2", "v2") - if err != nil { - t.Fatalf(`The dict should be valid: %v`, err) - } - - if value, found := d["k1"]; found { - if value != "v1" { - t.Fatalf(`Incorrect value for k1: %q`, value) - } - } - - if value, found := d["k2"]; found { - if value != "v2" { - t.Fatalf(`Incorrect value for k2: %q`, value) - } - } -} - -func TestDictWithIncorrectNumberOfPairs(t *testing.T) { - _, err := dict("k1", "v1", "k2") - if err == nil { - t.Fatalf(`The dict should not be valid because the number of keys/values pairs are incorrect`) - } -} - -func TestDictWithInvalidKey(t *testing.T) { - _, err := dict(1, "v1") - if err == nil { - t.Fatalf(`The dict should not be valid because the key is not a string`) - } -} diff --git a/template/engine.go b/template/engine.go index 629059a..bd99742 100644 --- a/template/engine.go +++ b/template/engine.go @@ -44,6 +44,8 @@ func (e *Engine) Render(name, language string, data interface{}) []byte { } lang := e.translator.GetLanguage(language) + + // Functions that need to be declared at runtime. tpl.Funcs(template.FuncMap{ "elapsed": func(timezone string, t time.Time) string { return elapsedTime(lang, timezone, t) diff --git a/template/functions.go b/template/functions.go index 13c57aa..9bf1423 100644 --- a/template/functions.go +++ b/template/functions.go @@ -5,6 +5,7 @@ package template // import "miniflux.app/template" import ( + "fmt" "html/template" "net/mail" "strings" @@ -23,8 +24,13 @@ type funcMap struct { router *mux.Router } +// Map returns a map of template functions that are compiled during template parsing. func (f *funcMap) Map() template.FuncMap { return template.FuncMap{ + "dict": dict, + "hasKey": hasKey, + "truncate": truncate, + "isEmail": isEmail, "baseURL": func() string { return f.cfg.BaseURL() }, @@ -34,12 +40,6 @@ func (f *funcMap) Map() template.FuncMap { "hasOAuth2Provider": func(provider string) bool { return f.cfg.OAuth2Provider() == provider }, - "hasKey": func(dict map[string]string, key string) bool { - if value, found := dict[key]; found { - return value != "" - } - return false - }, "route": func(name string, args ...interface{}) string { return route.Path(f.router, name, args...) }, @@ -61,13 +61,6 @@ func (f *funcMap) Map() template.FuncMap { "domain": func(websiteURL string) string { return url.Domain(websiteURL) }, - "isEmail": func(str string) bool { - _, err := mail.ParseAddress(str) - if err != nil { - return false - } - return true - }, "hasPrefix": func(str, prefix string) bool { return strings.HasPrefix(str, prefix) }, @@ -77,17 +70,6 @@ func (f *funcMap) Map() template.FuncMap { "isodate": func(ts time.Time) string { return ts.Format("2006-01-02 15:04:05") }, - "dict": dict, - "truncate": func(str string, max int) string { - runes := 0 - for i := range str { - runes++ - if runes > max { - return str[:i] + "…" - } - } - return str - }, "theme_color": func(theme string) string { return model.ThemeColor(theme) }, @@ -108,3 +90,44 @@ func (f *funcMap) Map() template.FuncMap { func newFuncMap(cfg *config.Config, router *mux.Router) *funcMap { return &funcMap{cfg, router} } + +func dict(values ...interface{}) (map[string]interface{}, error) { + if len(values)%2 != 0 { + return nil, fmt.Errorf("dict expects an even number of arguments") + } + dict := make(map[string]interface{}, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].(string) + if !ok { + return nil, fmt.Errorf("dict keys must be strings") + } + dict[key] = values[i+1] + } + return dict, nil +} + +func hasKey(dict map[string]string, key string) bool { + if value, found := dict[key]; found { + return value != "" + } + return false +} + +func truncate(str string, max int) string { + runes := 0 + for i := range str { + runes++ + if runes > max { + return str[:i] + "…" + } + } + return str +} + +func isEmail(str string) bool { + _, err := mail.ParseAddress(str) + if err != nil { + return false + } + return true +} \ No newline at end of file diff --git a/template/functions_test.go b/template/functions_test.go index a475036..393fca9 100644 --- a/template/functions_test.go +++ b/template/functions_test.go @@ -8,41 +8,87 @@ import ( "testing" ) -func TestTruncate(t *testing.T) { - fm := funcMap{} - if f, ok := fm.Map()["truncate"]; ok { - if truncate := f.(func(str string, max int) string); ok { - shortEnglishText := "Short text" - shortUnicodeText := "Короткий текст" - - // edge case - if truncate(shortEnglishText, len(shortEnglishText)) != shortEnglishText { - t.Fatal("Invalid truncation") - } - // real case - if truncate(shortEnglishText, 25) != shortEnglishText { - t.Fatal("Invalid truncation") - } - if truncate(shortUnicodeText, len(shortUnicodeText)) != shortUnicodeText { - t.Fatal("Invalid truncation") - } - if truncate(shortUnicodeText, 25) != shortUnicodeText { - t.Fatal("Invalid truncation") - } - - longEnglishText := "This is really pretty long English text" - longRussianText := "Это реально очень длинный русский текст" - - if truncate(longEnglishText, 25) != "This is really pretty lon…" { - t.Fatal("Invalid truncation") - } - if truncate(longRussianText, 25) != "Это реально очень длинный…" { - t.Fatal("Invalid truncation") - } - } else { - t.Fatal("Type assetion for this func is failed, check func, maybe it was changed") +func TestDict(t *testing.T) { + d, err := dict("k1", "v1", "k2", "v2") + if err != nil { + t.Fatalf(`The dict should be valid: %v`, err) + } + + if value, found := d["k1"]; found { + if value != "v1" { + t.Fatalf(`Unexpected value for k1: got %q`, value) + } + } + + if value, found := d["k2"]; found { + if value != "v2" { + t.Fatalf(`Unexpected value for k2: got %q`, value) } - } else { - t.Fatal("There is no such function in this map, check key, maybe it was changed") + } +} + +func TestDictWithInvalidNumberOfArguments(t *testing.T) { + _, err := dict("k1") + if err == nil { + t.Fatal(`An error should be returned if the number of arguments are not even`) + } +} + +func TestDictWithInvalidMap(t *testing.T) { + _, err := dict(1, 2) + if err == nil { + t.Fatal(`An error should be returned if the dict keys are not string`) + } +} + +func TestHasKey(t *testing.T) { + input := map[string]string{"k": "v"} + + if !hasKey(input, "k") { + t.Fatal(`This key exists in the map and should returns true`) + } + + if hasKey(input, "missing") { + t.Fatal(`This key doesn't exists in the given map and should returns false`) + } +} + +func TestTruncateWithShortTexts(t *testing.T) { + scenarios := []string{"Short text", "Короткий текст"} + + for _, input := range scenarios { + result := truncate(input, 25) + if result != input { + t.Fatalf(`Unexpected output, got %q instead of %q`, result, input) + } + + result = truncate(input, len(input)) + if result != input { + t.Fatalf(`Unexpected output, got %q instead of %q`, result, input) + } + } +} + +func TestTruncateWithLongTexts(t *testing.T) { + scenarios := map[string]string{ + "This is a really pretty long English text": "This is a really pretty l…", + "Это реально очень длинный русский текст": "Это реально очень длинный…", + } + + for input, expected := range scenarios { + result := truncate(input, 25) + if result != expected { + t.Fatalf(`Unexpected output, got %q instead of %q`, result, expected) + } + } +} + +func TestIsEmail(t *testing.T) { + if !isEmail("user@domain.tld") { + t.Fatal(`This email is valid and should returns true`) + } + + if isEmail("invalid") { + t.Fatal(`This email is not valid and should returns false`) } } -- cgit v1.2.3