mirror of
https://github.com/Sea-Haven-Industries/meal-order-manager.git
synced 2026-10-07 16:18:54 +00:00
test(form): cover sticky footer at narrow widths
This commit is contained in:
parent
57a6412811
commit
d450bf033f
1 changed files with 157 additions and 33 deletions
|
|
@ -124,6 +124,18 @@ class TestGenerateFormStructural:
|
||||||
local = _render(google=False)
|
local = _render(google=False)
|
||||||
assert "<script src=" not in local
|
assert "<script src=" not in local
|
||||||
|
|
||||||
|
def test_mobile_viewport_and_safe_area_clearance_are_preserved(self):
|
||||||
|
html = _render()
|
||||||
|
css = (SERVER_DIR / "templates" / "form.css").read_text()
|
||||||
|
assert (
|
||||||
|
'content="width=device-width, initial-scale=1.0, viewport-fit=cover"'
|
||||||
|
in html
|
||||||
|
)
|
||||||
|
assert "padding-bottom: calc(80px + env(safe-area-inset-bottom, 0px));" in css
|
||||||
|
assert (
|
||||||
|
"padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));"
|
||||||
|
) in css
|
||||||
|
|
||||||
def test_script_order_form_then_admin(self):
|
def test_script_order_form_then_admin(self):
|
||||||
html = _render()
|
html = _render()
|
||||||
form_pos = html.index("FORM_JS_MARKER")
|
form_pos = html.index("FORM_JS_MARKER")
|
||||||
|
|
@ -189,7 +201,7 @@ class TestGenerateFormStructural:
|
||||||
|
|
||||||
class TestGenerateFormPlaywright:
|
class TestGenerateFormPlaywright:
|
||||||
@pytest.fixture(scope="class")
|
@pytest.fixture(scope="class")
|
||||||
def browser_page(self, tmp_path_factory):
|
def browser(self):
|
||||||
try:
|
try:
|
||||||
from playwright.sync_api import sync_playwright
|
from playwright.sync_api import sync_playwright
|
||||||
except ImportError as exc: # pragma: no cover
|
except ImportError as exc: # pragma: no cover
|
||||||
|
|
@ -197,12 +209,6 @@ class TestGenerateFormPlaywright:
|
||||||
"playwright package is required; install requirements.txt"
|
"playwright package is required; install requirements.txt"
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
html = _render(google=False, api_key="")
|
|
||||||
# Stub status/roster so the page does not hang on network; roster fallback
|
|
||||||
# in loadRoster still applies if fetch fails — intercept to be deterministic.
|
|
||||||
out = tmp_path_factory.mktemp("form") / "order-form.html"
|
|
||||||
out.write_text(html)
|
|
||||||
|
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
try:
|
try:
|
||||||
browser = p.chromium.launch(headless=True)
|
browser = p.chromium.launch(headless=True)
|
||||||
|
|
@ -211,34 +217,152 @@ class TestGenerateFormPlaywright:
|
||||||
"Chromium is required for form Playwright tests. "
|
"Chromium is required for form Playwright tests. "
|
||||||
"Run: playwright install --with-deps chromium"
|
"Run: playwright install --with-deps chromium"
|
||||||
) from exc
|
) from exc
|
||||||
page = browser.new_page()
|
yield browser
|
||||||
page.route(
|
|
||||||
"**/api/form-status/**",
|
|
||||||
lambda route: route.fulfill(
|
|
||||||
status=200,
|
|
||||||
content_type="application/json",
|
|
||||||
body='{"status":"open"}',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
page.route(
|
|
||||||
"**/api/roster**",
|
|
||||||
lambda route: route.fulfill(
|
|
||||||
status=200,
|
|
||||||
content_type="application/json",
|
|
||||||
body=json.dumps(
|
|
||||||
{
|
|
||||||
"employees": [
|
|
||||||
{"name": "Ada Lovelace", "email": "ada@example.com"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
page.goto(out.as_uri())
|
|
||||||
page.wait_for_selector('[data-meal-index="0"]')
|
|
||||||
yield page
|
|
||||||
browser.close()
|
browser.close()
|
||||||
|
|
||||||
|
@pytest.fixture(scope="class")
|
||||||
|
def browser_page(self, browser, tmp_path_factory):
|
||||||
|
html = _render(google=False, api_key="")
|
||||||
|
# Stub status/roster so the page does not hang on network; roster fallback
|
||||||
|
# in loadRoster still applies if fetch fails — intercept to be deterministic.
|
||||||
|
out = tmp_path_factory.mktemp("form") / "order-form.html"
|
||||||
|
out.write_text(html)
|
||||||
|
|
||||||
|
page = browser.new_page()
|
||||||
|
page.route(
|
||||||
|
"**/api/form-status/**",
|
||||||
|
lambda route: route.fulfill(
|
||||||
|
status=200,
|
||||||
|
content_type="application/json",
|
||||||
|
body='{"status":"open"}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
page.route(
|
||||||
|
"**/api/roster**",
|
||||||
|
lambda route: route.fulfill(
|
||||||
|
status=200,
|
||||||
|
content_type="application/json",
|
||||||
|
body=json.dumps(
|
||||||
|
{
|
||||||
|
"employees": [
|
||||||
|
{"name": "Ada Lovelace", "email": "ada@example.com"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
page.goto(out.as_uri())
|
||||||
|
page.wait_for_selector('[data-meal-index="0"]')
|
||||||
|
yield page
|
||||||
|
page.close()
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("width", [320, 375, 480])
|
||||||
|
@pytest.mark.parametrize("google", [False, True], ids=["local", "google"])
|
||||||
|
def test_sticky_footer_fits_narrow_viewports(
|
||||||
|
self, browser, tmp_path, width, google
|
||||||
|
):
|
||||||
|
html_path = tmp_path / f"order-form-{'google' if google else 'local'}.html"
|
||||||
|
html_path.write_text(_render(google=google, api_key=""))
|
||||||
|
|
||||||
|
page = browser.new_page(viewport={"width": width, "height": 800})
|
||||||
|
page.add_init_script(
|
||||||
|
"""window.google = {
|
||||||
|
accounts: {
|
||||||
|
id: {
|
||||||
|
initialize() {},
|
||||||
|
renderButton() {},
|
||||||
|
disableAutoSelect() {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};"""
|
||||||
|
)
|
||||||
|
page.route(
|
||||||
|
"**/api/form-status/**",
|
||||||
|
lambda route: route.fulfill(
|
||||||
|
status=200,
|
||||||
|
content_type="application/json",
|
||||||
|
body='{"status":"open"}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
page.route(
|
||||||
|
"**/api/roster",
|
||||||
|
lambda route: route.fulfill(
|
||||||
|
status=200,
|
||||||
|
content_type="application/json",
|
||||||
|
body='{"employees":[{"name":"Ada Lovelace","email":"ada@example.com"}]}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
page.goto(html_path.as_uri())
|
||||||
|
page.wait_for_selector('[data-meal-index="0"]', state="attached")
|
||||||
|
if google:
|
||||||
|
page.evaluate(
|
||||||
|
"""() => {
|
||||||
|
const payload = btoa(JSON.stringify({
|
||||||
|
name: 'Ada Lovelace',
|
||||||
|
email: 'ada@example.com',
|
||||||
|
}));
|
||||||
|
handleCredentialResponse({ credential: `e30.${payload}.sig` });
|
||||||
|
}"""
|
||||||
|
)
|
||||||
|
page.wait_for_selector('[data-meal-index="0"]', state="visible")
|
||||||
|
|
||||||
|
first_increment = page.locator('[data-meal-index="0"] [data-qty-delta="1"]')
|
||||||
|
first_increment.click()
|
||||||
|
first_increment.click()
|
||||||
|
page.wait_for_function(
|
||||||
|
"() => document.getElementById('count-display').textContent === "
|
||||||
|
"'2 meals'"
|
||||||
|
)
|
||||||
|
page.evaluate("() => document.fonts.ready")
|
||||||
|
|
||||||
|
total = page.locator("#total-display")
|
||||||
|
count = page.locator("#count-display")
|
||||||
|
submit = page.locator("#submit-btn")
|
||||||
|
assert total.inner_text() == "$10.80"
|
||||||
|
assert count.inner_text() == "2 meals"
|
||||||
|
assert total.is_visible()
|
||||||
|
assert count.is_visible()
|
||||||
|
assert submit.is_visible()
|
||||||
|
assert submit.is_enabled()
|
||||||
|
submit.focus()
|
||||||
|
assert submit.evaluate("(element) => element === document.activeElement")
|
||||||
|
|
||||||
|
page.evaluate(
|
||||||
|
"() => window.scrollTo(0, document.documentElement.scrollHeight)"
|
||||||
|
)
|
||||||
|
layout = page.evaluate(
|
||||||
|
"""() => {
|
||||||
|
const rect = (selector) => {
|
||||||
|
const box = document.querySelector(selector).getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
left: box.left,
|
||||||
|
right: box.right,
|
||||||
|
top: box.top,
|
||||||
|
bottom: box.bottom,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
clientWidth: document.documentElement.clientWidth,
|
||||||
|
scrollWidth: document.documentElement.scrollWidth,
|
||||||
|
footer: rect('#sticky-footer'),
|
||||||
|
total: rect('#total-display'),
|
||||||
|
count: rect('#count-display'),
|
||||||
|
submit: rect('#submit-btn'),
|
||||||
|
lastCard: rect('#meals-list .meal-card:last-child'),
|
||||||
|
};
|
||||||
|
}"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert layout["scrollWidth"] == layout["clientWidth"]
|
||||||
|
for selector in ("footer", "total", "count", "submit"):
|
||||||
|
assert layout[selector]["left"] >= 0
|
||||||
|
assert layout[selector]["right"] <= layout["clientWidth"]
|
||||||
|
assert layout["lastCard"]["bottom"] <= layout["footer"]["top"]
|
||||||
|
finally:
|
||||||
|
page.close()
|
||||||
|
|
||||||
def test_qty_plus_updates_total_without_list_teardown(self, browser_page):
|
def test_qty_plus_updates_total_without_list_teardown(self, browser_page):
|
||||||
page = browser_page
|
page = browser_page
|
||||||
card = page.locator('[data-meal-index="0"]')
|
card = page.locator('[data-meal-index="0"]')
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue