summaryrefslogtreecommitdiff
path: root/internal/handlers/timeline_multiday_test.go
blob: 7af2cbe69eaaf7ffc5e8a80fced5f85d76cf4d64 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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)
	}
}

func TestIsMultiDayEvent_SingleDayAllDayEvent_False(t *testing.T) {
	// Exclusive end-date convention: a single-day all-day event on July 13
	// has Start=Jul13 00:00, End=Jul14 00:00 (one day past its own day).
	start, _ := time.Parse(time.RFC3339, "2026-07-13T00:00:00Z")
	end, _ := time.Parse(time.RFC3339, "2026-07-14T00:00:00Z")
	item := models.TimelineItem{
		ID: "allday-1", Type: models.TimelineItemTypeEvent,
		Time: start, EndTime: &end, IsAllDay: true,
	}
	if isMultiDayEvent(item) {
		t.Error("expected a single-day all-day event (exclusive end-date) to not be multi-day")
	}
}

func TestIsMultiDayEvent_MultiDayAllDayEvent_True(t *testing.T) {
	// A 3-day all-day event (Jul 13, 14, 15) has End=Jul16 00:00 (exclusive).
	start, _ := time.Parse(time.RFC3339, "2026-07-13T00:00:00Z")
	end, _ := time.Parse(time.RFC3339, "2026-07-16T00:00:00Z")
	item := models.TimelineItem{
		ID: "allday-2", Type: models.TimelineItemTypeEvent,
		Time: start, EndTime: &end, IsAllDay: true,
	}
	if !isMultiDayEvent(item) {
		t.Error("expected a 3-day all-day event to be multi-day")
	}
}

func TestMultiDayVariant_MultiDayAllDayEvent_LastRealDayIsEnds(t *testing.T) {
	// The 3-day all-day event above (Jul 13-15 inclusive, End=Jul16 exclusive)
	// must report "ends" on Jul 15 (its real last day), not Jul 16.
	start, _ := time.Parse(time.RFC3339, "2026-07-13T00:00:00Z")
	end, _ := time.Parse(time.RFC3339, "2026-07-16T00:00:00Z")
	item := models.TimelineItem{
		ID: "allday-2", Type: models.TimelineItemTypeEvent,
		Time: start, EndTime: &end, IsAllDay: true,
	}
	renderDay, _ := time.Parse(time.RFC3339, "2026-07-15T00:00:00Z")
	if got := multiDayVariant(item, renderDay); got != "ends" {
		t.Errorf("variant on last real day = %q, want %q", got, "ends")
	}
	exclusiveEndDay, _ := time.Parse(time.RFC3339, "2026-07-16T00:00:00Z")
	if got := multiDayVariant(item, exclusiveEndDay); got != "" {
		t.Errorf("variant on the exclusive (non-real) end day = %q, want empty", got)
	}
}