oficina / calendar Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build browser
2
3package calendartest
4
5import (
6 "context"
7 "io"
8 "log/slog"
9 "net"
10 "net/http"
11 "net/url"
12 "os/exec"
13 "path/filepath"
14 "regexp"
15 "strings"
16 "testing"
17 "time"
18
19 calendar "amadan.net/oficina/calendar/internal/calendar"
20)
21
22// TestWeekAndAllDayFormInARealBrowser is the painted-output half of
23// B8 and B9: the Go tests check the markup, this measures what a
24// browser draws from it. Column tops, where a 10:00 chip lands against
25// the 10:00 rule, whether a 30-minute chip's title is visible, and
26// what an all-day edit form's inputs actually hold — a datetime-local
27// input given a bare date reports "" to the page, which no markup
28// assertion can see.
29func TestWeekAndAllDayFormInARealBrowser(t *testing.T) {
30 ln, err := net.Listen("tcp", "127.0.0.1:0")
31 if err != nil {
32 t.Fatalf("listen: %v", err)
33 }
34 origin := "http://" + ln.Addr().String()
35 app, err := calendar.New(calendar.Config{
36 DBPath: filepath.Join(t.TempDir(), "calendar.db"),
37 Env: "development",
38 Origin: origin,
39 Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
40 })
41 if err != nil {
42 t.Fatalf("calendar.New: %v", err)
43 }
44 t.Cleanup(func() { app.Close() })
45 srv := &http.Server{Handler: app.Handler()}
46 go func() { _ = srv.Serve(ln) }()
47 t.Cleanup(func() { _ = srv.Close() })
48
49 session := signInOverHTTP(t, app, origin, "someone@example.com")
50 client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
51 do := func(method, path string, form url.Values) *http.Response {
52 t.Helper()
53 var body io.Reader
54 if form != nil {
55 body = strings.NewReader(form.Encode())
56 }
57 req, _ := http.NewRequest(method, origin+path, body)
58 if form != nil {
59 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
60 req.Header.Set("Origin", origin)
61 }
62 req.AddCookie(session)
63 res, err := client.Do(req)
64 if err != nil {
65 t.Fatal(err)
66 }
67 return res
68 }
69 res := do("GET", "/events/new", nil)
70 page, _ := io.ReadAll(res.Body)
71 res.Body.Close()
72 m := regexp.MustCompile(`<option value="(\d+)"[^>]*\bselected\b`).FindSubmatch(page)
73 if m == nil {
74 t.Fatal("no default calendar on the create form")
75 }
76 create := func(v url.Values) string {
77 t.Helper()
78 v.Set("calendar_id", string(m[1]))
79 v.Set("status", "confirmed")
80 v.Set("repeat", "none")
81 res := do("POST", "/events", v)
82 res.Body.Close()
83 if res.StatusCode != http.StatusSeeOther {
84 t.Fatalf("create %q: status %d", v.Get("title"), res.StatusCode)
85 }
86 return res.Header.Get("Location")
87 }
88 create(url.Values{"title": {"Birthday"}, "all_day": {"1"}, "start": {"2026-10-07"}, "end": {"2026-10-08"}, "kind": {"note"}})
89 create(url.Values{"title": {"Focus time"}, "start": {"2026-10-07T10:00"}, "end": {"2026-10-07T11:00"}, "kind": {"commitment"}})
90 create(url.Values{"title": {"Quick sync"}, "start": {"2026-10-07T14:00"}, "end": {"2026-10-07T14:30"}, "kind": {"meeting"}})
91 allDay := create(url.Values{"title": {"Conference"}, "all_day": {"1"}, "start": {"2026-10-12"}, "end": {"2026-10-14"}, "kind": {"commitment"}})
92
93 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
94 defer cancel()
95 cmd := exec.CommandContext(ctx, "node", "test/browser/week.mjs",
96 origin, session.Name, session.Value, allDay+"/edit")
97 cmd.Dir = repoRootForBrowser(t)
98 out, err := cmd.CombinedOutput()
99 t.Logf("node:\n%s", out)
100 if err != nil {
101 t.Fatalf("the browser checks failed: %v", err)
102 }
103}
104