Files
datatools-dev/tests/gui/test_chrome.py
Michael ff2eaeb6c4 feat(home): multi-file upload + per-file analysis, drop tool grid
Home is now upload + analysis only. The page accepts multiple files in
one go, analyzes each independently, and renders findings grouped by
filename in bordered containers. The 3-section tool-card grid is gone —
discovery happens via the sidebar now.

Mechanics:
- file_uploader uses accept_multiple_files=True. Each file's findings
  cache in session_state["home_findings_by_file"] keyed by filename so
  removing a file via Streamlit's "x" button drops its findings too,
  and re-clicking Run only re-analyzes pending files.
- The first uploaded file is mirrored into the singular
  home_uploaded_{name,bytes,size} keys so tool pages continue to pick
  up an "active" upload through pickup_or_upload — no tool-page changes.
- New i18n keys: upload.intro_multi, upload.uploader_label_multi,
  upload.clear_results, upload.empty_state. upload.heading text is
  updated to "Upload one or more files to start" (EN + ES).

Dropped tests pinning the tool grid:
- TestHomeToolGridLocalization (test_chrome.py)
- test_home_tool_card_uses_es_name (test_smoke.py)
- TestLiteHomeGridBadges (test_lite_tier.py — locked-card lock-badge
  assertions; locking is still enforced per-tool-page via
  require_feature_or_render_upgrade)

2009 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 20:12:48 +00:00

160 lines
6.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 uno o más archivos para empezar`` from the es pack.
assert "Sube uno o más archivos" 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