""" Step 22 — Save Filename Prompt Tests Green paths: - test_dialog_prefilled_with_default_name: Dialog opens with stem prefilled and extension shown - test_custom_filename_is_used: Editing the stem changes the downloaded file's name - test_enter_confirms_with_default: Pressing Enter accepts the prefilled default name - test_empty_stem_falls_back_to_default: Clearing the stem and saving falls back to the default name Red paths: - test_cancel_aborts_download: Cancelling the dialog produces no download - test_close_button_aborts_download: Closing the dialog (X button) produces no download - test_backup_cancel_does_not_update_last_backup_at: Cancelling a backup save leaves last_backup_at untouched - test_migration_cancel_keeps_update_disabled: Cancelling the forced migration backup leaves Update disabled """ import re from playwright.sync_api import Page, expect def _wait_for_db(page: Page, app_url: str): """Navigate and wait for the database to be ready.""" page.goto(app_url) page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000 ) def _go_to_exercises(page: Page, app_url: str): """Navigate to the exercises list and wait for it to load.""" _wait_for_db(page, app_url) page.click('a[href="#/exercises"]') page.wait_for_timeout(500) def _go_to_settings(page: Page, app_url: str): """Navigate to the settings page and wait for DB.""" _wait_for_db(page, app_url) page.click('a[href="#/settings"]') page.wait_for_selector('[data-testid="settings-language"]') def _set_db_version(page: Page, version: int): """Rewrite the schema_version table so it contains only `version`.""" page.evaluate(f""" (async () => {{ await window.__db.run("DELETE FROM schema_version"); await window.__db.run("INSERT INTO schema_version (version) VALUES ({version})"); }})() """) def test_dialog_prefilled_with_default_name(page: Page, app_url: str): """Save dialog opens with the input prefilled and the extension shown alongside it.""" _go_to_exercises(page, app_url) page.locator('[data-testid="exercise-export-btn"]').click() input_ = page.locator('[data-testid="save-file-input"]') expect(input_).to_be_visible() value = input_.input_value() assert re.match(r"^trainus-exercises-\d{4}-\d{2}-\d{2}$", value), f"Unexpected default stem: {value}" expect(page.locator('[data-testid="save-file-extension"]')).to_have_text(".json") page.locator('[data-testid="save-file-cancel"]').click() def test_custom_filename_is_used(page: Page, app_url: str): """Editing the stem changes the downloaded file's name, keeping the original extension.""" _go_to_exercises(page, app_url) page.locator('[data-testid="exercise-export-btn"]').click() page.locator('[data-testid="save-file-input"]').fill("my-custom-export") with page.expect_download() as download_info: page.locator('[data-testid="save-file-confirm"]').click() assert download_info.value.suggested_filename == "my-custom-export.json" def test_enter_confirms_with_default(page: Page, app_url: str): """Pressing Enter in the filename field accepts the prefilled default name.""" _go_to_exercises(page, app_url) page.locator('[data-testid="exercise-export-btn"]').click() input_ = page.locator('[data-testid="save-file-input"]') default_stem = input_.input_value() with page.expect_download() as download_info: input_.press("Enter") assert download_info.value.suggested_filename == f"{default_stem}.json" def test_empty_stem_falls_back_to_default(page: Page, app_url: str): """Clearing the filename field and saving falls back to the default name.""" _go_to_exercises(page, app_url) page.locator('[data-testid="exercise-export-btn"]').click() input_ = page.locator('[data-testid="save-file-input"]') default_stem = input_.input_value() input_.fill("") with page.expect_download() as download_info: page.locator('[data-testid="save-file-confirm"]').click() assert download_info.value.suggested_filename == f"{default_stem}.json" def test_cancel_aborts_download(page: Page, app_url: str): """Cancelling the save dialog triggers no download.""" _go_to_exercises(page, app_url) page.locator('[data-testid="exercise-export-btn"]').click() page.wait_for_selector('[data-testid="save-file-input"]') downloaded = [] page.on("download", lambda d: downloaded.append(d)) page.locator('[data-testid="save-file-cancel"]').click() page.wait_for_timeout(500) expect(page.locator('[data-testid="save-file-input"]')).not_to_be_visible() assert downloaded == [], "No download should occur when the save dialog is cancelled" def test_close_button_aborts_download(page: Page, app_url: str): """Closing the save dialog via the X button triggers no download.""" _go_to_exercises(page, app_url) page.locator('[data-testid="exercise-export-btn"]').click() page.wait_for_selector('[data-testid="save-file-input"]') downloaded = [] page.on("download", lambda d: downloaded.append(d)) page.locator('[data-testid="modal-close"]').click() page.wait_for_timeout(500) expect(page.locator('[data-testid="save-file-input"]')).not_to_be_visible() assert downloaded == [], "No download should occur when the save dialog is closed" def test_backup_cancel_does_not_update_last_backup_at(page: Page, app_url: str): """Cancelling a database backup save leaves last_backup_at untouched.""" _go_to_settings(page, app_url) before = page.evaluate("async () => await window.__repos.settings.get('last_backup_at')") page.locator('[data-testid="settings-download-db"]').click() page.wait_for_selector('[data-testid="save-file-cancel"]') page.locator('[data-testid="save-file-cancel"]').click() page.wait_for_timeout(500) after = page.evaluate("async () => await window.__repos.settings.get('last_backup_at')") assert after == before, "last_backup_at should not change when the backup save is cancelled" def test_migration_cancel_keeps_update_disabled(page: Page, app_url: str): """Cancelling the forced migration backup leaves the Update button disabled.""" _wait_for_db(page, app_url) _set_db_version(page, 0) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-migration') === 'pending'", timeout=15000 ) expect(page.locator('[data-testid="migration-update"]')).to_be_disabled() page.locator('[data-testid="migration-backup"]').click() page.wait_for_selector('[data-testid="save-file-cancel"]') page.locator('[data-testid="save-file-cancel"]').click() expect(page.locator('[data-testid="migration-update"]')).to_be_disabled()