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 "fmt"
8 "io"
9 "log/slog"
10 "net/http"
11 "path/filepath"
12 "testing"
13 "time"
14
15 "amadan.net/rastrillo/rastrillo/harness"
16 "github.com/chromedp/cdproto/emulation"
17 "github.com/chromedp/chromedp"
18
19 calendar "amadan.net/oficina/calendar/internal/calendar"
20)
21
22// TestFormFootOnANarrowScreen: rastrillo's form-foot draws its two
23// buttons at the lg size (v0.26). On a phone the booker's pair, "Hold
24// this time" and "Pick another time", no longer fit side by side, and the
25// row squeezed them instead of wrapping: two lines of label each at 390
26// wide, up to three at 320 (where the old size already broke them over
27// two). calendar.css lets that row wrap. This checks
28// at 320 and 390 that no form-foot label breaks across lines and no
29// button leaves the form, on the stranger's booking form, the one
30// most often opened on a phone.
31func TestFormFootOnANarrowScreen(t *testing.T) {
32 var app *calendar.App
33 rig := harness.New(t, func(origin string) http.Handler {
34 a, err := calendar.New(calendar.Config{
35 DBPath: filepath.Join(t.TempDir(), "calendar.db"),
36 Env: "development",
37 Origin: origin,
38 Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
39 })
40 if err != nil {
41 t.Fatalf("calendar.New: %v", err)
42 }
43 t.Cleanup(func() { a.Close() })
44 app = a
45 return a.Handler()
46 })
47 signInOverHTTP(t, app, rig.Origin, "host@example.com")
48 ctx := context.Background()
49 st := app.StoreForTest()
50 host := app.PersonByAddressForTest(t, "host@example.com")
51 if err := st.SaveHandle(ctx, host.ID, "paul"); err != nil {
52 t.Fatal(err)
53 }
54 ws, _ := calendar.ParseAvailability("every day 9-5")
55 page, err := st.CreateBookingPage(ctx, host.ID, calendar.BookingPageInput{
56 Title: "Quick chat", Description: "Half an hour.", Availability: "every day 9-5",
57 DurationMin: 30, HorizonDays: 30}, ws)
58 if err != nil {
59 t.Fatal(err)
60 }
61 if err := st.PublishBookingPage(ctx, host.ID, page.ID, true); err != nil {
62 t.Fatal(err)
63 }
64 public := "/book/paul/" + page.Code
65
66 for _, width := range []int64{320, 390} {
67 var day string
68 var problems []string
69 ctx, cancel := context.WithTimeout(rig.Context(), 20*time.Second)
70 err := chromedp.Run(ctx,
71 emulation.SetDeviceMetricsOverride(width, 844, 1, true),
72 chromedp.Navigate(rig.Origin+public),
73 chromedp.WaitVisible(`select[name="zone"]`, chromedp.ByQuery),
74 chromedp.Evaluate(`(document.querySelector('a[href*="day="]') || {}).href || ''`, &day),
75 )
76 if err == nil && day == "" {
77 err = fmt.Errorf("the booking page offers no day to pick")
78 }
79 if err == nil {
80 err = chromedp.Run(ctx,
81 chromedp.Navigate(day),
82 chromedp.WaitVisible(`[cal-book-slot]`, chromedp.ByQuery),
83 chromedp.Click(`[cal-book-slot]`, chromedp.ByQuery),
84 chromedp.WaitVisible(`[cal-book-details] [rst-form-foot]`, chromedp.ByQuery),
85 chromedp.Evaluate(footProblems, &problems),
86 )
87 }
88 cancel()
89 if err != nil {
90 t.Fatalf("%dpx: %v", width, err)
91 }
92 for _, p := range problems {
93 t.Errorf("%dpx, the booker's form: %s", width, p)
94 }
95 }
96}
97
98// footProblems lists every form-foot button whose label breaks across
99// lines, or which reaches past its form's edge.
100const footProblems = `[...document.querySelectorAll('[rst-form-foot] [rst-btn]')].flatMap(b => {
101 const out = [], r = document.createRange()
102 r.selectNodeContents(b)
103 const lines = new Set([...r.getClientRects()].filter(x => x.width > 0).map(x => Math.round(x.top))).size
104 if (lines > 1) out.push('"' + b.textContent.trim() + '" breaks over ' + lines + ' lines')
105 const f = b.closest('form').getBoundingClientRect(), bb = b.getBoundingClientRect()
106 if (bb.right > f.right + 0.5 || bb.left < f.left - 0.5) out.push('"' + b.textContent.trim() + '" spans ' + Math.round(bb.left) + '-' + Math.round(bb.right) + ', its form ' + Math.round(f.left) + '-' + Math.round(f.right))
107 return out
108})`
109