662 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			662 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			TypeScript
		
	
	
/*
 | 
						|
Copyright 2022 The Matrix.org Foundation C.I.C.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
import { mocked, MockedObject } from "jest-mock";
 | 
						|
import {
 | 
						|
    ClientEvent,
 | 
						|
    MatrixClient,
 | 
						|
    Room,
 | 
						|
    RoomEvent,
 | 
						|
    EventType,
 | 
						|
    MsgType,
 | 
						|
    IContent,
 | 
						|
    MatrixEvent,
 | 
						|
    SyncState,
 | 
						|
} from "matrix-js-sdk/src/matrix";
 | 
						|
import { waitFor } from "@testing-library/react";
 | 
						|
import { CallMembership, MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
 | 
						|
 | 
						|
import BasePlatform from "../src/BasePlatform";
 | 
						|
import Notifier from "../src/Notifier";
 | 
						|
import SettingsStore from "../src/settings/SettingsStore";
 | 
						|
import ToastStore from "../src/stores/ToastStore";
 | 
						|
import {
 | 
						|
    createLocalNotificationSettingsIfNeeded,
 | 
						|
    getLocalNotificationAccountDataEventType,
 | 
						|
} from "../src/utils/notifications";
 | 
						|
import {
 | 
						|
    getMockClientWithEventEmitter,
 | 
						|
    mkEvent,
 | 
						|
    mkMessage,
 | 
						|
    mockClientMethodsUser,
 | 
						|
    mockPlatformPeg,
 | 
						|
} from "./test-utils";
 | 
						|
import { getIncomingCallToastKey, IncomingCallToast } from "../src/toasts/IncomingCallToast";
 | 
						|
import { SdkContextClass } from "../src/contexts/SDKContext";
 | 
						|
import UserActivity from "../src/UserActivity";
 | 
						|
import Modal from "../src/Modal";
 | 
						|
import { mkThread } from "./test-utils/threads";
 | 
						|
import dis from "../src/dispatcher/dispatcher";
 | 
						|
import { ThreadPayload } from "../src/dispatcher/payloads/ThreadPayload";
 | 
						|
import { Action } from "../src/dispatcher/actions";
 | 
						|
import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoState } from "../src/voice-broadcast";
 | 
						|
import { mkVoiceBroadcastInfoStateEvent } from "./voice-broadcast/utils/test-utils";
 | 
						|
import { addReplyToMessageContent } from "../src/utils/Reply";
 | 
						|
 | 
						|
jest.mock("../src/utils/notifications", () => ({
 | 
						|
    // @ts-ignore
 | 
						|
    ...jest.requireActual("../src/utils/notifications"),
 | 
						|
    createLocalNotificationSettingsIfNeeded: jest.fn(),
 | 
						|
}));
 | 
						|
 | 
						|
jest.mock("../src/audio/compat", () => ({
 | 
						|
    ...jest.requireActual("../src/audio/compat"),
 | 
						|
    createAudioContext: jest.fn(),
 | 
						|
}));
 | 
						|
 | 
						|
describe("Notifier", () => {
 | 
						|
    const roomId = "!room1:server";
 | 
						|
    const testEvent = mkEvent({
 | 
						|
        event: true,
 | 
						|
        type: "m.room.message",
 | 
						|
        user: "@user1:server",
 | 
						|
        room: roomId,
 | 
						|
        content: {},
 | 
						|
    });
 | 
						|
 | 
						|
    let MockPlatform: MockedObject<BasePlatform>;
 | 
						|
    let mockClient: MockedObject<MatrixClient>;
 | 
						|
    let testRoom: Room;
 | 
						|
    let accountDataEventKey: string;
 | 
						|
    let accountDataStore: Record<string, MatrixEvent | undefined> = {};
 | 
						|
 | 
						|
    let mockSettings: Record<string, boolean> = {};
 | 
						|
 | 
						|
    const userId = "@bob:example.org";
 | 
						|
 | 
						|
    const emitLiveEvent = (event: MatrixEvent): void => {
 | 
						|
        mockClient!.emit(RoomEvent.Timeline, event, testRoom, false, false, {
 | 
						|
            liveEvent: true,
 | 
						|
            timeline: testRoom.getLiveTimeline(),
 | 
						|
        });
 | 
						|
    };
 | 
						|
 | 
						|
    const mkAudioEvent = (broadcastChunkContent?: object): MatrixEvent => {
 | 
						|
        const chunkContent = broadcastChunkContent ? { [VoiceBroadcastChunkEventType]: broadcastChunkContent } : {};
 | 
						|
 | 
						|
        return mkEvent({
 | 
						|
            event: true,
 | 
						|
            type: EventType.RoomMessage,
 | 
						|
            user: "@user:example.com",
 | 
						|
            room: "!room:example.com",
 | 
						|
            content: {
 | 
						|
                ...chunkContent,
 | 
						|
                msgtype: MsgType.Audio,
 | 
						|
                body: "test audio message",
 | 
						|
            },
 | 
						|
        });
 | 
						|
    };
 | 
						|
 | 
						|
    const mockAudioBufferSourceNode = {
 | 
						|
        addEventListener: jest.fn(),
 | 
						|
        connect: jest.fn(),
 | 
						|
        start: jest.fn(),
 | 
						|
    };
 | 
						|
    const mockAudioContext = {
 | 
						|
        decodeAudioData: jest.fn(),
 | 
						|
        suspend: jest.fn(),
 | 
						|
        resume: jest.fn(),
 | 
						|
        createBufferSource: jest.fn().mockReturnValue(mockAudioBufferSourceNode),
 | 
						|
        currentTime: 1337,
 | 
						|
    };
 | 
						|
 | 
						|
    beforeEach(() => {
 | 
						|
        accountDataStore = {};
 | 
						|
        mockClient = getMockClientWithEventEmitter({
 | 
						|
            ...mockClientMethodsUser(userId),
 | 
						|
            isGuest: jest.fn().mockReturnValue(false),
 | 
						|
            getAccountData: jest.fn().mockImplementation((eventType) => accountDataStore[eventType]),
 | 
						|
            setAccountData: jest.fn().mockImplementation((eventType, content) => {
 | 
						|
                accountDataStore[eventType] = content
 | 
						|
                    ? new MatrixEvent({
 | 
						|
                          type: eventType,
 | 
						|
                          content,
 | 
						|
                      })
 | 
						|
                    : undefined;
 | 
						|
            }),
 | 
						|
            decryptEventIfNeeded: jest.fn(),
 | 
						|
            getRoom: jest.fn(),
 | 
						|
            getPushActionsForEvent: jest.fn(),
 | 
						|
            supportsThreads: jest.fn().mockReturnValue(false),
 | 
						|
            matrixRTC: {
 | 
						|
                on: jest.fn(),
 | 
						|
                off: jest.fn(),
 | 
						|
                getRoomSession: jest.fn(),
 | 
						|
            },
 | 
						|
        });
 | 
						|
 | 
						|
        mockClient.pushRules = {
 | 
						|
            global: {},
 | 
						|
        };
 | 
						|
        accountDataEventKey = getLocalNotificationAccountDataEventType(mockClient.deviceId!);
 | 
						|
 | 
						|
        testRoom = new Room(roomId, mockClient, mockClient.getSafeUserId());
 | 
						|
 | 
						|
        MockPlatform = mockPlatformPeg({
 | 
						|
            supportsNotifications: jest.fn().mockReturnValue(true),
 | 
						|
            maySendNotifications: jest.fn().mockReturnValue(true),
 | 
						|
            displayNotification: jest.fn(),
 | 
						|
            loudNotification: jest.fn(),
 | 
						|
        });
 | 
						|
 | 
						|
        Notifier.isBodyEnabled = jest.fn().mockReturnValue(true);
 | 
						|
 | 
						|
        mockClient.getRoom.mockImplementation((id: string | undefined): Room | null => {
 | 
						|
            if (id === roomId) return testRoom;
 | 
						|
            if (id) return new Room(id, mockClient, mockClient.getSafeUserId());
 | 
						|
            return null;
 | 
						|
        });
 | 
						|
 | 
						|
        // @ts-ignore
 | 
						|
        Notifier.backgroundAudio.audioContext = mockAudioContext;
 | 
						|
    });
 | 
						|
 | 
						|
    describe("triggering notification from events", () => {
 | 
						|
        let hasStartedNotiferBefore = false;
 | 
						|
 | 
						|
        const event = new MatrixEvent({
 | 
						|
            sender: "@alice:server.org",
 | 
						|
            type: "m.room.message",
 | 
						|
            room_id: "!room:server.org",
 | 
						|
            content: {
 | 
						|
                body: "hey",
 | 
						|
            },
 | 
						|
        });
 | 
						|
 | 
						|
        beforeEach(() => {
 | 
						|
            // notifier defines some listener functions in start
 | 
						|
            // and references them in stop
 | 
						|
            // so blows up if stopped before it was started
 | 
						|
            if (hasStartedNotiferBefore) {
 | 
						|
                Notifier.stop();
 | 
						|
            }
 | 
						|
            Notifier.start();
 | 
						|
            hasStartedNotiferBefore = true;
 | 
						|
            mockClient.getRoom.mockReturnValue(testRoom);
 | 
						|
            mockClient.getPushActionsForEvent.mockReturnValue({
 | 
						|
                notify: true,
 | 
						|
                tweaks: {
 | 
						|
                    sound: true,
 | 
						|
                },
 | 
						|
            });
 | 
						|
 | 
						|
            mockSettings = {
 | 
						|
                notificationsEnabled: true,
 | 
						|
                audioNotificationsEnabled: true,
 | 
						|
            };
 | 
						|
 | 
						|
            // enable notifications by default
 | 
						|
            jest.spyOn(SettingsStore, "getValue")
 | 
						|
                .mockReset()
 | 
						|
                .mockImplementation((settingName) => mockSettings[settingName] ?? false);
 | 
						|
        });
 | 
						|
 | 
						|
        afterAll(() => {
 | 
						|
            Notifier.stop();
 | 
						|
        });
 | 
						|
 | 
						|
        it("does not create notifications before syncing has started", () => {
 | 
						|
            emitLiveEvent(event);
 | 
						|
 | 
						|
            expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
 | 
						|
            expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
 | 
						|
        it("does not create notifications for own event", () => {
 | 
						|
            const ownEvent = new MatrixEvent({ sender: userId });
 | 
						|
 | 
						|
            mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
 | 
						|
            emitLiveEvent(ownEvent);
 | 
						|
 | 
						|
            expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
 | 
						|
            expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
 | 
						|
        it("does not create notifications for non-live events (scrollback)", () => {
 | 
						|
            mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
 | 
						|
            mockClient!.emit(RoomEvent.Timeline, event, testRoom, false, false, {
 | 
						|
                liveEvent: false,
 | 
						|
                timeline: testRoom.getLiveTimeline(),
 | 
						|
            });
 | 
						|
 | 
						|
            expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
 | 
						|
            expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
 | 
						|
        it("does not create notifications for rooms which cannot be obtained via client.getRoom", () => {
 | 
						|
            mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
 | 
						|
            mockClient.getRoom.mockReturnValue(null);
 | 
						|
            mockClient!.emit(RoomEvent.Timeline, event, testRoom, false, false, {
 | 
						|
                liveEvent: true,
 | 
						|
                timeline: testRoom.getLiveTimeline(),
 | 
						|
            });
 | 
						|
 | 
						|
            expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
 | 
						|
            expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
 | 
						|
        it("does not create notifications when event does not have notify push action", () => {
 | 
						|
            mockClient.getPushActionsForEvent.mockReturnValue({
 | 
						|
                notify: false,
 | 
						|
                tweaks: {
 | 
						|
                    sound: true,
 | 
						|
                },
 | 
						|
            });
 | 
						|
 | 
						|
            mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
 | 
						|
            emitLiveEvent(event);
 | 
						|
 | 
						|
            expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
 | 
						|
            expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
 | 
						|
        it("creates desktop notification when enabled", () => {
 | 
						|
            mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
 | 
						|
            emitLiveEvent(event);
 | 
						|
 | 
						|
            expect(MockPlatform.displayNotification).toHaveBeenCalledWith(testRoom.name, "hey", null, testRoom, event);
 | 
						|
        });
 | 
						|
 | 
						|
        it("creates a loud notification when enabled", () => {
 | 
						|
            mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
 | 
						|
            emitLiveEvent(event);
 | 
						|
 | 
						|
            expect(MockPlatform.loudNotification).toHaveBeenCalledWith(event, testRoom);
 | 
						|
        });
 | 
						|
 | 
						|
        it("does not create loud notification when event does not have sound tweak in push actions", () => {
 | 
						|
            mockClient.getPushActionsForEvent.mockReturnValue({
 | 
						|
                notify: true,
 | 
						|
                tweaks: {
 | 
						|
                    sound: false,
 | 
						|
                },
 | 
						|
            });
 | 
						|
 | 
						|
            mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
 | 
						|
            emitLiveEvent(event);
 | 
						|
 | 
						|
            // desktop notification created
 | 
						|
            expect(MockPlatform.displayNotification).toHaveBeenCalled();
 | 
						|
            // without noisy
 | 
						|
            expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("displayPopupNotification", () => {
 | 
						|
        const testCases: { event: IContent | undefined; count: number }[] = [
 | 
						|
            { event: { is_silenced: true }, count: 0 },
 | 
						|
            { event: { is_silenced: false }, count: 1 },
 | 
						|
            { event: undefined, count: 1 },
 | 
						|
        ];
 | 
						|
        it.each(testCases)("does not dispatch when notifications are silenced", ({ event, count }) => {
 | 
						|
            mockClient.setAccountData(accountDataEventKey, event!);
 | 
						|
            Notifier.displayPopupNotification(testEvent, testRoom);
 | 
						|
            expect(MockPlatform.displayNotification).toHaveBeenCalledTimes(count);
 | 
						|
        });
 | 
						|
 | 
						|
        it("should display a notification for a voice message", () => {
 | 
						|
            const audioEvent = mkAudioEvent();
 | 
						|
            Notifier.displayPopupNotification(audioEvent, testRoom);
 | 
						|
            expect(MockPlatform.displayNotification).toHaveBeenCalledWith(
 | 
						|
                "@user:example.com (!room1:server)",
 | 
						|
                "@user:example.com: test audio message",
 | 
						|
                "data:image/png;base64,00",
 | 
						|
                testRoom,
 | 
						|
                audioEvent,
 | 
						|
            );
 | 
						|
        });
 | 
						|
 | 
						|
        it("should display the expected notification for a broadcast chunk with sequence = 1", () => {
 | 
						|
            const audioEvent = mkAudioEvent({ sequence: 1 });
 | 
						|
            Notifier.displayPopupNotification(audioEvent, testRoom);
 | 
						|
            expect(MockPlatform.displayNotification).toHaveBeenCalledWith(
 | 
						|
                "@user:example.com (!room1:server)",
 | 
						|
                "@user:example.com started a voice broadcast",
 | 
						|
                "data:image/png;base64,00",
 | 
						|
                testRoom,
 | 
						|
                audioEvent,
 | 
						|
            );
 | 
						|
        });
 | 
						|
 | 
						|
        it("should display the expected notification for a broadcast chunk with sequence = 2", () => {
 | 
						|
            const audioEvent = mkAudioEvent({ sequence: 2 });
 | 
						|
            Notifier.displayPopupNotification(audioEvent, testRoom);
 | 
						|
            expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
 | 
						|
        it("should strip reply fallback", () => {
 | 
						|
            const event = mkMessage({
 | 
						|
                msg: "Test",
 | 
						|
                event: true,
 | 
						|
                user: mockClient.getSafeUserId(),
 | 
						|
                room: testRoom.roomId,
 | 
						|
            });
 | 
						|
            const reply = mkMessage({
 | 
						|
                msg: "This was a triumph",
 | 
						|
                event: true,
 | 
						|
                user: mockClient.getSafeUserId(),
 | 
						|
                room: testRoom.roomId,
 | 
						|
            });
 | 
						|
            addReplyToMessageContent(reply.getContent(), event, { includeLegacyFallback: true });
 | 
						|
            Notifier.displayPopupNotification(reply, testRoom);
 | 
						|
            expect(MockPlatform.displayNotification).toHaveBeenCalledWith(
 | 
						|
                "@bob:example.org (!room1:server)",
 | 
						|
                "This was a triumph",
 | 
						|
                expect.any(String),
 | 
						|
                testRoom,
 | 
						|
                reply,
 | 
						|
            );
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("getSoundForRoom", () => {
 | 
						|
        it("should not explode if given invalid url", () => {
 | 
						|
            jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
 | 
						|
                return { url: { content_uri: "foobar" } };
 | 
						|
            });
 | 
						|
            expect(Notifier.getSoundForRoom("!roomId:server")).toBeNull();
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("_playAudioNotification", () => {
 | 
						|
        const testCases: { event: IContent | undefined; count: number }[] = [
 | 
						|
            { event: { is_silenced: true }, count: 0 },
 | 
						|
            { event: { is_silenced: false }, count: 1 },
 | 
						|
            { event: undefined, count: 1 },
 | 
						|
        ];
 | 
						|
        it.each(testCases)("does not dispatch when notifications are silenced", ({ event, count }) => {
 | 
						|
            // It's not ideal to only look at whether this function has been called
 | 
						|
            // but avoids starting to look into DOM stuff
 | 
						|
            Notifier.getSoundForRoom = jest.fn();
 | 
						|
 | 
						|
            mockClient.setAccountData(accountDataEventKey, event!);
 | 
						|
            Notifier.playAudioNotification(testEvent, testRoom);
 | 
						|
            expect(Notifier.getSoundForRoom).toHaveBeenCalledTimes(count);
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("group call notifications", () => {
 | 
						|
        beforeEach(() => {
 | 
						|
            jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
 | 
						|
            jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast");
 | 
						|
 | 
						|
            mockClient.getPushActionsForEvent.mockReturnValue({
 | 
						|
                notify: true,
 | 
						|
                tweaks: {},
 | 
						|
            });
 | 
						|
            Notifier.start();
 | 
						|
            Notifier.onSyncStateChange(SyncState.Syncing, null);
 | 
						|
        });
 | 
						|
 | 
						|
        afterEach(() => {
 | 
						|
            jest.resetAllMocks();
 | 
						|
        });
 | 
						|
 | 
						|
        const emitCallNotifyEvent = (type?: string, roomMention = true) => {
 | 
						|
            const callEvent = mkEvent({
 | 
						|
                type: type ?? EventType.CallNotify,
 | 
						|
                user: "@alice:foo",
 | 
						|
                room: roomId,
 | 
						|
                content: {
 | 
						|
                    "application": "m.call",
 | 
						|
                    "m.mentions": { user_ids: [], room: roomMention },
 | 
						|
                    "notify_type": "ring",
 | 
						|
                    "call_id": "abc123",
 | 
						|
                },
 | 
						|
                event: true,
 | 
						|
            });
 | 
						|
            emitLiveEvent(callEvent);
 | 
						|
            return callEvent;
 | 
						|
        };
 | 
						|
 | 
						|
        const setGroupCallsEnabled = (val: boolean) => {
 | 
						|
            jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
 | 
						|
                if (name === "feature_group_calls") return val;
 | 
						|
            });
 | 
						|
        };
 | 
						|
 | 
						|
        it("should show toast when group calls are supported", () => {
 | 
						|
            setGroupCallsEnabled(true);
 | 
						|
 | 
						|
            const notifyEvent = emitCallNotifyEvent();
 | 
						|
 | 
						|
            expect(ToastStore.sharedInstance().addOrReplaceToast).toHaveBeenCalledWith(
 | 
						|
                expect.objectContaining({
 | 
						|
                    key: getIncomingCallToastKey(notifyEvent.getContent().call_id ?? "", roomId),
 | 
						|
                    priority: 100,
 | 
						|
                    component: IncomingCallToast,
 | 
						|
                    bodyClassName: "mx_IncomingCallToast",
 | 
						|
                    props: { notifyEvent },
 | 
						|
                }),
 | 
						|
            );
 | 
						|
        });
 | 
						|
 | 
						|
        it("should not show toast when group calls are not supported", () => {
 | 
						|
            setGroupCallsEnabled(false);
 | 
						|
 | 
						|
            emitCallNotifyEvent();
 | 
						|
 | 
						|
            expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
 | 
						|
        it("should not show toast when group call is already connected", () => {
 | 
						|
            setGroupCallsEnabled(true);
 | 
						|
            const spyCallMemberships = jest.spyOn(MatrixRTCSession, "callMembershipsForRoom").mockReturnValue([
 | 
						|
                new CallMembership(
 | 
						|
                    mkEvent({
 | 
						|
                        event: true,
 | 
						|
                        room: testRoom.roomId,
 | 
						|
                        user: userId,
 | 
						|
                        type: EventType.GroupCallMemberPrefix,
 | 
						|
                        content: {},
 | 
						|
                    }),
 | 
						|
                    {
 | 
						|
                        call_id: "123",
 | 
						|
                        application: "m.call",
 | 
						|
                        focus_active: { type: "livekit" },
 | 
						|
                        foci_preferred: [],
 | 
						|
                        device_id: "DEVICE",
 | 
						|
                    },
 | 
						|
                ),
 | 
						|
            ]);
 | 
						|
 | 
						|
            const roomSession = MatrixRTCSession.roomSessionForRoom(mockClient, testRoom);
 | 
						|
 | 
						|
            mockClient.matrixRTC.getRoomSession.mockReturnValue(roomSession);
 | 
						|
            emitCallNotifyEvent();
 | 
						|
            expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
 | 
						|
            spyCallMemberships.mockRestore();
 | 
						|
        });
 | 
						|
 | 
						|
        it("should not show toast when calling with non-group call event", () => {
 | 
						|
            setGroupCallsEnabled(true);
 | 
						|
 | 
						|
            emitCallNotifyEvent("event_type");
 | 
						|
 | 
						|
            expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("local notification settings", () => {
 | 
						|
        const createLocalNotificationSettingsIfNeededMock = mocked(createLocalNotificationSettingsIfNeeded);
 | 
						|
        let hasStartedNotiferBefore = false;
 | 
						|
        beforeEach(() => {
 | 
						|
            // notifier defines some listener functions in start
 | 
						|
            // and references them in stop
 | 
						|
            // so blows up if stopped before it was started
 | 
						|
            if (hasStartedNotiferBefore) {
 | 
						|
                Notifier.stop();
 | 
						|
            }
 | 
						|
            Notifier.start();
 | 
						|
            hasStartedNotiferBefore = true;
 | 
						|
            createLocalNotificationSettingsIfNeededMock.mockClear();
 | 
						|
        });
 | 
						|
 | 
						|
        afterAll(() => {
 | 
						|
            Notifier.stop();
 | 
						|
        });
 | 
						|
 | 
						|
        it("does not create local notifications event after a sync error", () => {
 | 
						|
            mockClient.emit(ClientEvent.Sync, SyncState.Error, SyncState.Syncing);
 | 
						|
            expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
 | 
						|
        it("does not create local notifications event after sync stops", () => {
 | 
						|
            mockClient.emit(ClientEvent.Sync, SyncState.Stopped, SyncState.Syncing);
 | 
						|
            expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
 | 
						|
        it("does not create local notifications event after a cached sync", () => {
 | 
						|
            mockClient.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Syncing, {
 | 
						|
                fromCache: true,
 | 
						|
            });
 | 
						|
            expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
 | 
						|
        it("creates local notifications event after a non-cached sync", () => {
 | 
						|
            mockClient.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Syncing, {});
 | 
						|
            expect(createLocalNotificationSettingsIfNeededMock).toHaveBeenCalled();
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("evaluateEvent", () => {
 | 
						|
        beforeEach(() => {
 | 
						|
            jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(testRoom.roomId);
 | 
						|
 | 
						|
            jest.spyOn(UserActivity.sharedInstance(), "userActiveRecently").mockReturnValue(true);
 | 
						|
 | 
						|
            jest.spyOn(Modal, "hasDialogs").mockReturnValue(false);
 | 
						|
 | 
						|
            jest.spyOn(Notifier, "displayPopupNotification").mockReset();
 | 
						|
            jest.spyOn(Notifier, "isEnabled").mockReturnValue(true);
 | 
						|
 | 
						|
            mockClient.getPushActionsForEvent.mockReturnValue({
 | 
						|
                notify: true,
 | 
						|
                tweaks: {
 | 
						|
                    sound: true,
 | 
						|
                },
 | 
						|
            });
 | 
						|
        });
 | 
						|
 | 
						|
        it("should show a pop-up", () => {
 | 
						|
            expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(0);
 | 
						|
            Notifier.evaluateEvent(testEvent);
 | 
						|
            expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(0);
 | 
						|
 | 
						|
            const eventFromOtherRoom = mkEvent({
 | 
						|
                event: true,
 | 
						|
                type: "m.room.message",
 | 
						|
                user: "@user1:server",
 | 
						|
                room: "!otherroom:example.org",
 | 
						|
                content: {},
 | 
						|
            });
 | 
						|
 | 
						|
            Notifier.evaluateEvent(eventFromOtherRoom);
 | 
						|
            expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(1);
 | 
						|
        });
 | 
						|
 | 
						|
        it("should a pop-up for thread event", async () => {
 | 
						|
            const { events, rootEvent } = mkThread({
 | 
						|
                room: testRoom,
 | 
						|
                client: mockClient,
 | 
						|
                authorId: "@bob:example.org",
 | 
						|
                participantUserIds: ["@bob:example.org"],
 | 
						|
            });
 | 
						|
 | 
						|
            expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(0);
 | 
						|
 | 
						|
            Notifier.evaluateEvent(rootEvent);
 | 
						|
            expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(0);
 | 
						|
 | 
						|
            Notifier.evaluateEvent(events[1]);
 | 
						|
            expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(1);
 | 
						|
 | 
						|
            dis.dispatch<ThreadPayload>({
 | 
						|
                action: Action.ViewThread,
 | 
						|
                thread_id: rootEvent.getId()!,
 | 
						|
            });
 | 
						|
 | 
						|
            await waitFor(() => expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBe(rootEvent.getId()));
 | 
						|
 | 
						|
            Notifier.evaluateEvent(events[1]);
 | 
						|
            expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(1);
 | 
						|
        });
 | 
						|
 | 
						|
        it("should show a pop-up for an audio message", () => {
 | 
						|
            Notifier.evaluateEvent(mkAudioEvent());
 | 
						|
            expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(1);
 | 
						|
        });
 | 
						|
 | 
						|
        it("should not show a notification for broadcast info events in any case", () => {
 | 
						|
            // Let client decide to show a notification
 | 
						|
            mockClient.getPushActionsForEvent.mockReturnValue({
 | 
						|
                notify: true,
 | 
						|
                tweaks: {},
 | 
						|
            });
 | 
						|
 | 
						|
            const broadcastStartedEvent = mkVoiceBroadcastInfoStateEvent(
 | 
						|
                "!other:example.org",
 | 
						|
                VoiceBroadcastInfoState.Started,
 | 
						|
                "@user:example.com",
 | 
						|
                "ABC123",
 | 
						|
            );
 | 
						|
 | 
						|
            Notifier.evaluateEvent(broadcastStartedEvent);
 | 
						|
            expect(Notifier.displayPopupNotification).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("setPromptHidden", () => {
 | 
						|
        it("should persist by default", () => {
 | 
						|
            Notifier.setPromptHidden(true);
 | 
						|
            expect(localStorage.getItem("notifications_hidden")).toBeTruthy();
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe("onEvent", () => {
 | 
						|
        it("should not evaluate events from the thread list fake timeline sets", async () => {
 | 
						|
            mockClient.supportsThreads.mockReturnValue(true);
 | 
						|
 | 
						|
            const fn = jest.spyOn(Notifier, "evaluateEvent");
 | 
						|
 | 
						|
            await testRoom.createThreadsTimelineSets();
 | 
						|
            testRoom.threadsTimelineSets[0]!.addEventToTimeline(
 | 
						|
                mkEvent({
 | 
						|
                    event: true,
 | 
						|
                    type: "m.room.message",
 | 
						|
                    user: "@user1:server",
 | 
						|
                    room: roomId,
 | 
						|
                    content: { body: "this is a thread root" },
 | 
						|
                }),
 | 
						|
                testRoom.threadsTimelineSets[0]!.getLiveTimeline(),
 | 
						|
                false,
 | 
						|
                false,
 | 
						|
            );
 | 
						|
 | 
						|
            expect(fn).not.toHaveBeenCalled();
 | 
						|
        });
 | 
						|
    });
 | 
						|
});
 |