fix(09): refresh etape counts on task create
This commit is contained in:
parent
cf07c29ae5
commit
0c95049447
5 changed files with 79 additions and 5 deletions
|
|
@ -302,10 +302,14 @@ func TaskCreateHandler(deps TasksDeps) http.HandlerFunc {
|
|||
|
||||
// HTMX: set retarget/reswap headers and return combined card+OOB fragment.
|
||||
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")
|
||||
w.Header().Set("HX-Reswap", "beforeend")
|
||||
w.Header().Set("HX-Retarget", "#column-"+string(status))
|
||||
_ = templates.TaskCardOOB(status, task, tablo.ID, csrf.Token(r), filter).Render(ctx, w)
|
||||
_ = templates.TaskCardOOB(status, task, tablo.ID, csrf.Token(r), refreshedFilter, refreshedEtapes, refreshedCounts).Render(ctx, w)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/tablos/"+tablo.ID.String(), http.StatusSeeOther)
|
||||
|
|
|
|||
|
|
@ -167,6 +167,69 @@ func TestTaskCreate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTaskCreateRefreshesEtapeCounts(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, "taskcreatecount@example.com", "correct-horse-12")
|
||||
tablo, err := q.InsertTablo(ctx, sqlc.InsertTabloParams{
|
||||
UserID: user.ID,
|
||||
Title: "Task 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)
|
||||
}
|
||||
|
||||
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{
|
||||
"title": {"Refresh Count"},
|
||||
"status": {"todo"},
|
||||
"etape_id": {etape.ID.String()},
|
||||
"_csrf": {csrfToken},
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/tasks?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 create 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 create response did not include OOB etape strip refresh; body: %.800s", body)
|
||||
}
|
||||
if !strings.Contains(body, "Design") || !strings.Contains(body, ">1<") {
|
||||
t.Fatalf("task create response missing updated etape count; body: %.800s", body)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- TestTaskCreateValidation (TASK-02) ----
|
||||
|
||||
// TestTaskCreateValidation verifies that POST /tablos/{id}/tasks with an empty
|
||||
|
|
|
|||
|
|
@ -8,8 +8,14 @@ import (
|
|||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
templ EtapeStrip(tabloID uuid.UUID, etapes []sqlc.Etape, counts EtapeTaskCounts, filter EtapeFilter, csrfToken string) {
|
||||
<div id="etape-strip" class="mb-4 space-y-3">
|
||||
templ EtapeStrip(tabloID uuid.UUID, etapes []sqlc.Etape, counts EtapeTaskCounts, filter EtapeFilter, csrfToken string, oob bool) {
|
||||
<div
|
||||
id="etape-strip"
|
||||
class="mb-4 space-y-3"
|
||||
if oob {
|
||||
hx-swap-oob="outerHTML"
|
||||
}
|
||||
>
|
||||
<div class="flex items-center gap-2 overflow-x-auto pb-1">
|
||||
<a
|
||||
href={ templ.SafeURL("/tablos/" + tabloID.String() + "/tasks") }
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ templ TabloOverviewTabFragment(tablo sqlc.Tablo, csrfToken string) {
|
|||
// Lives in tablos.templ (tablo-level concern) per plan D-07.
|
||||
templ TasksTabFragment(tablo sqlc.Tablo, tasks []sqlc.Task, etapes []sqlc.Etape, counts EtapeTaskCounts, filter EtapeFilter, csrfToken string) {
|
||||
<div id="tasks-tab">
|
||||
@EtapeStrip(tablo.ID, etapes, counts, filter, csrfToken)
|
||||
@EtapeStrip(tablo.ID, etapes, counts, filter, csrfToken, false)
|
||||
@KanbanBoard(tablo.ID, csrfToken, tasks, filter)
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -390,9 +390,10 @@ templ TaskCardGone(taskID uuid.UUID) {
|
|||
// slot to AddTaskTrigger. Used by TaskCreateHandler to perform both operations
|
||||
// in a single HTMX response.
|
||||
// D-08/UI-SPEC §2: OOB swap resets #add-task-slot-{status} after create.
|
||||
templ TaskCardOOB(status sqlc.TaskStatus, task sqlc.Task, tabloID uuid.UUID, csrfToken string, filter EtapeFilter) {
|
||||
templ TaskCardOOB(status sqlc.TaskStatus, task sqlc.Task, tabloID uuid.UUID, csrfToken string, filter EtapeFilter, etapes []sqlc.Etape, counts EtapeTaskCounts) {
|
||||
@TaskCard(tabloID, task, csrfToken)
|
||||
<div hx-swap-oob={ "innerHTML:#add-task-slot-" + string(status) }>
|
||||
@AddTaskTrigger(tabloID, status, csrfToken, filter)
|
||||
</div>
|
||||
@EtapeStrip(tabloID, etapes, counts, filter, csrfToken, true)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue