diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-08-13 08:38:29 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-08-13 08:38:29 +0000 |
| commit | a03d7673e7adf9a575c7272b2b528a74b230535e (patch) | |
| tree | a4ac635d92eade4df117db2a4fbdfe1eff13d677 /internal/handlers | |
| parent | 2509dde6aa372a505b186657706f4d21bd391807 (diff) | |
Fix blank task-detail modal for Google Tasks
loadTaskDetailData/HandleUpdateTask only had cases for "trello" and
"doot" sources, so opening a gtask from the Tasks or Timeline tab
rendered an empty title/description. Reuses the existing
findGoogleTask cache lookup for both. Renamed the API client's
UpdateTaskNotes to UpdateTask(title, notes) so the web modal can save
an edited title too, not just description; the widget's
description-only edit popup now just round-trips the task's existing
title unchanged.
Diffstat (limited to 'internal/handlers')
| -rw-r--r-- | internal/handlers/handlers.go | 22 | ||||
| -rw-r--r-- | internal/handlers/handlers_test.go | 56 | ||||
| -rw-r--r-- | internal/handlers/widget.go | 2 | ||||
| -rw-r--r-- | internal/handlers/widget_test.go | 8 |
4 files changed, 85 insertions, 3 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 30aaf67..7220474 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -832,6 +832,10 @@ func (h *Handler) loadTaskDetailData(id, source string) taskDetailData { data.RecurrenceInterval = task.RecurrenceInterval data.RecurrenceWeekdays = task.RecurrenceWeekdays } + case "gtasks": + if t, ok := h.findGoogleTask(id); ok { + data.Title, data.Description = t.Title, t.Notes + } } return data } @@ -975,6 +979,24 @@ func (h *Handler) HandleUpdateTask(w http.ResponseWriter, r *http.Request) { updates["name"] = title } err = h.trelloClient.UpdateCard(r.Context(), id, updates) + case "gtasks": + if h.googleTasksClient == nil { + JSONError(w, http.StatusServiceUnavailable, "Google Tasks not configured", nil) + return + } + t, ok := h.findGoogleTask(id) + if !ok { + JSONError(w, http.StatusNotFound, "task not found", nil) + return + } + saveTitle := title + if saveTitle == "" { + saveTitle = t.Title + } + err = h.googleTasksClient.UpdateTask(r.Context(), t.ListID, id, saveTitle, description) + if err == nil { + _ = h.store.InvalidateCache(store.CacheKeyGoogleTasks) + } default: JSONError(w, http.StatusBadRequest, "Unknown source", nil) return diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index b618b71..8900f66 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -1965,6 +1965,62 @@ func TestHandleUpdateTask_DootSource_NoTitle_LeavesContentUntouched(t *testing.T } } +func TestHandleGetTaskDetail_GtasksSource_LoadsRealTaskFields(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + + if err := h.store.SaveGoogleTasks([]models.GoogleTask{ + {ID: "g1", Title: "Renew passport", Notes: "bring photo", ListID: "list-a", UpdatedAt: time.Now()}, + }); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/tasks/detail?id=g1&source=gtasks", nil) + w := httptest.NewRecorder() + h.HandleGetTaskDetail(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("status = %d, want 200, body=%s", w.Code, w.Body.String()) + } + mock := h.renderer.(*MockRenderer) + data, ok := mock.Calls[len(mock.Calls)-1].Data.(taskDetailData) + if !ok { + t.Fatalf("unexpected data type %T", mock.Calls[len(mock.Calls)-1].Data) + } + if data.Title != "Renew passport" || data.Description != "bring photo" { + t.Errorf("Title/Description = %q/%q, want %q/%q", data.Title, data.Description, "Renew passport", "bring photo") + } + if data.IsDoot { + t.Error("IsDoot = true, want false for a Google Task") + } +} + +func TestHandleUpdateTask_GtasksSource_SavesTitleAndNotes(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + + if err := h.store.SaveGoogleTasks([]models.GoogleTask{ + {ID: "g1", Title: "Old title", Notes: "Old notes", ListID: "list-a", UpdatedAt: time.Now()}, + }); err != nil { + t.Fatal(err) + } + mock := &mockGoogleTasksClient{} + h.googleTasksClient = mock + + req := httptest.NewRequest("POST", "/tasks/update", strings.NewReader("id=g1&source=gtasks&title=New+title&description=New+notes")) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("HX-Request", "true") + w := httptest.NewRecorder() + h.HandleUpdateTask(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("status = %d, want 200, body=%s", w.Code, w.Body.String()) + } + if mock.notesListID != "list-a" || mock.notesTaskID != "g1" || mock.notesTitle != "New title" || mock.notes != "New notes" { + t.Errorf("unexpected update call: listID=%q taskID=%q title=%q notes=%q", mock.notesListID, mock.notesTaskID, mock.notesTitle, mock.notes) + } +} + func TestHandleDeleteTask_DootSource_RemovesTask(t *testing.T) { h, cleanup := setupTestHandler(t) defer cleanup() diff --git a/internal/handlers/widget.go b/internal/handlers/widget.go index 1f6911b..7557da1 100644 --- a/internal/handlers/widget.go +++ b/internal/handlers/widget.go @@ -367,7 +367,7 @@ func (h *Handler) HandleWidgetUpdate(w http.ResponseWriter, r *http.Request) { http.Error(w, "task not found", http.StatusNotFound) return } - if err := h.googleTasksClient.UpdateTaskNotes(r.Context(), t.ListID, req.ID, req.Description); err != nil { + if err := h.googleTasksClient.UpdateTask(r.Context(), t.ListID, req.ID, t.Title, req.Description); err != nil { http.Error(w, "failed to update task", http.StatusInternalServerError) return } diff --git a/internal/handlers/widget_test.go b/internal/handlers/widget_test.go index 2bb92bc..fcbba52 100644 --- a/internal/handlers/widget_test.go +++ b/internal/handlers/widget_test.go @@ -17,6 +17,7 @@ import ( type mockGoogleTasksClient struct { completedListID, completedTaskID string notesListID, notesTaskID, notes string + notesTitle string } func (m *mockGoogleTasksClient) GetTasks(ctx context.Context) ([]models.GoogleTask, error) { @@ -32,8 +33,8 @@ func (m *mockGoogleTasksClient) CompleteTask(ctx context.Context, listID, taskID func (m *mockGoogleTasksClient) UncompleteTask(ctx context.Context, listID, taskID string) error { return nil } -func (m *mockGoogleTasksClient) UpdateTaskNotes(ctx context.Context, listID, taskID, notes string) error { - m.notesListID, m.notesTaskID, m.notes = listID, taskID, notes +func (m *mockGoogleTasksClient) UpdateTask(ctx context.Context, listID, taskID, title, notes string) error { + m.notesListID, m.notesTaskID, m.notesTitle, m.notes = listID, taskID, title, notes return nil } func (m *mockGoogleTasksClient) GetTaskLists(ctx context.Context) ([]models.TaskListInfo, error) { @@ -678,6 +679,9 @@ func TestHandleWidgetUpdate_GoogleTask(t *testing.T) { if mock.notesListID != "list-a" || mock.notesTaskID != "g1" || mock.notes != "bring photo and $170" { t.Errorf("unexpected update call: listID=%q taskID=%q notes=%q", mock.notesListID, mock.notesTaskID, mock.notes) } + if mock.notesTitle != "Renew passport" { + t.Errorf("title = %q, want unchanged %q (widget's edit popup has no title field)", mock.notesTitle, "Renew passport") + } } func seedTestCard(t *testing.T, db *store.Store) { |
