Test the note formatter

pull/6/head
Mickaël Menu 3 years ago
parent 5759896fd4
commit 5c59d7946b
No known key found for this signature in database
GPG Key ID: 53D73664CD359895

@ -7,8 +7,8 @@ import (
"github.com/mickael-menu/zk/core/style"
"github.com/mickael-menu/zk/util"
"github.com/mickael-menu/zk/util/test/assert"
"github.com/mickael-menu/zk/util/fixtures"
"github.com/mickael-menu/zk/util/test/assert"
)
func init() {

@ -3,19 +3,16 @@ package note
import (
"fmt"
"testing"
"time"
"github.com/mickael-menu/zk/core/templ"
"github.com/mickael-menu/zk/core/zk"
"github.com/mickael-menu/zk/util/test/assert"
"github.com/mickael-menu/zk/util/opt"
"github.com/mickael-menu/zk/util/test/assert"
)
var now = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
func TestCreate(t *testing.T) {
filenameTemplate := spyTemplateString("filename")
bodyTemplate := spyTemplateString("body")
filenameTemplate := NewRendererSpyString("filename")
bodyTemplate := NewRendererSpyString("body")
res, err := create(
CreateOpts{
@ -33,11 +30,11 @@ func TestCreate(t *testing.T) {
Content: opt.NewString("Note content"),
},
createDeps{
filenameTemplate: &filenameTemplate,
bodyTemplate: &bodyTemplate,
filenameTemplate: filenameTemplate,
bodyTemplate: bodyTemplate,
genId: func() string { return "abc" },
validatePath: func(path string) (bool, error) { return true, nil },
now: now,
now: Now,
},
)
@ -49,7 +46,7 @@ func TestCreate(t *testing.T) {
})
// Check that the templates received the proper render contexts.
assert.Equal(t, filenameTemplate.Contexts, []renderContext{{
assert.Equal(t, filenameTemplate.Contexts, []interface{}{renderContext{
ID: "abc",
Title: "Note title",
Content: "Note content",
@ -57,9 +54,9 @@ func TestCreate(t *testing.T) {
Extra: map[string]string{
"hello": "world",
},
Now: now,
Now: Now,
}})
assert.Equal(t, bodyTemplate.Contexts, []renderContext{{
assert.Equal(t, bodyTemplate.Contexts, []interface{}{renderContext{
ID: "abc",
Title: "Note title",
Content: "Note content",
@ -69,15 +66,15 @@ func TestCreate(t *testing.T) {
Extra: map[string]string{
"hello": "world",
},
Now: now,
Now: Now,
}})
}
func TestCreateTriesUntilValidPath(t *testing.T) {
filenameTemplate := spyTemplate(func(context renderContext) string {
return context.ID
filenameTemplate := NewRendererSpy(func(context interface{}) string {
return context.(renderContext).ID
})
bodyTemplate := spyTemplateString("body")
bodyTemplate := NewRendererSpyString("body")
res, err := create(
CreateOpts{
@ -91,13 +88,13 @@ func TestCreateTriesUntilValidPath(t *testing.T) {
Title: opt.NewString("Note title"),
},
createDeps{
filenameTemplate: &filenameTemplate,
bodyTemplate: &bodyTemplate,
filenameTemplate: filenameTemplate,
bodyTemplate: bodyTemplate,
genId: incrementingID(),
validatePath: func(path string) (bool, error) {
return path == "/test/log/3.md", nil
},
now: now,
now: Now,
},
)
@ -108,24 +105,24 @@ func TestCreateTriesUntilValidPath(t *testing.T) {
content: "body",
})
assert.Equal(t, filenameTemplate.Contexts, []renderContext{
{
assert.Equal(t, filenameTemplate.Contexts, []interface{}{
renderContext{
ID: "1",
Title: "Note title",
Dir: "log",
Now: now,
Now: Now,
},
{
renderContext{
ID: "2",
Title: "Note title",
Dir: "log",
Now: now,
Now: Now,
},
{
renderContext{
ID: "3",
Title: "Note title",
Dir: "log",
Now: now,
Now: Now,
},
})
}
@ -148,38 +145,14 @@ func TestCreateErrorWhenNoValidPaths(t *testing.T) {
bodyTemplate: templ.NullRenderer,
genId: func() string { return "abc" },
validatePath: func(path string) (bool, error) { return false, nil },
now: now,
now: Now,
},
)
assert.Err(t, err, "/test/log/filename.md: note already exists")
}
func spyTemplate(result func(renderContext) string) TemplateSpy {
return TemplateSpy{
Contexts: make([]renderContext, 0),
Result: result,
}
}
func spyTemplateString(result string) TemplateSpy {
return TemplateSpy{
Contexts: make([]renderContext, 0),
Result: func(_ renderContext) string { return result },
}
}
type TemplateSpy struct {
Result func(renderContext) string
Contexts []renderContext
}
func (m *TemplateSpy) Render(context interface{}) (string, error) {
renderContext := context.(renderContext)
m.Contexts = append(m.Contexts, renderContext)
return m.Result(renderContext), nil
}
// incrementingID returns a generator of incrementing string ID.
func incrementingID() func() string {
i := 0
return func() string {

@ -63,28 +63,24 @@ var formatTemplates = map[string]string{
"short": `{{style "title" title}} {{style "path" path}} ({{date created "elapsed"}})
{{prepend " " snippet}}
`,
{{prepend " " snippet}}`,
"medium": `{{style "title" title}} {{style "path" path}}
Created: {{date created "short"}}
{{prepend " " snippet}}
`,
{{prepend " " snippet}}`,
"long": `{{style "title" title}} {{style "path" path}}
Created: {{date created "short"}}
Modified: {{date created "short"}}
{{prepend " " snippet}}
`,
{{prepend " " snippet}}`,
"full": `{{style "title" title}} {{style "path" path}}
Created: {{date created "short"}}
Modified: {{date created "short"}}
{{prepend " " body}}
`,
{{prepend " " body}}`,
}
var termRegex = regexp.MustCompile(`<zk:match>(.*?)</zk:match>`)
@ -108,6 +104,7 @@ func (f *Formatter) Format(match Match) (string, error) {
WordCount: match.WordCount,
Created: match.Created,
Modified: match.Modified,
Checksum: match.Checksum,
})
}
@ -121,4 +118,5 @@ type formatRenderContext struct {
WordCount int `handlebars:"word-count"`
Created time.Time
Modified time.Time
Checksum string
}

@ -0,0 +1,164 @@
package note
import (
"testing"
"time"
"github.com/mickael-menu/zk/util/opt"
"github.com/mickael-menu/zk/util/test/assert"
)
func TestEmptyFormat(t *testing.T) {
f, _ := newFormatter(t, opt.NewString(""))
res, err := f.Format(Match{})
assert.Nil(t, err)
assert.Equal(t, res, "")
}
func TestDefaultFormat(t *testing.T) {
f, _ := newFormatter(t, opt.NullString)
res, err := f.Format(Match{})
assert.Nil(t, err)
assert.Equal(t, res, `{{style "title" title}} {{style "path" path}} ({{date created "elapsed"}})
{{prepend " " snippet}}`)
}
func TestFormats(t *testing.T) {
test := func(format string, expected string) {
f, _ := newFormatter(t, opt.NewString(format))
actual, err := f.Format(Match{})
assert.Nil(t, err)
assert.Equal(t, actual, expected)
}
// Known formats
test("path", `{{path}}`)
test("oneline", `{{style "title" title}} {{style "path" path}} ({{date created "elapsed"}})`)
test("short", `{{style "title" title}} {{style "path" path}} ({{date created "elapsed"}})
{{prepend " " snippet}}`)
test("medium", `{{style "title" title}} {{style "path" path}}
Created: {{date created "short"}}
{{prepend " " snippet}}`)
test("long", `{{style "title" title}} {{style "path" path}}
Created: {{date created "short"}}
Modified: {{date created "short"}}
{{prepend " " snippet}}`)
test("full", `{{style "title" title}} {{style "path" path}}
Created: {{date created "short"}}
Modified: {{date created "short"}}
{{prepend " " body}}`)
// Known formats are case sensitive.
test("Path", "Path")
// Custom formats are used literally.
test("{{title}}", "{{title}}")
// \n and \t in custom formats are expanded.
test(`{{title}}\t{{path}}\n{{snippet}}`, "{{title}}\t{{path}}\n{{snippet}}")
}
func TestFormatRenderContext(t *testing.T) {
f, templs := newFormatter(t, opt.NewString("path"))
_, err := f.Format(Match{
Snippet: "Note snippet",
Metadata: Metadata{
Path: "dir/note.md",
Title: "Note title",
Lead: "Lead paragraph",
Body: "Note body",
RawContent: "Raw content",
WordCount: 42,
Created: Now,
Modified: Now.Add(48 * time.Hour),
Checksum: "Note checksum",
},
})
assert.Nil(t, err)
// Check that the template was provided with the proper information in the
// render context.
assert.Equal(t, templs.Contexts, []interface{}{
formatRenderContext{
Path: "dir/note.md",
Title: "Note title",
Lead: "Lead paragraph",
Body: "Note body",
Snippet: "Note snippet",
RawContent: "Raw content",
WordCount: 42,
Created: Now,
Modified: Now.Add(48 * time.Hour),
Checksum: "Note checksum",
},
})
}
func TestFormatPath(t *testing.T) {
test := func(basePath, currentPath, path string, expected string) {
f, templs := newFormatterWithPaths(t, basePath, currentPath, opt.NullString)
_, err := f.Format(Match{
Metadata: Metadata{Path: path},
})
assert.Nil(t, err)
assert.Equal(t, templs.Contexts, []interface{}{
formatRenderContext{
Path: expected,
},
})
}
// Check that the path is relative to the current directory.
test("", "", "note.md", "note.md")
test("", "", "dir/note.md", "dir/note.md")
test("/abs/zk", "/abs/zk", "note.md", "note.md")
test("/abs/zk", "/abs/zk", "dir/note.md", "dir/note.md")
test("/abs/zk", "/abs/zk/dir", "note.md", "../note.md")
test("/abs/zk", "/abs/zk/dir", "dir/note.md", "note.md")
test("/abs/zk", "/abs", "note.md", "zk/note.md")
test("/abs/zk", "/abs", "dir/note.md", "zk/dir/note.md")
}
func TestFormatStylesSnippetTerm(t *testing.T) {
test := func(snippet string, expected string) {
f, templs := newFormatter(t, opt.NullString)
_, err := f.Format(Match{
Snippet: snippet,
})
assert.Nil(t, err)
assert.Equal(t, templs.Contexts, []interface{}{
formatRenderContext{
Path: ".",
Snippet: expected,
},
})
}
test("Hello world!", "Hello world!")
test("Hello <zk:match>world</zk:match>!", "Hello term(world)!")
test("Hello <zk:match>world</zk:match> with <zk:match>several matches</zk:match>!", "Hello term(world) with term(several matches)!")
test("Hello <zk:match>world</zk:match> with <zk:match>several<zk:match> matches</zk:match>!", "Hello term(world) with term(several<zk:match> matches)!")
}
func newFormatter(t *testing.T, format opt.String) (*Formatter, *TemplLoaderSpy) {
return newFormatterWithPaths(t, "", "", format)
}
func newFormatterWithPaths(t *testing.T, basePath, currentPath string, format opt.String) (*Formatter, *TemplLoaderSpy) {
loader := NewTemplLoaderSpy()
styler := &StylerMock{}
formatter, err := NewFormatter(basePath, currentPath, format, loader, styler)
assert.Nil(t, err)
return formatter, loader
}

@ -0,0 +1,73 @@
package note
import (
"fmt"
"time"
"github.com/mickael-menu/zk/core/style"
"github.com/mickael-menu/zk/core/templ"
)
var Now = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
// TemplLoaderSpy implements templ.Loader and saves the render contexts
// provided to the templates it creates.
//
// The generated Renderer returns the template used to create them without
// modification.
type TemplLoaderSpy struct {
Contexts []interface{}
}
func NewTemplLoaderSpy() *TemplLoaderSpy {
return &TemplLoaderSpy{
Contexts: make([]interface{}, 0),
}
}
func (l *TemplLoaderSpy) Load(template string) (templ.Renderer, error) {
return NewRendererSpy(func(context interface{}) string {
l.Contexts = append(l.Contexts, context)
return template
}), nil
}
func (l *TemplLoaderSpy) LoadFile(path string) (templ.Renderer, error) {
panic("not implemented")
}
// RendererSpy implements templ.Renderer and saves the provided render contexts.
type RendererSpy struct {
Result func(interface{}) string
Contexts []interface{}
}
func NewRendererSpy(result func(interface{}) string) *RendererSpy {
return &RendererSpy{
Contexts: make([]interface{}, 0),
Result: result,
}
}
func NewRendererSpyString(result string) *RendererSpy {
return &RendererSpy{
Contexts: make([]interface{}, 0),
Result: func(_ interface{}) string { return result },
}
}
func (m *RendererSpy) Render(context interface{}) (string, error) {
m.Contexts = append(m.Contexts, context)
return m.Result(context), nil
}
// StylerMock implements core.Styler by doing the transformation:
// "hello", "red" -> "red(hello)"
type StylerMock struct{}
func (s *StylerMock) Style(text string, rules ...style.Rule) (string, error) {
for _, rule := range rules {
text = fmt.Sprintf("%s(%s)", rule, text)
}
return text, nil
}
Loading…
Cancel
Save