Merge remote-tracking branch 'origin/main' into release/v1.47.5

This commit is contained in:
Marc-André Bombeck committed 2026-11-12 15:46:29 +01:00
commit 6994cb5ce8
6 files changed
+63 -17

No files matched your search

+2 -1
View File
@@ -118,7 +118,8 @@ test.describe("login page", () => {
await page.getByTestId("login-password-submit").click();
expect((await answer).status()).toBe(401);
await expect(page.getByText("Invalid credentials")).toBeVisible();
// The page says it in its own words, not the server's.
await expect(page.getByText("Login failed")).toBeVisible();
await expect(page).toHaveURL(/\/auth\/login/);
expect(
(await context.cookies()).some((c) => c.name === SESSION_COOKIE),
-11
View File
@@ -68,7 +68,6 @@ self.addEventListener("activate", (event) => {
self.registration.navigationPreload
? self.registration.navigationPreload.enable()
: Promise.resolve(),
clearLeftoverAppBadge(),
]).then(() => self.clients.claim()),
);
});
@@ -353,16 +352,6 @@ async function trimCache(cacheName, maxEntries) {
}
}
// Earlier releases set a dose count on the app icon; nothing sends one now,
// so clear what they left. Never fails the activation.
async function clearLeftoverAppBadge() {
try {
await self.navigator?.clearAppBadge?.();
} catch {
// Optional.
}
}
self.addEventListener("push", (event) => {
if (!event.data) return;
+4 -2
View File
@@ -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();
});