From e85b42d373de55781af9d699b246c0d6a492aec1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Mar 2026 10:04:57 +0000 Subject: refactor: RF-03/06 extract groupMeals helper, eliminate convertSyncItemToTask wrapper RF-03: Extract shared groupMeals helper into internal/handlers/meals.go. Both HandleTabMeals and BuildTimeline now call groupMeals instead of duplicating the date+mealType grouping algorithm inline. CombinedMeal gains ID and Meals fields to carry the first-meal ID and original records needed by BuildTimeline when constructing TimelineItems. RF-06: Add api.ConvertSyncItemToTask for single-item conversion. ConvertSyncItemsToTasks now delegates to it, eliminating duplication. The Handler.convertSyncItemToTask wrapper (which allocated a one-element slice just to unwrap it) is deleted; its caller uses api.ConvertSyncItemToTask directly. Covered by TestConvertSyncItemToTask in internal/api/todoist_test.go. Co-Authored-By: Claude Sonnet 4.6 --- internal/api/todoist_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) (limited to 'internal/api/todoist_test.go') diff --git a/internal/api/todoist_test.go b/internal/api/todoist_test.go index 159be61..2204469 100644 --- a/internal/api/todoist_test.go +++ b/internal/api/todoist_test.go @@ -421,6 +421,57 @@ func TestConvertSyncItemsToTasks(t *testing.T) { } } +func TestConvertSyncItemToTask(t *testing.T) { + projects := map[string]string{"proj-1": "Project 1"} + + t.Run("active item returns task and true", func(t *testing.T) { + item := SyncItemResponse{ + ID: "item-1", + Content: "Active Task", + Description: "desc", + ProjectID: "proj-1", + Priority: 2, + Labels: []string{"work"}, + AddedAt: "2026-01-01T00:00:00Z", + } + task, ok := ConvertSyncItemToTask(item, projects) + if !ok { + t.Fatal("expected ok=true for active item") + } + if task.ID != "item-1" { + t.Errorf("expected ID 'item-1', got '%s'", task.ID) + } + if task.Content != "Active Task" { + t.Errorf("expected Content 'Active Task', got '%s'", task.Content) + } + if task.ProjectName != "Project 1" { + t.Errorf("expected ProjectName 'Project 1', got '%s'", task.ProjectName) + } + if task.Completed { + t.Error("expected Completed=false") + } + if task.URL != "https://todoist.com/app/task/item-1" { + t.Errorf("unexpected URL: %s", task.URL) + } + }) + + t.Run("completed item returns false", func(t *testing.T) { + item := SyncItemResponse{ID: "item-2", Content: "Done", ProjectID: "proj-1", IsCompleted: true} + _, ok := ConvertSyncItemToTask(item, projects) + if ok { + t.Error("expected ok=false for completed item") + } + }) + + t.Run("deleted item returns false", func(t *testing.T) { + item := SyncItemResponse{ID: "item-3", Content: "Gone", ProjectID: "proj-1", IsDeleted: true} + _, ok := ConvertSyncItemToTask(item, projects) + if ok { + t.Error("expected ok=false for deleted item") + } + }) +} + func TestBuildProjectMapFromSync(t *testing.T) { projects := []SyncProjectResponse{ {ID: "proj-1", Name: "Project 1"}, -- cgit v1.2.3