Until now every test ran against core or the CLI; the Streamlit GUI was verified by hand. This commit adds tests/gui/ — 139 AppTest- driven tests behind a 'gui' marker so the quick loop (``pytest -m 'not gui'``) stays at 1777 tests / ~10s while ``pytest`` runs everything (1916 / ~14s). Coverage: - test_smoke.py (59): every page renders in EN and ES, expected substring present, sidebar selector mounted. - test_chrome.py (18): language selector flips session state and re-renders; quit button + farewell strings localize; tool-card names use the active language. - test_gate.py (9): require_normalization_gate no-op / warning / short-circuit / hash-mismatch invariants; warning + button localized. - test_workflows.py (14): happy path per Ready tool — stash upload, render, find primary action, verify result lands in session state. - test_dedup_review.py (8): Accept All / Reject All / Clear Decisions wire through to review_decisions; apply_review_decisions semantics (keep-all, merge, column override). - test_advanced_panels.py (15): config_panel widget defaults and options (algorithm, threshold, survivor rule, merge, multiselects, config save/load). - test_errors.py (4): garbage / empty / single-column uploads don't crash; duplicate-target mapping raises InputValidationError. - test_findings_panel.py (12): driven via a small standalone harness page so we test the component without faking a file_uploader. EN + ES strings, per-tool grouping, open-tool button label, untargeted expander, severity summary. Shared infrastructure in tests/gui/conftest.py: - ``stash_upload`` / ``stash_upload_without_gate`` — populate session_state to pre-pass or block the gate. - ``with_language`` — set ``ui_lang`` before run(). - ``collected_text`` — flatten title/caption/markdown/etc. into one string for substring assertions. - Auto-marking: every test in tests/gui/ gets ``@pytest.mark.gui`` via ``pytest_collection_modifyitems``, so the marker isn't per-test boilerplate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
182 lines
7.0 KiB
Python
182 lines
7.0 KiB
Python
"""Chrome tests — language selector, hide_streamlit_chrome, quit flow.
|
|
|
|
These verify the GUI plumbing that every page depends on. Failures here
|
|
cascade into every other page, so they run cheap and run first
|
|
(alphabetical name ordering after smoke).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from .conftest import collected_text, with_language
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# hide_streamlit_chrome mounts the selector
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestHideChromeMountsSelector:
|
|
"""``hide_streamlit_chrome()`` is the one place the language selector
|
|
is mounted. Every page that hides chrome (= every page) must get
|
|
exactly one sidebar selectbox with the i18n label."""
|
|
|
|
def test_home_has_one_sidebar_selectbox(self, home_app):
|
|
home_app.run()
|
|
# Only one selectbox in the sidebar today; if a page adds
|
|
# another, this becomes a weaker bound.
|
|
assert len(home_app.sidebar.selectbox) == 1, (
|
|
"expected exactly one sidebar selectbox (the language picker); "
|
|
f"got {len(home_app.sidebar.selectbox)}"
|
|
)
|
|
|
|
def test_selector_label_is_localized(self, home_app):
|
|
with_language(home_app, "es")
|
|
home_app.run()
|
|
labels = [sb.label for sb in home_app.sidebar.selectbox]
|
|
assert "Idioma" in labels, (
|
|
f"Spanish selector should be labelled 'Idioma'; got {labels}"
|
|
)
|
|
|
|
def test_selector_label_english_default(self, home_app):
|
|
home_app.run() # no with_language → default = en
|
|
labels = [sb.label for sb in home_app.sidebar.selectbox]
|
|
assert "Language" in labels
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Language selector switches session state
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestLanguageSwitch:
|
|
"""Picking 'es' in the selector flips ``st.session_state['ui_lang']``
|
|
and re-renders the page with Spanish strings on the next run."""
|
|
|
|
def test_default_language_is_english(self, home_app):
|
|
home_app.run()
|
|
# AppTest's session_state proxy doesn't implement .get(); use
|
|
# membership check + attribute access. Absence == default ("en").
|
|
lang = home_app.session_state["ui_lang"] if "ui_lang" in home_app.session_state else "en"
|
|
assert lang == "en"
|
|
text = collected_text(home_app)
|
|
assert "Data Cleaning Mastery" in text
|
|
|
|
def test_selecting_spanish_persists_in_session(self, home_app):
|
|
home_app.run()
|
|
selector = home_app.sidebar.selectbox[0]
|
|
selector.select("es").run()
|
|
assert home_app.session_state["ui_lang"] == "es"
|
|
|
|
def test_selecting_spanish_re_renders_in_spanish(self, home_app):
|
|
home_app.run()
|
|
selector = home_app.sidebar.selectbox[0]
|
|
selector.select("es").run()
|
|
text = collected_text(home_app)
|
|
assert "Maestría" in text, (
|
|
"after selecting Spanish, the home title should switch to "
|
|
f"'🧹 DataTools — Maestría…'; got:\n{text[:300]}"
|
|
)
|
|
|
|
def test_selecting_back_to_english_reverts(self, home_app):
|
|
# Start in Spanish, then flip back.
|
|
with_language(home_app, "es")
|
|
home_app.run()
|
|
assert "Maestría" in collected_text(home_app)
|
|
|
|
selector = home_app.sidebar.selectbox[0]
|
|
selector.select("en").run()
|
|
text = collected_text(home_app)
|
|
assert "Data Cleaning Mastery" in text
|
|
assert "Maestría" not in text
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Footer + page_title localization
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestLocalizedChrome:
|
|
"""A spot-check on the parts of the chrome that aren't the selector:
|
|
the bottom footer caption and the home-page hero text. Other strings
|
|
are pinned indirectly by ``TestEveryPageRenders.test_expected_*``."""
|
|
|
|
def test_footer_english(self, home_app):
|
|
home_app.run()
|
|
text = collected_text(home_app)
|
|
assert "Your data never leaves" in text
|
|
|
|
def test_footer_spanish(self, home_app):
|
|
with_language(home_app, "es")
|
|
home_app.run()
|
|
text = collected_text(home_app)
|
|
assert "Tus datos nunca salen" in text
|
|
|
|
def test_upload_section_heading_localizes(self, home_app):
|
|
with_language(home_app, "es")
|
|
home_app.run()
|
|
text = collected_text(home_app)
|
|
# ``📤 Sube un archivo para empezar`` from the es pack.
|
|
assert "Sube un archivo" in text
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Quit / Close page
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestQuitButtonRenders:
|
|
"""The Close page must show the localized title, body, and the
|
|
Close-the-app button. We don't actually click the button — that
|
|
would call ``os._exit(0)`` and kill the test process. We only
|
|
assert the button is present and its label is localized."""
|
|
|
|
def test_close_page_english(self, app_factory):
|
|
app = app_factory("99_Close")
|
|
app.run()
|
|
text = collected_text(app)
|
|
assert "Close DataTools" in text
|
|
labels = [b.label for b in app.button]
|
|
assert any("Close the app" in lbl for lbl in labels), (
|
|
f"Close-the-app button missing; buttons: {labels}"
|
|
)
|
|
|
|
def test_close_page_spanish(self, app_factory):
|
|
app = app_factory("99_Close")
|
|
with_language(app, "es")
|
|
app.run()
|
|
text = collected_text(app)
|
|
assert "Cerrar DataTools" in text
|
|
labels = [b.label for b in app.button]
|
|
assert any("Cerrar la app" in lbl for lbl in labels), (
|
|
f"Spanish Close button missing; buttons: {labels}"
|
|
)
|
|
|
|
def test_close_body_describes_unsaved_work_warning_es(self, app_factory):
|
|
app = app_factory("99_Close")
|
|
with_language(app, "es")
|
|
app.run()
|
|
text = collected_text(app)
|
|
assert "trabajo sin guardar" in text
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tool cards use localized names on the home grid
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestHomeToolGridLocalization:
|
|
"""The home grid pulls tool display names through ``tool_name()`` in
|
|
``tools_registry``. The Spanish pack provides translations for every
|
|
tool id; a regression in that wiring would make Spanish users see
|
|
English names. Pin a few representative ones."""
|
|
|
|
@pytest.mark.parametrize("needle", [
|
|
"Eliminador de duplicados",
|
|
"Limpiador de texto",
|
|
"Estandarizador de formatos",
|
|
"Gestor de valores faltantes",
|
|
"Mapeador de columnas",
|
|
])
|
|
def test_es_tool_name_on_home_grid(self, home_app, needle):
|
|
with_language(home_app, "es")
|
|
home_app.run()
|
|
text = collected_text(home_app)
|
|
assert needle in text, f"missing localized tool name {needle!r}"
|