""" 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)