Sidebar nav now groups tools under Data Review / Data Cleaners / Transformations / Automations via st.navigation, replacing the flat auto-discovered list. Tool display names switch to action-first phrasing (Find Duplicates, Fix Missing Values, Find Unusual Values, Standardize Formats, Clean Text, Quality Check, Map Columns, Combine Files, Automated Workflows) in EN + ES packs and on each page's H1. The Data Cleaners section follows the requested order: Missing Values → Outliers → Text Cleaner → Format Standardizer → Deduplicator → Quality Check. (Text Cleaner kept inside cleaners since the request didn't list it but the tool still ships.) Registry now carries a section field; helpers added: tools_in_section(), section_label(). 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", [
|
|
"Buscar duplicados",
|
|
"Limpiar texto",
|
|
"Estandarizar formatos",
|
|
"Corregir valores faltantes",
|
|
"Mapear 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}"
|