From c23dfc13126d57460bb86d9c80d91227e4eb74b4 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Fri, 9 Oct 2026 16:21:38 -0700 Subject: [PATCH] Settings: the test notification shows in the tab you're in showNotification holds back new-mail notifications for a visible, focused tab, and the Test notification button is always pressed in one, so the test never appeared (only its sound played). The button now passes force, which skips that check; new mail keeps it. Port of coffey-labs/ihasmail#51, which fixes coffey-labs/ihasmail#48. (cherry picked from commit 28822cd2648371049e8795ef9c51a7c815b4053a) --- .../__tests__/show-notification.test.ts | 46 +++++++++++++++++++ web/src/lib/notify/notify.ts | 9 ++-- .../views/settings/NotificationsSettings.tsx | 2 +- 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 web/src/lib/notify/__tests__/show-notification.test.ts diff --git a/web/src/lib/notify/__tests__/show-notification.test.ts b/web/src/lib/notify/__tests__/show-notification.test.ts new file mode 100644 index 0000000..438acd6 --- /dev/null +++ b/web/src/lib/notify/__tests__/show-notification.test.ts @@ -0,0 +1,46 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { showNotification } from "@/lib/notify/notify"; + +/** + * The Settings test button runs in a visible, focused tab, which is exactly + * when a new-mail notification is held back. It has to show anyway (#48). + */ +describe("showNotification", () => { + const shown = vi.fn(); + + beforeEach(() => { + shown.mockClear(); + class FakeNotification { + static permission = "granted"; + onclick: (() => void) | null = null; + constructor(title: string) { + shown(title); + } + close() {} + } + vi.stubGlobal("Notification", FakeNotification); + vi.spyOn(document, "visibilityState", "get").mockReturnValue("visible"); + vi.spyOn(document, "hasFocus").mockReturnValue(true); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it("holds back new mail in a tab you are looking at", () => { + showNotification("New mail"); + expect(shown).not.toHaveBeenCalled(); + }); + + it("shows a forced one in that same tab", () => { + showNotification("ihasmail test", { force: true }); + expect(shown).toHaveBeenCalledWith("ihasmail test"); + }); + + it("still shows nothing without permission, forced or not", () => { + (Notification as unknown as { permission: string }).permission = "denied"; + showNotification("ihasmail test", { force: true }); + expect(shown).not.toHaveBeenCalled(); + }); +}); diff --git a/web/src/lib/notify/notify.ts b/web/src/lib/notify/notify.ts index 4c01cd4..e1318a6 100644 --- a/web/src/lib/notify/notify.ts +++ b/web/src/lib/notify/notify.ts @@ -91,10 +91,13 @@ export async function requestNotificationPermission(): Promise`), so if both ever show it, the second * replaces the first instead of stacking beside it. */ -export function showNotification(title: string, opts: NotificationOptions & { onClick?: () => void } = {}): void { +export function showNotification(title: string, opts: NotificationOptions & { onClick?: () => void; force?: boolean } = {}): void { if (!("Notification" in window) || Notification.permission !== "granted") return; - if (document.visibilityState === "visible" && document.hasFocus()) return; - const { onClick, ...options } = opts; + // New mail in a tab you're looking at needs no notification. A test asked + // for from Settings is always in such a tab, so it says to show anyway + // (coffey-labs/ihasmail#48). + if (!opts.force && document.visibilityState === "visible" && document.hasFocus()) return; + const { onClick, force: _force, ...options } = opts; const full = { icon: brandImage("/img/icon-192.png"), badge: brandImage("/img/favicon-64.png"), ...options }; const viaWorker = navigator.serviceWorker?.controller ? navigator.serviceWorker.ready : null; if (viaWorker) { diff --git a/web/src/views/settings/NotificationsSettings.tsx b/web/src/views/settings/NotificationsSettings.tsx index d4531b3..5584a80 100644 --- a/web/src/views/settings/NotificationsSettings.tsx +++ b/web/src/views/settings/NotificationsSettings.tsx @@ -81,7 +81,7 @@ export function NotificationsSettings() { /> update({ notificationSound: v })} label={t("Play a sound for new mail")} />
- +

{t("The tab title and favicon always show your unread Inbox count.")}

-- 2.54.0