test(form): cover a11y labels, status region, and desc toggle

Structural checks for dual status nodes and no alert(); Playwright for
init-time qty labels, aria-pressed chips, overflow More/Less, and
failed-submit text landing in role=alert.
This commit is contained in:
Adam Moussa 2026-07-29 20:04:26 -04:00
parent 9f34cb093f
commit d5a54b2e78
No known key found for this signature in database

View file

@ -235,6 +235,26 @@ class TestGenerateFormStructural:
assert "data-meal-index" in html
assert "data-qty-delta" in html
def test_a11y_status_and_live_total_markup(self):
html = _render()
assert 'id="form-status-alert"' in html
assert 'id="form-status-info"' in html
assert 'id="admin-status-alert"' in html
assert 'id="admin-status-info"' in html
assert 'role="alert"' in html
assert 'role="status"' in html
assert 'aria-live="polite"' in html
assert 'id="success-heading"' in html
assert 'tabindex="-1"' in html
def test_no_alert_dialogs_in_form_scripts(self):
form_js = (SERVER_DIR / "templates" / "form.js").read_text()
admin_js = (SERVER_DIR / "templates" / "admin.js").read_text()
assert "alert(" not in form_js
assert "alert(" not in admin_js
# confirm() retained for destructive admin delete / zero-qty save.
assert "confirm(" in admin_js
def test_design_tokens_only_in_definitions(self):
css = (SERVER_DIR / "templates" / "form.css").read_text()
# Strip :root block, then assert brand/muted/border hexes are gone
@ -430,6 +450,111 @@ class TestGenerateFormPlaywright:
assert page.locator("#count-display").inner_text() == "2 meals"
assert page.locator("#total-display").inner_text() == "$10.80"
def test_qty_labels_present_after_init(self, browser_page):
page = browser_page
card = page.locator('[data-meal-index="0"]')
assert (
card.locator('[data-qty-delta="-1"]').get_attribute("aria-label")
== "Remove one Chicken Bowl"
)
assert (
card.locator('[data-qty-delta="1"]').get_attribute("aria-label")
== "Add one Chicken Bowl"
)
assert (
card.locator(".qty").get_attribute("aria-label")
== "Quantity of Chicken Bowl"
)
def test_filter_chip_aria_pressed(self, browser_page):
page = browser_page
all_btn = page.locator("#filters button", has_text="All Meals")
assert all_btn.get_attribute("aria-pressed") == "true"
other = page.locator("#filters button").nth(1)
other_label = other.inner_text()
other.click()
page.wait_for_function(
"""(label) => {
const active = document.querySelector('#filters button.active');
return active && active.textContent === label
&& active.getAttribute('aria-pressed') === 'true';
}""",
arg=other_label,
)
assert all_btn.get_attribute("aria-pressed") == "false"
assert other.get_attribute("aria-pressed") == "true"
all_btn.click()
page.wait_for_function(
"""() => {
const active = document.querySelector('#filters button.active');
return active && active.textContent === 'All Meals'
&& active.getAttribute('aria-pressed') === 'true';
}"""
)
def test_desc_more_only_when_overflow(self, browser_page):
page = browser_page
# Ensure All Meals filter so both short and long cards are visible.
page.locator("#filters button", has_text="All Meals").click()
page.wait_for_selector('[data-meal-index="0"]')
page.wait_for_selector('[data-meal-index="1"]')
short_card = page.locator('[data-meal-index="0"]')
long_card = page.locator('[data-meal-index="1"]')
page.wait_for_function(
"""() => {
const long = document.querySelector('[data-meal-index="1"] .meal-desc-toggle');
return !!long;
}"""
)
assert short_card.locator(".meal-desc-toggle").count() == 0
toggle = long_card.locator(".meal-desc-toggle")
assert toggle.inner_text() == "More"
assert toggle.get_attribute("aria-expanded") == "false"
toggle.click()
assert toggle.inner_text() == "Less"
assert toggle.get_attribute("aria-expanded") == "true"
assert "is-expanded" in (
long_card.locator(".meal-desc").get_attribute("class") or ""
)
def test_submit_failure_lands_in_alert_region(self, browser_page):
page = browser_page
page.route(
"**/api/submit-order**",
lambda route: route.fulfill(
status=500,
content_type="application/json",
body='{"error":"Simulated submit failure"}',
),
)
page.locator("#emp-name").select_option(label="Ada Lovelace")
page.evaluate(
"""() => {
document.querySelectorAll('[data-meal-index] .qty').forEach((input) => {
input.value = '0';
input.dispatchEvent(new Event('change', { bubbles: true }));
});
}"""
)
page.wait_for_function(
"() => document.getElementById('count-display').textContent === '0 meals'"
)
page.locator('[data-meal-index="0"] [data-qty-delta="1"]').click()
page.wait_for_function(
"() => document.getElementById('count-display').textContent === '1 meal'"
)
page.locator("#submit-btn").click()
page.wait_for_function(
"""() => document.getElementById('form-status-alert').textContent
=== 'Simulated submit failure'"""
)
assert (
page.locator("#form-status-alert").inner_text()
== "Simulated submit failure"
)
assert page.locator("#form-status-info").inner_text() == ""
page.unroute("**/api/submit-order**")
def test_qty_typein_clamps_negative(self, browser_page):
page = browser_page
# Reset first card via type-in