//go:build browser package gallery import ( "context" "fmt" "math" "net/http" "testing" "time" "github.com/chromedp/chromedp" "amadan.net/rastrillo/fiesta/internal/harness" ) // realSize reads every showing frame's transform, its laid-out and // rendered widths, a text field's computed font size where the // document has one, and the box's inline edges against the document's. // The field is the first plain one: the headline field is sized on // purpose at --rst-fs-lg. const realSize = `JSON.stringify([...document.querySelectorAll(".ds-view")].filter(v => getComputedStyle(v.querySelector(".ds-view__stage")).display !== "none").map(v => { const box = v.querySelector(".ds-view__box"), f = v.querySelector(".ds-view__frame"), b = box.getBoundingClientRect(), fr = f.getBoundingClientRect(); const lit = [...v.querySelectorAll(".ds-view__tab")].findIndex(l => getComputedStyle(l).fontWeight === "600"); let field = null; try { const i = f.contentDocument.querySelector(':is(input:not([type]), input[type=text], input[type=email], input[type=url]):not(.rst-input--primary, [rst-input~="primary"])'); if (i) field = parseFloat(f.contentDocument.defaultView.getComputedStyle(i).fontSize); } catch (e) {} return {ID: (v.closest("article, section") || {}).id || "", Lit: lit, Transform: getComputedStyle(f).transform, Layout: f.offsetWidth, Rendered: fr.width, Field: field, BoxL: b.left, BoxR: b.right, DocW: document.documentElement.clientWidth}; }))` type realReading struct { ID string Lit int Transform string Layout, Rendered, BoxL, BoxR, DocW float64 Field *float64 } func readReal(t *testing.T, ctx context.Context, where string) []realReading { t.Helper() var rs []realReading at(t, ctx, realSize, &rs) if len(rs) == 0 { t.Fatalf("%s: no frame showing", where) } return rs } // TestMobileIsThePageAtItsRealSize: on a phone, Mobile is the page at // its real size: the frame is laid out at the stage's width up to // 390px and never scaled, a 16px field is 16px, and the box reaches // the screen's edges. On a coarse pointer at 390px and 320px and a // fine one at 390px, in English and Arabic, on a component page and a // page-class one. The control: on a desktop the Mobile rendering is the // 390px phone, centred in its stage. func TestMobileIsThePageAtItsRealSize(t *testing.T) { coarse := harness.New(t, func(string) http.Handler { return treeHandler(t) }, harness.WithCoarsePointer(), harness.WithScrollbars()) cctx, ccancel := context.WithTimeout(coarse.Context(), 240*time.Second) defer ccancel() fine := harness.New(t, func(string) http.Handler { return treeHandler(t) }, harness.WithScrollbars()) fctx, fcancel := context.WithTimeout(fine.Context(), 240*time.Second) defer fcancel() for _, rig := range []struct { name string ctx context.Context rig *harness.Rig widths []int64 }{{"coarse", cctx, coarse, []int64{390, 320}}, {"fine", fctx, fine, []int64{390}}} { for _, file := range []string{"field.html", "layout-column.html"} { for _, locale := range []string{"en", "ar"} { for _, w := range rig.widths { where := fmt.Sprintf("day/%s %s at %dpx, %s pointer", locale, file, w, rig.name) openPage(t, rig.ctx, where, pageURL(rig.rig, locale, file), w, 844) if rig.name == "coarse" { requireCoarse(t, rig.ctx) } eagerly(t, rig.ctx, where) requireDrawnScrollbar(t, rig.ctx, where) var sideways float64 mustRun(t, rig.ctx, chromedp.Evaluate(`document.documentElement.scrollWidth - document.documentElement.clientWidth`, &sideways)) if sideways > 0.5 { t.Errorf("%s: the page scrolls %.1fpx sideways", where, sideways) } fields := 0 for _, r := range readReal(t, rig.ctx, where) { if r.Lit != 1 { t.Fatalf("%s: %s opens with tab %d lit; on a phone the widget opens on Mobile, and the readings below assume it", where, r.ID, r.Lit) } if r.Transform != "matrix(1, 0, 0, 1, 0, 0)" && r.Transform != "none" { t.Errorf("%s: %s is transformed %s; Mobile is never scaled", where, r.ID, r.Transform) } if math.Abs(r.Layout-r.Rendered) > 0.5 || r.Layout > 390 { t.Errorf("%s: %s lays out at %.1fpx and renders at %.1fpx; want one width, at most 390px", where, r.ID, r.Layout, r.Rendered) } if math.Abs(r.BoxL) > 1 || math.Abs(r.BoxR-r.DocW) > 1 { t.Errorf("%s: %s's box runs %.1f…%.1f in a %.0fpx document; on a phone the stage is the screen's width", where, r.ID, r.BoxL, r.BoxR, r.DocW) } if r.Field != nil { fields++ if rendered := *r.Field * r.Rendered / r.Layout; math.Abs(*r.Field-16) > 0.1 || math.Abs(rendered-16) > 0.1 { t.Errorf("%s: %s's text field computes %.2fpx and renders %.2fpx; a phone's field is 16px", where, r.ID, *r.Field, rendered) } } } if file == "field.html" && fields == 0 { t.Errorf("%s: no text field measured on the field page; the 16px claim was checked against nothing", where) } } } } } where := "day/en field.html at 1280px, Mobile chosen" openPage(t, fctx, where, pageURL(fine, "en", "field.html"), 1280, 900) eagerly(t, fctx, where) clickAll(t, fctx, where, clickedMobile, "Mobile") const want = 3 shown := readReal(t, fctx, where) if len(shown) < want { t.Fatalf("%s: %d frames showing, want at least %d to check", where, len(shown), want) } for _, r := range shown[:want] { if r.Layout != 390 { t.Errorf("%s: %s lays out at %.1fpx, want 390", where, r.ID, r.Layout) } } } // TestDesktopPansFromTheInlineStart: Desktop on a phone pans, and from // the page's start in either direction: in Arabic the frame is // anchored at the box's right edge and the box scrolls left. func TestDesktopPansFromTheInlineStart(t *testing.T) { rig := harness.New(t, func(string) http.Handler { return treeHandler(t) }) ctx, cancel := context.WithTimeout(rig.Context(), 180*time.Second) defer cancel() for _, c := range []struct { file string width float64 }{{"field.html", 900}, {"layout-column.html", 1200}} { for _, locale := range []string{"ar", "en"} { where := fmt.Sprintf("day/%s %s at 390px, Desktop chosen", locale, c.file) openPage(t, ctx, where, pageURL(rig, locale, c.file), 390, 844) eagerly(t, ctx, where) clickAll(t, ctx, where, clickedDesktop, "Desktop") var rs []struct { ID string Checked bool Layout float64 Pans bool Start, End float64 } at(t, ctx, `(() => { const rtl = document.documentElement.dir === "rtl", out = []; for (const v of document.querySelectorAll(".ds-view")) { const box = v.querySelector(".ds-view__box"), f = v.querySelector(".ds-view__frame"); const checked = v.querySelector(".ds-view__tab--d input").checked; const startEdge = () => { const b = box.getBoundingClientRect(), r = f.getBoundingClientRect(); return rtl ? r.right - b.right : r.left - b.left; }; const endEdge = () => { const b = box.getBoundingClientRect(), r = f.getBoundingClientRect(); return rtl ? b.left - r.left : r.right - b.right; }; const atStart = startEdge(); box.scrollLeft = rtl ? -(box.scrollWidth - box.clientWidth) : box.scrollWidth - box.clientWidth; const atEnd = endEdge(); box.scrollLeft = 0; out.push({ID: (v.closest("article, section") || {}).id || "", Checked: checked, Layout: f.offsetWidth, Pans: box.scrollWidth > box.clientWidth, Start: atStart, End: atEnd}); } return JSON.stringify(out); })()`, &rs) for _, r := range rs { if !r.Checked || r.Layout != c.width { t.Fatalf("%s: %s has Desktop checked %v and lays out at %.0fpx, want %.0fpx", where, r.ID, r.Checked, r.Layout, c.width) } } for _, r := range rs { if !r.Pans { t.Errorf("%s: %s's box does not scroll", where, r.ID) } if math.Abs(r.Start) > 1 { t.Errorf("%s: at rest %s's frame starts %.1fpx from its box's inline start", where, r.ID, r.Start) } if math.Abs(r.End) > 1 { t.Errorf("%s: scrolled to the end, %s's frame ends %.1fpx from its box's inline end", where, r.ID, r.End) } } } } }