fix: make the update checker visible -- persistent banner + a menu item
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 10s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 11s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 10s
CI / Catalog signature (pull_request) Successful in 6s
CI / Tests (py3.12 / windows-latest) (pull_request) Has been cancelled
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 10s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 11s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 10s
CI / Catalog signature (pull_request) Successful in 6s
CI / Tests (py3.12 / windows-latest) (pull_request) Has been cancelled
Reported from the field: running v1.2 against a repo with v1.3.0 published gave no prompt, and there appeared to be no way to check manually. The checker itself works; it was invisible, for two reasons. #78 -- the notice was written to the shared status label, which 21 other call sites rewrite. The check runs off-thread and lands a second or two after launch, right as the user starts clicking, so the next selection or refresh wiped it. Exactly the bug fixed for the MSIX warning in #35, which got a persistent banner; that fix was never carried to the update notice. Adds NoticeBanner: a persistent, dismissible notice carrying its own action button. It's a shared widget rather than a second bespoke banner, so the next thing needing the user's attention doesn't reach for the status bar again. (The MSIX banner still uses its own QLabel -- migrating it is a follow-up, deliberately not bundled with a bug fix.) #79 -- the only 'Check for updates' affordance was a button inside the About dialog, which is not where anyone looks. Worse, the About action was created without a menu role, and Qt auto-assigns AboutRole to actions whose text begins with 'About', relocating it into the macOS application menu -- so the notice's own hint, 'Help > About to view it', pointed at a menu that on macOS doesn't contain the item. Help now has its own 'Check for updates...' item with an explicit ApplicationSpecificRole, and the About action states its AboutRole rather than inheriting it invisibly. The menu-driven check is never throttled and always reports back -- the user asked, so silence would read as broken. The decision and the wording live in core.update_notice() because the test suite has no PySide6 (CI installs pytest + cryptography only), so anything in bcc.py is untestable. A test asserts the notice text names no menu path, which is what went stale here in the first place. Closes #78 Closes #79
This commit is contained in:
@@ -2370,3 +2370,57 @@ def test_config_has_unfilled_placeholders_false_after_fill():
|
||||
def test_config_has_unfilled_placeholders_checks_env_too():
|
||||
cfg = {"command": "uvx", "args": ["mcp-grafana"], "env": {"GRAFANA_URL": "<GRAFANA_URL>"}}
|
||||
assert c.config_has_unfilled_placeholders(cfg) is True
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# #78/#79 -- update notice: when to show it, and what it says
|
||||
# --------------------------------------------------------------------------- #
|
||||
def test_update_notice_when_a_newer_release_exists():
|
||||
n = c.update_notice("1.2.0", {"version": "v1.3.0", "url": "https://example.test/rel"})
|
||||
assert n is not None
|
||||
assert n["version"] == "v1.3.0"
|
||||
assert n["url"] == "https://example.test/rel"
|
||||
assert "1.3.0" in n["text"] and "1.2.0" in n["text"]
|
||||
|
||||
|
||||
def test_update_notice_is_silent_when_current():
|
||||
assert c.update_notice("1.3.0", {"version": "v1.3.0"}) is None
|
||||
assert c.update_notice("1.4.0", {"version": "v1.3.0"}) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bad", [None, {}, {"version": ""}, {"version": None}, {"version": 3}, []])
|
||||
def test_update_notice_is_silent_on_a_failed_or_malformed_check(bad):
|
||||
"""fetch_latest_release returns None on any failure; a half-formed payload
|
||||
must not produce a notice pointing at nothing."""
|
||||
assert c.update_notice("1.0.0", bad) is None
|
||||
|
||||
|
||||
def test_update_notice_falls_back_to_the_releases_page_without_a_url():
|
||||
n = c.update_notice("1.0.0", {"version": "v2.0.0"})
|
||||
assert n["url"] == c.RELEASES_URL
|
||||
|
||||
|
||||
def test_update_notice_names_no_menu_path():
|
||||
"""The old status-line text said 'Help > About to view it', which is wrong
|
||||
on macOS -- Qt moves the About action into the application menu (#79). The
|
||||
notice carries its own action, so it must not describe a menu path."""
|
||||
n = c.update_notice("1.0.0", {"version": "v2.0.0"})
|
||||
lowered = n["text"].lower()
|
||||
for phrase in ("help", "about", "menu", "▸", ">"):
|
||||
assert phrase not in lowered, f"notice text should not reference {phrase!r}"
|
||||
|
||||
|
||||
def test_update_notice_handles_the_v_prefix_consistently():
|
||||
assert c.update_notice("1.2.0", {"version": "1.3.0"}) is not None
|
||||
assert c.update_notice("v1.2.0", {"version": "v1.3.0"}) is not None
|
||||
assert c.update_notice("1.3.0", {"version": "v1.3.0"}) is None
|
||||
|
||||
|
||||
def test_update_notice_renders_both_versions_the_same_way():
|
||||
"""Tags carry a 'v' prefix, __version__ doesn't -- don't show both forms
|
||||
in one sentence."""
|
||||
n = c.update_notice("1.2.0", {"version": "v1.3.0"})
|
||||
assert "v1.3.0" not in n["text"]
|
||||
assert "1.3.0" in n["text"] and "1.2.0" in n["text"]
|
||||
# the machine-readable field keeps the real tag
|
||||
assert n["version"] == "v1.3.0"
|
||||
|
||||
Reference in New Issue
Block a user