From 7126474f607eca46ee657e73007f9ee591a0aae3 Mon Sep 17 00:00:00 2001 From: Naveen Fernando <90832919+NPFernando@users.noreply.github.com> Date: Sun, 16 Aug 2026 15:49:45 +0530 Subject: [PATCH] fix: make home tool cards responsive --- tests/test_app_page.py | 17 +++++++++++++++++ tests/test_ip_geolocation.py | 29 +++++++++++++++++++++++++++-- utils/ui.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/tests/test_app_page.py b/tests/test_app_page.py index 1c61513..50a6dbb 100644 --- a/tests/test_app_page.py +++ b/tests/test_app_page.py @@ -33,6 +33,23 @@ def test_css_injects_before_sidebar_and_has_no_blocking_import(): assert ui_source.index("_inject_global_css(\"dark\")") < ui_source.index("render_sidebar(active_page)") +def test_home_tool_card_rows_wrap_at_responsive_breakpoints(): + """Home card rows must reflow rather than shrink five cards into an + unreadable strip on laptop, tablet, and phone-sized viewports.""" + ui_source = UI_MODULE.read_text(encoding="utf-8") + tool_row = '[data-testid="stHorizontalBlock"]:has([class*="st-key-tool_card_"])' + + assert tool_row in ui_source + assert '[data-testid="stColumn"]' in ui_source + assert "@media (max-width: 1180px)" in ui_source + assert "flex-wrap: wrap;" in ui_source + assert "flex: 1 1 calc(33.333% - 1rem) !important;" in ui_source + assert "@media (max-width: 860px)" in ui_source + assert "flex-basis: calc(50% - 0.75rem) !important;" in ui_source + assert "@media (max-width: 560px)" in ui_source + assert "flex-basis: 100% !important;" in ui_source + + def test_home_pills_are_required_and_cannot_deselect_to_none(): """Regression: st.pills defaults to required=False, meaning a click on an already-selected pill deselects it to None. The sort pill's value used to diff --git a/tests/test_ip_geolocation.py b/tests/test_ip_geolocation.py index b4692f1..66f6080 100644 --- a/tests/test_ip_geolocation.py +++ b/tests/test_ip_geolocation.py @@ -1,3 +1,6 @@ +from types import SimpleNamespace + +from utils import ip_geolocation from utils.ip_geolocation import lookup_ip_geolocation @@ -15,12 +18,34 @@ def test_lookup_ip_geolocation_rejects_invalid_ip(): assert "valid IPv4 or IPv6" in result["error"] -def test_lookup_ip_geolocation_live_public_ip(): +def test_lookup_ip_geolocation_public_ip_parses_successful_response(monkeypatch): + """Public API behavior is covered without making the suite rate-limit- or + network-dependent; live endpoint availability is not an application unit + test contract.""" + payload = { + "status": "success", + "country": "United States", + "regionName": "Virginia", + "city": "Ashburn", + "zip": "20149", + "lat": 39.03, + "lon": -77.5, + "timezone": "America/New_York", + "isp": "Google LLC", + "org": "Google Public DNS", + "as": "AS15169 Google LLC", + } + monkeypatch.setattr( + ip_geolocation.requests, + "get", + lambda *args, **kwargs: SimpleNamespace(status_code=200, json=lambda: payload), + ) + result = lookup_ip_geolocation("8.8.8.8") assert result["ok"] is True assert result["country"] == "United States" - assert result["asn"] + assert result["asn"] == "AS15169 Google LLC" def test_lookup_ip_geolocation_live_private_ip_returns_error(): diff --git a/utils/ui.py b/utils/ui.py index ead3213..ed77206 100644 --- a/utils/ui.py +++ b/utils/ui.py @@ -4259,6 +4259,38 @@ def _inject_global_css(mode: str) -> None: box-shadow: 0 18px 36px rgba(0, 0, 0, 0.28); } + /* Streamlit columns stay on one row by default, which makes five-card + Home sections too narrow on laptops and tablet-sized windows. Scope + wrapping to rows containing tool-card containers so metric rows and + ordinary form columns retain Streamlit's native layout behavior. */ + [data-testid="stHorizontalBlock"]:has([class*="st-key-tool_card_"]) { + align-items: stretch; + } + + @media (max-width: 1180px) { + [data-testid="stHorizontalBlock"]:has([class*="st-key-tool_card_"]) { + flex-wrap: wrap; + } + + [data-testid="stHorizontalBlock"]:has([class*="st-key-tool_card_"]) > [data-testid="stColumn"] { + flex: 1 1 calc(33.333% - 1rem) !important; + min-width: min(17rem, 100%) !important; + } + } + + @media (max-width: 860px) { + [data-testid="stHorizontalBlock"]:has([class*="st-key-tool_card_"]) > [data-testid="stColumn"] { + flex-basis: calc(50% - 0.75rem) !important; + } + } + + @media (max-width: 560px) { + [data-testid="stHorizontalBlock"]:has([class*="st-key-tool_card_"]) > [data-testid="stColumn"] { + flex-basis: 100% !important; + min-width: 0 !important; + } + } + .tool-card-shell { position: relative; min-height: 15.1rem;