aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-18 14:07:16 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-18 14:07:16 +0200
commitc384878aa0d6ef7a33bda23887dc264b94386725 (patch)
tree67279ddc7cfbb0902b0cc1348bb66c2724a63d36 /packages/meshbay-hub/tests
parentcf418a095b07c8127042390c748e721d1433879b (diff)
downloadmeshbay-c384878aa0d6ef7a33bda23887dc264b94386725.tar.gz
fix(client): a film can go full-screen, and automatic saving is automatic
**Full-screen was denied, and the denial was invisible.** The permission handler was written from a true sentence — nothing here needs a camera, a microphone or a location — and implemented as `callback(false)` for everything. Chromium's own video controls ask for the `fullscreen` permission, so a film could not be watched full-screen. What made it hard to find, and what the test now pins: **a denied `fullscreen` does not reject.** `requestFullscreen()` returns a promise that never settles. No exception, no console message, nothing in the renderer that mentions a permission — the button just does nothing. Measured rather than reasoned: the probe reported `NEVER SETTLED` while the main process, instrumented for one run, logged `PERMISSION ASKED: fullscreen`. After the fix the same probe reports `granted` with `document.fullscreenElement` set. The handler now enumerates what is *granted* — `fullscreen`, and nothing else — so a camera, a microphone, a location, notifications and MIDI are still refused and whatever Chromium adds next arrives refused rather than quietly allowed. `Permissions.query` takes the other handler, so both now answer from the one list instead of eventually disagreeing. The old test asserted `callback(false)`, which is to say it locked in the bug. It is replaced by three: what must stay denied, that `fullscreen` is granted, and that both handlers read the same list. **"Save automatically" opened a dialog.** The automatic path required a folder to have been chosen first, and on a new profile nobody has chosen one — so the very first download fell through to Save As, which is the one thing the setting promises not to do. A browser does not make you pick a folder before it will save a file; the system Downloads folder is the answer when there is no other. Verified on a fresh profile with a home of its own: no dialog, 1024 bytes on disk, destination reported as the default (`/home/…/Téléchargements` on this machine, via the localized XDG directory). A folder that *was* chosen and has since gone still asks. Silently redirecting those files is worse than a dialog: someone who picked an external drive wants to be told it is not there, not to find the film in their home directory a week later. Settings shows the effective destination either way, and offers "forget" only for a folder somebody actually chose. 813 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests')
-rw-r--r--packages/meshbay-hub/tests/test_desktop_shell.py65
1 files changed, 61 insertions, 4 deletions
diff --git a/packages/meshbay-hub/tests/test_desktop_shell.py b/packages/meshbay-hub/tests/test_desktop_shell.py
index c4f5b08..0abdb68 100644
--- a/packages/meshbay-hub/tests/test_desktop_shell.py
+++ b/packages/meshbay-hub/tests/test_desktop_shell.py
@@ -13,6 +13,7 @@ Every assertion here corresponds to a sentence in `docs/desktop-client-v1.md`
person with an installed client is what confirms the rest.
"""
+import re
from pathlib import Path
import pytest
@@ -69,10 +70,40 @@ def test_navigation_away_from_the_package_is_refused():
assert "event.preventDefault()" in source
-def test_no_permission_is_granted_to_the_page():
- """Nothing here needs a camera, a microphone or a location."""
- assert "setPermissionRequestHandler" in _main()
- assert "callback(false)" in _main()
+def _granted_permissions() -> set[str]:
+ """The allowlist, read out of the source rather than described here."""
+ match = re.search(r"GRANTED_PERMISSIONS = new Set\(\[([^\]]*)\]\)", _main())
+ assert match, "the permission allowlist is gone or was renamed"
+ return set(re.findall(r"'([^']+)'", match.group(1)))
+
+
+def test_the_page_gets_no_camera_microphone_or_location():
+ denied = {"media", "geolocation", "midi", "midiSysex", "notifications",
+ "pointerLock", "openExternal", "clipboard-read", "hid", "serial",
+ "usb", "idle-detection", "window-management"}
+ assert not (_granted_permissions() & denied)
+
+
+def test_video_may_go_fullscreen():
+ """
+ The regression this replaced a blanket denial to fix, and the reason it is
+ worth a test: **a denied `fullscreen` does not reject.** Chromium's own
+ video controls ask for it, `requestFullscreen()` returns a promise that
+ never settles, and the button does nothing with no error raised anywhere.
+ Nothing observable says "permission" — so nothing would have led back here.
+ """
+ assert "fullscreen" in _granted_permissions()
+
+
+def test_both_permission_handlers_answer_from_the_same_list():
+ """`Permissions.query` takes the check handler and a request takes the
+ other; two lists would eventually disagree about what the page may do."""
+ source = _main()
+ for handler in ("setPermissionRequestHandler", "setPermissionCheckHandler"):
+ assert handler in source
+ after = source.split(handler, 1)[1][:400]
+ assert "GRANTED_PERMISSIONS" in after, (
+ f"{handler} decides on its own rather than from the allowlist")
# ── The custom scheme ───────────────────────────────────────────────────────
@@ -241,3 +272,29 @@ def test_the_packaged_page_loads_the_shared_modules():
"style.css", "argon2.min.js"):
assert module in page, f"{module} is not loaded by the packaged page"
assert "/a/" not in page, "the packaged page points at the hub's asset prefix"
+
+
+# ── Where downloads land ────────────────────────────────────────────────────
+
+def test_automatic_saving_never_opens_a_dialog_for_want_of_a_folder():
+ """
+ "Save automatically" opened a Save As dialog on the first download, because
+ the automatic path required a folder to have been chosen first and nobody
+ had chosen one. A browser does not ask before it will save a file; the
+ system Downloads folder is the answer when there is no other.
+ """
+ source = _main()
+ begin = source.split("ipcMain.handle('save:begin'", 1)[1].split("ipcMain.handle", 1)[0]
+ assert "defaultDownloadDir()" in begin, (
+ "the automatic path has no destination when no folder was chosen")
+ assert "app.getPath('downloads')" in source
+
+
+def test_a_chosen_folder_that_has_gone_is_not_silently_replaced():
+ """Someone who picked an external drive should be told it is not there,
+ not find the film in their home directory a week later."""
+ source = _main()
+ begin = source.split("ipcMain.handle('save:begin'", 1)[1].split("ipcMain.handle", 1)[0]
+ assert "config.downloadDir && !chosen" in begin, (
+ "a chosen-but-missing folder falls through to the default instead of asking")
+ assert "showSaveDialog" in begin