"""Smoke tests: every page renders without exception in EN and ES. The cheapest, highest-value GUI tests in the project. They catch: - Page-level Python errors (import failures, syntax errors that ``ast.parse`` misses because they're runtime, e.g., a missing attribute on a module). - i18n pack key drift (a string that used to render in EN now renders literally as ``"chrome.language_label"`` because someone renamed the key in en.json but forgot es.json or the call site). - Streamlit API churn that breaks ``set_page_config`` / ``hide_streamlit_chrome`` on a single page. What they don't cover: user interactions. Those live in the workflow tests. """ from __future__ import annotations import pytest from .conftest import collected_text, with_language # Every page that ships in the sidebar nav. Slugs match the filenames # under ``src/gui/pages/`` so failures point at a real file. PAGE_SLUGS = [ "1_Deduplicator", "2_Text_Cleaner", "3_Format_Standardizer", "4_Missing_Values", "5_Column_Mapper", "6_Outlier_Detector", "7_Multi_File_Merger", "8_Validator_Reporter", "9_Pipeline_Runner", "10_PDF_Extractor", "11_Reconciler", "99_Close", ] # Substrings that must appear on each page for each language. # # v1.6 coverage reality (also documented in docs/USER-GUIDE.md §3.4): # only the home page, the Close page, and the shared chrome / # components ship Spanish strings. Per-tool page bodies are still # hard-coded English in both modes — translating them is tracked as a # follow-up. The substrings below reflect that reality: a page that # isn't translated yet asserts the same English substring under both # languages. The fact that the page *renders at all* in 'es' is still # the value of the smoke test. # # When a page gains real Spanish translation, flip its 'es' entry to # the localized substring — the test surface stays the same. EXPECTED_SUBSTRINGS: dict[str, dict[str, str]] = { "1_Deduplicator": {"en": "Find Duplicates", "es": "Buscar duplicados"}, "2_Text_Cleaner": {"en": "Clean Text", "es": "Limpiar texto"}, "3_Format_Standardizer": {"en": "Standardize", "es": "Estandarizar"}, "4_Missing_Values": {"en": "Fix Missing", "es": "Corregir valores"}, "5_Column_Mapper": {"en": "Map Columns", "es": "Mapear columnas"}, "6_Outlier_Detector": {"en": "Unusual", "es": "atípicos"}, "7_Multi_File_Merger": {"en": "Combine Files", "es": "Combinar archivos"}, "8_Validator_Reporter": {"en": "Quality Check", "es": "Verificación de calidad"}, "9_Pipeline_Runner": {"en": "Automated", "es": "Flujos automatizados"}, # PDF Extractor + Reconciler page titles are now translated in # both packs (``tools..page_title``). Their hero copy diverges # by language, so the smoke test pins the localized substring. "10_PDF_Extractor": {"en": "PDF to CSV", "es": "PDF a CSV"}, "11_Reconciler": {"en": "Reconcile", "es": "Reconciliar"}, "99_Close": {"en": "Shutting down", "es": "Cerrando"}, } class TestHomePageRenders: """Pin the home hero in both languages. Since the v3 brand refresh the title is the literal wordmark ("UNALOGIX DataTools") in both packs; the localized tagline is what shifts between en and es. We assert against the tagline string, which lives in ``home.caption`` of each pack. """ @pytest.mark.parametrize("lang,expected", [ ("en", "Clean. Normalize. Transform."), ("es", "Limpia. Normaliza. Transforma."), ]) def test_home_renders_in_language(self, home_app, lang, expected): with_language(home_app, lang) home_app.run() assert home_app.exception is None or home_app.exception == [], ( f"home page raised: {home_app.exception}" ) assert expected in collected_text(home_app) def test_home_renders_privacy_pill_in_es(self, home_app): # The footer caption is rendered via a component-iframe so # ``collected_text`` can't see it. The privacy pill on the # home header IS visible to AppTest and carries the same # locality story, so we pin that instead. with_language(home_app, "es") home_app.run() text = collected_text(home_app) assert "Se ejecuta 100% en local" in text class TestEveryPageRenders: """Parametrize over (page, language). Failure tells you exactly which page + which language broke.""" @pytest.mark.parametrize("slug", PAGE_SLUGS) @pytest.mark.parametrize("lang", ["en", "es"]) def test_renders_without_exception(self, app_factory, slug, lang): app = app_factory(slug) with_language(app, lang) app.run() # AppTest exposes ``exception`` as a list of element-wrapped # exceptions (empty when no error fired). assert not app.exception, ( f"page {slug!r} raised in language {lang!r}: {app.exception}" ) @pytest.mark.parametrize("slug", PAGE_SLUGS) @pytest.mark.parametrize("lang", ["en", "es"]) def test_expected_substring_present(self, app_factory, slug, lang): app = app_factory(slug) with_language(app, lang) app.run() needle = EXPECTED_SUBSTRINGS[slug][lang] text = collected_text(app) assert needle in text, ( f"page {slug!r} ({lang!r}) missing expected substring " f"{needle!r}\nGot:\n{text[:500]}…" ) class TestPageHasLanguageSelector: """Every page that calls ``hide_streamlit_chrome`` should mount the sidebar language selector. This is the only place the picker is rendered — if the chrome helper stops calling it, the test fails.""" @pytest.mark.parametrize("slug", PAGE_SLUGS) def test_sidebar_selectbox_present(self, app_factory, slug): app = app_factory(slug) app.run() # The selector is the only sidebar selectbox we ship today; if # a page adds another the test should be loosened to "at least # one selectbox with the language label." assert len(app.sidebar.selectbox) >= 1, ( f"page {slug!r} has no sidebar selectbox — " f"hide_streamlit_chrome() should have mounted the language " f"selector." )