diff --git a/tests/test_generate_form.py b/tests/test_generate_form.py
index d9a1b0b..9a705b8 100644
--- a/tests/test_generate_form.py
+++ b/tests/test_generate_form.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+import base64
import json
import re
import sys
@@ -66,6 +67,21 @@ def _render(*, google: bool = False, api_key: str = "test-key") -> str:
)
+def _google_credential() -> str:
+ payload = json.dumps(
+ {
+ "name": "Test Admin",
+ "email": "test-admin@example.com",
+ "picture": (
+ "data:image/svg+xml,"
+ "
"
+ ),
+ }
+ ).encode()
+ encoded = base64.urlsafe_b64encode(payload).decode().rstrip("=")
+ return f"e30.{encoded}.signature"
+
+
class TestGenerateFormStructural:
def test_local_and_google_render(self):
local = _render(google=False)
@@ -292,3 +308,165 @@ class TestGenerateFormPlaywright:
"() => document.getElementById('count-display').textContent === '2 meals'"
)
assert page.locator("#total-display").inner_text() == "$10.80"
+
+ @pytest.mark.parametrize("width", [320, 375, 479])
+ def test_local_roster_has_no_horizontal_overflow(self, browser_page, width):
+ page = browser_page
+ page.set_viewport_size({"width": width, "height": 800})
+ dimensions = page.evaluate(
+ """() => ({
+ scrollWidth: document.documentElement.scrollWidth,
+ clientWidth: document.documentElement.clientWidth,
+ })"""
+ )
+ assert dimensions["scrollWidth"] == dimensions["clientWidth"]
+
+
+class TestGenerateFormGooglePlaywright:
+ @pytest.fixture(scope="class")
+ @classmethod
+ def signed_in_admin_page(cls, tmp_path_factory):
+ try:
+ from playwright.sync_api import sync_playwright
+ except ImportError as exc: # pragma: no cover
+ raise RuntimeError(
+ "playwright package is required; install requirements.txt"
+ ) from exc
+
+ out = tmp_path_factory.mktemp("google-form") / "order-form.html"
+ out.write_text(_render(google=True, api_key=""))
+
+ with sync_playwright() as p:
+ try:
+ browser = p.chromium.launch(headless=True)
+ except Exception as exc:
+ raise RuntimeError(
+ "Chromium is required for form Playwright tests. "
+ "Run: playwright install --with-deps chromium"
+ ) from exc
+ try:
+ page = browser.new_page()
+ page.add_init_script(
+ """window.google = {
+ accounts: {
+ id: {
+ initialize() {},
+ renderButton() {},
+ disableAutoSelect() {},
+ },
+ },
+ };"""
+ )
+ page.route(
+ "https://accounts.google.com/gsi/client",
+ lambda route: route.abort(),
+ )
+
+ def handle_api(route):
+ if "/api/form-status/" in route.request.url:
+ route.fulfill(
+ status=200,
+ content_type="application/json",
+ body='{"status":"open"}',
+ )
+ elif "/api/admin/orders" in route.request.url:
+ route.fulfill(
+ status=200,
+ content_type="application/json",
+ body='{"weeks":["2026-W30"]}',
+ )
+ else:
+ route.fulfill(
+ status=404,
+ content_type="application/json",
+ body="{}",
+ )
+
+ page.route("https://api.example.com/**", handle_api)
+ page.goto(out.as_uri(), wait_until="domcontentloaded")
+ page.evaluate(
+ "(credential) => handleCredentialResponse({ credential })",
+ _google_credential(),
+ )
+ page.locator("#admin-btn").wait_for(state="visible")
+ yield page
+ finally:
+ browser.close()
+
+ @pytest.mark.parametrize("width", [320, 375])
+ def test_signed_in_admin_user_bar_fits_phone_width(
+ self, signed_in_admin_page, width
+ ):
+ page = signed_in_admin_page
+ page.set_viewport_size({"width": width, "height": 800})
+
+ for selector in (
+ "#user-avatar",
+ "#user-name",
+ "#user-email",
+ "#admin-btn",
+ "#sign-out-btn",
+ ):
+ locator = page.locator(selector)
+ assert locator.is_visible()
+ bounds = locator.evaluate(
+ """element => {
+ const rect = element.getBoundingClientRect();
+ return {
+ left: rect.left,
+ top: rect.top,
+ right: rect.right,
+ bottom: rect.bottom,
+ viewportWidth: window.innerWidth,
+ viewportHeight: window.innerHeight,
+ };
+ }"""
+ )
+ assert bounds["left"] >= 0
+ assert bounds["top"] >= 0
+ assert bounds["right"] <= bounds["viewportWidth"]
+ assert bounds["bottom"] <= bounds["viewportHeight"]
+
+ for selector in ("#user-name", "#user-email"):
+ assert page.locator(selector).evaluate(
+ """element =>
+ element.scrollWidth <= element.clientWidth &&
+ element.scrollHeight <= element.clientHeight"""
+ )
+ assert page.locator("#admin-btn").is_enabled()
+ assert page.locator("#sign-out-btn").is_enabled()
+
+ dimensions = page.evaluate(
+ """() => ({
+ scrollWidth: document.documentElement.scrollWidth,
+ clientWidth: document.documentElement.clientWidth,
+ })"""
+ )
+ assert dimensions["scrollWidth"] == dimensions["clientWidth"]
+
+ def test_user_bar_stays_single_row_at_480(self, signed_in_admin_page):
+ page = signed_in_admin_page
+ page.set_viewport_size({"width": 480, "height": 800})
+ assert (
+ page.locator(".user-bar").evaluate(
+ "(element) => getComputedStyle(element).flexWrap"
+ )
+ == "nowrap"
+ )
+ dimensions = page.evaluate(
+ """() => ({
+ scrollWidth: document.documentElement.scrollWidth,
+ clientWidth: document.documentElement.clientWidth,
+ })"""
+ )
+ assert dimensions["scrollWidth"] == dimensions["clientWidth"]
+
+ def test_user_bar_wraps_below_shared_mobile_breakpoint(self, signed_in_admin_page):
+ page = signed_in_admin_page
+ page.set_viewport_size({"width": 479, "height": 800})
+ assert (
+ page.locator(".user-bar").evaluate(
+ "(element) => getComputedStyle(element).flexWrap"
+ )
+ == "wrap"
+ )