From 89e62ad65a2d6a76893d77bbe9397ff91b0a0578 Mon Sep 17 00:00:00 2001 From: ruinivist Date: Sun, 18 Jan 2026 01:59:00 +0000 Subject: [PATCH] add tests (ai gen) --- README.md | 4 ++ fs_test.go | 109 ++++++++++++++++++++++++++++++++++++ fstree_test.go | 147 +++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 2 + notes_test.go | 107 +++++++++++++++++++++++++++++++++++ 6 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 fs_test.go create mode 100644 fstree_test.go create mode 100644 notes_test.go diff --git a/README.md b/README.md index cd0ffd4..ff25d2d 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ my take on how notes and srs should be combind and simplified + +# Tests + +Run tests with `go test ./...` diff --git a/fs_test.go b/fs_test.go new file mode 100644 index 0000000..217e246 --- /dev/null +++ b/fs_test.go @@ -0,0 +1,109 @@ +package main + +import ( + "os" + "path/filepath" + "testing" +) + +// tests creating a file in a temp dir +func TestCreateFile(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "mend_fs_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + // test valid creation + filePath := filepath.Join(tmpDir, "test.md") + err = createFile(filePath, []byte("content")) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + if _, err := os.Stat(filePath); os.IsNotExist(err) { + t.Error("file was not created") + } + + // test creation failure (exists) + err = createFile(filePath, []byte("content")) + if err == nil { + t.Error("expected error for existing file, got nil") + } + + // test empty path + err = createFile("", []byte{}) + if err == nil { + t.Error("expected error for empty path, got nil") + } +} + +// tests creating a folder +func TestCreateFolder(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "mend_fs_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + folderPath := filepath.Join(tmpDir, "subfolder") + err = createFolder(folderPath) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + info, err := os.Stat(folderPath) + if os.IsNotExist(err) { + t.Error("folder was not created") + } + if !info.IsDir() { + t.Error("created path is not a directory") + } + + // test creation failure (exists) + err = createFolder(folderPath) + if err == nil { + t.Error("expected error for existing folder, got nil") + } + + // test empty path + err = createFolder("") + if err == nil { + t.Error("expected error for empty path, got nil") + } +} + +// tests recursive deletion +func TestDeletePath(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "mend_fs_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + subDir := filepath.Join(tmpDir, "subdir") + os.Mkdir(subDir, 0755) + fileInSubDir := filepath.Join(subDir, "file.txt") + os.WriteFile(fileInSubDir, []byte("data"), 0644) + + err = deletePath(subDir) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + if _, err := os.Stat(subDir); !os.IsNotExist(err) { + t.Error("directory was not deleted") + } + + // test delete non-existent + err = deletePath(filepath.Join(tmpDir, "nonexistent")) + if err == nil { + t.Error("expected error for nonexistent path, got nil") + } + + // test empty path + err = deletePath("") + if err == nil { + t.Error("expected error for empty path, got nil") + } +} diff --git a/fstree_test.go b/fstree_test.go new file mode 100644 index 0000000..f4d33a9 --- /dev/null +++ b/fstree_test.go @@ -0,0 +1,147 @@ +package main + +import ( + "os" + "path/filepath" + "testing" +) + +// tests creating a new fstree +func TestNewFsTree(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "mend_fstree_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + // setup structure: + // root/ + // folder1/ + // file1.md + // file2.md + + os.Mkdir(filepath.Join(tmpDir, "folder1"), 0755) + os.WriteFile(filepath.Join(tmpDir, "folder1", "file1.md"), []byte(""), 0644) + os.WriteFile(filepath.Join(tmpDir, "file2.md"), []byte(""), 0644) + + tree := NewFsTree(tmpDir, 0) + + if tree == nil { + t.Fatal("expected tree to be created, got nil") + } + if tree.root.path != tmpDir { + t.Errorf("expected root path %s, got %s", tmpDir, tree.root.path) + } + // root children: folder1, file2.md + if len(tree.root.children) != 2 { + t.Errorf("expected 2 children, got %d", len(tree.root.children)) + } +} + +// tests creating nodes via tree +func TestTreeCreateNode(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "mend_fstree_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + tree := NewFsTree(tmpDir, 0) + // create folder + err = tree.CreateNode(tree.root, "new_folder", FolderNode) + if err != nil { + t.Errorf("expected no error creating folder, got %v", err) + } + // check fs + if _, err := os.Stat(filepath.Join(tmpDir, "new_folder")); os.IsNotExist(err) { + t.Error("folder not created on fs") + } + // check tree + if len(tree.root.children) != 1 || tree.root.children[0].nodeType != FolderNode { + t.Error("tree block not updated with new folder") + } + + // create file in new folder + newFolder := tree.root.children[0] + err = tree.CreateNode(newFolder, "new_file", FileNode) + if err != nil { + t.Errorf("expected no error creating file, got %v", err) + } + + // check fs (.md appended automatically) + if _, err := os.Stat(filepath.Join(tmpDir, "new_folder", "new_file.md")); os.IsNotExist(err) { + t.Error("file not created on fs") + } + // check tree + if len(newFolder.children) != 1 || newFolder.children[0].nodeType != FileNode { + t.Error("tree node not updated with new file") + } +} + +// tests deleting nodes +func TestTreeDeleteNode(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "mend_fstree_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + // setup: root/file.md + os.WriteFile(filepath.Join(tmpDir, "file.md"), []byte(""), 0644) + + tree := NewFsTree(tmpDir, 0) + targetNode := tree.root.children[0] + + err = tree.DeleteNode(targetNode) + if err != nil { + t.Errorf("expected no error deleting node, got %v", err) + } + + // check fs + if _, err := os.Stat(filepath.Join(tmpDir, "file.md")); !os.IsNotExist(err) { + t.Error("file not deleted from fs") + } + // check tree + if len(tree.root.children) != 0 { + t.Error("tree node not updated after deletion") + } +} + +// tests toggling expand +func TestTreeToggleExpand(t *testing.T) { + root := &FsNode{ + nodeType: FolderNode, + expanded: true, + children: []*FsNode{}, // Initialize children + } + + node := &FsNode{ + nodeType: FolderNode, + expanded: true, + parent: root, + } + root.children = append(root.children, node) + + // mocked tree for this test + tree := &FsTree{ + root: root, + } + // Initial buildLines to setup state + tree.buildLines() + + err := tree.ToggleExpand(node) + if err != nil { + t.Error(err) + } + if node.expanded { + t.Error("expected node to be collapsed") + } + + err = tree.ToggleExpand(node) + if err != nil { + t.Error(err) + } + if !node.expanded { + t.Error("expected node to be expanded") + } +} diff --git a/go.mod b/go.mod index 9072696..fa9b05b 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/charmbracelet/glamour v0.10.0 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/mattn/go-runewidth v0.0.16 + github.com/yuin/goldmark v1.7.8 ) require ( @@ -33,7 +34,6 @@ require ( github.com/muesli/termenv v0.16.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark-emoji v1.0.5 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.36.0 // indirect diff --git a/go.sum b/go.sum index 150244c..6fa14e8 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= diff --git a/notes_test.go b/notes_test.go new file mode 100644 index 0000000..219fa80 --- /dev/null +++ b/notes_test.go @@ -0,0 +1,107 @@ +package main + +import ( + "reflect" + "testing" +) + +// tests hint extraction from text +func TestExtractHints(t *testing.T) { + tests := []struct { + name string + content string + expected []string + }{ + { + name: "single bold hint", + content: "This is a **hint**.", + expected: []string{"hint"}, + }, + { + name: "multiple bold hints", + content: "**one** and **two**", + expected: []string{"one", "two"}, + }, + { + name: "underscore hints", + content: "__one__ and __two__", + expected: []string{"one", "two"}, + }, + { + name: "mixed hints", + content: "**one** and __two__", + expected: []string{"one", "two"}, + }, + { + name: "no hints", + content: "just plain text", + expected: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := extractHints(tt.content) + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("extractHints() = %v, want %v", got, tt.expected) + } + }) + } +} + +// tests section parsing from markdown +func TestParseSections(t *testing.T) { + content := []byte(`# Title 1 +Content 1 with **hint1**. + +# Title 2 +Content 2 with __hint2__. +`) + + sections := parseSections(content) + + if len(sections) != 2 { + t.Fatalf("expected 2 sections, got %d", len(sections)) + } + + // verify first section + if sections[0].Title != "# Title 1" { + t.Errorf("expected title '# Title 1', got '%s'", sections[0].Title) + } + if sections[0].Content != "Content 1 with **hint1**." { + t.Errorf("expected content match for section 1, got '%s'", sections[0].Content) + } + if len(sections[0].Hints) != 1 || sections[0].Hints[0] != "hint1" { + t.Error("failed to extract hint from section 1") + } + + // verify second section + if sections[1].Title != "# Title 2" { + t.Errorf("expected title '# Title 2', got '%s'", sections[1].Title) + } + if sections[1].Content != "Content 2 with __hint2__." { + t.Errorf("expected content match for section 2, got '%s'", sections[1].Content) + } + if len(sections[1].Hints) != 1 || sections[1].Hints[0] != "hint2" { + t.Error("failed to extract hint from section 2") + } +} + +// tests parsing with no headers +func TestParseSectionsNoHeaders(t *testing.T) { + content := []byte(`Just some content without any headers. +It has **one hint**.`) + + sections := parseSections(content) + + if len(sections) != 1 { + t.Fatalf("expected 1 section, got %d", len(sections)) + } + + if sections[0].Title != "no title" { + t.Errorf("expected 'no title', got '%s'", sections[0].Title) + } + if len(sections[0].Hints) != 1 || sections[0].Hints[0] != "one hint" { + t.Error("failed to extract hint") + } +}