| new file mode 100644 |
| index 0000000..65d1509 |
| --- /dev/null |
| +++ b/internal/calendartest/formfoot_browser_test.go |
| @@ -0,0 +1,108 @@ |
| +//go:build browser |
| + |
| +package calendartest |
| + |
| +import ( |
| + "context" |
| + "fmt" |
| + "io" |
| + "log/slog" |
| + "net/http" |
| + "path/filepath" |
| + "testing" |
| + "time" |
| + |
| + "amadan.net/rastrillo/rastrillo/harness" |
| + "github.com/chromedp/cdproto/emulation" |
| + "github.com/chromedp/chromedp" |
| + |
| + calendar "amadan.net/oficina/calendar/internal/calendar" |
| +) |
| + |
| +// TestFormFootOnANarrowScreen: rastrillo's form-foot draws its two |
| +// buttons at the lg size (v0.26). On a phone the booker's pair, "Hold |
| +// this time" and "Pick another time", no longer fit side by side, and the |
| +// row squeezed them instead of wrapping: two lines of label each at 390 |
| +// wide, up to three at 320 (where the old size already broke them over |
| +// two). calendar.css lets the row wrap. This checks |
| +// at 320 and 390 that no form-foot label breaks across lines and no |
| +// button leaves the form, on the stranger's booking form, the one |
| +// most often opened on a phone. |
| +func TestFormFootOnANarrowScreen(t *testing.T) { |
| + var app *calendar.App |
| + rig := harness.New(t, func(origin string) http.Handler { |
| + a, err := calendar.New(calendar.Config{ |
| + DBPath: filepath.Join(t.TempDir(), "calendar.db"), |
| + Env: "development", |
| + Origin: origin, |
| + Logger: slog.New(slog.NewTextHandler(io.Discard, nil)), |
| + }) |
| + if err != nil { |
| + t.Fatalf("calendar.New: %v", err) |
| + } |
| + t.Cleanup(func() { a.Close() }) |
| + app = a |
| + return a.Handler() |
| + }) |
| + signInOverHTTP(t, app, rig.Origin, "host@example.com") |
| + ctx := context.Background() |
| + st := app.StoreForTest() |
| + host := app.PersonByAddressForTest(t, "host@example.com") |
| + if err := st.SaveHandle(ctx, host.ID, "paul"); err != nil { |
| + t.Fatal(err) |
| + } |
| + ws, _ := calendar.ParseAvailability("every day 9-5") |
| + page, err := st.CreateBookingPage(ctx, host.ID, calendar.BookingPageInput{ |
| + Title: "Quick chat", Description: "Half an hour.", Availability: "every day 9-5", |
| + DurationMin: 30, HorizonDays: 30}, ws) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := st.PublishBookingPage(ctx, host.ID, page.ID, true); err != nil { |
| + t.Fatal(err) |
| + } |
| + public := "/book/paul/" + page.Code |
| + |
| + for _, width := range []int64{320, 390} { |
| + var day string |
| + var problems []string |
| + ctx, cancel := context.WithTimeout(rig.Context(), 20*time.Second) |
| + err := chromedp.Run(ctx, |
| + emulation.SetDeviceMetricsOverride(width, 844, 1, true), |
| + chromedp.Navigate(rig.Origin+public), |
| + chromedp.WaitVisible(`select[name="zone"]`, chromedp.ByQuery), |
| + chromedp.Evaluate(`(document.querySelector('a[href*="day="]') || {}).href || ''`, &day), |
| + ) |
| + if err == nil && day == "" { |
| + err = fmt.Errorf("the booking page offers no day to pick") |
| + } |
| + if err == nil { |
| + err = chromedp.Run(ctx, |
| + chromedp.Navigate(day), |
| + chromedp.WaitVisible(`[cal-book-slot]`, chromedp.ByQuery), |
| + chromedp.Click(`[cal-book-slot]`, chromedp.ByQuery), |
| + chromedp.WaitVisible(`[cal-book-details] [rst-form-foot]`, chromedp.ByQuery), |
| + chromedp.Evaluate(footProblems, &problems), |
| + ) |
| + } |
| + cancel() |
| + if err != nil { |
| + t.Fatalf("%dpx: %v", width, err) |
| + } |
| + for _, p := range problems { |
| + t.Errorf("%dpx, the booker's form: %s", width, p) |
| + } |
| + } |
| +} |
| + |
| +// footProblems lists every form-foot button whose label breaks across |
| +// lines, or which reaches past its form's edge. |
| +const footProblems = `[...document.querySelectorAll('[rst-form-foot] [rst-btn]')].flatMap(b => { |
| + const out = [], r = document.createRange() |
| + r.selectNodeContents(b) |
| + const lines = new Set([...r.getClientRects()].filter(x => x.width > 0).map(x => Math.round(x.top))).size |
| + if (lines > 1) out.push('"' + b.textContent.trim() + '" breaks over ' + lines + ' lines') |
| + const f = b.closest('form').getBoundingClientRect(), bb = b.getBoundingClientRect() |
| + 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)) |
| + return out |
| +})` |