cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
1package ui
2
3import (
4 "bytes"
5 "context"
6 "fmt"
7 "strings"
8 "testing"
9
10 "github.com/stormlightlabs/noteleaf/internal/repo"
11)
12
13// mockProjectRepository implements ProjectRepository for testing
14type mockProjectRepository struct {
15 projects []repo.ProjectSummary
16 err error
17}
18
19func (m *mockProjectRepository) GetProjects(ctx context.Context) ([]repo.ProjectSummary, error) {
20 if m.err != nil {
21 return nil, m.err
22 }
23 return m.projects, nil
24}
25
26func TestProjectAdapter(t *testing.T) {
27 t.Run("ProjectSummaryRecord", func(t *testing.T) {
28 summary := repo.ProjectSummary{
29 Name: "Test Project",
30 TaskCount: 5,
31 }
32 record := &ProjectSummaryRecord{summary: summary}
33
34 t.Run("GetField", func(t *testing.T) {
35 tests := []struct {
36 field string
37 expected any
38 name string
39 }{
40 {"name", "Test Project", "should return project name"},
41 {"task_count", 5, "should return task count"},
42 {"unknown", "", "should return empty string for unknown field"},
43 }
44
45 for _, tt := range tests {
46 t.Run(tt.name, func(t *testing.T) {
47 result := record.GetField(tt.field)
48 if result != tt.expected {
49 t.Errorf("GetField(%q) = %v, want %v", tt.field, result, tt.expected)
50 }
51 })
52 }
53 })
54
55 t.Run("ModelInterface", func(t *testing.T) {
56 if record.GetID() != 5 {
57 t.Errorf("GetID() = %d, want 5", record.GetID())
58 }
59
60 if record.GetTableName() != "projects" {
61 t.Errorf("GetTableName() = %q, want 'projects'", record.GetTableName())
62 }
63
64 Expect.AssertZeroTime(t, record.GetCreatedAt, "GetCreatedAt")
65 Expect.AssertZeroTime(t, record.GetUpdatedAt, "GetUpdatedAt")
66 })
67 })
68
69 t.Run("ProjectDataSource", func(t *testing.T) {
70 t.Run("Load", func(t *testing.T) {
71 projects := []repo.ProjectSummary{
72 {Name: "Project 1", TaskCount: 5},
73 {Name: "Project 2", TaskCount: 3},
74 }
75 repo := &mockProjectRepository{projects: projects}
76 source := &ProjectDataSource{repo: repo}
77
78 records, err := source.Load(context.Background(), DataOptions{})
79 if err != nil {
80 t.Fatalf("Load() failed: %v", err)
81 }
82
83 if len(records) != 2 {
84 t.Errorf("Load() returned %d records, want 2", len(records))
85 }
86
87 if records[0].GetField("name") != "Project 1" {
88 t.Errorf("First record name = %v, want 'Project 1'", records[0].GetField("name"))
89 }
90
91 if records[0].GetField("task_count") != 5 {
92 t.Errorf("First record task_count = %v, want 5", records[0].GetField("task_count"))
93 }
94 })
95
96 t.Run("Load_Error", func(t *testing.T) {
97 testErr := fmt.Errorf("test error")
98 repo := &mockProjectRepository{err: testErr}
99 source := &ProjectDataSource{repo: repo}
100
101 _, err := source.Load(context.Background(), DataOptions{})
102 if err != testErr {
103 t.Errorf("Load() error = %v, want %v", err, testErr)
104 }
105 })
106
107 t.Run("Count", func(t *testing.T) {
108 projects := []repo.ProjectSummary{
109 {Name: "Project 1", TaskCount: 5},
110 {Name: "Project 2", TaskCount: 3},
111 {Name: "Project 3", TaskCount: 1},
112 }
113 repo := &mockProjectRepository{projects: projects}
114 source := &ProjectDataSource{repo: repo}
115
116 count, err := source.Count(context.Background(), DataOptions{})
117 if err != nil {
118 t.Fatalf("Count() failed: %v", err)
119 }
120
121 if count != 3 {
122 t.Errorf("Count() = %d, want 3", count)
123 }
124 })
125
126 t.Run("Count_Error", func(t *testing.T) {
127 testErr := fmt.Errorf("test error")
128 repo := &mockProjectRepository{err: testErr}
129 source := &ProjectDataSource{repo: repo}
130
131 _, err := source.Count(context.Background(), DataOptions{})
132 if err != testErr {
133 t.Errorf("Count() error = %v, want %v", err, testErr)
134 }
135 })
136 })
137
138 t.Run("NewProjectDataTable", func(t *testing.T) {
139 repo := &mockProjectRepository{
140 projects: []repo.ProjectSummary{
141 {Name: "Test Project", TaskCount: 2},
142 },
143 }
144
145 opts := DataTableOptions{
146 Output: &bytes.Buffer{},
147 Input: strings.NewReader("q\n"),
148 Static: true,
149 }
150
151 table := NewProjectDataTable(repo, opts)
152 if table == nil {
153 t.Fatal("NewProjectDataTable() returned nil")
154 }
155
156 emptyOpts := DataTableOptions{
157 Output: &bytes.Buffer{},
158 Input: strings.NewReader("q\n"),
159 Static: true,
160 }
161
162 table2 := NewProjectDataTable(repo, emptyOpts)
163 if table2 == nil {
164 t.Fatal("NewProjectDataTable() with empty opts returned nil")
165 }
166
167 err := table.Browse(context.Background())
168 if err != nil {
169 t.Errorf("Browse() failed: %v", err)
170 }
171 })
172
173 t.Run("NewProjectListFromTable", func(t *testing.T) {
174 repo := &mockProjectRepository{
175 projects: []repo.ProjectSummary{
176 {Name: "Test Project", TaskCount: 1},
177 },
178 }
179
180 output := &bytes.Buffer{}
181 input := strings.NewReader("q\n")
182
183 table := NewProjectListFromTable(repo, output, input, true)
184 if table == nil {
185 t.Fatal("NewProjectListFromTable() returned nil")
186 }
187
188 err := table.Browse(context.Background())
189 if err != nil {
190 t.Errorf("Browse() failed: %v", err)
191 }
192
193 outputStr := output.String()
194 if !strings.Contains(outputStr, "Projects") {
195 t.Error("Output should contain 'Projects' title")
196 }
197 if !strings.Contains(outputStr, "Test Project") {
198 t.Error("Output should contain project name")
199 }
200 if !strings.Contains(outputStr, "1 task") {
201 t.Error("Output should contain formatted task count")
202 }
203 })
204}