From ee62ff9f9b68ac026a900304232884dc1f6838ea Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Fri, 15 May 2026 23:02:05 +0200 Subject: [PATCH] fix(09): refresh etape counts on task delete --- backend/internal/web/handlers_tasks.go | 6 +- backend/internal/web/handlers_tasks_test.go | 69 +++++++++++++++++++++ backend/templates/tasks.templ | 3 +- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/backend/internal/web/handlers_tasks.go b/backend/internal/web/handlers_tasks.go index 87aa021..e4e3db8 100644 --- a/backend/internal/web/handlers_tasks.go +++ b/backend/internal/web/handlers_tasks.go @@ -366,9 +366,13 @@ func TaskDeleteHandler(deps TasksDeps) http.HandlerFunc { } if r.Header.Get("HX-Request") == "true" { + _, refreshedEtapes, refreshedCounts, refreshedFilter, ok := loadTasksTabData(w, r, deps.Queries, tablo) + if !ok { + return + } w.Header().Set("Content-Type", "text/html; charset=utf-8") // Return empty zone div so HTMX removes the card from the DOM (TASK-06). - _ = templates.TaskCardGone(task.ID).Render(r.Context(), w) + _ = templates.TaskCardGone(task.ID, tablo.ID, csrf.Token(r), refreshedFilter, refreshedEtapes, refreshedCounts).Render(r.Context(), w) return } http.Redirect(w, r, "/tablos/"+tablo.ID.String(), http.StatusSeeOther) diff --git a/backend/internal/web/handlers_tasks_test.go b/backend/internal/web/handlers_tasks_test.go index d87b660..9ff3d38 100644 --- a/backend/internal/web/handlers_tasks_test.go +++ b/backend/internal/web/handlers_tasks_test.go @@ -1069,6 +1069,75 @@ func TestTaskDelete(t *testing.T) { } } +func TestTaskDeleteRefreshesEtapeCounts(t *testing.T) { + pool, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + q := sqlc.New(pool) + store := auth.NewStore(q) + router := newTaskTestRouter(q, store) + + user := preInsertUser(t, ctx, q, "taskdeletecount@example.com", "correct-horse-12") + tablo, err := q.InsertTablo(ctx, sqlc.InsertTabloParams{ + UserID: user.ID, + Title: "Task Delete Count Tablo", + Description: pgtype.Text{Valid: false}, + Color: pgtype.Text{Valid: false}, + }) + if err != nil { + t.Fatalf("InsertTablo: %v", err) + } + etape, err := q.InsertEtape(ctx, sqlc.InsertEtapeParams{ + TabloID: tablo.ID, + Title: "Design", + Description: pgtype.Text{Valid: false}, + Position: 100, + }) + if err != nil { + t.Fatalf("InsertEtape: %v", err) + } + task, err := q.InsertTask(ctx, sqlc.InsertTaskParams{ + TabloID: tablo.ID, + Title: "Delete Count", + Description: pgtype.Text{Valid: false}, + Status: sqlc.TaskStatusTodo, + Position: 100, + EtapeID: pgtype.UUID{Bytes: etape.ID, Valid: true}, + }) + if err != nil { + t.Fatalf("InsertTask: %v", err) + } + + cookieVal, _, err := store.Create(ctx, user.ID) + if err != nil { + t.Fatalf("store.Create: %v", err) + } + sessionCookie := &http.Cookie{Name: auth.SessionCookieName, Value: cookieVal} + csrfToken, csrfCookies := getCSRFToken(t, router, "/tablos/"+tablo.ID.String()+"/tasks?etape="+etape.ID.String(), []*http.Cookie{sessionCookie}) + + form := url.Values{"_csrf": {csrfToken}} + req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/tasks/"+task.ID.String()+"/delete?etape="+etape.ID.String(), strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("HX-Request", "true") + for _, c := range csrfCookies { + req.AddCookie(c) + } + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("POST task delete status = %d; want 200", rec.Code) + } + body := rec.Body.String() + if !strings.Contains(body, `id="etape-strip"`) || !strings.Contains(body, `hx-swap-oob="outerHTML"`) { + t.Fatalf("task delete response did not include OOB etape strip refresh; body: %.800s", body) + } + if !strings.Contains(body, "Design") || !strings.Contains(body, ">0<") { + t.Fatalf("task delete response missing updated etape count; body: %.800s", body) + } +} + // ---- TestTaskOrderPersists (TASK-07) ---- // TestTaskOrderPersists verifies that after a reorder, a subsequent GET diff --git a/backend/templates/tasks.templ b/backend/templates/tasks.templ index 98c0ba6..a350a8e 100644 --- a/backend/templates/tasks.templ +++ b/backend/templates/tasks.templ @@ -382,8 +382,9 @@ templ AddTaskTrigger(tabloID uuid.UUID, status sqlc.TaskStatus, csrfToken string // TaskCardGone renders an empty zone div with the task's id so HTMX outerHTML // swap removes the card from the DOM after a successful delete (TASK-06). -templ TaskCardGone(taskID uuid.UUID) { +templ TaskCardGone(taskID uuid.UUID, tabloID uuid.UUID, csrfToken string, filter EtapeFilter, etapes []sqlc.Etape, counts EtapeTaskCounts) {
+ @EtapeStrip(tabloID, etapes, counts, filter, csrfToken, true) } // TaskCardOOB renders a new TaskCard AND an OOB swap that resets the add-task