From d0f207704a406d4fbc65e89ef37f19bacfa0deef Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Mon, 13 Jul 2026 18:10:10 +0000 Subject: feat(web): show multi-day calendar events on every day they span TimelineItemView wraps a TimelineItem with a per-render-day MultiDayVariant (starts/ends/spans/none). HandleTimeline's bucketing now places a multi-day event into both TodayItems and TomorrowItems when it touches both, instead of only the list matching its start day. --- internal/handlers/timeline_multiday_test.go | 83 +++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 internal/handlers/timeline_multiday_test.go (limited to 'internal/handlers/timeline_multiday_test.go') diff --git a/internal/handlers/timeline_multiday_test.go b/internal/handlers/timeline_multiday_test.go new file mode 100644 index 0000000..3dc2005 --- /dev/null +++ b/internal/handlers/timeline_multiday_test.go @@ -0,0 +1,83 @@ +package handlers + +import ( + "testing" + "time" + + "task-dashboard/internal/models" +) + +func multiDayEvent(startISO, endISO string) models.TimelineItem { + start, _ := time.Parse(time.RFC3339, startISO) + end, _ := time.Parse(time.RFC3339, endISO) + return models.TimelineItem{ + ID: "evt-1", + Title: "Offsite", + Type: models.TimelineItemTypeEvent, + Time: start, + EndTime: &end, + } +} + +func TestIsMultiDayEvent_SameDayTimedEvent_False(t *testing.T) { + item := multiDayEvent("2026-07-13T10:00:00Z", "2026-07-13T11:00:00Z") + if isMultiDayEvent(item) { + t.Error("expected same-day timed event to not be multi-day") + } +} + +func TestIsMultiDayEvent_SpansMultipleDays_True(t *testing.T) { + item := multiDayEvent("2026-07-13T17:35:00Z", "2026-07-30T22:30:00Z") + if !isMultiDayEvent(item) { + t.Error("expected event spanning multiple days to be multi-day") + } +} + +func TestIsMultiDayEvent_NonEventType_AlwaysFalse(t *testing.T) { + start, _ := time.Parse(time.RFC3339, "2026-07-13T10:00:00Z") + end, _ := time.Parse(time.RFC3339, "2026-07-30T22:30:00Z") + item := models.TimelineItem{ID: "t1", Type: models.TimelineItemTypeTask, Time: start, EndTime: &end} + if isMultiDayEvent(item) { + t.Error("expected non-event type to never be considered multi-day, regardless of span") + } +} + +func TestIsMultiDayEvent_NoEndTime_False(t *testing.T) { + start, _ := time.Parse(time.RFC3339, "2026-07-13T10:00:00Z") + item := models.TimelineItem{ID: "e1", Type: models.TimelineItemTypeEvent, Time: start} + if isMultiDayEvent(item) { + t.Error("expected event with nil EndTime to not be multi-day") + } +} + +func TestMultiDayVariant_StartDay_ReturnsStarts(t *testing.T) { + item := multiDayEvent("2026-07-13T17:35:00Z", "2026-07-30T22:30:00Z") + renderDay, _ := time.Parse(time.RFC3339, "2026-07-13T00:00:00Z") + if got := multiDayVariant(item, renderDay); got != "starts" { + t.Errorf("variant = %q, want %q", got, "starts") + } +} + +func TestMultiDayVariant_EndDay_ReturnsEnds(t *testing.T) { + item := multiDayEvent("2026-07-13T17:35:00Z", "2026-07-30T22:30:00Z") + renderDay, _ := time.Parse(time.RFC3339, "2026-07-30T00:00:00Z") + if got := multiDayVariant(item, renderDay); got != "ends" { + t.Errorf("variant = %q, want %q", got, "ends") + } +} + +func TestMultiDayVariant_MiddleDay_ReturnsSpans(t *testing.T) { + item := multiDayEvent("2026-07-13T17:35:00Z", "2026-07-30T22:30:00Z") + renderDay, _ := time.Parse(time.RFC3339, "2026-07-20T00:00:00Z") + if got := multiDayVariant(item, renderDay); got != "spans" { + t.Errorf("variant = %q, want %q", got, "spans") + } +} + +func TestMultiDayVariant_DayOutsideSpan_ReturnsEmpty(t *testing.T) { + item := multiDayEvent("2026-07-13T17:35:00Z", "2026-07-30T22:30:00Z") + renderDay, _ := time.Parse(time.RFC3339, "2026-08-01T00:00:00Z") + if got := multiDayVariant(item, renderDay); got != "" { + t.Errorf("variant = %q, want empty", got) + } +} -- cgit v1.2.3