oficina / calendar Public

Clone
git clone https://amadan.net/oficina/calendar

Plain git — no account needed to clone.

Download

Download this file

1package calendartest
2
3import (
4 "strings"
5 "testing"
6 "time"
7
8 calendar "amadan.net/oficina/calendar/internal/calendar"
9)
10
11// The sentence at the top of Today is the app's one opinion, so it gets
12// a test rather than a hope. These assert the wording, because the
13// wording IS the feature — a summary that says the right number in the
14// wrong voice is a summary nobody reads twice.
15func TestDaySummaryReadsLikeASentence(t *testing.T) {
16 const tz = "UTC"
17 day := time.Date(2026, 9, 2, 12, 0, 0, 0, time.UTC) // a Wednesday
18 dayStart, dayEnd, err := calendar.DayBounds(day, tz)
19 if err != nil {
20 t.Fatalf("DayBounds: %v", err)
21 }
22
23 at := func(hh, mm int) int64 {
24 return time.Date(2026, 9, 2, hh, mm, 0, 0, time.UTC).Unix()
25 }
26 ev := func(kind string, fromH, fromM, toH, toM int) calendar.EventView {
27 return calendar.EventView{
28 Event: calendar.Event{Kind: kind},
29 OccStartUTC: at(fromH, fromM),
30 OccEndUTC: at(toH, toM),
31 }
32 }
33
34 t.Run("an empty day says so plainly", func(t *testing.T) {
35 got := calendar.SummariseDay(day, nil, dayStart, dayEnd, tz).Sentence
36 if got != "Wednesday. Nothing booked." {
37 t.Errorf("sentence = %q", got)
38 }
39 })
40
41 t.Run("one thing is not pluralised", func(t *testing.T) {
42 got := calendar.SummariseDay(day, []calendar.EventView{
43 ev(calendar.KindMeeting, 9, 30, 10, 0),
44 }, dayStart, dayEnd, tz).Sentence
45 if !strings.HasPrefix(got, "Wednesday. One thing; the first at 09:30, the last ends at 10:00.") {
46 t.Errorf("sentence = %q", got)
47 }
48 })
49
50 t.Run("the gap is between the first and last, not the whole day", func(t *testing.T) {
51 s := calendar.SummariseDay(day, []calendar.EventView{
52 ev(calendar.KindMeeting, 9, 30, 10, 0),
53 ev(calendar.KindMeeting, 16, 0, 17, 0),
54 }, dayStart, dayEnd, tz)
55 // 09:30 to 17:00 is 7h30; 1h30 of it is booked; 6h free.
56 if s.UnbookedMin != 360 {
57 t.Errorf("UnbookedMin = %d, want 360", s.UnbookedMin)
58 }
59 if !strings.Contains(s.Sentence, "That leaves six hours unbooked between them.") {
60 t.Errorf("sentence = %q", s.Sentence)
61 }
62 })
63
64 t.Run("overlapping meetings do not consume the day twice", func(t *testing.T) {
65 s := calendar.SummariseDay(day, []calendar.EventView{
66 ev(calendar.KindMeeting, 9, 0, 11, 0),
67 ev(calendar.KindMeeting, 10, 0, 12, 0),
68 ev(calendar.KindMeeting, 15, 0, 16, 0),
69 }, dayStart, dayEnd, tz)
70 // 09:00-12:00 merged is three hours, plus one at 15:00. The
71 // span is 09:00-16:00, so three hours are free. Counting the
72 // overlap twice would report four.
73 if s.UnbookedMin != 180 {
74 t.Errorf("UnbookedMin = %d, want 180 (the 10:00-11:00 overlap counted once)", s.UnbookedMin)
75 }
76 })
77
78 t.Run("a full day says the alarming thing plainly", func(t *testing.T) {
79 s := calendar.SummariseDay(day, []calendar.EventView{
80 ev(calendar.KindMeeting, 9, 0, 13, 0),
81 ev(calendar.KindMeeting, 13, 0, 17, 0),
82 }, dayStart, dayEnd, tz)
83 if !strings.Contains(s.Sentence, "There is no unbooked hour between them.") {
84 t.Errorf("sentence = %q", s.Sentence)
85 }
86 })
87
88 // A birthday does not make a Tuesday fuller. Notes are excluded
89 // from the arithmetic entirely.
90 t.Run("notes do not occupy the day", func(t *testing.T) {
91 s := calendar.SummariseDay(day, []calendar.EventView{
92 ev(calendar.KindNote, 9, 0, 10, 0),
93 }, dayStart, dayEnd, tz)
94 if s.Count != 0 {
95 t.Errorf("Count = %d, want 0: a note is not a thing that occupies time", s.Count)
96 }
97 if s.Sentence != "Wednesday. Nothing booked." {
98 t.Errorf("sentence = %q", s.Sentence)
99 }
100 })
101
102 // The sentence must never contain a digit where a word was meant,
103 // nor a stray fragment — the failure mode of assembling prose in
104 // code is a sentence that is grammatical nonsense.
105 t.Run("hours and minutes read as English", func(t *testing.T) {
106 s := calendar.SummariseDay(day, []calendar.EventView{
107 ev(calendar.KindMeeting, 9, 0, 9, 30),
108 ev(calendar.KindMeeting, 11, 45, 12, 0),
109 }, dayStart, dayEnd, tz)
110 // 09:00-12:00 span, 45 minutes booked, so 2h15 free.
111 if !strings.Contains(s.Sentence, "two hours and 15 minutes unbooked") {
112 t.Errorf("sentence = %q", s.Sentence)
113 }
114 })
115}
116