Skip to content

Commit 739724d

Browse files
committed
refactor: split tests into separate files by functionality
- Created test_helpers.go with shared test utilities (newTestModel, setupTestDB, extractColorCode) - Created database_test.go with all database-related tests (11 tests) - Created utils_test.go with formatting and utility function tests (2 tests) - Created view_test.go with view rendering tests (6 tests) - Created handlers_test.go with event handler and keyboard shortcut tests (28 tests) - Removed original tiny_timer_test.go file All 47 tests pass successfully after refactoring.
1 parent 1b2ba77 commit 739724d

5 files changed

Lines changed: 706 additions & 672 deletions

File tree

database_test.go

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
package main
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
"os"
7+
"testing"
8+
"time"
9+
10+
"github.com/charmbracelet/bubbles/progress"
11+
tea "github.com/charmbracelet/bubbletea"
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestInitDB(t *testing.T) {
16+
// Set up a temporary database path
17+
tempDBPath, cleanup := setupTestDB(t)
18+
defer cleanup()
19+
20+
// Initialize the database
21+
err := initDB()
22+
assert.NoError(t, err, "initDB should not return an error")
23+
24+
// Verify the database file was created
25+
_, err = os.Stat(tempDBPath)
26+
assert.NoError(t, err, "Database file should exist after initDB")
27+
28+
// Verify the sessions table was created
29+
db, err := sql.Open("sqlite3", tempDBPath)
30+
assert.NoError(t, err)
31+
defer db.Close()
32+
33+
// Query the table to ensure it exists
34+
var tableName string
35+
err = db.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name='sessions'").Scan(&tableName)
36+
assert.NoError(t, err, "sessions table should exist")
37+
assert.Equal(t, "sessions", tableName, "Table name should be 'sessions'")
38+
}
39+
40+
func TestInitDBIdempotent(t *testing.T) {
41+
// Set up a temporary database path
42+
_, cleanup := setupTestDB(t)
43+
defer cleanup()
44+
45+
// Initialize the database twice
46+
err := initDB()
47+
assert.NoError(t, err, "First initDB should not return an error")
48+
49+
err = initDB()
50+
assert.NoError(t, err, "Second initDB should not return an error (idempotent)")
51+
}
52+
53+
func TestGetRecentSessionsOnEmptyDB(t *testing.T) {
54+
// Set up a temporary database path
55+
_, cleanup := setupTestDB(t)
56+
defer cleanup()
57+
58+
// Initialize the database
59+
err := initDB()
60+
assert.NoError(t, err)
61+
62+
// Fetch recent sessions from empty database
63+
sessions, err := getRecentSessions(10)
64+
assert.NoError(t, err, "getRecentSessions should not error on empty database")
65+
assert.Empty(t, sessions, "Expected no sessions in empty database")
66+
}
67+
68+
func TestSaveSessionToDB(t *testing.T) {
69+
// Set up a temporary database path
70+
tempDBPath, cleanup := setupTestDB(t)
71+
defer cleanup()
72+
73+
// Test data.
74+
// Assumes that each tuple of (duration, completed, title) is unique.
75+
tests := []struct {
76+
duration int64
77+
completed bool
78+
title string
79+
}{
80+
{60, true, "Test Session 1"},
81+
{120, false, "Test Session 2"},
82+
{180, true, ""},
83+
}
84+
85+
for _, test := range tests {
86+
err := saveSessionToDB(test.duration, test.completed, test.title)
87+
assert.NoError(t, err, "saveSessionToDB(%d, %v, %s)", test.duration, test.completed, test.title)
88+
89+
// Verify the session was saved correctly
90+
db, err := sql.Open("sqlite3", tempDBPath)
91+
assert.NoError(t, err)
92+
defer db.Close()
93+
94+
var count int
95+
err = db.QueryRow("SELECT COUNT(*) FROM sessions WHERE duration = ? AND completed = ? AND title = ?", test.duration, test.completed, test.title).Scan(&count)
96+
assert.NoError(t, err)
97+
assert.Equal(t, 1, count, "Expected one session with duration %d, completed %v, and title %s", test.duration, test.completed, test.title)
98+
}
99+
}
100+
101+
func TestSaveSessionToDBWithTitle(t *testing.T) {
102+
// Set up a temporary database path
103+
tempDBPath, cleanup := setupTestDB(t)
104+
defer cleanup()
105+
106+
// Test with a specific title
107+
title := "Important Work Session"
108+
duration := int64(1500)
109+
completed := true
110+
111+
err := saveSessionToDB(duration, completed, title)
112+
assert.NoError(t, err)
113+
114+
// Verify the title was saved correctly
115+
db, err := sql.Open("sqlite3", tempDBPath)
116+
assert.NoError(t, err)
117+
defer db.Close()
118+
119+
var savedTitle string
120+
err = db.QueryRow("SELECT title FROM sessions WHERE duration = ? AND completed = ?", duration, completed).Scan(&savedTitle)
121+
assert.NoError(t, err)
122+
assert.Equal(t, title, savedTitle, "Expected title to be saved correctly")
123+
}
124+
125+
func TestSaveSessionToDBWithoutTitle(t *testing.T) {
126+
// Set up a temporary database path
127+
tempDBPath, cleanup := setupTestDB(t)
128+
defer cleanup()
129+
130+
// Test without title (empty string)
131+
duration := int64(900)
132+
completed := false
133+
134+
err := saveSessionToDB(duration, completed, "")
135+
assert.NoError(t, err)
136+
137+
// Verify the empty title was saved correctly
138+
db, err := sql.Open("sqlite3", tempDBPath)
139+
assert.NoError(t, err)
140+
defer db.Close()
141+
142+
var savedTitle string
143+
err = db.QueryRow("SELECT title FROM sessions WHERE duration = ? AND completed = ?", duration, completed).Scan(&savedTitle)
144+
assert.NoError(t, err)
145+
assert.Equal(t, "", savedTitle, "Expected title to be empty")
146+
}
147+
148+
func TestDatabaseSchemaHasTitleColumn(t *testing.T) {
149+
// Set up a temporary database path
150+
tempDBPath, cleanup := setupTestDB(t)
151+
defer cleanup()
152+
153+
// Create a session to initialize the database
154+
err := saveSessionToDB(60, true, "Test")
155+
assert.NoError(t, err)
156+
157+
// Verify the title column exists
158+
db, err := sql.Open("sqlite3", tempDBPath)
159+
assert.NoError(t, err)
160+
defer db.Close()
161+
162+
rows, err := db.Query("PRAGMA table_info(sessions)")
163+
assert.NoError(t, err)
164+
defer rows.Close()
165+
166+
columnNames := make(map[string]bool)
167+
for rows.Next() {
168+
var cid int
169+
var name string
170+
var ctype string
171+
var notnull int
172+
var dfltValue interface{}
173+
var pk int
174+
175+
err = rows.Scan(&cid, &name, &ctype, &notnull, &dfltValue, &pk)
176+
assert.NoError(t, err)
177+
columnNames[name] = true
178+
}
179+
180+
// Verify all expected columns exist
181+
assert.True(t, columnNames["id"], "Expected 'id' column to exist")
182+
assert.True(t, columnNames["datetime"], "Expected 'datetime' column to exist")
183+
assert.True(t, columnNames["duration"], "Expected 'duration' column to exist")
184+
assert.True(t, columnNames["completed"], "Expected 'completed' column to exist")
185+
assert.True(t, columnNames["title"], "Expected 'title' column to exist")
186+
}
187+
188+
func TestGetRecentSessions(t *testing.T) {
189+
// Set up a temporary database path
190+
_, cleanup := setupTestDB(t)
191+
defer cleanup()
192+
193+
// Create several test sessions
194+
sessions := []struct {
195+
duration int64
196+
completed bool
197+
title string
198+
}{
199+
{1500, true, "Task 1"},
200+
{900, false, "Task 2"},
201+
{1800, true, "Task 3"},
202+
{600, true, "Task 4"},
203+
}
204+
205+
for _, s := range sessions {
206+
err := saveSessionToDB(s.duration, s.completed, s.title)
207+
assert.NoError(t, err)
208+
}
209+
210+
// Fetch recent sessions
211+
recentSessions, err := getRecentSessions(10)
212+
assert.NoError(t, err)
213+
assert.Equal(t, len(sessions), len(recentSessions), "Expected to retrieve all sessions")
214+
215+
// Verify that all session titles are present
216+
titles := make(map[string]bool)
217+
for _, s := range recentSessions {
218+
titles[s.title] = true
219+
}
220+
for _, s := range sessions {
221+
assert.True(t, titles[s.title], "Expected session with title '%s' to be in results", s.title)
222+
}
223+
}
224+
225+
func TestGetRecentSessionsWithLimit(t *testing.T) {
226+
// Set up a temporary database path
227+
_, cleanup := setupTestDB(t)
228+
defer cleanup()
229+
230+
// Create several test sessions
231+
for i := 0; i < 5; i++ {
232+
err := saveSessionToDB(1500, true, fmt.Sprintf("Task %d", i))
233+
assert.NoError(t, err)
234+
time.Sleep(10 * time.Millisecond)
235+
}
236+
237+
// Fetch only 3 most recent sessions
238+
recentSessions, err := getRecentSessions(3)
239+
assert.NoError(t, err)
240+
assert.Equal(t, 3, len(recentSessions), "Expected to retrieve only 3 sessions")
241+
}
242+
243+
func TestDateFormatInTableFromDatabase(t *testing.T) {
244+
// Set up a temporary database path
245+
_, cleanup := setupTestDB(t)
246+
defer cleanup()
247+
248+
// Save a session to the database
249+
err := saveSessionToDB(1500, true, "Test Task")
250+
assert.NoError(t, err)
251+
252+
// Retrieve the session
253+
sessions, err := getRecentSessions(1)
254+
assert.NoError(t, err)
255+
assert.Equal(t, 1, len(sessions), "Expected one session")
256+
257+
// Verify the datetime can be parsed and formatted correctly
258+
s := sessions[0]
259+
// SQLite DATETIME can store dates in different formats, try both
260+
var parsedTime time.Time
261+
if parsedTime, err = time.Parse("2006-01-02 15:04:05", s.datetime); err != nil {
262+
parsedTime, err = time.Parse("2006-01-02T15:04:05Z", s.datetime)
263+
}
264+
assert.NoError(t, err, "Should be able to parse datetime from database: %s", s.datetime)
265+
266+
// Format it the way the table displays it
267+
formatted := parsedTime.Format("Monday, 2 Jan 06")
268+
269+
// Verify the format matches expected pattern (e.g., "Wednesday, 7 Jan 26")
270+
assert.Regexp(t, `^\w+, \d{1,2} \w+ \d{2}$`, formatted, "Date should match pattern 'DayName, D Mon YY'")
271+
}
272+
273+
func TestFirstSaveThenImmediateHistoryRead(t *testing.T) {
274+
_, cleanup := setupTestDB(t)
275+
defer cleanup()
276+
277+
// Initialize database (creates new empty DB)
278+
err := initDB()
279+
assert.NoError(t, err)
280+
281+
startTime := time.Now().Unix() - 120
282+
m := model{
283+
progress: progress.New(progress.WithGradient(colorMontezumaGold, colorCream), progress.WithoutPercentage()),
284+
startTime: startTime,
285+
targetDuration: 3600,
286+
countUpMode: true,
287+
mode: timerView,
288+
title: "First Task",
289+
}
290+
291+
// Press 'd' to mark done and activate prompt
292+
keyMsg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'d'}}
293+
newModel, _ := m.Update(keyMsg)
294+
modelTyped := newModel.(model)
295+
296+
assert.True(t, modelTyped.promptActive, "Expected prompt to be active")
297+
assert.Equal(t, promptLogAndReset, modelTyped.promptType, "Expected promptType to be promptLogAndReset")
298+
299+
// Complete the prompt (this saves to DB and refreshes table)
300+
newModel, _ = modelTyped.Update(promptMsg{title: "First Task", logDB: true})
301+
modelTyped = newModel.(model)
302+
303+
// Immediately press 'h' to show history (this is the bug scenario)
304+
keyMsg = tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'h'}}
305+
newModel, _ = modelTyped.Update(keyMsg)
306+
modelTyped = newModel.(model)
307+
308+
// Verify we're in table view
309+
assert.Equal(t, tableView, modelTyped.mode, "Expected to switch to table view")
310+
311+
// Verify the table has rows (this would fail with the bug)
312+
tableRows := modelTyped.table.Rows()
313+
assert.NotEmpty(t, tableRows, "Expected table to have rows after saving first task")
314+
assert.GreaterOrEqual(t, len(tableRows), 1, "Expected at least one row in history table")
315+
316+
// Verify the saved task appears in the table
317+
found := false
318+
for _, row := range tableRows {
319+
if len(row) > 0 && row[0] == "First Task" {
320+
found = true
321+
break
322+
}
323+
}
324+
assert.True(t, found, "Expected 'First Task' to appear in history table")
325+
}

0 commit comments

Comments
 (0)