mirror of
https://github.com/mickael-menu/zk
synced 2024-11-15 12:12:56 +00:00
0eaf26483f
Finalise transfer from old repo (github.com/mickael-menu/zk) to new (github.com/zk-org/zk) Co-authored-by: tjex <tjex@tjex.net>
403 lines
10 KiB
Go
403 lines
10 KiB
Go
package handlebars
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/zk-org/zk/internal/adapter/handlebars/helpers"
|
|
"github.com/zk-org/zk/internal/core"
|
|
"github.com/zk-org/zk/internal/util"
|
|
"github.com/zk-org/zk/internal/util/fixtures"
|
|
"github.com/zk-org/zk/internal/util/paths"
|
|
"github.com/zk-org/zk/internal/util/test/assert"
|
|
)
|
|
|
|
func init() {
|
|
Init(true, &util.NullLogger)
|
|
}
|
|
|
|
// styler is a test double for core.Styler
|
|
// "hello", "red" -> "red(hello)"
|
|
type styler struct{}
|
|
|
|
func (s *styler) Style(text string, rules ...core.Style) (string, error) {
|
|
return s.MustStyle(text, rules...), nil
|
|
}
|
|
|
|
func (s *styler) MustStyle(text string, rules ...core.Style) string {
|
|
for _, rule := range rules {
|
|
text = fmt.Sprintf("%s(%s)", rule, text)
|
|
}
|
|
return text
|
|
}
|
|
|
|
func testString(t *testing.T, template string, context interface{}, expected string) {
|
|
sut := testLoader(LoaderOpts{})
|
|
|
|
templ, err := sut.LoadTemplate(template)
|
|
assert.Nil(t, err)
|
|
|
|
actual, err := templ.Render(context)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, actual, expected)
|
|
}
|
|
|
|
func testFile(t *testing.T, name string, context interface{}, expected string) {
|
|
sut := testLoader(LoaderOpts{})
|
|
|
|
templ, err := sut.LoadTemplateAt(fixtures.Path(name))
|
|
assert.Nil(t, err)
|
|
|
|
actual, err := templ.Render(context)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, actual, expected)
|
|
}
|
|
|
|
func TestLookupPaths(t *testing.T) {
|
|
root := fmt.Sprintf("/tmp/zk-test-%d", time.Now().Unix())
|
|
os.Remove(root)
|
|
path1 := filepath.Join(root, "1")
|
|
os.MkdirAll(path1, os.ModePerm)
|
|
path2 := filepath.Join(root, "1")
|
|
os.MkdirAll(filepath.Join(path2, "subdir"), os.ModePerm)
|
|
|
|
sut := testLoader(LoaderOpts{LookupPaths: []string{path1, path2}})
|
|
|
|
test := func(path string, expected string) {
|
|
tpl, err := sut.LoadTemplateAt(path)
|
|
assert.Nil(t, err)
|
|
res, err := tpl.Render(nil)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, res, expected)
|
|
}
|
|
|
|
test1 := filepath.Join(path1, "test1.tpl")
|
|
|
|
tpl1, err := sut.LoadTemplateAt(test1)
|
|
assert.Err(t, err, "cannot find template at "+test1)
|
|
assert.Nil(t, tpl1)
|
|
|
|
paths.WriteString(test1, "Test 1")
|
|
test(test1, "Test 1") // absolute
|
|
test("test1.tpl", "Test 1") // relative
|
|
|
|
test2 := filepath.Join(path2, "test2.tpl")
|
|
paths.WriteString(test2, "Test 2")
|
|
test(test2, "Test 2") // absolute
|
|
test("test2.tpl", "Test 2") // relative
|
|
|
|
test3 := filepath.Join(path2, "subdir/test3.tpl")
|
|
paths.WriteString(test3, "Test 3")
|
|
test(test3, "Test 3") // absolute
|
|
test("subdir/test3.tpl", "Test 3") // relative
|
|
}
|
|
|
|
func TestRenderString(t *testing.T) {
|
|
testString(t,
|
|
"Goodbye, {{name}}",
|
|
map[string]string{"name": "Ed"},
|
|
"Goodbye, Ed",
|
|
)
|
|
}
|
|
|
|
func TestRenderFile(t *testing.T) {
|
|
testFile(t,
|
|
"template.txt",
|
|
map[string]string{"name": "Thom"},
|
|
"Hello, Thom\n",
|
|
)
|
|
}
|
|
|
|
func TestUnknownVariable(t *testing.T) {
|
|
testString(t,
|
|
"Hi, {{unknown}}!",
|
|
nil,
|
|
"Hi, !",
|
|
)
|
|
}
|
|
|
|
func TestDoesntEscapeHTML(t *testing.T) {
|
|
testString(t,
|
|
"Salut, {{name}}!",
|
|
map[string]string{"name": "l'ami"},
|
|
"Salut, l'ami!",
|
|
)
|
|
|
|
testFile(t,
|
|
"unescape.txt",
|
|
map[string]string{"name": "l'ami"},
|
|
"Salut, l'ami!\n",
|
|
)
|
|
}
|
|
|
|
func TestConcatHelper(t *testing.T) {
|
|
testString(t, "{{concat '> ' 'A quote'}}", nil, "> A quote")
|
|
}
|
|
|
|
func TestSubstringHelper(t *testing.T) {
|
|
testString(t, "{{substring '' 2 4}}", nil, "")
|
|
testString(t, "{{substring 'A full quote' 2 4}}", nil, "full")
|
|
testString(t, "{{substring 'A full quote' 40 4}}", nil, "")
|
|
testString(t, "{{substring 'A full quote' -5 5}}", nil, "quote")
|
|
testString(t, "{{substring 'A full quote' -5 6}}", nil, "quote")
|
|
}
|
|
|
|
func TestJoinHelper(t *testing.T) {
|
|
test := func(items []string, expected string) {
|
|
context := map[string]interface{}{"items": items}
|
|
testString(t, "{{join items '-'}}", context, expected)
|
|
}
|
|
|
|
test([]string{}, "")
|
|
test([]string{"Item 1"}, "Item 1")
|
|
test([]string{"Item 1", "Item 2"}, "Item 1-Item 2")
|
|
test([]string{"Item 1", "Item 2", "Item 3"}, "Item 1-Item 2-Item 3")
|
|
}
|
|
|
|
type testJSONObject struct {
|
|
Foo string
|
|
Missing string `json:"missing,omitempty"`
|
|
List []string `json:"stringList"`
|
|
}
|
|
|
|
func TestJSONHelper(t *testing.T) {
|
|
test := func(value interface{}, expected string) {
|
|
context := map[string]interface{}{"value": value}
|
|
testString(t, "{{json value}}", context, expected)
|
|
}
|
|
|
|
test(`foo"bar"`, `"foo\"bar\""`)
|
|
test([]string{"foo", "bar"}, `["foo","bar"]`)
|
|
test(map[string]string{"foo": "bar"}, `{"foo":"bar"}`)
|
|
test(map[string]string{"foo": "bar"}, `{"foo":"bar"}`)
|
|
test(testJSONObject{
|
|
Foo: "baz",
|
|
List: []string{"foo", "bar"},
|
|
}, `{"Foo":"baz","stringList":["foo","bar"]}`)
|
|
}
|
|
|
|
func TestPrependHelper(t *testing.T) {
|
|
// inline
|
|
testString(t, "{{prepend '> ' 'A quote'}}", nil, "> A quote")
|
|
|
|
// block
|
|
testString(t, "{{#prepend '> '}}A quote{{/prepend}}", nil, "> A quote")
|
|
testString(t, "{{#prepend '> '}}A quote on\nseveral lines{{/prepend}}", nil, "> A quote on\n> several lines")
|
|
}
|
|
|
|
func TestListHelper(t *testing.T) {
|
|
test := func(items []string, expected string) {
|
|
context := map[string]interface{}{"items": items}
|
|
testString(t, "{{list items}}", context, expected)
|
|
}
|
|
test([]string{}, "")
|
|
test([]string{"Item 1"}, " ‣ Item 1\n")
|
|
test([]string{"Item 1", "Item 2"}, " ‣ Item 1\n ‣ Item 2\n")
|
|
test([]string{"Item 1", "Item 2", "Item 3"}, " ‣ Item 1\n ‣ Item 2\n ‣ Item 3\n")
|
|
test([]string{"An item\non several\nlines\n"}, " ‣ An item\n on several\n lines\n")
|
|
}
|
|
|
|
func TestLinkHelper(t *testing.T) {
|
|
sut := testLoader(LoaderOpts{})
|
|
|
|
templ, err := sut.LoadTemplate(`{{format-link "path/to note.md" "An interesting subject"}}`)
|
|
assert.Nil(t, err)
|
|
|
|
actual, err := templ.Render(map[string]interface{}{})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, actual, "path/to note.md - An interesting subject")
|
|
}
|
|
|
|
func TestSlugHelper(t *testing.T) {
|
|
// inline
|
|
testString(t,
|
|
`{{slug "This will be slugified!"}}`,
|
|
nil,
|
|
"this-will-be-slugified",
|
|
)
|
|
// block
|
|
testString(t,
|
|
"{{#slug}}This will be slugified!{{/slug}}",
|
|
nil,
|
|
"this-will-be-slugified",
|
|
)
|
|
}
|
|
|
|
func TestFormatDateHelper(t *testing.T) {
|
|
context := map[string]interface{}{"now": time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)}
|
|
testString(t, "{{format-date now}}", context, "2009-11-17")
|
|
testString(t, "{{format-date now 'short'}}", context, "11/17/2009")
|
|
testString(t, "{{format-date now 'medium'}}", context, "Nov 17, 2009")
|
|
testString(t, "{{format-date now 'long'}}", context, "November 17, 2009")
|
|
testString(t, "{{format-date now 'full'}}", context, "Tuesday, November 17, 2009")
|
|
testString(t, "{{format-date now 'year'}}", context, "2009")
|
|
testString(t, "{{format-date now 'time'}}", context, "20:34")
|
|
testString(t, "{{format-date now 'timestamp'}}", context, "200911172034")
|
|
testString(t, "{{format-date now 'timestamp-unix'}}", context, "1258490098")
|
|
testString(t, "{{format-date now 'cust: %Y-%m'}}", context, "cust: 2009-11")
|
|
}
|
|
|
|
func TestFormatDateHelperElapsedYear(t *testing.T) {
|
|
year := time.Now().UTC().Year() - 14
|
|
context := map[string]interface{}{"now": time.Date(year, 11, 17, 20, 34, 58, 651387237, time.UTC)}
|
|
testString(t, "{{format-date now 'elapsed'}}", context, "14 years ago")
|
|
}
|
|
|
|
func TestFormatDateHelperElapsedViaTimeMultiplication(t *testing.T) {
|
|
// test for time being provided in via multiplications on seconds, minutes
|
|
// and hours, as expected by github.com/rvflash/elapsed
|
|
cases := []struct {
|
|
elapsed time.Duration
|
|
want string
|
|
}{
|
|
{
|
|
elapsed: -12 * time.Second,
|
|
want: "not yet",
|
|
},
|
|
{
|
|
elapsed: time.Second,
|
|
want: "just now",
|
|
},
|
|
{
|
|
elapsed: 59 * time.Second,
|
|
want: "just now",
|
|
},
|
|
{
|
|
elapsed: 60 * time.Second,
|
|
want: "1 minute ago",
|
|
},
|
|
{
|
|
elapsed: 1 * time.Minute,
|
|
want: "1 minute ago",
|
|
},
|
|
{
|
|
elapsed: 2 * time.Minute,
|
|
want: "2 minutes ago",
|
|
},
|
|
{
|
|
elapsed: 62 * time.Minute,
|
|
want: "1 hour ago",
|
|
},
|
|
{
|
|
elapsed: time.Hour,
|
|
want: "1 hour ago",
|
|
},
|
|
{
|
|
elapsed: 2 * time.Hour,
|
|
want: "2 hours ago",
|
|
},
|
|
{
|
|
elapsed: 24 * time.Hour,
|
|
want: "yesterday",
|
|
},
|
|
{
|
|
elapsed: 4 * 24 * time.Hour,
|
|
want: "4 days ago",
|
|
},
|
|
{
|
|
elapsed: 7 * 24 * time.Hour,
|
|
want: "1 week ago",
|
|
},
|
|
{
|
|
elapsed: 8 * 24 * time.Hour,
|
|
want: "2 weeks ago",
|
|
},
|
|
{
|
|
elapsed: 18 * 24 * time.Hour,
|
|
want: "3 weeks ago",
|
|
},
|
|
{
|
|
elapsed: 30 * 24 * time.Hour,
|
|
want: "1 month ago",
|
|
},
|
|
{
|
|
elapsed: 31 * 24 * time.Hour,
|
|
want: "2 months ago",
|
|
},
|
|
{
|
|
elapsed: 60 * 24 * time.Hour,
|
|
want: "2 months ago",
|
|
},
|
|
{
|
|
elapsed: 61 * 24 * time.Hour,
|
|
want: "3 months ago",
|
|
},
|
|
{
|
|
elapsed: 330 * 24 * time.Hour,
|
|
want: "11 months ago",
|
|
},
|
|
{
|
|
elapsed: 331 * 24 * time.Hour,
|
|
want: "1 year ago",
|
|
},
|
|
{
|
|
elapsed: 366 * 24 * time.Hour,
|
|
want: "2 years ago",
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
t.Run(fmt.Sprintf("%d_%s", i, tc.want), func(t *testing.T) {
|
|
templateContext := map[string]interface{}{"now": time.Now().Add(-tc.elapsed)}
|
|
|
|
testString(t, "{{format-date now 'elapsed'}}", templateContext, tc.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDateHelper(t *testing.T) {
|
|
context := map[string]interface{}{"now": time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)}
|
|
testString(t, "{{format-date (date \"2009-11-17T20:34:58\") 'timestamp'}}", context, "200911172034")
|
|
}
|
|
|
|
func TestShellHelper(t *testing.T) {
|
|
// block is passed as piped input
|
|
testString(t,
|
|
`{{#sh "tr '[a-z]' '[A-Z]'"}}Hello, world!{{/sh}}`,
|
|
nil,
|
|
"HELLO, WORLD!",
|
|
)
|
|
// inline
|
|
testString(t,
|
|
`{{sh "echo 'Hello, world!'"}}`,
|
|
nil,
|
|
"Hello, world!",
|
|
)
|
|
|
|
// using pipes
|
|
testString(t, `{{sh "echo hello | tr '[:lower:]' '[:upper:]'"}}`, nil, "HELLO")
|
|
}
|
|
|
|
func TestStyleHelper(t *testing.T) {
|
|
// inline
|
|
testString(t, "{{style 'single' 'Some text'}}", nil, "single(Some text)")
|
|
testString(t, "{{style 'red bold' 'Another text'}}", nil, "bold(red(Another text))")
|
|
|
|
// block
|
|
testString(t, "{{#style 'single'}}A multiline\ntext{{/style}}", nil, "single(A multiline\ntext)")
|
|
}
|
|
|
|
func testLoader(opts LoaderOpts) *Loader {
|
|
if opts.LookupPaths == nil {
|
|
opts.LookupPaths = []string{}
|
|
}
|
|
if opts.Styler == nil {
|
|
opts.Styler = &styler{}
|
|
}
|
|
|
|
loader := NewLoader(opts)
|
|
|
|
loader.RegisterHelper("style", helpers.NewStyleHelper(opts.Styler, &util.NullLogger))
|
|
loader.RegisterHelper("slug", helpers.NewSlugHelper("en", &util.NullLogger))
|
|
|
|
formatter := func(context core.LinkFormatterContext) (string, error) {
|
|
return context.Path + " - " + context.Title, nil
|
|
}
|
|
loader.RegisterHelper("format-link", helpers.NewLinkHelper(formatter, &util.NullLogger))
|
|
|
|
return loader
|
|
}
|