diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-08-12 23:35:47 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-08-12 23:35:47 +0000 |
| commit | 2509dde6aa372a505b186657706f4d21bd391807 (patch) | |
| tree | e4147991747b8dd14d0b8654b2b8e50717b666bd /internal/handlers/handlers_test.go | |
| parent | 3e8ad60431d6cc783f9f7c555bfde5db54ebec75 (diff) | |
Add task title editing/deletion, timeline click-to-open, widget app launch
Task-detail modal was description-only with no delete affordance;
HandleUpdateTask now saves the title too and a Delete button hits a new
DELETE /tasks/{id} route backed by store.DeleteNativeTask, which repairs
chain_position/unlocks the successor when the deleted task belongs to a
chain. Timeline tab task/card/gtask rows now open the same detail modal
as the Tasks tab. Android widget's "TODAY" header is now a tap target
that launches DashboardActivity, since nothing previously opened the
full app from the widget.
Diffstat (limited to 'internal/handlers/handlers_test.go')
| -rw-r--r-- | internal/handlers/handlers_test.go | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index a43dec2..b618b71 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -3,6 +3,7 @@ package handlers import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -1910,6 +1911,106 @@ func TestHandleGetTaskDetail_DootSource_LoadsRealTaskFields(t *testing.T) { } } +func TestHandleUpdateTask_DootSource_UpdatesTitleAndDescription(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + + if err := h.store.CreateNativeTask(models.Task{ID: "task-1", Content: "Old title", Description: "Old desc", Priority: 1}); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("POST", "/tasks/update", strings.NewReader("id=task-1&source=doot&title=New+title&description=New+desc")) + 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()) + } + task, err := h.store.GetNativeTaskByID("task-1") + if err != nil { + t.Fatal(err) + } + if task.Content != "New title" { + t.Errorf("Content = %q, want %q", task.Content, "New title") + } + if task.Description != "New desc" { + t.Errorf("Description = %q, want %q", task.Description, "New desc") + } +} + +func TestHandleUpdateTask_DootSource_NoTitle_LeavesContentUntouched(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + + if err := h.store.CreateNativeTask(models.Task{ID: "task-1", Content: "Keep me", Description: "Old desc", Priority: 1}); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("POST", "/tasks/update", strings.NewReader("id=task-1&source=doot&description=New+desc")) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + w := httptest.NewRecorder() + h.HandleUpdateTask(w, req) + + if w.Code != http.StatusOK && w.Code != http.StatusSeeOther { + t.Fatalf("status = %d, want 200 or 303, body=%s", w.Code, w.Body.String()) + } + task, err := h.store.GetNativeTaskByID("task-1") + if err != nil { + t.Fatal(err) + } + if task.Content != "Keep me" { + t.Errorf("Content = %q, want unchanged %q (backward compat with task-detail-page.html, which has no title field)", task.Content, "Keep me") + } +} + +func TestHandleDeleteTask_DootSource_RemovesTask(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + + if err := h.store.CreateNativeTask(models.Task{ID: "task-1", Content: "Delete me", Priority: 1}); err != nil { + t.Fatal(err) + } + + req := withURLParam(httptest.NewRequest("DELETE", "/tasks/task-1?source=doot", nil), "id", "task-1") + w := httptest.NewRecorder() + h.HandleDeleteTask(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("status = %d, want 200, body=%s", w.Code, w.Body.String()) + } + if _, err := h.store.GetNativeTaskByID("task-1"); !errors.Is(err, store.ErrNativeTaskNotFound) { + t.Errorf("err = %v, want ErrNativeTaskNotFound", err) + } +} + +func TestHandleDeleteTask_NonDootSource_Rejected(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + + req := withURLParam(httptest.NewRequest("DELETE", "/tasks/card-1?source=trello", nil), "id", "card-1") + w := httptest.NewRecorder() + h.HandleDeleteTask(w, req) + + if w.Code != http.StatusBadRequest { + t.Errorf("status = %d, want 400", w.Code) + } +} + +func TestHandleDeleteTask_UnknownID_ReturnsNotFound(t *testing.T) { + h, cleanup := setupTestHandler(t) + defer cleanup() + + req := withURLParam(httptest.NewRequest("DELETE", "/tasks/does-not-exist?source=doot", nil), "id", "does-not-exist") + w := httptest.NewRecorder() + h.HandleDeleteTask(w, req) + + if w.Code != http.StatusNotFound { + t.Errorf("status = %d, want 404", w.Code) + } +} + func TestHandleSetTaskRecurrence_SetsAndClears(t *testing.T) { h, cleanup := setupTestHandler(t) defer cleanup() |
