diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-08 03:14:34 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-08 03:14:34 +0200 |
| commit | 8b0f4ba3bc5fc146bad4730b1ccdcd5be55f192d (patch) | |
| tree | 49d7d169115a9b7c87d46d251abaf8590133191e /packages/meshbay-hub/tests | |
| parent | 6a5997655d9f2fa583bf707a5611bbb0c0f109fc (diff) | |
| download | meshbay-8b0f4ba3bc5fc146bad4730b1ccdcd5be55f192d.tar.gz | |
feat(client): create the system tray at launch, not on first minimise
ensureTray() was reachable only from the window:minimize-to-tray handler, so
the indicator did not exist until you had already hidden the window into it.
That is backwards on both desktops — most of what a tray is for is finding an
application that is not in front of you — and on Windows it read as the app
having no tray presence at all.
Created during app.whenReady(), after registerBridge() and before
createWindow(). The order matters: buildTrayMenu reads the nodeService that
registerBridge assigns, so the other way round puts the Start/Stop entry on
the menu one five-second poll late.
The menu's labels were the one thing that came *from* the minimise call, since
the main process has no i18n. A new tray:labels IPC (platform.setTrayLabels)
carries them instead, sent from the renderer's boot once initLocale() has a
catalogue; a language change reloads the page, so the same call covers it. The
window between launch and that first message shows TRAY_FALLBACK, in English.
§5.10's two platform gates become one — trayOS() in main.js, which every tray
path calls. test_desktop_shell.py's existing test is rewritten against it and
two are added: the launch ordering, and that no tray path tests
process.platform inline instead of calling the gate.
Windows still files a new tray icon under hidden icons until the person drags
it onto the taskbar. No API promotes it; documented in WINDOWS-PORT.md §5.11
rather than worked around.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8EDjk6pkYZrCbo63m2x87
Diffstat (limited to 'packages/meshbay-hub/tests')
| -rw-r--r-- | packages/meshbay-hub/tests/test_desktop_shell.py | 78 |
1 files changed, 61 insertions, 17 deletions
diff --git a/packages/meshbay-hub/tests/test_desktop_shell.py b/packages/meshbay-hub/tests/test_desktop_shell.py index 45e3ed4..c9d8683 100644 --- a/packages/meshbay-hub/tests/test_desktop_shell.py +++ b/packages/meshbay-hub/tests/test_desktop_shell.py @@ -380,19 +380,18 @@ def test_package_json_does_not_define_a_second_linux_package(): # ── System tray is cross-platform ──────────────────────────────────────────── -def test_tray_capability_and_ipc_handler_agree_on_which_platforms(): +def test_tray_capability_and_main_process_agree_on_which_platforms(): """ - The tray (GNOME, then Windows) has exactly two platform gates: the - `tray` capability preload.js exposes, which is all app.js's nav button - is keyed on, and the `window:minimize-to-tray` IPC handler in main.js - that actually creates it. Both must name the same platform set, or one - of two broken states results: a button that renders but does nothing - (handler stricter than the capability), or a hidden window with no way - back (capability stricter than the handler, on a desktop with no tray - to have hidden it into). Everything else the tray uses (`ensureTray`, - `tray.on('click')`, the poll-refresh, `nodeService.status/stop/restart`) - is unconditional Electron/abstraction code with no platform check of its - own -- these two lines are the whole gate. + The tray (GNOME, then Windows) has exactly two platform gates: the `tray` + capability preload.js exposes, which is all app.js's nav button is keyed + on, and main.js's `trayOS()`, which every path that creates or uses the + indicator goes through. Both must name the same platform set, or one of + two broken states results: a button that renders but does nothing (main + stricter than the capability), or a hidden window with no way back + (capability stricter than main, on a desktop with no tray to have hidden + it into). Everything else the tray uses (`ensureTray`, `tray.on('click')`, + the poll-refresh, `nodeService.status/stop/restart`) is unconditional + Electron/abstraction code with no platform check of its own. """ preload = _preload() cap_line = next( @@ -400,11 +399,56 @@ def test_tray_capability_and_ipc_handler_agree_on_which_platforms(): and "process.platform" in line), None) assert cap_line, "no platform-gated `tray:` capability found in preload.js" - lines = _main().splitlines() - handler_start = next( - i for i, line in enumerate(lines) if "'window:minimize-to-tray'" in line) - handler_block = "\n".join(lines[handler_start:handler_start + 5]) + main = _main() + gate = next( + (line for line in main.splitlines() + if line.startswith("const trayOS =") and "process.platform" in line), None) + assert gate, "no `trayOS` platform gate found in main.js" for plat in ("'linux'", "'win32'"): assert plat in cap_line, f"tray capability must name {plat}" - assert plat in handler_block, f"minimize-to-tray handler must name {plat}" + assert plat in gate, f"trayOS must name {plat}" + + +def test_every_tray_path_goes_through_the_one_gate(): + """ + There is one platform test, not a copy per call site. A second inline + `process.platform === ...` beside a tray call is how the two drift, which + is the whole failure the test above exists to catch — it can only compare + what it can find. + + Both entry points are named explicitly: the launch-time creation (the + indicator is there before anyone minimises, which is most of what a tray + is for) and the minimise handler. + """ + lines = _main().splitlines() + + # Both entry points still exist, so a rename cannot make this vacuous. + for marker in ("ensureTray()", "'window:minimize-to-tray'", "'tray:labels'"): + assert any(marker in line for line in lines), f"{marker} not found in main.js" + + inline = [line for line in lines + if "process.platform" in line and "tray" in line.lower() + and not line.startswith("const trayOS =")] + assert not inline, ( + "a tray path is testing process.platform itself instead of calling " + f"trayOS(): {inline}") + + +def test_the_tray_is_created_at_launch(): + """ + Not on the first minimise. An indicator that only appears once you have + already hidden the window cannot be used to find the application. + + `ensureTray` must be called after `registerBridge`, because buildTrayMenu + reads the `nodeService` registerBridge assigns: the other order puts the + Start/Stop entry on the menu one five-second poll late. + """ + main = _main() + ready = main.index("app.whenReady()") + bridge = main.index("registerBridge();", ready) + launch = main.index("ensureTray()", ready) + window = main.index("createWindow();", ready) + + assert bridge < launch, "ensureTray must come after registerBridge" + assert launch < window, "the tray is created as part of startup" |