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>
155 lines
6.0 KiB
Python
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]}"
|
|
)
|
|
|
|
|