mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
fix(pwa): clear the leftover app badge from the page, not the worker
A badge call from the service worker stalled Chromium's worker thread while it activated, awaited or not: the worker reached "activated" but never claimed its pages, so no page was ever controlled and every offline path was gone. The worker no longer touches the badge. The installed app clears a leftover count from the page once the worker is registered; a browser tab leaves it alone. The filter-sync test no longer reads `history.length`, which the browser caps; the recorded push without a replace already proves the new entry.
This commit is contained in:
1 parent
45a5d4e353
commit
80ee228899
5 files changed
+61
-18
No files matched your search
@@ -482,13 +482,15 @@ describe("sw.js — activate", () => {
|
||||
expect(harness.navPreload.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it("clears the dose count an earlier release left on the app icon", async () => {
|
||||
it("leaves the app icon badge to the page, never touches it while activating", async () => {
|
||||
// A badge call from the worker can stall Chromium's worker thread, so the
|
||||
// activation never claims its pages. The page clears a leftover count.
|
||||
const harness = bootServiceWorker();
|
||||
const { badge } = swFakes(harness);
|
||||
|
||||
await dispatchActivate(harness);
|
||||
|
||||
expect(badge.cleared).toBe(true);
|
||||
expect(badge.cleared).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { shouldRegisterServiceWorker } from "../service-worker-registrar";
|
||||
import {
|
||||
clearLeftoverAppBadge,
|
||||
shouldRegisterServiceWorker,
|
||||
} from "../service-worker-registrar";
|
||||
|
||||
describe("shouldRegisterServiceWorker", () => {
|
||||
it("registers in production with window + serviceWorker support", () => {
|
||||
@@ -29,3 +32,37 @@ describe("shouldRegisterServiceWorker", () => {
|
||||
expect(shouldRegisterServiceWorker("production", true, false)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("clearLeftoverAppBadge", () => {
|
||||
function fakeWindow(standalone: boolean, hasApi = true) {
|
||||
const calls = { cleared: 0 };
|
||||
const win = {
|
||||
matchMedia: () => ({ matches: standalone }),
|
||||
navigator: hasApi
|
||||
? {
|
||||
clearAppBadge: async () => {
|
||||
calls.cleared += 1;
|
||||
},
|
||||
}
|
||||
: {},
|
||||
} as unknown as Window;
|
||||
return { win, calls };
|
||||
}
|
||||
|
||||
it("clears the icon badge in the installed app", () => {
|
||||
const { win, calls } = fakeWindow(true);
|
||||
clearLeftoverAppBadge(win);
|
||||
expect(calls.cleared).toBe(1);
|
||||
});
|
||||
|
||||
it("leaves a browser tab alone", () => {
|
||||
const { win, calls } = fakeWindow(false);
|
||||
clearLeftoverAppBadge(win);
|
||||
expect(calls.cleared).toBe(0);
|
||||
});
|
||||
|
||||
it("does nothing where the badge API does not exist", () => {
|
||||
const { win } = fakeWindow(true, false);
|
||||
expect(() => clearLeftoverAppBadge(win)).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -19,6 +19,22 @@ export function shouldRegisterServiceWorker(
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Earlier releases set a dose count on the installed app's icon, and nothing
|
||||
* sets one now, so a leftover count would stay for good. Cleared from the page
|
||||
* of the installed app only, where the badge exists; never from the worker,
|
||||
* where the call can stall Chromium's worker thread.
|
||||
*/
|
||||
export function clearLeftoverAppBadge(win: Window): void {
|
||||
const standalone =
|
||||
win.matchMedia?.("(display-mode: standalone)").matches ?? false;
|
||||
const nav = win.navigator as Navigator & {
|
||||
clearAppBadge?: () => Promise<void>;
|
||||
};
|
||||
if (!standalone || typeof nav.clearAppBadge !== "function") return;
|
||||
nav.clearAppBadge().catch(() => undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* App-wide service-worker registration.
|
||||
*
|
||||
@@ -64,6 +80,7 @@ export function ServiceWorkerRegistrar(): null {
|
||||
.catch(() => {
|
||||
/* best effort — registration failure must never break the app */
|
||||
});
|
||||
clearLeftoverAppBadge(window);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -164,12 +164,12 @@ describe("useUrlFilterSync with FilterBar", () => {
|
||||
|
||||
it("writes a facet picked from the open menu as a new history entry", async () => {
|
||||
const { pill, history, pick } = await mount();
|
||||
const entries = window.history.length;
|
||||
await pick("Type", "Pulse");
|
||||
await expectSearch("?type=PULSE");
|
||||
// A push and no replace is the new entry. `history.length` is no proof:
|
||||
// the browser caps it, and other files in this frame push entries too.
|
||||
expect(history.pushes()).toEqual(["?type=PULSE"]);
|
||||
expect(history.replaces()).toEqual([]);
|
||||
expect(window.history.length).toBe(entries + 1);
|
||||
await expect.element(pill("Type: Pulse")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user