summaryrefslogtreecommitdiff
path: root/internal/models/atom_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/models/atom_test.go')
-rw-r--r--internal/models/atom_test.go291
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")
+ }
+ })
+}