163 lines
6.7 KiB
Python
163 lines
6.7 KiB
Python
"""
|
|
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()
|