Spaces:
Running
Running
| from __future__ import annotations | |
| import os | |
| from pathlib import Path | |
| import gradio as gr | |
| def test_build_app_returns_blocks(app): | |
| built = app.build_app() | |
| assert isinstance(built, gr.Blocks) | |
| def test_build_app_title(app): | |
| built = app.build_app() | |
| assert built.title == "ShopStack" | |
| def test_today_dashboard_returns_correct_shape(app): | |
| from shopstack.ui.screens import today_dashboard | |
| results = today_dashboard() | |
| assert len(results) == 6 | |
| for r in results: | |
| assert isinstance(r, str) | |
| def test_launch_reinstalls_pwa_and_health_routes(monkeypatch, app): | |
| """The launch wrapper should restore routes after Gradio rebuilds the app.""" | |
| import app as app_module | |
| calls: list[str] = [] | |
| def fake_mount_pwa_static(blocks): | |
| calls.append("pwa") | |
| def fake_mount_health_endpoint(blocks, db): | |
| calls.append("health") | |
| def fake_launch(self, *args, **kwargs): | |
| calls.append("launch") | |
| return "launched" | |
| monkeypatch.setattr(app_module, "mount_pwa_static", fake_mount_pwa_static) | |
| monkeypatch.setattr(app_module, "mount_health_endpoint", fake_mount_health_endpoint) | |
| monkeypatch.setattr(gr.Blocks, "launch", fake_launch, raising=False) | |
| built = app_module.build_app() | |
| assert calls.count("pwa") == 1 | |
| assert calls.count("health") == 1 | |
| result = built.launch() | |
| assert result == "launched" | |
| assert calls.count("launch") == 1 | |
| assert calls.count("pwa") == 2 | |
| assert calls.count("health") == 2 | |
| def test_all_view_functions_importable(app): | |
| """Verify all tab builders are registered in the tab registry.""" | |
| from shopstack.ui.tabs.registry import get_builder, registered_tab_ids | |
| expected = { | |
| "today", "basket", "market", "reconcile", "memory", | |
| "cookbook", "scanner", "timeline", "find_trail", "photo_map", | |
| "repair_inbox", "nutrition", "store_mode", "smart_basket", | |
| "analytics", "consumption", "recipe", "parser", "community", | |
| "market_intel", "trip_advisor", | |
| } | |
| registered = registered_tab_ids() | |
| missing = expected - registered | |
| assert not missing, f"Registry missing builders for tabs: {missing}" | |
| for tab_id in expected: | |
| builder = get_builder(tab_id) | |
| assert callable(builder), f"Builder for {tab_id} is not callable" | |
| from shopstack.ui.screens import ( | |
| shelf_scan_confirm, | |
| shelf_scan_process, | |
| shelf_scan_save_trace, | |
| shelf_scan_skip, | |
| ) | |
| for fn in ( | |
| shelf_scan_process, | |
| shelf_scan_confirm, | |
| shelf_scan_skip, | |
| shelf_scan_save_trace, | |
| ): | |
| assert callable(fn) | |
| def test_build_app_appears_to_have_tabs(app): | |
| built = app.build_app() | |
| children = list(built.children) | |
| assert len(children) > 0 | |
| def test_generate_shopping_poster_e2e(app): | |
| from datetime import date, timedelta | |
| from shopstack.app_context import db | |
| from shopstack.schemas.models import InventoryLot, ShoppingListItem, Store | |
| uid = db.active_household_id or "default_household" | |
| db.active_household_id = uid | |
| assert uid in {h["household_id"] for h in db.list_households()} | |
| db.add_store(Store( | |
| store_id="test_store", name="Test Store", | |
| location="Test", store_type="supermarket", | |
| )) | |
| today = date.today() | |
| db.add_inventory_lot(InventoryLot( | |
| lot_id="test_milk", canonical_name="milk", | |
| display_name="Test Milk", quantity=0.5, unit="L", | |
| storage_location_id="fridge", | |
| purchase_date=today - timedelta(days=2), | |
| ), user_id=uid) | |
| sl = db.create_shopping_list( | |
| name="Weekly Grocery Run", | |
| goal="Stock up for the week", | |
| user_id=uid, | |
| ) | |
| items_data = [ | |
| ("milk", 2.0, "L", "must_buy", "Only 0.5L left"), | |
| ("tomato", 1.0, "kg", "must_buy", "Almost out"), | |
| ("bread", 1.0, "loaf", "optional", "Current loaf expires soon"), | |
| ("toor_dal", 1.0, "kg", "must_buy", "Running low on dal"), | |
| ] | |
| for canonical, qty, unit, priority, reason in items_data: | |
| item = ShoppingListItem( | |
| canonical_name=canonical, | |
| requested_quantity=qty, | |
| unit=unit, | |
| priority=priority, | |
| reason=reason, | |
| ) | |
| db.add_list_item(sl.list_id, item) | |
| from shopstack.ui.screens import generate_shopping_poster | |
| poster_path, status_html = generate_shopping_poster() | |
| assert status_html, "Status HTML should not be empty" | |
| if poster_path: | |
| assert os.path.isfile(poster_path), f"Poster file should exist: {poster_path}" | |
| is_svg = poster_path.endswith(".svg") | |
| is_png = poster_path.endswith(".png") | |
| assert is_svg or is_png, f"Expected .svg or .png, got {Path(poster_path).suffix}" | |
| if is_svg: | |
| with open(poster_path, errors="replace") as f: | |
| content = f.read() | |
| assert "milk" in content.lower() | |
| assert "tomato" in content.lower() | |
| assert "bread" in content.lower() | |
| else: | |
| try: | |
| from PIL import Image | |
| img = Image.open(poster_path).convert("RGB") | |
| w, h = img.size | |
| assert w > 0 and h > 0, f"Poster image has invalid dimensions: {w}x{h}" | |
| except ImportError: | |
| pass | |
| assert "saved" in status_html.lower() or "✓" in status_html | |
| os.unlink(poster_path) | |
| try: | |
| Path(poster_path).parent.rmdir() | |
| except OSError: | |
| pass | |
| else: | |
| assert "provider" in status_html.lower() or "available" in status_html.lower() | |
| def test_today_tab_does_not_expose_demo_loader(): | |
| app_source = Path(__file__).resolve().parents[1] / "app.py" | |
| text = app_source.read_text() | |
| assert "Load Demo Data" not in text | |
| assert "seed_demo_inventory" not in text | |