diff --git a/backend/internal/web/handlers_events_test.go b/backend/internal/web/handlers_events_test.go index 8db8ae3..511aac5 100644 --- a/backend/internal/web/handlers_events_test.go +++ b/backend/internal/web/handlers_events_test.go @@ -13,6 +13,7 @@ import ( "backend/internal/auth" "backend/internal/db/sqlc" + "backend/templates" "github.com/google/uuid" "github.com/jackc/pgx/v5" @@ -445,3 +446,189 @@ func TestEventMutationOwnershipReturns404(t *testing.T) { } } } + +func TestEventsMonthNavigationPushesMonthParam(t *testing.T) { + pool, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + q := sqlc.New(pool) + store := auth.NewStore(q) + router := newEventTestRouter(q, store) + + user := preInsertUser(t, ctx, q, "eventnav@example.com", "correct-horse-12") + tablo := insertEventTestTablo(t, ctx, q, user, "Event Navigation Tablo") + sessionCookie := sessionCookieForUser(t, ctx, store, user) + + req := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/events?month=2026-05", nil) + req.Header.Set("HX-Request", "true") + req.AddCookie(sessionCookie) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("GET events month status = %d; want 200", rec.Code) + } + body := rec.Body.String() + for _, want := range []string{"month=2026-04", "month=2026-06", "Previous month: April 2026", "Next month: June 2026"} { + if !strings.Contains(body, want) { + t.Errorf("month navigation missing %q; body: %.800s", want, body) + } + } +} + +func TestEventNewFromDayPrefillsDate(t *testing.T) { + pool, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + q := sqlc.New(pool) + store := auth.NewStore(q) + router := newEventTestRouter(q, store) + + user := preInsertUser(t, ctx, q, "eventprefill@example.com", "correct-horse-12") + tablo := insertEventTestTablo(t, ctx, q, user, "Event Prefill Tablo") + sessionCookie := sessionCookieForUser(t, ctx, store, user) + + req := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/events/new?date=2026-05-20&month=2026-05", nil) + req.Header.Set("HX-Request", "true") + req.AddCookie(sessionCookie) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("GET event new status = %d; want 200", rec.Code) + } + if !strings.Contains(rec.Body.String(), `value="2026-05-20"`) { + t.Fatalf("date input was not prefilled from day query; body: %.800s", rec.Body.String()) + } +} + +func TestEventCreateSubmittedDateOverridesQueryDefault(t *testing.T) { + pool, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + q := sqlc.New(pool) + store := auth.NewStore(q) + router := newEventTestRouter(q, store) + + user := preInsertUser(t, ctx, q, "eventsubmitteddate@example.com", "correct-horse-12") + tablo := insertEventTestTablo(t, ctx, q, user, "Submitted Date Tablo") + sessionCookie := sessionCookieForUser(t, ctx, store, user) + csrfToken, csrfCookies := getCSRFToken(t, router, "/tablos/"+tablo.ID.String()+"/events?month=2026-05", []*http.Cookie{sessionCookie}) + + form := url.Values{ + "title": {"Submitted Date"}, + "event_date": {"2026-05-21"}, + "start_time": {"09:30"}, + "_csrf": {csrfToken}, + } + req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/events?date=2026-05-20&month=2026-05", 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 event create status = %d; want 200; body: %.500s", rec.Code, rec.Body.String()) + } + events, err := q.ListEventsByTabloRange(ctx, sqlc.ListEventsByTabloRangeParams{ + TabloID: tablo.ID, + EventDate: eventTestDate(t, "2026-05-01"), + EventDate_2: eventTestDate(t, "2026-05-31"), + }) + if err != nil { + t.Fatalf("ListEventsByTabloRange: %v", err) + } + if len(events) != 1 { + t.Fatalf("events len = %d; want 1", len(events)) + } + if got := templates.FormatEventDate(events[0].EventDate); got != "2026-05-21" { + t.Fatalf("stored event date = %s; want 2026-05-21", got) + } +} + +func TestEventsCalendarShowsMoreIndicator(t *testing.T) { + pool, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + q := sqlc.New(pool) + store := auth.NewStore(q) + router := newEventTestRouter(q, store) + + user := preInsertUser(t, ctx, q, "eventoverflow@example.com", "correct-horse-12") + tablo := insertEventTestTablo(t, ctx, q, user, "Overflow Tablo") + sessionCookie := sessionCookieForUser(t, ctx, store, user) + for i, title := range []string{"Overflow 1", "Overflow 2", "Overflow 3", "Overflow 4"} { + insertEventTestEvent(t, ctx, q, tablo.ID, title, "2026-05-20", "09:0"+string(rune('0'+i)), "", "", "") + } + + req := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/events?month=2026-05", nil) + req.Header.Set("HX-Request", "true") + req.AddCookie(sessionCookie) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("GET events overflow status = %d; want 200", rec.Code) + } + body := rec.Body.String() + for _, want := range []string{"Overflow 1", "Overflow 2", "Overflow 3", "+1 more"} { + if !strings.Contains(body, want) { + t.Errorf("overflow calendar missing %q; body: %.800s", want, body) + } + } + if strings.Contains(body, "Overflow 4") { + t.Fatalf("fourth event title should be hidden behind overflow indicator; body: %.800s", body) + } +} + +func TestListUserEventsRangeReturnsOnlyOwnedTablos(t *testing.T) { + pool, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + q := sqlc.New(pool) + + owner := preInsertUser(t, ctx, q, "eventrangeowner@example.com", "correct-horse-12") + other := preInsertUser(t, ctx, q, "eventrangeother@example.com", "correct-horse-12") + ownedTablo, err := q.InsertTablo(ctx, sqlc.InsertTabloParams{ + UserID: owner.ID, + Title: "Owned Calendar", + Description: pgtype.Text{Valid: false}, + Color: pgtype.Text{String: "#123abc", Valid: true}, + }) + if err != nil { + t.Fatalf("InsertTablo owned: %v", err) + } + foreignTablo := insertEventTestTablo(t, ctx, q, other, "Foreign Calendar") + insertEventTestEvent(t, ctx, q, ownedTablo.ID, "Owned Range Event", "2026-05-20", "09:30", "", "", "") + insertEventTestEvent(t, ctx, q, foreignTablo.ID, "Foreign Range Event", "2026-05-20", "09:30", "", "", "") + + rows, err := q.ListUserEventsRange(ctx, sqlc.ListUserEventsRangeParams{ + UserID: owner.ID, + StartDate: eventTestDate(t, "2026-05-01"), + EndDate: eventTestDate(t, "2026-05-31"), + }) + if err != nil { + t.Fatalf("ListUserEventsRange: %v", err) + } + if len(rows) != 1 { + t.Fatalf("rows len = %d; want 1", len(rows)) + } + row := rows[0] + if row.Title != "Owned Range Event" { + t.Fatalf("row title = %q; want Owned Range Event", row.Title) + } + if row.TabloTitle != "Owned Calendar" { + t.Fatalf("row tablo title = %q; want Owned Calendar", row.TabloTitle) + } + if !row.TabloColor.Valid || row.TabloColor.String != "#123abc" { + t.Fatalf("row tablo color = %#v; want #123abc", row.TabloColor) + } +}