From 2f64dbc48c01903c019ff17f9dd1519c8fc0b6dc Mon Sep 17 00:00:00 2001 From: ChuckBuilds Date: Mon, 17 Aug 2026 11:36:08 -0400 Subject: [PATCH] fix(web): verify the onboarding timezone step, don't compare it to the default The Getting Started card's timezone step ticked when the saved timezone differed from the value config.template.json ships (America/New_York), OR-ed with the saved city differing from Tampa. Both halves were wrong. "Differs from the default" answers "did somebody edit this?", but what the checklist needs to know is whether the value is right. A user genuinely in America/New_York could never satisfy it, so the card nagged forever with four of five steps done -- the case that prompted this, on a panel whose timezone was correct all along. The city half was worse than useless: the saved city says nothing about whether the timezone is set, and because the two were OR-ed, saving a city ticked the step off with the timezone still wrong. That is the direction that actually breaks displays, since event times then render in the wrong zone. The browser already knows its own zone, so compare against that. No new persisted state, no network, and it catches the reverse case the old test got backwards: a panel still set to the old zone after a move now stays unticked, where before it ticked the moment the value stopped being the default. Zones are compared by the wall-clock time they produce for one instant rather than by identifier, so aliases (Asia/Calcutta vs Asia/Kolkata, Europe/Kiev vs Europe/Kyiv) don't read as a mismatch. When they genuinely differ the step names the browser's zone, so an unticked box says why. Configs with no timezone, an unparseable zone, or a browser without Intl leave the step open for the existing manual tick. The step still deep-links to the General tab, and the location value stays visible in its label -- it just no longer votes on whether the timezone is configured. Tests render the partial across configured zones and both cities: the step never pre-ticks server-side, carries the configured zone for the client to check, is unmoved by the city, and the panel-size step still resolves server-side. Reverting the template fails 9 of the 11. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01STMbQE4YctTacQXfbYqKuW --- test/test_onboarding_checklist.py | 152 ++++++++++++++++++ .../templates/v3/partials/overview.html | 71 +++++++- 2 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 test/test_onboarding_checklist.py diff --git a/test/test_onboarding_checklist.py b/test/test_onboarding_checklist.py new file mode 100644 index 00000000..52fd2e69 --- /dev/null +++ b/test/test_onboarding_checklist.py @@ -0,0 +1,152 @@ +""" +Getting Started checklist: what the server decides, and what it must not. + +The timezone step used to tick server-side when the saved timezone differed +from the shipped default, OR-ed with the saved city. That made the step +unsatisfiable for anyone genuinely in the default zone (the card nagged +forever), and let a saved city tick it off while the timezone was still wrong. +The step is now verified in the browser against its own zone, so the server's +only job is to hand over the configured value and stay out of the decision. + +These tests pin that contract: the panel-size step still reflects config, the +timezone step never pre-ticks, it carries the configured zone, and the city +has no influence on it. +""" + +import copy +import re +import sys +from pathlib import Path +from unittest.mock import MagicMock + +import pytest +from flask import Flask + +PROJECT_ROOT = Path(__file__).parent.parent +sys.path.insert(0, str(PROJECT_ROOT)) + +BASE_CONFIG = { + "timezone": "America/New_York", + "location": {"city": "Tampa", "state": "Florida", "country": "US"}, + "display": { + "hardware": {"rows": 32, "cols": 64, "chain_length": 2, "parallel": 1}, + "runtime": {}, + "double_sided": {"enabled": False}, + "vegas_scroll": {"plugin_order": [], "excluded_plugins": []}, + "plugin_rotation_order": [], + }, + "plugin_system": {}, + "schedule": {}, + "dim_schedule": {}, + "sync": {}, +} + + +def render(config): + """Render the overview partial against one config, as app.py would.""" + base = PROJECT_ROOT / "web_interface" + app = Flask( + __name__, + template_folder=str(base / "templates"), + static_folder=str(base / "static"), + ) + app.config["TESTING"] = True + + from web_interface.blueprints import pages_v3 as pv + + # pages_v3 is a module-level singleton shared across the test process; + # restore whatever the previous test left on it. + original_cm = getattr(pv.pages_v3, "config_manager", None) + original_pm = getattr(pv.pages_v3, "plugin_manager", None) + + mock_cm = MagicMock() + mock_cm.load_config.return_value = config + mock_cm.get_raw_file_content.return_value = config + pv.pages_v3.config_manager = mock_cm + + mock_pm = MagicMock() + mock_pm.plugins = {} + mock_pm.get_all_plugin_info.return_value = [] + mock_pm.get_plugin_display_modes.side_effect = lambda pid: [] + pv.pages_v3.plugin_manager = mock_pm + + app.register_blueprint(pv.pages_v3, url_prefix="") + try: + resp = app.test_client().get("/partials/overview") + assert resp.status_code == 200, resp.status_code + return resp.get_data(as_text=True) + finally: + pv.pages_v3.config_manager = original_cm + pv.pages_v3.plugin_manager = original_pm + + +def timezone_step(body): + """The checklist -
  • +