diff options
Diffstat (limited to 'internal/models/atom_test.go')
| -rw-r--r-- | internal/models/atom_test.go | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/internal/models/atom_test.go b/internal/models/atom_test.go new file mode 100644 index 0000000..537d232 --- /dev/null +++ b/internal/models/atom_test.go @@ -0,0 +1,291 @@ +package models + +import ( + "testing" + "time" +) + +func TestTaskToAtom(t *testing.T) { + now := time.Now() + task := Task{ + ID: "task-123", + Content: "Test task", + Description: "Task description", + DueDate: &now, + Priority: 3, + URL: "https://todoist.com/task/123", + CreatedAt: now, + IsRecurring: true, + } + + atom := TaskToAtom(task) + + if atom.ID != "task-123" { + t.Errorf("Expected ID 'task-123', got '%s'", atom.ID) + } + if atom.Title != "Test task" { + t.Errorf("Expected title 'Test task', got '%s'", atom.Title) + } + if atom.Source != SourceTodoist { + t.Errorf("Expected source Todoist, got '%s'", atom.Source) + } + if atom.Type != TypeTask { + t.Errorf("Expected type Task, got '%s'", atom.Type) + } + if atom.Priority != 3 { + t.Errorf("Expected priority 3, got %d", atom.Priority) + } + if atom.SourceIcon != "🔴" { + t.Error("Expected red circle icon") + } + if !atom.IsRecurring { + t.Error("Expected IsRecurring to be true") + } +} + +func TestTaskToAtom_PriorityClamping(t *testing.T) { + // Test priority below 1 + lowTask := Task{Priority: 0} + lowAtom := TaskToAtom(lowTask) + if lowAtom.Priority != 1 { + t.Errorf("Priority should be clamped to 1, got %d", lowAtom.Priority) + } + + // Test priority above 4 + highTask := Task{Priority: 10} + highAtom := TaskToAtom(highTask) + if highAtom.Priority != 4 { + t.Errorf("Priority should be clamped to 4, got %d", highAtom.Priority) + } +} + +func TestCardToAtom(t *testing.T) { + now := time.Now() + card := Card{ + ID: "card-456", + Name: "Test card", + ListName: "To Do", + DueDate: &now, + URL: "https://trello.com/c/456", + } + + atom := CardToAtom(card) + + if atom.ID != "card-456" { + t.Errorf("Expected ID 'card-456', got '%s'", atom.ID) + } + if atom.Title != "Test card" { + t.Errorf("Expected title 'Test card', got '%s'", atom.Title) + } + if atom.Description != "To Do" { + t.Errorf("Expected description 'To Do', got '%s'", atom.Description) + } + if atom.Source != SourceTrello { + t.Errorf("Expected source Trello, got '%s'", atom.Source) + } + if atom.Priority != 2 { + t.Errorf("Expected default priority 2, got %d", atom.Priority) + } + if atom.SourceIcon != "📋" { + t.Error("Expected clipboard icon") + } +} + +func TestMealToAtom(t *testing.T) { + date := time.Now() + meal := Meal{ + ID: "meal-789", + RecipeName: "Pasta", + MealType: "dinner", + Date: date, + RecipeURL: "https://plantoeat.com/recipe/789", + } + + atom := MealToAtom(meal) + + if atom.ID != "meal-789" { + t.Errorf("Expected ID 'meal-789', got '%s'", atom.ID) + } + if atom.Title != "Pasta" { + t.Errorf("Expected title 'Pasta', got '%s'", atom.Title) + } + if atom.Description != "dinner" { + t.Errorf("Expected description 'dinner', got '%s'", atom.Description) + } + if atom.Source != SourceMeal { + t.Errorf("Expected source Meal, got '%s'", atom.Source) + } + if atom.Type != TypeMeal { + t.Errorf("Expected type Meal, got '%s'", atom.Type) + } + if atom.Priority != 1 { + t.Errorf("Expected priority 1, got %d", atom.Priority) + } +} + +func TestBugToAtom(t *testing.T) { + now := time.Now() + bug := Bug{ + ID: 42, + Description: "Something is broken", + CreatedAt: now, + } + + atom := BugToAtom(bug) + + if atom.ID != "bug-42" { + t.Errorf("Expected ID 'bug-42', got '%s'", atom.ID) + } + if atom.Title != "Something is broken" { + t.Errorf("Expected title 'Something is broken', got '%s'", atom.Title) + } + if atom.Source != SourceBug { + t.Errorf("Expected source Bug, got '%s'", atom.Source) + } + if atom.Type != TypeBug { + t.Errorf("Expected type Bug, got '%s'", atom.Type) + } + if atom.Priority != 3 { + t.Errorf("Expected high priority 3, got %d", atom.Priority) + } + if atom.SourceIcon != "🐛" { + t.Error("Expected bug icon") + } +} + +func TestAtom_ComputeUIFields(t *testing.T) { + // Test nil due date + t.Run("nil due date", func(t *testing.T) { + atom := Atom{} + atom.ComputeUIFields() + // Should not panic and fields should remain default + if atom.IsOverdue || atom.IsFuture || atom.HasSetTime { + t.Error("Fields should be false for nil due date") + } + }) + + // Test with due date at midnight (no specific time) + t.Run("midnight due date", func(t *testing.T) { + now := time.Now() + midnightTomorrow := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.UTC) + atom := Atom{DueDate: &midnightTomorrow} + atom.ComputeUIFields() + if atom.HasSetTime { + t.Error("HasSetTime should be false for midnight") + } + }) + + // Test with specific time + t.Run("specific time", func(t *testing.T) { + now := time.Now() + withTime := time.Date(now.Year(), now.Month(), now.Day()+1, 14, 30, 0, 0, time.UTC) + atom := Atom{DueDate: &withTime} + atom.ComputeUIFields() + if !atom.HasSetTime { + t.Error("HasSetTime should be true for 14:30") + } + }) +} + +func TestTimelineItem_ComputeDaySection(t *testing.T) { + // Use UTC since that's the default display timezone when not configured + tz := time.UTC + now := time.Now().In(tz) + today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, tz) + tomorrow := today.AddDate(0, 0, 1) + nextWeek := today.AddDate(0, 0, 7) + yesterday := today.AddDate(0, 0, -1) + + tests := []struct { + name string + itemTime time.Time + wantSection DaySection + wantOverdue bool + wantAllDay bool + }{ + { + name: "today with specific time", + itemTime: time.Date(now.Year(), now.Month(), now.Day(), 14, 30, 0, 0, tz), + wantSection: DaySectionToday, + wantOverdue: false, + wantAllDay: false, + }, + { + name: "today all day (midnight)", + itemTime: today, + wantSection: DaySectionToday, + wantOverdue: false, + wantAllDay: true, + }, + { + name: "tomorrow", + itemTime: tomorrow.Add(10 * time.Hour), + wantSection: DaySectionTomorrow, + wantOverdue: false, + wantAllDay: false, + }, + { + name: "later (next week)", + itemTime: nextWeek, + wantSection: DaySectionLater, + wantOverdue: false, + wantAllDay: true, + }, + { + name: "overdue (yesterday)", + itemTime: yesterday, + wantSection: DaySectionToday, // Overdue items show in today section + wantOverdue: true, + wantAllDay: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + item := &TimelineItem{Time: tc.itemTime} + item.ComputeDaySection(now) + + if item.DaySection != tc.wantSection { + t.Errorf("Expected section %s, got %s", tc.wantSection, item.DaySection) + } + if item.IsOverdue != tc.wantOverdue { + t.Errorf("Expected overdue=%v, got %v", tc.wantOverdue, item.IsOverdue) + } + if item.IsAllDay != tc.wantAllDay { + t.Errorf("Expected allDay=%v, got %v", tc.wantAllDay, item.IsAllDay) + } + }) + } +} + +func TestCacheMetadata_IsCacheValid(t *testing.T) { + t.Run("valid cache", func(t *testing.T) { + cm := CacheMetadata{ + LastFetch: time.Now(), + TTLMinutes: 5, + } + if !cm.IsCacheValid() { + t.Error("Cache should be valid") + } + }) + + t.Run("expired cache", func(t *testing.T) { + cm := CacheMetadata{ + LastFetch: time.Now().Add(-10 * time.Minute), + TTLMinutes: 5, + } + if cm.IsCacheValid() { + t.Error("Cache should be expired") + } + }) + + t.Run("zero TTL", func(t *testing.T) { + cm := CacheMetadata{ + LastFetch: time.Now().Add(-1 * time.Second), + TTLMinutes: 0, + } + if cm.IsCacheValid() { + t.Error("Cache with zero TTL should be expired") + } + }) +} |
