tangled
alpha
login
or
join now
desertthunder.dev
/
noteleaf
cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists ๐
charm
leaflet
readability
golang
29
fork
atom
overview
issues
2
pulls
pipelines
fix: note list testing
desertthunder.dev
5 months ago
7da45884
1b08b0ca
+292
-1
2 changed files
expand all
collapse all
unified
split
internal
ui
note_list.go
note_list_test.go
-1
internal/ui/note_list.go
···
1
1
-
// FIXME: this module is missing test coverage
2
1
package ui
3
2
4
3
import (
+292
internal/ui/note_list_test.go
···
1
1
+
package ui
2
2
+
3
3
+
import (
4
4
+
"bytes"
5
5
+
"context"
6
6
+
"database/sql"
7
7
+
"errors"
8
8
+
"strings"
9
9
+
"testing"
10
10
+
"time"
11
11
+
12
12
+
_ "github.com/mattn/go-sqlite3"
13
13
+
"github.com/stormlightlabs/noteleaf/internal/models"
14
14
+
"github.com/stormlightlabs/noteleaf/internal/repo"
15
15
+
)
16
16
+
17
17
+
// newTestNoteRepo sets up an in-memory SQLite database and returns a [repo.NoteRepository].
18
18
+
func newTestNoteRepo(t *testing.T) (*repo.NoteRepository, func()) {
19
19
+
t.Helper()
20
20
+
21
21
+
db, err := sql.Open("sqlite3", ":memory:")
22
22
+
if err != nil {
23
23
+
t.Fatalf("Failed to open in-memory database: %v", err)
24
24
+
}
25
25
+
26
26
+
// Schema based on [models.Note] and observed errors
27
27
+
createTableSQL := `
28
28
+
CREATE TABLE notes (
29
29
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
30
30
+
title TEXT NOT NULL,
31
31
+
content TEXT,
32
32
+
tags TEXT,
33
33
+
created DATETIME NOT NULL,
34
34
+
modified DATETIME NOT NULL,
35
35
+
archived BOOLEAN NOT NULL DEFAULT 0,
36
36
+
file_path TEXT
37
37
+
);`
38
38
+
if _, err := db.Exec(createTableSQL); err != nil {
39
39
+
t.Fatalf("Failed to create notes table: %v", err)
40
40
+
}
41
41
+
42
42
+
noteRepo := repo.NewNoteRepository(db)
43
43
+
44
44
+
cleanup := func() {
45
45
+
db.Close()
46
46
+
}
47
47
+
48
48
+
return noteRepo, cleanup
49
49
+
}
50
50
+
51
51
+
func TestNoteListOptions(t *testing.T) {
52
52
+
t.Run("default options", func(t *testing.T) {
53
53
+
repo, cleanup := newTestNoteRepo(t)
54
54
+
defer cleanup()
55
55
+
56
56
+
nl := NewNoteList(repo, NoteListOptions{})
57
57
+
if nl.opts.Static {
58
58
+
t.Error("Static should default to false")
59
59
+
}
60
60
+
})
61
61
+
62
62
+
t.Run("custom options", func(t *testing.T) {
63
63
+
repo, cleanup := newTestNoteRepo(t)
64
64
+
defer cleanup()
65
65
+
66
66
+
var buf bytes.Buffer
67
67
+
var in strings.Reader
68
68
+
69
69
+
opts := NoteListOptions{
70
70
+
Output: &buf,
71
71
+
Input: &in,
72
72
+
Static: true,
73
73
+
}
74
74
+
75
75
+
nl := NewNoteList(repo, opts)
76
76
+
77
77
+
if !nl.opts.Static {
78
78
+
t.Error("Static should be true")
79
79
+
}
80
80
+
if nl.opts.Output != &buf {
81
81
+
t.Error("Output should be set to buffer")
82
82
+
}
83
83
+
if nl.opts.Input != &in {
84
84
+
t.Error("Input should be set to reader")
85
85
+
}
86
86
+
})
87
87
+
}
88
88
+
89
89
+
func TestStaticList(t *testing.T) {
90
90
+
ctx := context.Background()
91
91
+
92
92
+
t.Run("no notes", func(t *testing.T) {
93
93
+
repo, cleanup := newTestNoteRepo(t)
94
94
+
defer cleanup()
95
95
+
96
96
+
var buf bytes.Buffer
97
97
+
nl := NewNoteList(repo, NoteListOptions{
98
98
+
Output: &buf,
99
99
+
Static: true,
100
100
+
})
101
101
+
102
102
+
err := nl.Browse(ctx)
103
103
+
if err != nil {
104
104
+
t.Fatalf("Browse failed: %v", err)
105
105
+
}
106
106
+
107
107
+
output := buf.String()
108
108
+
if !strings.Contains(output, "No notes found") {
109
109
+
t.Errorf("Expected 'No notes found', got %q", output)
110
110
+
}
111
111
+
})
112
112
+
113
113
+
t.Run("with notes", func(t *testing.T) {
114
114
+
repo, cleanup := newTestNoteRepo(t)
115
115
+
defer cleanup()
116
116
+
117
117
+
// Create some instances of [models.Note]
118
118
+
note1 := &models.Note{Title: "Test Note 1", Content: "Content 1", Tags: []string{"t1"}, Created: time.Now(), Modified: time.Now()}
119
119
+
note2 := &models.Note{Title: "Test Note 2", Content: "Content 2", Tags: []string{"t2"}, Created: time.Now(), Modified: time.Now()}
120
120
+
if _, err := repo.Create(ctx, note1); err != nil {
121
121
+
t.Fatalf("Failed to create note: %v", err)
122
122
+
}
123
123
+
if _, err := repo.Create(ctx, note2); err != nil {
124
124
+
t.Fatalf("Failed to create note: %v", err)
125
125
+
}
126
126
+
127
127
+
var buf bytes.Buffer
128
128
+
nl := NewNoteList(repo, NoteListOptions{
129
129
+
Output: &buf,
130
130
+
Static: true,
131
131
+
})
132
132
+
133
133
+
err := nl.Browse(ctx)
134
134
+
if err != nil {
135
135
+
t.Fatalf("Browse failed: %v", err)
136
136
+
}
137
137
+
138
138
+
output := buf.String()
139
139
+
if !strings.Contains(output, "Test Note 1") {
140
140
+
t.Error("Output does not contain first note")
141
141
+
}
142
142
+
if !strings.Contains(output, "Test Note 2") {
143
143
+
t.Error("Output does not contain second note")
144
144
+
}
145
145
+
if !strings.Contains(output, "t1") {
146
146
+
t.Error("Output does not contain first note's tag")
147
147
+
}
148
148
+
})
149
149
+
150
150
+
t.Run("with archived notes", func(t *testing.T) {
151
151
+
repo, cleanup := newTestNoteRepo(t)
152
152
+
defer cleanup()
153
153
+
154
154
+
note1 := &models.Note{Title: "Active Note", Created: time.Now(), Modified: time.Now()}
155
155
+
note2 := &models.Note{Title: "Archived Note", Archived: true, Created: time.Now(), Modified: time.Now()}
156
156
+
if _, err := repo.Create(ctx, note1); err != nil {
157
157
+
t.Fatalf("Failed to create note: %v", err)
158
158
+
}
159
159
+
if _, err := repo.Create(ctx, note2); err != nil {
160
160
+
t.Fatalf("Failed to create note: %v", err)
161
161
+
}
162
162
+
163
163
+
var buf bytes.Buffer
164
164
+
nl := NewNoteList(repo, NoteListOptions{
165
165
+
Output: &buf,
166
166
+
Static: true,
167
167
+
ShowArchived: false,
168
168
+
})
169
169
+
170
170
+
// Test with ShowArchived: false (default behavior)
171
171
+
err := nl.Browse(ctx)
172
172
+
if err != nil {
173
173
+
t.Fatalf("Browse failed: %v", err)
174
174
+
}
175
175
+
176
176
+
output := buf.String()
177
177
+
if !strings.Contains(output, "Active Note") {
178
178
+
t.Error("Expected to see active note")
179
179
+
}
180
180
+
if strings.Contains(output, "Archived Note") {
181
181
+
t.Error("Did not expect to see archived note")
182
182
+
}
183
183
+
184
184
+
buf.Reset()
185
185
+
nl.opts.ShowArchived = true
186
186
+
err = nl.Browse(ctx)
187
187
+
if err != nil {
188
188
+
t.Fatalf("Browse failed: %v", err)
189
189
+
}
190
190
+
191
191
+
output = buf.String()
192
192
+
if !strings.Contains(output, "Active Note") {
193
193
+
t.Error("Expected to see active note")
194
194
+
}
195
195
+
if !strings.Contains(output, "Archived Note") {
196
196
+
t.Error("Expected to see archived note")
197
197
+
}
198
198
+
})
199
199
+
}
200
200
+
201
201
+
func TestNoteListModelView(t *testing.T) {
202
202
+
repo, cleanup := newTestNoteRepo(t)
203
203
+
defer cleanup()
204
204
+
205
205
+
opts := NoteListOptions{}
206
206
+
207
207
+
t.Run("initial view", func(t *testing.T) {
208
208
+
model := noteListModel{
209
209
+
repo: repo,
210
210
+
opts: opts,
211
211
+
notes: []*models.Note{},
212
212
+
}
213
213
+
view := model.View()
214
214
+
if !strings.Contains(view, "No notes found") {
215
215
+
t.Errorf("Expected 'No notes found', got %q", view)
216
216
+
}
217
217
+
})
218
218
+
219
219
+
t.Run("error view", func(t *testing.T) {
220
220
+
model := noteListModel{
221
221
+
repo: repo,
222
222
+
opts: opts,
223
223
+
err: errors.New("test error"),
224
224
+
}
225
225
+
view := model.View()
226
226
+
if !strings.Contains(view, "Error: test error") {
227
227
+
t.Errorf("Expected error message, got %q", view)
228
228
+
}
229
229
+
})
230
230
+
231
231
+
t.Run("with notes view", func(t *testing.T) {
232
232
+
note := &models.Note{ID: 1, Title: "My Test Note", Tags: []string{"testing"}, Modified: time.Now()}
233
233
+
model := noteListModel{
234
234
+
repo: repo,
235
235
+
opts: opts,
236
236
+
notes: []*models.Note{note},
237
237
+
selected: 0,
238
238
+
}
239
239
+
view := model.View()
240
240
+
if !strings.Contains(view, "My Test Note") {
241
241
+
t.Error("Expected to see note title")
242
242
+
}
243
243
+
if !strings.Contains(view, ">") {
244
244
+
t.Error("Expected to see selection indicator")
245
245
+
}
246
246
+
})
247
247
+
248
248
+
t.Run("viewing note", func(t *testing.T) {
249
249
+
model := noteListModel{
250
250
+
repo: repo,
251
251
+
opts: opts,
252
252
+
viewing: true,
253
253
+
viewContent: "## My Note Content",
254
254
+
}
255
255
+
view := model.View()
256
256
+
if !strings.Contains(view, "## My Note Content") {
257
257
+
t.Error("Expected to see note content")
258
258
+
}
259
259
+
if !strings.Contains(view, "Press q/esc/backspace to return to list") {
260
260
+
t.Error("Expected to see exit instructions")
261
261
+
}
262
262
+
})
263
263
+
}
264
264
+
265
265
+
func TestFormatNoteForView(t *testing.T) {
266
266
+
note := &models.Note{
267
267
+
Title: "My Note Title",
268
268
+
Content: "This is the content.",
269
269
+
Tags: []string{"go", "testing"},
270
270
+
Created: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC),
271
271
+
Modified: time.Date(2023, 1, 1, 13, 0, 0, 0, time.UTC),
272
272
+
}
273
273
+
274
274
+
m := noteListModel{}
275
275
+
formatted := m.formatNoteForView(note)
276
276
+
277
277
+
if !strings.Contains(formatted, "# My Note Title") {
278
278
+
t.Error("Expected title markdown")
279
279
+
}
280
280
+
if !strings.Contains(formatted, "**Tags:** `go`, `testing`") {
281
281
+
t.Error("Expected tags markdown")
282
282
+
}
283
283
+
if !strings.Contains(formatted, "**Created:** 2023-01-01 12:00") {
284
284
+
t.Error("Expected created timestamp")
285
285
+
}
286
286
+
if !strings.Contains(formatted, "**Modified:** 2023-01-01 13:00") {
287
287
+
t.Error("Expected modified timestamp")
288
288
+
}
289
289
+
if !strings.Contains(formatted, "This is the content.") {
290
290
+
t.Error("Expected content")
291
291
+
}
292
292
+
}