summaryrefslogtreecommitdiff
path: root/internal/handlers/handlers_test.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-08-12 23:35:47 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-08-12 23:35:47 +0000
commit2509dde6aa372a505b186657706f4d21bd391807 (patch)
treee4147991747b8dd14d0b8654b2b8e50717b666bd /internal/handlers/handlers_test.go
parent3e8ad60431d6cc783f9f7c555bfde5db54ebec75 (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.go101
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()