* Add PWA notification controls (#3229, #3196) Rebased onto fresh master (#3229 was 281 commits behind). Routes browser notifications through the service worker (reg.showNotification) with a direct Notification fallback, threads the originating session deep link/tag into the payload, and adds Enable/Send-test/permission-status controls in Settings. notificationclick prefers an already-open target-session tab before opening a new window. Added the 6 new i18n keys to the Polish (pl) locale block, which the original PR missed. Co-authored-by: AJV20 <AJV20@users.noreply.github.com> * Apply Opus review fixes to #3229 PWA notifications - MUST-FIX: _showPwaNotification used navigator.serviceWorker.ready, which never settles when no SW registration ever activates (reverse-proxy MIME mishap, SW disabled) — every notification was silently dropped, a regression vs master. Switch to getRegistration() raced against a 2s timeout, falling back to new Notification(). - SHOULD-FIX: notificationclick matched client.url === targetUrl exactly, but _sessionUrlForSid copies the current query/hash into the deep link, so an already-open session tab missed the match and spawned a duplicate window. Compare pathnames instead. - SHOULD-FIX: "Send test" with permission denied was a silent no-op — now surfaces the notifications_denied toast on the force path. - SHOULD-FIX: permission-status span went stale after granting via the button — refresh it in requestNotificationPermission().then. - Consistency: the request-permission grant branch now has the same new Notification fallback as the granted branch. - Update test_pwa_notification_controls.py to assert the pathname match. * Stamp v0.51.346 — Release LJ (PWA notification controls #3229/#3196) --------- Co-authored-by: nesquena-hermes <[email protected]> Co-authored-by: AJV20 <AJV20@users.noreply.github.com>
68 lines
3.2 KiB
Python
68 lines
3.2 KiB
Python
"""Regression coverage for PWA-backed browser notifications (#3196)."""
|
|
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
MESSAGES_JS = (ROOT / "static" / "messages.js").read_text(encoding="utf-8")
|
|
SW_JS = (ROOT / "static" / "sw.js").read_text(encoding="utf-8")
|
|
INDEX_HTML = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
|
|
PANELS_JS = (ROOT / "static" / "panels.js").read_text(encoding="utf-8")
|
|
I18N_JS = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8")
|
|
CHANGELOG = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
|
|
|
|
|
|
def test_browser_notifications_use_service_worker_when_available():
|
|
assert "function _showPwaNotification" in MESSAGES_JS
|
|
assert "navigator.serviceWorker.ready" in MESSAGES_JS
|
|
assert "reg.showNotification" in MESSAGES_JS
|
|
assert "new Notification" in MESSAGES_JS
|
|
assert "function sendBrowserNotification" in MESSAGES_JS
|
|
|
|
|
|
def test_notification_payload_uses_completion_session_when_provided():
|
|
assert "function _notificationOptions" in MESSAGES_JS
|
|
assert "const sid=(options&&options.sid)||(S&&S.session&&S.session.session_id);" in MESSAGES_JS
|
|
assert "_sessionUrlForSid(sid)" in MESSAGES_JS
|
|
assert "data:{url}" in MESSAGES_JS
|
|
assert "tag:sid?`hermes-${sid}`" in MESSAGES_JS
|
|
assert "sendBrowserNotification('Response complete',assistantText?assistantText.slice(0,100):'Task finished',{sid:activeSid})" in MESSAGES_JS
|
|
assert "sendBrowserNotification('Approval required',d.description||'Tool approval needed',{sid:activeSid})" in MESSAGES_JS
|
|
assert "sendBrowserNotification('Clarification needed',d.question||'Tool clarification needed',{sid:activeSid})" in MESSAGES_JS
|
|
|
|
|
|
def test_service_worker_handles_notification_clicks_without_hijacking_other_sessions():
|
|
assert "notificationclick" in SW_JS
|
|
assert "event.notification.close()" in SW_JS
|
|
assert "clients.matchAll" in SW_JS
|
|
assert "clients.openWindow" in SW_JS
|
|
# Match the open tab on pathname, not the full href (query/hash differ).
|
|
assert "samePath(client.url)" in SW_JS
|
|
assert "new URL(clientUrl).pathname === targetPath" in SW_JS
|
|
assert "targetClient.focus()" in SW_JS
|
|
exact_idx = SW_JS.index("targetClient.focus()")
|
|
open_idx = SW_JS.index("self.clients.openWindow(targetUrl)")
|
|
navigate_idx = SW_JS.index("focusableClient.navigate(targetUrl)")
|
|
assert exact_idx < open_idx < navigate_idx
|
|
|
|
|
|
def test_settings_expose_permission_and_test_controls():
|
|
assert "notificationPermissionStatus" in INDEX_HTML
|
|
assert "requestNotificationPermission()" in INDEX_HTML
|
|
assert "sendBrowserNotification('Hermes test'" in INDEX_HTML
|
|
assert "{force:true}" in INDEX_HTML
|
|
assert "function updateNotificationPermissionStatus" in PANELS_JS
|
|
assert "notifications_permission_status" in PANELS_JS
|
|
|
|
|
|
def test_notification_i18n_and_changelog_entries_exist():
|
|
for key in [
|
|
"notifications_enable_btn",
|
|
"notifications_test_btn",
|
|
"notifications_permission_status",
|
|
"notifications_enabled_toast",
|
|
"notifications_denied",
|
|
"notifications_unsupported",
|
|
]:
|
|
assert key in I18N_JS
|
|
assert "PWA notifications now use the service worker" in CHANGELOG
|
|
assert "#3196" in CHANGELOG
|