port tests from original author, fix bugs
This commit is contained in:
parent
a4f3b9048f
commit
79c3d5aa23
|
|
@ -0,0 +1,680 @@
|
|||
package metadata
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestFormatTags validates tag formatting for different file types
|
||||
// Maps to dt-denote--format-front-matter from original tests
|
||||
func TestFormatTags(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tags []string
|
||||
fileType string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "org with multiple tags",
|
||||
tags: []string{"tag1", "tag2"},
|
||||
fileType: "org",
|
||||
want: ":tag1:tag2:",
|
||||
},
|
||||
{
|
||||
name: "org with single tag",
|
||||
tags: []string{"single"},
|
||||
fileType: "org",
|
||||
want: ":single:",
|
||||
},
|
||||
{
|
||||
name: "org with empty tags",
|
||||
tags: []string{},
|
||||
fileType: "org",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "md-yaml with multiple tags",
|
||||
tags: []string{"tag1", "tag2"},
|
||||
fileType: "md-yaml",
|
||||
want: "[tag1, tag2]",
|
||||
},
|
||||
{
|
||||
name: "md-yaml with single tag",
|
||||
tags: []string{"single"},
|
||||
fileType: "md-yaml",
|
||||
want: "[single]",
|
||||
},
|
||||
{
|
||||
name: "md-yaml with empty tags",
|
||||
tags: []string{},
|
||||
fileType: "md-yaml",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "md-toml with multiple tags",
|
||||
tags: []string{"tag1", "tag2"},
|
||||
fileType: "md-toml",
|
||||
want: "[tag1, tag2]",
|
||||
},
|
||||
{
|
||||
name: "md-toml with empty tags",
|
||||
tags: []string{},
|
||||
fileType: "md-toml",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "txt with multiple tags",
|
||||
tags: []string{"tag1", "tag2"},
|
||||
fileType: "txt",
|
||||
want: "tag1 tag2",
|
||||
},
|
||||
{
|
||||
name: "txt with single tag",
|
||||
tags: []string{"single"},
|
||||
fileType: "txt",
|
||||
want: "single",
|
||||
},
|
||||
{
|
||||
name: "txt with empty tags",
|
||||
tags: []string{},
|
||||
fileType: "txt",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := FormatTags(tt.tags, tt.fileType)
|
||||
if got != tt.want {
|
||||
t.Errorf("FormatTags(%v, %q) = %q, want %q", tt.tags, tt.fileType, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerate validates front matter generation for all file types
|
||||
// Maps to dt-denote--format-front-matter from original tests
|
||||
func TestGenerate(t *testing.T) {
|
||||
identifier := "20240101T120000"
|
||||
title := "Test Note"
|
||||
tags := []string{"tag1", "tag2"}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fileType string
|
||||
wantContains []string
|
||||
wantNotContains []string
|
||||
}{
|
||||
{
|
||||
name: "org format",
|
||||
fileType: "org",
|
||||
wantContains: []string{
|
||||
"#+title: Test Note",
|
||||
"#+filetags: :tag1:tag2:",
|
||||
"#+identifier: 20240101T120000",
|
||||
"#+date:",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "md-yaml format",
|
||||
fileType: "md-yaml",
|
||||
wantContains: []string{
|
||||
"---",
|
||||
"title: Test Note",
|
||||
"tags: [tag1, tag2]",
|
||||
"identifier: 20240101T120000",
|
||||
"date:",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "md-toml format",
|
||||
fileType: "md-toml",
|
||||
wantContains: []string{
|
||||
"+++",
|
||||
"title = Test Note",
|
||||
"tags = [tag1, tag2]",
|
||||
"identifier = 20240101T120000",
|
||||
"date =",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "txt format",
|
||||
fileType: "txt",
|
||||
wantContains: []string{
|
||||
"title: Test Note",
|
||||
"tags: tag1 tag2",
|
||||
"identifier: 20240101T120000",
|
||||
"date:",
|
||||
"---------------------------",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Generate(title, tags, tt.fileType, identifier)
|
||||
|
||||
for _, want := range tt.wantContains {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("Generate(%q, %v, %q, %q) missing %q\nGot:\n%s",
|
||||
title, tags, tt.fileType, identifier, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
for _, notWant := range tt.wantNotContains {
|
||||
if strings.Contains(got, notWant) {
|
||||
t.Errorf("Generate(%q, %v, %q, %q) should not contain %q\nGot:\n%s",
|
||||
title, tags, tt.fileType, identifier, notWant, got)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateEmptyTags validates front matter generation with no tags
|
||||
func TestGenerateEmptyTags(t *testing.T) {
|
||||
identifier := "20240101T120000"
|
||||
title := "Test Note"
|
||||
tags := []string{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fileType string
|
||||
wantTags string
|
||||
}{
|
||||
{
|
||||
name: "org with empty tags",
|
||||
fileType: "org",
|
||||
wantTags: "#+filetags:",
|
||||
},
|
||||
{
|
||||
name: "md-yaml with empty tags",
|
||||
fileType: "md-yaml",
|
||||
wantTags: "tags:",
|
||||
},
|
||||
{
|
||||
name: "md-toml with empty tags",
|
||||
fileType: "md-toml",
|
||||
wantTags: "tags =",
|
||||
},
|
||||
{
|
||||
name: "txt with empty tags",
|
||||
fileType: "txt",
|
||||
wantTags: "tags:",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Generate(title, tags, tt.fileType, identifier)
|
||||
|
||||
if !strings.Contains(got, tt.wantTags) {
|
||||
t.Errorf("Generate() with empty tags should contain %q\nGot:\n%s",
|
||||
tt.wantTags, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseFrontMatter validates front matter parsing from content
|
||||
func TestParseFrontMatter(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
ext string
|
||||
wantTitle string
|
||||
wantTags []string
|
||||
wantIdentifier string
|
||||
wantFileType string
|
||||
}{
|
||||
{
|
||||
name: "org format",
|
||||
content: `#+title: Org Note
|
||||
#+date: [2024-01-01 Mon 12:00]
|
||||
#+filetags: :work:emacs:
|
||||
#+identifier: 20240101T120000
|
||||
|
||||
* First Heading`,
|
||||
ext: ".org",
|
||||
wantTitle: "Org Note",
|
||||
wantTags: []string{"work", "emacs"},
|
||||
wantIdentifier: "20240101T120000",
|
||||
wantFileType: "org",
|
||||
},
|
||||
{
|
||||
name: "org with single tag",
|
||||
content: `#+title: Single Tag
|
||||
#+filetags: :single:
|
||||
#+identifier: 20240101T120000`,
|
||||
ext: ".org",
|
||||
wantTitle: "Single Tag",
|
||||
wantTags: []string{"single"},
|
||||
wantIdentifier: "20240101T120000",
|
||||
wantFileType: "org",
|
||||
},
|
||||
{
|
||||
name: "org without tags",
|
||||
content: `#+title: No Tags
|
||||
#+identifier: 20240101T120000`,
|
||||
ext: ".org",
|
||||
wantTitle: "No Tags",
|
||||
wantTags: nil,
|
||||
wantIdentifier: "20240101T120000",
|
||||
wantFileType: "org",
|
||||
},
|
||||
{
|
||||
name: "markdown yaml",
|
||||
content: `---
|
||||
title: Markdown Note
|
||||
date: 2024-01-01
|
||||
tags: [work, personal]
|
||||
identifier: 20240101T120000
|
||||
---
|
||||
|
||||
# Content`,
|
||||
ext: ".md",
|
||||
wantTitle: "Markdown Note",
|
||||
wantTags: []string{"work", "personal"},
|
||||
wantIdentifier: "20240101T120000",
|
||||
wantFileType: "md-yaml",
|
||||
},
|
||||
{
|
||||
name: "markdown yaml with quoted title",
|
||||
content: `---
|
||||
title: "Quoted Title"
|
||||
tags: [test]
|
||||
identifier: 20240101T120000
|
||||
---`,
|
||||
ext: ".md",
|
||||
wantTitle: "Quoted Title",
|
||||
wantTags: []string{"test"},
|
||||
wantIdentifier: "20240101T120000",
|
||||
wantFileType: "md-yaml",
|
||||
},
|
||||
{
|
||||
name: "markdown toml",
|
||||
content: `+++
|
||||
title = TOML Note
|
||||
date = 2024-01-01
|
||||
tags = [rust, go]
|
||||
identifier = 20240101T120000
|
||||
+++
|
||||
|
||||
Content`,
|
||||
ext: ".md",
|
||||
wantTitle: "TOML Note",
|
||||
wantTags: []string{"rust", "go"},
|
||||
wantIdentifier: "20240101T120000",
|
||||
wantFileType: "md-toml",
|
||||
},
|
||||
{
|
||||
name: "txt format",
|
||||
content: `title: Plain Text
|
||||
date: 2024-01-01
|
||||
tags: simple plain
|
||||
identifier: 20240101T120000
|
||||
---------------------------
|
||||
|
||||
Content here`,
|
||||
ext: ".txt",
|
||||
wantTitle: "Plain Text",
|
||||
wantTags: []string{"simple", "plain"},
|
||||
wantIdentifier: "20240101T120000",
|
||||
wantFileType: "txt",
|
||||
},
|
||||
{
|
||||
name: "txt without tags",
|
||||
content: `title: No Tags Text
|
||||
identifier: 20240101T120000
|
||||
---------------------------`,
|
||||
ext: ".txt",
|
||||
wantTitle: "No Tags Text",
|
||||
wantTags: nil,
|
||||
wantIdentifier: "20240101T120000",
|
||||
wantFileType: "txt",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseFrontMatter(tt.content, tt.ext)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseFrontMatter() error = %v", err)
|
||||
}
|
||||
|
||||
if got.Title != tt.wantTitle {
|
||||
t.Errorf("ParseFrontMatter().Title = %q, want %q", got.Title, tt.wantTitle)
|
||||
}
|
||||
|
||||
if !SlicesEqual(got.Tags, tt.wantTags) {
|
||||
t.Errorf("ParseFrontMatter().Tags = %v, want %v", got.Tags, tt.wantTags)
|
||||
}
|
||||
|
||||
if got.Identifier != tt.wantIdentifier {
|
||||
t.Errorf("ParseFrontMatter().Identifier = %q, want %q", got.Identifier, tt.wantIdentifier)
|
||||
}
|
||||
|
||||
if got.FileType != tt.wantFileType {
|
||||
t.Errorf("ParseFrontMatter().FileType = %q, want %q", got.FileType, tt.wantFileType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseFrontMatterMissingFields validates handling of missing fields
|
||||
func TestParseFrontMatterMissingFields(t *testing.T) {
|
||||
content := `---
|
||||
date: 2024-01-01
|
||||
---`
|
||||
|
||||
got, err := ParseFrontMatter(content, ".md")
|
||||
if err != nil {
|
||||
t.Fatalf("ParseFrontMatter() error = %v", err)
|
||||
}
|
||||
|
||||
// Should have empty values, not error
|
||||
if got.Title != "" {
|
||||
t.Errorf("ParseFrontMatter().Title = %q, want empty", got.Title)
|
||||
}
|
||||
if got.Identifier != "" {
|
||||
t.Errorf("ParseFrontMatter().Identifier = %q, want empty", got.Identifier)
|
||||
}
|
||||
if got.Tags != nil {
|
||||
t.Errorf("ParseFrontMatter().Tags = %v, want nil", got.Tags)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateFrontMatter validates front matter updates
|
||||
func TestUpdateFrontMatter(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
original string
|
||||
fm *FrontMatter
|
||||
wantContains []string
|
||||
wantPreserve string
|
||||
}{
|
||||
{
|
||||
name: "update org front matter",
|
||||
original: `#+title: Old Title
|
||||
#+date: [2024-01-01 Mon 12:00]
|
||||
#+filetags: :old:
|
||||
#+identifier: 20240101T120000
|
||||
|
||||
* Original Content
|
||||
This should be preserved`,
|
||||
fm: &FrontMatter{
|
||||
Title: "New Title",
|
||||
Tags: []string{"new", "updated"},
|
||||
Identifier: "20240101T120000",
|
||||
FileType: "org",
|
||||
},
|
||||
wantContains: []string{
|
||||
"#+title: New Title",
|
||||
"#+filetags: :new:updated:",
|
||||
"* Original Content",
|
||||
"This should be preserved",
|
||||
},
|
||||
wantPreserve: "* Original Content",
|
||||
},
|
||||
{
|
||||
name: "update markdown yaml front matter",
|
||||
original: `---
|
||||
title: Old Title
|
||||
tags: [old]
|
||||
identifier: 20240101T120000
|
||||
---
|
||||
|
||||
# Original Heading
|
||||
Content preserved`,
|
||||
fm: &FrontMatter{
|
||||
Title: "New Title",
|
||||
Tags: []string{"new"},
|
||||
Identifier: "20240101T120000",
|
||||
FileType: "md-yaml",
|
||||
},
|
||||
wantContains: []string{
|
||||
"---",
|
||||
"title: New Title",
|
||||
"tags: [new]",
|
||||
"# Original Heading",
|
||||
"Content preserved",
|
||||
},
|
||||
wantPreserve: "# Original Heading",
|
||||
},
|
||||
{
|
||||
name: "update markdown toml front matter",
|
||||
original: `+++
|
||||
title = Old Title
|
||||
tags = [old]
|
||||
identifier = 20240101T120000
|
||||
+++
|
||||
|
||||
Content here`,
|
||||
fm: &FrontMatter{
|
||||
Title: "New Title",
|
||||
Tags: []string{"updated"},
|
||||
Identifier: "20240101T120000",
|
||||
FileType: "md-toml",
|
||||
},
|
||||
wantContains: []string{
|
||||
"+++",
|
||||
"title = New Title",
|
||||
"tags = [updated]",
|
||||
"Content here",
|
||||
},
|
||||
wantPreserve: "Content here",
|
||||
},
|
||||
{
|
||||
name: "update txt front matter",
|
||||
original: `title: Old Title
|
||||
tags: old
|
||||
identifier: 20240101T120000
|
||||
---------------------------
|
||||
|
||||
Text content`,
|
||||
fm: &FrontMatter{
|
||||
Title: "New Title",
|
||||
Tags: []string{"new"},
|
||||
Identifier: "20240101T120000",
|
||||
FileType: "txt",
|
||||
},
|
||||
wantContains: []string{
|
||||
"title: New Title",
|
||||
"tags: new",
|
||||
"---------------------------",
|
||||
"Text content",
|
||||
},
|
||||
wantPreserve: "Text content",
|
||||
},
|
||||
{
|
||||
name: "add front matter when missing (org)",
|
||||
original: `* Original Heading`,
|
||||
fm: &FrontMatter{
|
||||
Title: "Added Title",
|
||||
Tags: []string{"added"},
|
||||
Identifier: "20240101T120000",
|
||||
FileType: "org",
|
||||
},
|
||||
wantContains: []string{
|
||||
"#+title: Added Title",
|
||||
"#+filetags: :added:",
|
||||
"* Original Heading",
|
||||
},
|
||||
wantPreserve: "* Original Heading",
|
||||
},
|
||||
{
|
||||
name: "add front matter when missing (md-yaml)",
|
||||
original: `# Original Heading`,
|
||||
fm: &FrontMatter{
|
||||
Title: "Added Title",
|
||||
Tags: []string{"added"},
|
||||
Identifier: "20240101T120000",
|
||||
FileType: "md-yaml",
|
||||
},
|
||||
wantContains: []string{
|
||||
"---",
|
||||
"title: Added Title",
|
||||
"tags: [added]",
|
||||
"# Original Heading",
|
||||
},
|
||||
wantPreserve: "# Original Heading",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := UpdateFrontMatter(tt.original, tt.fm)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateFrontMatter() error = %v", err)
|
||||
}
|
||||
|
||||
for _, want := range tt.wantContains {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("UpdateFrontMatter() missing %q\nGot:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify original content is preserved
|
||||
if !strings.Contains(got, tt.wantPreserve) {
|
||||
t.Errorf("UpdateFrontMatter() didn't preserve %q\nGot:\n%s", tt.wantPreserve, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateFrontMatterEmptyTags validates updating with empty tags
|
||||
func TestUpdateFrontMatterEmptyTags(t *testing.T) {
|
||||
original := `---
|
||||
title: Test
|
||||
tags: [old, tags]
|
||||
identifier: 20240101T120000
|
||||
---
|
||||
|
||||
Content`
|
||||
|
||||
fm := &FrontMatter{
|
||||
Title: "Test",
|
||||
Tags: []string{},
|
||||
Identifier: "20240101T120000",
|
||||
FileType: "md-yaml",
|
||||
}
|
||||
|
||||
got, err := UpdateFrontMatter(original, fm)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateFrontMatter() error = %v", err)
|
||||
}
|
||||
|
||||
// Should have empty tags field, not omit it
|
||||
if !strings.Contains(got, "tags:") {
|
||||
t.Errorf("UpdateFrontMatter() should include tags field even when empty")
|
||||
}
|
||||
|
||||
// Content should be preserved
|
||||
if !strings.Contains(got, "Content") {
|
||||
t.Errorf("UpdateFrontMatter() should preserve content")
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateFrontMatterUnsupportedType validates error handling
|
||||
func TestUpdateFrontMatterUnsupportedType(t *testing.T) {
|
||||
fm := &FrontMatter{
|
||||
Title: "Test",
|
||||
Tags: []string{"test"},
|
||||
Identifier: "20240101T120000",
|
||||
FileType: "unsupported",
|
||||
}
|
||||
|
||||
_, err := UpdateFrontMatter("content", fm)
|
||||
if err == nil {
|
||||
t.Error("UpdateFrontMatter() should error on unsupported file type")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "unsupported file type") {
|
||||
t.Errorf("UpdateFrontMatter() error = %v, want 'unsupported file type'", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFileExtensions validates file extension mapping
|
||||
func TestFileExtensions(t *testing.T) {
|
||||
tests := []struct {
|
||||
fileType string
|
||||
want string
|
||||
}{
|
||||
{"org", ".org"},
|
||||
{"md-yaml", ".md"},
|
||||
{"md-toml", ".md"},
|
||||
{"txt", ".txt"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.fileType, func(t *testing.T) {
|
||||
got, ok := FileExtensions[tt.fileType]
|
||||
if !ok {
|
||||
t.Errorf("FileExtensions[%q] not found", tt.fileType)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("FileExtensions[%q] = %q, want %q", tt.fileType, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFrontMatterRoundTrip validates generate -> parse -> update cycle
|
||||
func TestFrontMatterRoundTrip(t *testing.T) {
|
||||
fileTypes := []string{"org", "md-yaml", "md-toml", "txt"}
|
||||
title := "Test Note"
|
||||
tags := []string{"tag1", "tag2"}
|
||||
identifier := "20240101T120000"
|
||||
|
||||
for _, fileType := range fileTypes {
|
||||
t.Run(fileType, func(t *testing.T) {
|
||||
// Generate front matter
|
||||
content := Generate(title, tags, fileType, identifier)
|
||||
|
||||
// Parse it back
|
||||
ext := FileExtensions[fileType]
|
||||
fm, err := ParseFrontMatter(content, ext)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseFrontMatter() error = %v", err)
|
||||
}
|
||||
|
||||
// Verify parsed values
|
||||
if fm.Title != title {
|
||||
t.Errorf("Parsed title = %q, want %q", fm.Title, title)
|
||||
}
|
||||
if !SlicesEqual(fm.Tags, tags) {
|
||||
t.Errorf("Parsed tags = %v, want %v", fm.Tags, tags)
|
||||
}
|
||||
if fm.Identifier != identifier {
|
||||
t.Errorf("Parsed identifier = %q, want %q", fm.Identifier, identifier)
|
||||
}
|
||||
if fm.FileType != fileType {
|
||||
t.Errorf("Parsed fileType = %q, want %q", fm.FileType, fileType)
|
||||
}
|
||||
|
||||
// Update with new values
|
||||
newTitle := "Updated Title"
|
||||
newTags := []string{"new"}
|
||||
fm.Title = newTitle
|
||||
fm.Tags = newTags
|
||||
|
||||
updated, err := UpdateFrontMatter(content, fm)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateFrontMatter() error = %v", err)
|
||||
}
|
||||
|
||||
// Parse again and verify
|
||||
fm2, err := ParseFrontMatter(updated, ext)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseFrontMatter() after update error = %v", err)
|
||||
}
|
||||
|
||||
if fm2.Title != newTitle {
|
||||
t.Errorf("After update title = %q, want %q", fm2.Title, newTitle)
|
||||
}
|
||||
if !SlicesEqual(fm2.Tags, newTags) {
|
||||
t.Errorf("After update tags = %v, want %v", fm2.Tags, newTags)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -37,8 +38,8 @@ func (es Results) Bytes() []byte {
|
|||
func (es Results) FromString(data string) (Results, error) {
|
||||
var results Results
|
||||
lines := strings.Split(strings.TrimSpace(data), "\n")
|
||||
// Allow lowercase unicode letters and digits, no spaces
|
||||
tagPattern := regexp.MustCompile(`^([\p{Ll}\p{Nd}]+,)*[\p{Ll}\p{Nd}]+$`)
|
||||
// Allow lowercase Latin letters, other letters (CJK, etc.), and digits, no spaces
|
||||
tagPattern := regexp.MustCompile(`^([\p{Ll}\p{Lo}\p{Nd}]+,)*[\p{Ll}\p{Lo}\p{Nd}]+$`)
|
||||
|
||||
for lineNum, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
|
@ -79,16 +80,10 @@ func (es Results) FromString(data string) (Results, error) {
|
|||
return results, nil
|
||||
}
|
||||
|
||||
// SlicesEqual compares two string slices for equality.
|
||||
// Deprecated: Use slices.Equal from standard library instead.
|
||||
func SlicesEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return slices.Equal(a, b)
|
||||
}
|
||||
|
||||
type SortBy string
|
||||
|
|
@ -195,7 +190,9 @@ func GenerateIdentifier() string {
|
|||
|
||||
// slugifyTitle converts a title to a filesystem-safe slug.
|
||||
func slugifyTitle(title string) string {
|
||||
slug := strings.ReplaceAll(strings.ToLower(title), " ", "-")
|
||||
slug := strings.ToLower(title)
|
||||
slug = strings.ReplaceAll(slug, " ", "-")
|
||||
slug = strings.ReplaceAll(slug, "_", "-")
|
||||
return regexp.MustCompile(`[^a-z0-9-]`).ReplaceAllString(slug, "")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,729 @@
|
|||
package metadata
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestGenerateIdentifier validates timestamp format (YYYYMMDDThhmmss)
|
||||
// Maps to dt-denote-identifier-p from original tests
|
||||
func TestGenerateIdentifier(t *testing.T) {
|
||||
id := GenerateIdentifier()
|
||||
|
||||
// Validate format
|
||||
pattern := regexp.MustCompile(`^\d{8}T\d{6}$`)
|
||||
if !pattern.MatchString(id) {
|
||||
t.Errorf("GenerateIdentifier() = %q, want format YYYYMMDDThhmmss", id)
|
||||
}
|
||||
|
||||
// Validate it's a valid timestamp
|
||||
_, err := time.Parse("20060102T150405", id)
|
||||
if err != nil {
|
||||
t.Errorf("GenerateIdentifier() = %q, not a valid timestamp: %v", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSlugifyTitle validates title slugification
|
||||
// Maps to dt-denote-sluggify-title and dt-denote-sluggify from original tests
|
||||
func TestSlugifyTitle(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "basic spaces to hyphens",
|
||||
input: "Hello World",
|
||||
want: "hello-world",
|
||||
},
|
||||
{
|
||||
name: "underscores to hyphens",
|
||||
input: "Test_File Name",
|
||||
want: "test-file-name",
|
||||
},
|
||||
{
|
||||
name: "remove punctuation",
|
||||
input: "Test File!",
|
||||
want: "test-file",
|
||||
},
|
||||
{
|
||||
name: "mixed case",
|
||||
input: "Mixed CASE 123",
|
||||
want: "mixed-case-123",
|
||||
},
|
||||
{
|
||||
name: "special characters",
|
||||
input: "Special@#$Chars",
|
||||
want: "specialchars",
|
||||
},
|
||||
{
|
||||
name: "multiple spaces",
|
||||
input: "Multiple Spaces",
|
||||
want: "multiple---spaces",
|
||||
},
|
||||
{
|
||||
name: "leading and trailing spaces",
|
||||
input: " Trim Me ",
|
||||
want: "--trim-me--",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "numbers only",
|
||||
input: "12345",
|
||||
want: "12345",
|
||||
},
|
||||
{
|
||||
name: "hyphens preserved",
|
||||
input: "already-hyphenated",
|
||||
want: "already-hyphenated",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := slugifyTitle(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("slugifyTitle(%q) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFormatKeywords validates keyword formatting for filenames
|
||||
// Maps to dt-denote-sluggify-keywords from original tests
|
||||
func TestFormatKeywords(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "multiple keywords",
|
||||
input: []string{"tag1", "tag2"},
|
||||
want: "__tag1_tag2",
|
||||
},
|
||||
{
|
||||
name: "single keyword",
|
||||
input: []string{"single"},
|
||||
want: "__single",
|
||||
},
|
||||
{
|
||||
name: "empty keywords",
|
||||
input: []string{},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "nil keywords",
|
||||
input: nil,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "three keywords",
|
||||
input: []string{"one", "two", "three"},
|
||||
want: "__one_two_three",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := formatKeywords(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("formatKeywords(%v) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildFilename validates filename construction
|
||||
// Maps to dt-denote-format-file-name from original tests
|
||||
func TestBuildFilename(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
identifier string
|
||||
title string
|
||||
keywords []string
|
||||
ext string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "complete filename with keywords",
|
||||
identifier: "20231225T120000",
|
||||
title: "My Title",
|
||||
keywords: []string{"tag1", "tag2"},
|
||||
ext: ".md",
|
||||
want: "20231225T120000--my-title__tag1_tag2.md",
|
||||
},
|
||||
{
|
||||
name: "filename without keywords",
|
||||
identifier: "20231225T120000",
|
||||
title: "My Title",
|
||||
keywords: []string{},
|
||||
ext: ".md",
|
||||
want: "20231225T120000--my-title.md",
|
||||
},
|
||||
{
|
||||
name: "filename with special chars in title",
|
||||
identifier: "20231225T120000",
|
||||
title: "Special!@# Title",
|
||||
keywords: []string{"work"},
|
||||
ext: ".org",
|
||||
want: "20231225T120000--special-title__work.org",
|
||||
},
|
||||
{
|
||||
name: "org format",
|
||||
identifier: "20240101T000000",
|
||||
title: "Org Note",
|
||||
keywords: []string{"emacs"},
|
||||
ext: ".org",
|
||||
want: "20240101T000000--org-note__emacs.org",
|
||||
},
|
||||
{
|
||||
name: "txt format",
|
||||
identifier: "20240101T000000",
|
||||
title: "Plain Text",
|
||||
keywords: []string{},
|
||||
ext: ".txt",
|
||||
want: "20240101T000000--plain-text.txt",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := BuildFilename(tt.identifier, tt.title, tt.keywords, tt.ext)
|
||||
if got != tt.want {
|
||||
t.Errorf("BuildFilename() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseFilename validates filename parsing
|
||||
// Maps to dt-denote-retrieve-filename-* tests from original
|
||||
func TestParseFilename(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
wantIdentifier string
|
||||
wantTitle string
|
||||
wantTags []string
|
||||
}{
|
||||
{
|
||||
name: "complete filename with tags",
|
||||
path: "/home/notes/20231225T120000--my-title__tag1_tag2.md",
|
||||
wantIdentifier: "20231225T120000",
|
||||
wantTitle: "my title",
|
||||
wantTags: []string{"tag1", "tag2"},
|
||||
},
|
||||
{
|
||||
name: "filename without tags",
|
||||
path: "/home/notes/20231225T120000--simple-title.md",
|
||||
wantIdentifier: "20231225T120000",
|
||||
wantTitle: "simple title",
|
||||
wantTags: nil,
|
||||
},
|
||||
{
|
||||
name: "filename with single tag",
|
||||
path: "20240101T000000--note__work.org",
|
||||
wantIdentifier: "20240101T000000",
|
||||
wantTitle: "note",
|
||||
wantTags: []string{"work"},
|
||||
},
|
||||
{
|
||||
name: "identifier only",
|
||||
path: "20240101T000000.txt",
|
||||
wantIdentifier: "20240101T000000",
|
||||
wantTitle: "",
|
||||
wantTags: nil,
|
||||
},
|
||||
{
|
||||
name: "multi-word title",
|
||||
path: "20240101T000000--multi-word-title__personal_ideas.md",
|
||||
wantIdentifier: "20240101T000000",
|
||||
wantTitle: "multi word title",
|
||||
wantTags: []string{"personal", "ideas"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ParseFilename(tt.path)
|
||||
|
||||
if got.Identifier != tt.wantIdentifier {
|
||||
t.Errorf("ParseFilename(%q).Identifier = %q, want %q",
|
||||
tt.path, got.Identifier, tt.wantIdentifier)
|
||||
}
|
||||
|
||||
if got.Title != tt.wantTitle {
|
||||
t.Errorf("ParseFilename(%q).Title = %q, want %q",
|
||||
tt.path, got.Title, tt.wantTitle)
|
||||
}
|
||||
|
||||
if !SlicesEqual(got.Tags, tt.wantTags) {
|
||||
t.Errorf("ParseFilename(%q).Tags = %v, want %v",
|
||||
tt.path, got.Tags, tt.wantTags)
|
||||
}
|
||||
|
||||
if got.Path != tt.path {
|
||||
t.Errorf("ParseFilename(%q).Path = %q, want %q",
|
||||
tt.path, got.Path, tt.path)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractTitleFromContent validates title extraction from file content
|
||||
func TestExtractTitleFromContent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
ext string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "org mode with title directive",
|
||||
content: `#+title: My Org Note
|
||||
#+date: [2024-01-01]
|
||||
|
||||
* First Heading`,
|
||||
ext: ".org",
|
||||
want: "My Org Note",
|
||||
},
|
||||
{
|
||||
name: "org mode fallback to heading",
|
||||
content: `#+date: [2024-01-01]
|
||||
|
||||
* Heading Title
|
||||
|
||||
Some content`,
|
||||
ext: ".org",
|
||||
want: "Heading Title",
|
||||
},
|
||||
{
|
||||
name: "markdown yaml front matter",
|
||||
content: `---
|
||||
title: Markdown Note
|
||||
date: 2024-01-01
|
||||
---
|
||||
|
||||
# First Heading`,
|
||||
ext: ".md",
|
||||
want: "Markdown Note",
|
||||
},
|
||||
{
|
||||
name: "markdown yaml with quotes",
|
||||
content: `---
|
||||
title: "Quoted Title"
|
||||
date: 2024-01-01
|
||||
---`,
|
||||
ext: ".md",
|
||||
want: "Quoted Title",
|
||||
},
|
||||
{
|
||||
name: "markdown fallback to heading",
|
||||
content: `---
|
||||
date: 2024-01-01
|
||||
---
|
||||
|
||||
# Heading Title
|
||||
|
||||
Content here`,
|
||||
ext: ".md",
|
||||
want: "Heading Title",
|
||||
},
|
||||
{
|
||||
name: "markdown heading without front matter",
|
||||
content: `# Simple Title
|
||||
|
||||
Content`,
|
||||
ext: ".md",
|
||||
want: "Simple Title",
|
||||
},
|
||||
{
|
||||
name: "unsupported extension",
|
||||
content: "Some content",
|
||||
ext: ".pdf",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "empty content",
|
||||
content: "",
|
||||
ext: ".md",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "no title found",
|
||||
content: "Just some text without structure",
|
||||
ext: ".md",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ExtractTitleFromContent(tt.content, tt.ext)
|
||||
if got != tt.want {
|
||||
t.Errorf("ExtractTitleFromContent() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSort validates sorting functionality
|
||||
func TestSort(t *testing.T) {
|
||||
notes := Results{
|
||||
{Identifier: "20240103T120000", Title: "Charlie"},
|
||||
{Identifier: "20240101T120000", Title: "Alice"},
|
||||
{Identifier: "20240102T120000", Title: "Bob"},
|
||||
}
|
||||
|
||||
t.Run("sort by id ascending", func(t *testing.T) {
|
||||
testData := make(Results, len(notes))
|
||||
copy(testData, notes)
|
||||
|
||||
Sort(testData, SortById, SortOrderAsc)
|
||||
|
||||
if testData[0].Identifier != "20240101T120000" {
|
||||
t.Errorf("First item identifier = %q, want %q", testData[0].Identifier, "20240101T120000")
|
||||
}
|
||||
if testData[2].Identifier != "20240103T120000" {
|
||||
t.Errorf("Last item identifier = %q, want %q", testData[2].Identifier, "20240103T120000")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("sort by id descending", func(t *testing.T) {
|
||||
testData := make(Results, len(notes))
|
||||
copy(testData, notes)
|
||||
|
||||
Sort(testData, SortById, SortOrderDesc)
|
||||
|
||||
if testData[0].Identifier != "20240103T120000" {
|
||||
t.Errorf("First item identifier = %q, want %q", testData[0].Identifier, "20240103T120000")
|
||||
}
|
||||
if testData[2].Identifier != "20240101T120000" {
|
||||
t.Errorf("Last item identifier = %q, want %q", testData[2].Identifier, "20240101T120000")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("sort by title ascending", func(t *testing.T) {
|
||||
testData := make(Results, len(notes))
|
||||
copy(testData, notes)
|
||||
|
||||
Sort(testData, SortByTitle, SortOrderAsc)
|
||||
|
||||
if testData[0].Title != "Alice" {
|
||||
t.Errorf("First item title = %q, want %q", testData[0].Title, "Alice")
|
||||
}
|
||||
if testData[2].Title != "Charlie" {
|
||||
t.Errorf("Last item title = %q, want %q", testData[2].Title, "Charlie")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("sort by title descending", func(t *testing.T) {
|
||||
testData := make(Results, len(notes))
|
||||
copy(testData, notes)
|
||||
|
||||
Sort(testData, SortByTitle, SortOrderDesc)
|
||||
|
||||
if testData[0].Title != "Charlie" {
|
||||
t.Errorf("First item title = %q, want %q", testData[0].Title, "Charlie")
|
||||
}
|
||||
if testData[2].Title != "Alice" {
|
||||
t.Errorf("Last item title = %q, want %q", testData[2].Title, "Alice")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("sort by title case insensitive", func(t *testing.T) {
|
||||
testData := Results{
|
||||
{Identifier: "1", Title: "zebra"},
|
||||
{Identifier: "2", Title: "Apple"},
|
||||
{Identifier: "3", Title: "banana"},
|
||||
}
|
||||
|
||||
Sort(testData, SortByTitle, SortOrderAsc)
|
||||
|
||||
// Should be: Apple, banana, zebra (case-insensitive)
|
||||
if testData[0].Title != "Apple" {
|
||||
t.Errorf("First item title = %q, want %q", testData[0].Title, "Apple")
|
||||
}
|
||||
if testData[1].Title != "banana" {
|
||||
t.Errorf("Second item title = %q, want %q", testData[1].Title, "banana")
|
||||
}
|
||||
if testData[2].Title != "zebra" {
|
||||
t.Errorf("Third item title = %q, want %q", testData[2].Title, "zebra")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestResultsBytes validates serialization
|
||||
func TestResultsBytes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input Results
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "single note with tags",
|
||||
input: Results{
|
||||
{
|
||||
Identifier: "20240101T120000",
|
||||
Title: "Test Note",
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
},
|
||||
},
|
||||
want: "20240101T120000 | Test Note | tag1,tag2\n",
|
||||
},
|
||||
{
|
||||
name: "note without tags",
|
||||
input: Results{
|
||||
{
|
||||
Identifier: "20240101T120000",
|
||||
Title: "Simple Note",
|
||||
Tags: []string{},
|
||||
},
|
||||
},
|
||||
want: "20240101T120000 | Simple Note | \n",
|
||||
},
|
||||
{
|
||||
name: "note without title",
|
||||
input: Results{
|
||||
{
|
||||
Identifier: "20240101T120000",
|
||||
Title: "",
|
||||
Tags: []string{"tag"},
|
||||
},
|
||||
},
|
||||
want: "20240101T120000 | (untitled) | tag\n",
|
||||
},
|
||||
{
|
||||
name: "multiple notes",
|
||||
input: Results{
|
||||
{Identifier: "20240101T120000", Title: "First", Tags: []string{"a"}},
|
||||
{Identifier: "20240102T120000", Title: "Second", Tags: []string{"b", "c"}},
|
||||
},
|
||||
want: "20240101T120000 | First | a\n20240102T120000 | Second | b,c\n",
|
||||
},
|
||||
{
|
||||
name: "empty results",
|
||||
input: Results{},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := string(tt.input.Bytes())
|
||||
if got != tt.want {
|
||||
t.Errorf("Results.Bytes() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestResultsFromString validates parsing from string format
|
||||
func TestResultsFromString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want Results
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "single note with tags",
|
||||
input: "20240101T120000 | Test Note | tag1,tag2",
|
||||
want: Results{
|
||||
{Identifier: "20240101T120000", Title: "Test Note", Tags: []string{"tag1", "tag2"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "note without tags",
|
||||
input: "20240101T120000 | Simple Note | ",
|
||||
want: Results{
|
||||
{Identifier: "20240101T120000", Title: "Simple Note", Tags: []string{}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "multiple notes",
|
||||
input: "20240101T120000 | First | a\n20240102T120000 | Second | b,c",
|
||||
want: Results{
|
||||
{Identifier: "20240101T120000", Title: "First", Tags: []string{"a"}},
|
||||
{Identifier: "20240102T120000", Title: "Second", Tags: []string{"b", "c"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty input",
|
||||
input: "",
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "wrong column count",
|
||||
input: "20240101T120000 | Title",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty identifier",
|
||||
input: " | Title | tags",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid tags with spaces",
|
||||
input: "20240101T120000 | Title | tag with spaces",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid tags with uppercase",
|
||||
input: "20240101T120000 | Title | Tag1,tag2",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid lowercase unicode tags",
|
||||
input: "20240101T120000 | Title | tag1,测试,αβγ",
|
||||
want: Results{
|
||||
{Identifier: "20240101T120000", Title: "Title", Tags: []string{"tag1", "测试", "αβγ"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "blank lines ignored",
|
||||
input: "20240101T120000 | First | a\n\n20240102T120000 | Second | b",
|
||||
want: Results{
|
||||
{Identifier: "20240101T120000", Title: "First", Tags: []string{"a"}},
|
||||
{Identifier: "20240102T120000", Title: "Second", Tags: []string{"b"}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var r Results
|
||||
got, err := r.FromString(tt.input)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Results.FromString() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if tt.wantErr {
|
||||
return
|
||||
}
|
||||
|
||||
if len(got) != len(tt.want) {
|
||||
t.Fatalf("Results.FromString() length = %d, want %d", len(got), len(tt.want))
|
||||
}
|
||||
|
||||
for i := range got {
|
||||
if got[i].Identifier != tt.want[i].Identifier {
|
||||
t.Errorf("Result[%d].Identifier = %q, want %q", i, got[i].Identifier, tt.want[i].Identifier)
|
||||
}
|
||||
if got[i].Title != tt.want[i].Title {
|
||||
t.Errorf("Result[%d].Title = %q, want %q", i, got[i].Title, tt.want[i].Title)
|
||||
}
|
||||
if !SlicesEqual(got[i].Tags, tt.want[i].Tags) {
|
||||
t.Errorf("Result[%d].Tags = %v, want %v", i, got[i].Tags, tt.want[i].Tags)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSlicesEqual validates slice comparison
|
||||
func TestSlicesEqual(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a []string
|
||||
b []string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "equal slices",
|
||||
a: []string{"a", "b", "c"},
|
||||
b: []string{"a", "b", "c"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "different length",
|
||||
a: []string{"a", "b"},
|
||||
b: []string{"a", "b", "c"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "different content",
|
||||
a: []string{"a", "b", "c"},
|
||||
b: []string{"a", "x", "c"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "both nil",
|
||||
a: nil,
|
||||
b: nil,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "both empty",
|
||||
a: []string{},
|
||||
b: []string{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
// slices.Equal treats nil and empty slices as equal (both have len 0)
|
||||
name: "nil vs empty",
|
||||
a: nil,
|
||||
b: []string{},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := SlicesEqual(tt.a, tt.b)
|
||||
if got != tt.want {
|
||||
t.Errorf("SlicesEqual(%v, %v) = %v, want %v", tt.a, tt.b, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateNote validates note generation (integration test)
|
||||
func TestGenerateNote(t *testing.T) {
|
||||
dir := "/tmp/notes"
|
||||
title := "Test Note"
|
||||
keywords := []string{"test", "example"}
|
||||
fileType := "md-yaml"
|
||||
|
||||
path, content := GenerateNote(dir, title, keywords, fileType)
|
||||
|
||||
// Validate path format
|
||||
if !strings.HasPrefix(path, dir+"/") {
|
||||
t.Errorf("GenerateNote() path = %q, should start with %q", path, dir+"/")
|
||||
}
|
||||
|
||||
// Validate filename contains identifier and title
|
||||
if !strings.Contains(path, "--test-note__test_example.md") {
|
||||
t.Errorf("GenerateNote() path = %q, should contain proper filename", path)
|
||||
}
|
||||
|
||||
// Validate content has front matter
|
||||
if !strings.Contains(content, "---") {
|
||||
t.Errorf("GenerateNote() content should contain YAML front matter")
|
||||
}
|
||||
if !strings.Contains(content, "title:") {
|
||||
t.Errorf("GenerateNote() content should contain title field")
|
||||
}
|
||||
if !strings.Contains(content, "[test, example]") {
|
||||
t.Errorf("GenerateNote() content should contain formatted tags")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue