package web import ( "testing" "time" "github.com/m/projax/caldav" "github.com/m/projax/store" ) func TestTaskFromTodo(t *testing.T) { due := time.Date(2026, 6, 20, 0, 0, 0, 0, time.UTC) mod := time.Date(2026, 6, 1, 9, 0, 0, 0, time.UTC) td := caldav.Todo{ UID: "vtodo-123", Summary: "Pour foundation", Status: "NEEDS-ACTION", Due: &due, LastModified: &mod, } got := taskFromTodo(td, "https://dav/cal/", "item-uuid") if got.Source != store.TaskSourceCalDAV { t.Fatalf("source = %q, want caldav", got.Source) } if got.ID != "vtodo-123" || got.UID != "vtodo-123" { t.Fatalf("id/uid = %q/%q", got.ID, got.UID) } if got.Title != "Pour foundation" { t.Fatalf("title = %q", got.Title) } if got.Done { t.Fatal("NEEDS-ACTION should not be done") } if got.CalendarURL != "https://dav/cal/" { t.Fatalf("calURL = %q", got.CalendarURL) } if got.ParentItemID != "item-uuid" { t.Fatalf("parent = %q", got.ParentItemID) } if got.Due == nil || !got.Due.Equal(due) { t.Fatalf("due = %v, want %v", got.Due, due) } if !got.CreatedAt.Equal(mod) { t.Fatalf("createdAt = %v, want last-modified %v", got.CreatedAt, mod) } } func TestTaskFromTodoDoneStates(t *testing.T) { for _, st := range []string{"COMPLETED", "CANCELLED"} { got := taskFromTodo(caldav.Todo{UID: "x", Status: st}, "c", "i") if !got.Done { t.Fatalf("status %q should map to done", st) } } for _, st := range []string{"NEEDS-ACTION", "IN-PROCESS", ""} { got := taskFromTodo(caldav.Todo{UID: "x", Status: st}, "c", "i") if got.Done { t.Fatalf("status %q should NOT map to done", st) } } } func TestRenderHint(t *testing.T) { if renderHint(true) != "checklist" { t.Fatal("true should map to checklist") } if renderHint(false) != "" { t.Fatal("false should map to empty") } } func TestSortTaskRows(t *testing.T) { d := func(s string) *time.Time { tm, _ := time.Parse("2006-01-02", s) return &tm } mk := func(title string, due *time.Time, created string) taskRow { c, _ := time.Parse("2006-01-02", created) return taskRow{Task: &store.Task{Title: title, Due: due, CreatedAt: c}} } rows := []taskRow{ mk("undated-late", nil, "2026-06-03"), mk("due-later", d("2026-06-20"), "2026-06-01"), mk("undated-early", nil, "2026-06-02"), mk("due-soon", d("2026-06-10"), "2026-06-05"), } sortTaskRows(rows) got := []string{rows[0].Title, rows[1].Title, rows[2].Title, rows[3].Title} want := []string{"due-soon", "due-later", "undated-early", "undated-late"} for i := range want { if got[i] != want[i] { t.Fatalf("sort order = %v, want %v", got, want) } } } func TestAddTarget(t *testing.T) { cal := func(url string) caldav.Calendar { return caldav.Calendar{URL: url, DisplayName: url} } // CalDAV-bound, single calendar → caldav + that URL. u := unifiedTasks{CalDAVBound: true, LinkedCalendars: []caldav.Calendar{cal("https://c1/")}} if at := u.AddTarget(); at.Mode != "caldav" || at.CalendarURL != "https://c1/" { t.Fatalf("single-cal bound = %+v, want caldav+https://c1/", at) } // CalDAV-bound, multiple calendars → caldav + empty URL (form shows select). u = unifiedTasks{CalDAVBound: true, LinkedCalendars: []caldav.Calendar{cal("https://c1/"), cal("https://c2/")}} if at := u.AddTarget(); at.Mode != "caldav" || at.CalendarURL != "" { t.Fatalf("multi-cal bound = %+v, want caldav+empty", at) } // Unbound + mBrian backend → mbrian. u = unifiedTasks{CalDAVBound: false, MBrianOn: true} if at := u.AddTarget(); at.Mode != "mbrian" { t.Fatalf("unbound+mbrian = %+v, want mbrian", at) } // Unbound + no mBrian backend → no add affordance. u = unifiedTasks{CalDAVBound: false, MBrianOn: false} if at := u.AddTarget(); at.Mode != "" { t.Fatalf("unbound+no-backend = %+v, want empty mode", at) } }