Files
datatools-dev/tests/gui/test_chrome.py
Michael c568aec8a7 feat(gui): one-click Close in its own bottom sidebar section
Close is now a direct shutdown trigger: visiting the Close page (the
sidebar entry) fires shutdown_app() immediately — no confirm step, no
intermediate body. The farewell overlay paints and os._exit(0) lands
~1s later from a daemon thread.

Layout: Close moved into its own bottom-of-sidebar section so the
destructive action is visually separated from Account/Activate.

- New shutdown_app() in components/_legacy.py replaces quit_button.
  os._exit thread is skipped when "pytest" is in sys.modules so the
  test suite doesn't suicide on rendering 99_Close.
- pages/99_Close.py shrinks to set_page_config + chrome + shutdown_app.
- app.py nav grows a new "Close" section header (new
  nav.section_close key in en/es packs) pinned at the bottom of the
  navigation dict.

Tests updated:
- TestQuitButtonRenders → TestClosePageShutsDownImmediately.
  Assert the shutdown caption renders + no confirm button exists.
- test_smoke EXPECTED_SUBSTRINGS["99_Close"] now pins
  "Shutting down" / "Cerrando" (the visible page body) instead of
  the removed page title.

2008 tests pass.

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

155 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 TestClosePageShutsDownImmediately:
"""The Close page no longer renders a confirm button — visiting the
page IS the close action. Under pytest the ``os._exit`` thread is
skipped (so the test runner doesn't suicide), but the farewell
success caption still renders and we assert against it."""
def test_english_close_renders_shutdown_message(self, app_factory):
app = app_factory("99_Close")
app.run()
text = collected_text(app)
assert "Shutting down" in text or "Goodbye" in text, (
f"Close page missing shutdown caption; got:\n{text[:300]}"
)
# No confirm button — the page is the action.
labels = [b.label for b in app.button]
assert not any("Close the app" in lbl for lbl in labels), (
f"Close page should not render a confirm button; got: {labels}"
)
def test_spanish_close_renders_localized_shutdown_message(self, app_factory):
app = app_factory("99_Close")
with_language(app, "es")
app.run()
text = collected_text(app)
# ``quit.shutting_down`` in the es pack.
assert "Cerrando" in text or "Apagando" in text or "Adiós" in text, (
f"Spanish Close page missing localized shutdown caption; got:\n{text[:300]}"
)