Support rendering notification badges on platforms that do their own icon overlays (#30315)

* Support rendering a seperate overlay icon on supported platforms.

* Add required globals.

* i18n-ize

* Add tests

* lint

* lint

* lint

* update copyrights

* Fix test

* lint

* Fixup

* lint

* remove unused string

* fix test
This commit is contained in:
Will Hunt
2025-07-17 13:59:17 +01:00
committed by GitHub
parent 3b0c04c2e9
commit bc1effd2a2
5 changed files with 263 additions and 68 deletions

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024-2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { mocked, type MockedObject } from "jest-mock";
import { waitFor } from "jest-matrix-react";
import { UpdateCheckStatus } from "../../../../src/BasePlatform";
import { Action } from "../../../../src/dispatcher/actions";
@@ -26,18 +27,20 @@ jest.mock("../../../../src/rageshake/rageshake", () => ({
}));
describe("ElectronPlatform", () => {
const initialiseValues = jest.fn().mockReturnValue({
protocol: "io.element.desktop",
sessionId: "session-id",
config: { _config: true },
supportedSettings: { setting1: false, setting2: true },
supportsBadgeOverlay: false,
});
const defaultUserAgent =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " +
"(KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36";
const mockElectron = {
on: jest.fn(),
send: jest.fn(),
initialise: jest.fn().mockResolvedValue({
protocol: "io.element.desktop",
sessionId: "session-id",
config: { _config: true },
supportedSettings: { setting1: false, setting2: true },
}),
initialise: initialiseValues,
setSettingValue: jest.fn().mockResolvedValue(undefined),
getSettingValue: jest.fn().mockResolvedValue(undefined),
} as unknown as MockedObject<Electron>;
@@ -405,4 +408,101 @@ describe("ElectronPlatform", () => {
state: "connected",
});
});
describe("Notification overlay badges", () => {
beforeEach(() => {
initialiseValues.mockReturnValue({
protocol: "io.element.desktop",
sessionId: "session-id",
config: { _config: true },
supportsBadgeOverlay: true,
});
});
afterEach(() => {
jest.clearAllMocks();
});
it("should send a badge with a notification count", async () => {
const platform = new ElectronPlatform();
await platform.initialised;
platform.setNotificationCount(1);
// Badges are sent asynchronously
await waitFor(() => {
const ipcMessage = mockElectron.send.mock.lastCall;
expect(ipcMessage?.[1]).toEqual(1);
expect(ipcMessage?.[2] instanceof ArrayBuffer).toEqual(true);
});
});
it("should update badge and skip duplicates", async () => {
const platform = new ElectronPlatform();
await platform.initialised;
platform.setNotificationCount(1);
platform.setNotificationCount(1); // Test that duplicates do not fire.
platform.setNotificationCount(2);
// Badges are sent asynchronously
await waitFor(() => {
const [ipcMessageA, ipcMessageB] = mockElectron.send.mock.calls.filter(
(call) => call[0] === "setBadgeCount",
);
expect(ipcMessageA?.[1]).toEqual(1);
expect(ipcMessageA?.[2] instanceof ArrayBuffer).toEqual(true);
expect(ipcMessageB?.[1]).toEqual(2);
expect(ipcMessageB?.[2] instanceof ArrayBuffer).toEqual(true);
});
});
it("should remove badge when notification count zeros", async () => {
const platform = new ElectronPlatform();
await platform.initialised;
platform.setNotificationCount(1);
platform.setNotificationCount(0); // Test that duplicates do not fire.
// Badges are sent asynchronously
await waitFor(() => {
const [ipcMessageB, ipcMessageA] = mockElectron.send.mock.calls.filter(
(call) => call[0] === "setBadgeCount",
);
expect(ipcMessageA?.[1]).toEqual(1);
expect(ipcMessageA?.[2] instanceof ArrayBuffer).toEqual(true);
expect(ipcMessageB?.[1]).toEqual(0);
expect(ipcMessageB?.[2]).toBeNull();
});
});
it("should show an error badge when the application errors", async () => {
const platform = new ElectronPlatform();
await platform.initialised;
platform.setErrorStatus(true);
// Badges are sent asynchronously
await waitFor(() => {
const ipcMessage = mockElectron.send.mock.calls.find((call) => call[0] === "setBadgeCount");
expect(ipcMessage?.[1]).toEqual(0);
expect(ipcMessage?.[2] instanceof ArrayBuffer).toEqual(true);
expect(ipcMessage?.[3]).toEqual(true);
});
});
it("should restore after error is resolved", async () => {
const platform = new ElectronPlatform();
await platform.initialised;
platform.setErrorStatus(true);
platform.setErrorStatus(false);
// Badges are sent asynchronously
await waitFor(() => {
const [ipcMessageB, ipcMessageA] = mockElectron.send.mock.calls.filter(
(call) => call[0] === "setBadgeCount",
);
expect(ipcMessageA?.[1]).toEqual(0);
expect(ipcMessageA?.[2] instanceof ArrayBuffer).toEqual(true);
expect(ipcMessageA?.[3]).toEqual(true);
expect(ipcMessageB?.[1]).toEqual(0);
expect(ipcMessageB?.[2]).toBeNull();
});
});
});
});