trainUs/tests/test_step14_form_shortcuts.py
David 352a3d5e88
Some checks are pending
CI / Lint & Format (push) Waiting to run
CI / Build (push) Waiting to run
CI / Tests (${{ matrix.browser }}) (chromium) (push) Waiting to run
CI / Tests (${{ matrix.browser }}) (firefox) (push) Waiting to run
Initial commit
2026-06-18 13:06:57 +02:00

203 lines
9 KiB
Python

"""
Step 14 — Form Keyboard Shortcuts Tests
Tests for Ctrl+Enter (save) and Escape (cancel) shortcuts on form views.
The exercise form is used as the representative form; one test each covers
the session and combo forms to confirm the shortcuts are wired consistently.
- test_ctrl_enter_saves_from_body: Ctrl+Enter with no input focused saves the form
- test_ctrl_enter_saves_from_input: Ctrl+Enter while typing in a field saves the form
- test_ctrl_enter_validates_before_saving: Ctrl+Enter with empty title shows validation error
- test_escape_cancels_from_body: Escape with no input focused navigates back to list
- test_escape_cancels_from_input: Escape while a field is focused navigates back to list
- test_escape_cancel_edit_returns_to_detail: Escape on an edit form returns to detail view
- test_save_button_tooltip_contains_shortcut: Save button title attribute shows Ctrl+Enter hint
- test_ctrl_enter_on_session_form: Ctrl+Enter saves from the session new form
- test_ctrl_enter_on_combo_form: Ctrl+Enter saves from the combo new form
- test_ctrl_enter_on_collection_form: Ctrl+Enter saves from the collection new form
"""
from playwright.sync_api import Page, expect
def _wait_db(page: Page, app_url: str):
page.goto(app_url)
page.wait_for_function(
"document.documentElement.getAttribute('data-db-ready') === 'true'",
timeout=15000,
)
def _clean_exercises(page: Page):
page.evaluate(
"""async () => {
const all = await window.__repos.exercises.getAll();
for (const ex of all) await window.__repos.exercises.delete(ex.id);
}"""
)
def _clean_sessions(page: Page):
page.evaluate(
"""async () => {
const all = await window.__repos.sessions.getAll();
for (const s of all) await window.__repos.sessions.delete(s.id);
}"""
)
def _clean_combos(page: Page):
page.evaluate(
"""async () => {
const all = await window.__repos.combos.getAll();
for (const c of all) await window.__repos.combos.delete(c.id);
}"""
)
def _clean_collections(page: Page):
page.evaluate(
"""async () => {
const all = await window.__repos.collections.getAll();
for (const c of all) await window.__repos.collections.delete(c.id);
}"""
)
def _open_exercise_new_form(page: Page, app_url: str):
_wait_db(page, app_url)
_clean_exercises(page)
page.click('a[href="#/exercises"]')
page.wait_for_timeout(300)
page.click('[data-testid="exercise-new-btn"]')
page.wait_for_selector('[data-testid="exercise-title"]')
def test_ctrl_enter_saves_from_body(page: Page, app_url: str):
"""Ctrl+Enter with focus outside any input saves the form and navigates to detail view."""
_open_exercise_new_form(page, app_url)
page.fill('[data-testid="exercise-title"]', "Shortcut Save Test")
# Move focus away from the input (Escape would cancel the form, so blur instead)
page.evaluate("document.activeElement.blur()")
page.keyboard.press("Control+Enter")
page.wait_for_selector('[data-testid="exercise-detail-title"]', timeout=5000)
expect(page.locator('[data-testid="exercise-detail-title"]')).to_have_text("Shortcut Save Test")
_clean_exercises(page)
def test_ctrl_enter_saves_from_input(page: Page, app_url: str):
"""Ctrl+Enter while a text input is focused saves the form."""
_open_exercise_new_form(page, app_url)
page.fill('[data-testid="exercise-title"]', "Shortcut From Input")
# Keep focus inside the title input and press Ctrl+Enter
page.locator('[data-testid="exercise-title"]').press("Control+Enter")
page.wait_for_selector('[data-testid="exercise-detail-title"]', timeout=5000)
expect(page.locator('[data-testid="exercise-detail-title"]')).to_have_text("Shortcut From Input")
_clean_exercises(page)
def test_ctrl_enter_validates_before_saving(page: Page, app_url: str):
"""Ctrl+Enter with an empty title triggers validation rather than saving."""
_open_exercise_new_form(page, app_url)
# Do not fill the title — press Ctrl+Enter immediately
page.locator('[data-testid="exercise-title"]').press("Control+Enter")
expect(page.locator('[data-testid="exercise-title-error"]')).to_be_visible()
# Still on the form
expect(page.locator('[data-testid="exercise-title"]')).to_be_visible()
def test_escape_cancels_from_body(page: Page, app_url: str):
"""Escape with focus on the page body (no input focused) cancels the form and goes back to the list."""
_open_exercise_new_form(page, app_url)
page.fill('[data-testid="exercise-title"]', "Should Not Be Saved")
# Click outside the form to move focus away from any input
page.locator("h1").first.click()
page.keyboard.press("Escape")
page.wait_for_url("**#/exercises", timeout=5000)
expect(page.locator('[data-testid="exercise-new-btn"]')).to_be_visible()
def test_escape_cancels_from_input(page: Page, app_url: str):
"""Escape while a text input is focused cancels the form and goes back to the list."""
_open_exercise_new_form(page, app_url)
page.fill('[data-testid="exercise-title"]', "Should Not Be Saved Either")
page.locator('[data-testid="exercise-title"]').press("Escape")
page.wait_for_url("**#/exercises", timeout=5000)
expect(page.locator('[data-testid="exercise-new-btn"]')).to_be_visible()
def test_escape_cancel_edit_returns_to_detail(page: Page, app_url: str):
"""Escape on an edit form returns to the detail view, not the list."""
_wait_db(page, app_url)
_clean_exercises(page)
# Create an exercise first
page.click('a[href="#/exercises"]')
page.wait_for_timeout(300)
page.click('[data-testid="exercise-new-btn"]')
page.wait_for_selector('[data-testid="exercise-title"]')
page.fill('[data-testid="exercise-title"]', "Edit Escape Test")
page.click('[data-testid="exercise-save"]')
page.wait_for_selector('[data-testid="exercise-detail-title"]', timeout=5000)
# Open the edit form
page.click('[data-testid="exercise-edit-btn"]')
page.wait_for_selector('[data-testid="exercise-title"]')
# Press Escape — should go back to the detail view, not the list
page.keyboard.press("Escape")
page.wait_for_selector('[data-testid="exercise-detail-title"]', timeout=5000)
expect(page.locator('[data-testid="exercise-detail-title"]')).to_have_text("Edit Escape Test")
_clean_exercises(page)
def test_save_button_tooltip_contains_shortcut(page: Page, app_url: str):
"""The save button has a title attribute that includes 'Ctrl+Enter'."""
_open_exercise_new_form(page, app_url)
title_attr = page.locator('[data-testid="exercise-save"]').get_attribute("title")
assert title_attr is not None, "Save button should have a title attribute"
assert "Ctrl+Enter" in title_attr or "Ctrl+Entrée" in title_attr, (
f"Expected Ctrl+Enter hint in title, got: {title_attr}"
)
def test_ctrl_enter_on_session_form(page: Page, app_url: str):
"""Ctrl+Enter saves the session new form from within the title input."""
_wait_db(page, app_url)
_clean_sessions(page)
page.click('a[href="#/sessions"]')
page.wait_for_timeout(300)
page.click('[data-testid="session-new-btn"]')
page.wait_for_selector('[data-testid="session-title"]')
page.fill('[data-testid="session-title"]', "Session Shortcut Test")
page.locator('[data-testid="session-title"]').press("Control+Enter")
page.wait_for_selector('[data-testid="session-detail-title"]', timeout=5000)
expect(page.locator('[data-testid="session-detail-title"]')).to_have_text("Session Shortcut Test")
_clean_sessions(page)
def test_ctrl_enter_on_combo_form(page: Page, app_url: str):
"""Ctrl+Enter saves the combo new form from within the title input."""
_wait_db(page, app_url)
_clean_combos(page)
page.click('a[href="#/combos"]')
page.wait_for_timeout(300)
page.click('[data-testid="combo-new-btn"]')
page.wait_for_selector('[data-testid="combo-title"]')
page.fill('[data-testid="combo-title"]', "Combo Shortcut Test")
page.locator('[data-testid="combo-title"]').press("Control+Enter")
page.wait_for_selector('[data-testid="combo-detail-title"]', timeout=5000)
expect(page.locator('[data-testid="combo-detail-title"]')).to_have_text("Combo Shortcut Test")
_clean_combos(page)
def test_ctrl_enter_on_collection_form(page: Page, app_url: str):
"""Ctrl+Enter saves the collection new form from within the label input."""
_wait_db(page, app_url)
_clean_collections(page)
page.click('a[href="#/collections"]')
page.wait_for_timeout(300)
page.click('[data-testid="collection-new-btn"]')
page.wait_for_selector('[data-testid="collection-label"]')
page.fill('[data-testid="collection-label"]', "Collection Shortcut Test")
page.locator('[data-testid="collection-label"]').press("Control+Enter")
page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000)
expect(page.locator('[data-testid="collection-detail-title"]')).to_have_text("Collection Shortcut Test")
_clean_collections(page)