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 "net/http"
5 "net/url"
6 "regexp"
7 "strings"
8 "testing"
9)
10
11// B8, first half. Each week day used to carry its own all-day strip
12// above its hour column, so a day with a birthday started its timed
13// grid ~42px lower than the others and its 10:00 meeting drew beside
14// the 11:00 label. The all-day strip is now one row of the grid: every
15// all-day cell comes before every timed column in the markup, and
16// there is one cell per day whether that day has anything or not.
17func TestWeekAllDayIsOneRowAboveEveryTimedColumn(t *testing.T) {
18 app, session := signedIn(t, "someone@example.com")
19 h := app.Handler()
20 createEvent(t, h, session, url.Values{
21 "title": {"Birthday"}, "all_day": {"1"}, "start": {"2026-10-07"}, "end": {"2026-10-08"},
22 "kind": {"note"}, "status": {"confirmed"}, "repeat": {"none"},
23 })
24 createEvent(t, h, session, url.Values{
25 "title": {"Focus time"}, "start": {"2026-10-07T10:00"}, "end": {"2026-10-07T11:00"},
26 "kind": {"commitment"}, "status": {"confirmed"}, "repeat": {"none"},
27 })
28
29 body := getAs(t, h, session, "/week/2026-10-07").Body.String()
30 cells := strings.Count(body, `cal-allday="week"`)
31 if cells != 7 {
32 t.Fatalf("want one all-day cell per day (7), got %d", cells)
33 }
34 lastCell := strings.LastIndex(body, `cal-allday="week"`)
35 firstColumn := strings.Index(body, "<div cal-column>")
36 if firstColumn < 0 || lastCell > firstColumn {
37 t.Fatalf("an all-day cell sits inside the timed grid (last cell at %d, first column at %d)", lastCell, firstColumn)
38 }
39 if !strings.Contains(body[:firstColumn], "Birthday") {
40 t.Error("the all-day event is not in the all-day row")
41 }
42 if !strings.Contains(body[firstColumn:], "Focus time") {
43 t.Error("the timed event is not in the timed grid")
44 }
45
46 // A week with nothing all-day draws no all-day row at all.
47 empty := getAs(t, h, session, "/week/2026-11-04").Body.String()
48 if strings.Contains(empty, `cal-allday`) {
49 t.Error("a week with no all-day events drew an all-day row")
50 }
51}
52
53// B8, second half. Under an hour the column has room for one line,
54// and a stacked chip put the title on the second — the line the chip's
55// overflow cut off. A short chip is marked so time and title share one
56// line; an hour or more keeps the stacked layout.
57func TestShortEventsKeepTheirTitleLine(t *testing.T) {
58 app, session := signedIn(t, "someone@example.com")
59 h := app.Handler()
60 createEvent(t, h, session, url.Values{
61 "title": {"Quick sync"}, "start": {"2026-10-07T14:00"}, "end": {"2026-10-07T14:30"},
62 "kind": {"meeting"}, "status": {"confirmed"}, "repeat": {"none"},
63 })
64 createEvent(t, h, session, url.Values{
65 "title": {"Long review"}, "start": {"2026-10-07T09:00"}, "end": {"2026-10-07T10:00"},
66 "kind": {"meeting"}, "status": {"confirmed"}, "repeat": {"none"},
67 })
68 placed := regexp.MustCompile(`(?s)<div cal-placed="([a-z]*)"[^>]*>.*?<span cal-chip-title>([^<]*)</span>`)
69 for _, path := range []string{"/week/2026-10-07", "/day/2026-10-07"} {
70 body := getAs(t, h, session, path).Body.String()
71 got := map[string]string{}
72 for _, m := range placed.FindAllStringSubmatch(body, -1) {
73 got[strings.TrimSpace(m[2])] = m[1]
74 }
75 if v, ok := got["Quick sync"]; !ok || v != "short" {
76 t.Errorf("%s: a 30-minute chip is not marked short: %q (found %v)", path, v, got)
77 }
78 if v, ok := got["Long review"]; !ok || v != "" {
79 t.Errorf("%s: an hour-long chip is marked %q, want stacked", path, v)
80 }
81 }
82}
83
84// B9. An all-day event stores bare dates, and a datetime-local input
85// refuses a bare date — the edit form opened with Starts and Ends
86// blank and could not be saved without retyping both. The form now
87// offers date inputs for an all-day event.
88func TestEditingAnAllDayEventShowsItsDates(t *testing.T) {
89 app, session := signedIn(t, "someone@example.com")
90 h := app.Handler()
91 loc := createEvent(t, h, session, url.Values{
92 "title": {"Conference"}, "all_day": {"1"}, "start": {"2026-10-07"}, "end": {"2026-10-09"},
93 "kind": {"commitment"}, "status": {"confirmed"}, "repeat": {"none"},
94 })
95 body := getAs(t, h, session, loc+"/edit").Body.String()
96 for name, want := range map[string]string{"start": "2026-10-07", "end": "2026-10-09"} {
97 input := regexp.MustCompile(`<input[^>]*\bname="` + name + `"[^>]*>`).FindString(body)
98 if !strings.Contains(input, `type="date"`) || !strings.Contains(input, `value="`+want+`"`) {
99 t.Errorf("%s input on an all-day edit form: %s\nwant type=date value=%s", name, input, want)
100 }
101 }
102
103 // A timed event keeps its datetime inputs.
104 timed := createEvent(t, h, session, url.Values{
105 "title": {"Standup"}, "start": {"2026-10-07T09:30"}, "end": {"2026-10-07T09:45"},
106 "kind": {"meeting"}, "status": {"confirmed"}, "repeat": {"none"},
107 })
108 body = getAs(t, h, session, timed+"/edit").Body.String()
109 input := regexp.MustCompile(`<input[^>]*\bname="start"[^>]*>`).FindString(body)
110 if !strings.Contains(input, `type="datetime-local"`) || !strings.Contains(input, `value="2026-10-07T09:30"`) {
111 t.Errorf("timed edit form start input: %s", input)
112 }
113}
114
115// Without script, ticking or unticking All day cannot swap the input
116// under it, so each shape of form must accept the other's values: a
117// datetime under All day means its date, and a bare date without it
118// means midnight.
119func TestAllDayToggleWithoutScriptStillSaves(t *testing.T) {
120 app, session := signedIn(t, "someone@example.com")
121 h := app.Handler()
122
123 loc := createEvent(t, h, session, url.Values{
124 "title": {"Offsite"}, "all_day": {"1"}, "start": {"2026-10-07T09:00"}, "end": {"2026-10-08T00:00"},
125 "kind": {"commitment"}, "status": {"confirmed"}, "repeat": {"none"},
126 })
127 body := getAs(t, h, session, loc+"/edit").Body.String()
128 if in := regexp.MustCompile(`<input[^>]*\bname="start"[^>]*>`).FindString(body); !strings.Contains(in, `value="2026-10-07"`) {
129 t.Errorf("an all-day event submitted with a datetime did not store its date: %s", in)
130 }
131
132 cal := regexp.MustCompile(`<option value="(\d+)"[^>]*\bselected\b`).FindStringSubmatch(body)
133 if cal == nil {
134 t.Fatal("no calendar selected on the edit form")
135 }
136 rec := postAs(t, h, session, loc, url.Values{
137 "calendar_id": {cal[1]}, "title": {"Offsite"}, "start": {"2026-10-07"}, "end": {"2026-10-08"},
138 "kind": {"commitment"}, "status": {"confirmed"}, "repeat": {"none"},
139 })
140 if rec.Code != http.StatusSeeOther {
141 t.Fatalf("unticking All day on a date form: status %d\n%s", rec.Code, rec.Body.String())
142 }
143 body = getAs(t, h, session, loc+"/edit").Body.String()
144 if in := regexp.MustCompile(`<input[^>]*\bname="start"[^>]*>`).FindString(body); !strings.Contains(in, `value="2026-10-07T00:00"`) {
145 t.Errorf("a timed save of a bare date did not mean midnight: %s", in)
146 }
147}
148