"""The theme bundle (goal 41 task 2): the account-settings appearance picker backed by Spry's built-in themes. The SetThemeAction persists the choice per user in user_preferences and writes the DEVICE-scope "ui" slot ({theme, mode}) the client applies as data-theme/data-mode; login seeds that slot from the stored preferences.""" from harness import * # noqa: F401,F403 def run(ctx): admin = ctx.auth def login_frames(username, password): """The login action's FULL directive chain (the harness login() only keeps the auth slot; the ui seed rides the same chain).""" frames = directives("/login") content = json.loads(frames[0]["snapshot_frame"]["content"]) action = content["public"]["login"] status, body = http(action.get("method", "POST"), action["uri"], {"username": username, "password": password}) if status != 200: raise SystemExit("login -> HTTP %d: %s" % (status, body[:300])) return json.loads(body) print("== profile page: the appearance card ==") profile = slot_public(admin, "/profile", "profile") check(profile.get("set_theme") is not None, "profile carries the set_theme action") check(profile.get("theme", "") == "" and profile.get("mode") == "auto", "no stored preference reads as core theme + auto mode") page = http_get_page(admin, "/profile") check("Appearance" in page, "profile page shows the Appearance card") for label in ("Spry (default)", "Airy", "Dwarf", "Formal", "Glassy", "H2O", "Living Sky", "Modern"): check(label in page, "theme choice offered: %s" % label) for label in ("Auto (follow system)", "Light", "Dark"): check(label in page, "mode choice offered: %s" % label) check("remembered for this account" in page, "the card hints the choice is remembered") print("== the seven theme stylesheets are served ==") for slug in ("airy", "dwarf", "formal", "glassy", "h2o", "living-sky", "modern"): status, _ = http("GET", "/_statum/resource/spry-theme-%s.css" % slug) check(status == 200, "spry-theme-%s.css served (HTTP %d)" % (slug, status)) status, _ = http("GET", "/_statum/resource/spry.css") check(status == 200, "spry.css served (HTTP %d)" % status) print("== saving a theme persists + answers the ui slot ==") result = act(admin, profile["set_theme"], {"theme": "dwarf", "mode": "dark"}) check("saved" in (action_notify(result) or ""), "set_theme accepts dwarf/dark") ui = [d for d in result if d["type"] == "set" and json.loads(d["snapshot_frame"]["content"])["slot"]["type"] == "ui"] check(len(ui) == 1, "the action answers a ui slot") ui_public = json.loads(ui[0]["snapshot_frame"]["content"])["public"] check(ui_public.get("theme") == "dwarf" and ui_public.get("mode") == "dark", "ui slot public data carries {theme, mode}") ui_scope = json.loads(ui[0]["snapshot_frame"]["content"])["slot"] check(ui_scope.get("scope") == "device", "the ui slot is DEVICE scope (cached across sessions)") conn = db() try: rows = conn.execute( "select key, value from user_preferences").fetchall() finally: conn.close() check(dict(rows).get("theme") == "dwarf" and dict(rows).get("mode") == "dark", "user_preferences stores theme=dwarf mode=dark: %s" % rows) print("== the saved choice reflects back on the profile ==") profile = slot_public(admin, "/profile", "profile") check(profile.get("theme") == "dwarf" and profile.get("mode") == "dark", "profile reads the stored choice back") print("== invalid values are refused, nothing stored ==") result = act(admin, profile["set_theme"], {"theme": "gruvbox", "mode": "auto"}) check("Unknown theme" in (action_notify(result) or ""), "an unknown theme slug is refused with a friendly error") result = act(admin, profile["set_theme"], {"theme": "airy", "mode": "sepia"}) check("Unknown colour mode" in (action_notify(result) or ""), "an unknown colour mode is refused") conn = db() try: rows = conn.execute( "select key, value from user_preferences").fetchall() finally: conn.close() check(dict(rows).get("theme") == "dwarf", "the refused slugs wrote nothing: %s" % rows) print("== core default + upsert overwrite ==") result = act(admin, profile["set_theme"], {"theme": "", "mode": "light"}) check("saved" in (action_notify(result) or ""), "core default + light accepted") conn = db() try: rows = dict(conn.execute( "select key, value from user_preferences").fetchall()) finally: conn.close() check(rows.get("theme") == "" and rows.get("mode") == "light", "the upsert replaced both rows in place: %s" % rows) profile = slot_public(admin, "/profile", "profile") check(profile.get("theme") == "" and profile.get("mode") == "light", "profile reads the core default back") print("== login seeds the ui slot from stored prefs ==") # The seed rides the LOGIN response's directive chain (the same # builder Spry's login action returns, extended by our subclass) - # so assert it there, not on a later entrypoint. frames = login_frames("admin", "adminpass123") seeded = [d for d in frames if d["type"] == "set" and json.loads(d["snapshot_frame"]["content"])["slot"]["type"] == "ui"] check(len(seeded) == 1, "the login response answers a ui slot") seeded_public = json.loads(seeded[0]["snapshot_frame"]["content"])["public"] check(seeded_public.get("theme") == "" and seeded_public.get("mode") == "light", "login seeds the ui slot from the stored preferences")