/* * Copyright (c) 2025 Element Creations Ltd. * * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial * Please see LICENSE files in the repository root for full details. */ import { SyncState, MatrixError, ClientEvent, type MatrixClient, type Room, type MatrixEvent, EventStatus, } from "matrix-js-sdk/src/matrix"; import { RoomStatusBarState } from "@element-hq/web-shared-components"; import { type MockedObject } from "jest-mock"; import { mkEvent, mkRoom, stubClient } from "../../test-utils"; import { RoomStatusBarViewModel } from "../../../src/viewmodels/room/RoomStatusBar"; import { LocalRoom, LocalRoomState } from "../../../src/models/LocalRoom"; const userId = "@example:example.org"; function mkEventWithError(error: MatrixError): MatrixEvent { const event = mkEvent({ event: true, user: userId, type: "org.example.test", content: {}, status: EventStatus.NOT_SENT, }); event.error = error; return event; } describe("RoomStatusBarViewModel", () => { let client: MockedObject; let vm: RoomStatusBarViewModel; let room: MockedObject; let roomEmitFn!: () => void; beforeEach(() => { client = stubClient() as MockedObject; room = mkRoom(client, "!example"); jest.spyOn(room, "on").mockImplementationOnce((_event, fn) => { roomEmitFn = fn as any; return room; }); vm = new RoomStatusBarViewModel({ room, }); }); afterEach(() => { jest.restoreAllMocks(); }); it("should not be visible by default", () => { expect(vm.getSnapshot()).toEqual({ state: null }); }); it("should resolve state to ConnectionLost on failed sync", () => { client.getSyncState.mockReturnValue(SyncState.Error); client.emit(ClientEvent.Sync, SyncState.Error, null); expect(vm.getSnapshot()).toEqual({ state: RoomStatusBarState.ConnectionLost }); }); // Because we expect LoggedInView to pop a toast it("should resolve state to nothing if sync error is M_RESOURCE_LIMIT_EXCEEDED", () => { client.getSyncState.mockReturnValue(SyncState.Error); client.getSyncStateData.mockReturnValue({ error: new MatrixError({ errcode: "M_RESOURCE_LIMIT_EXCEEDED" }) }); client.emit(ClientEvent.Sync, SyncState.Error, null); expect(vm.getSnapshot()).toEqual({ state: null }); }); it("should resolve state to NeedsConsent if a pending event has a M_CONSENT_NOT_GIVEN error", () => { room.getPendingEvents.mockReturnValue([ mkEventWithError(new MatrixError({ errcode: "M_CONSENT_NOT_GIVEN", consent_uri: "https://example.org" })), ]); roomEmitFn(); expect(vm.getSnapshot()).toEqual({ state: RoomStatusBarState.NeedsConsent, consentUri: "https://example.org", }); }); it("should resolve state to UnsentMessages once onTermsAndConditionsClicked is called", () => { room.getPendingEvents.mockReturnValue([mkEventWithError(new MatrixError({ errcode: "M_CONSENT_NOT_GIVEN" }))]); roomEmitFn(); expect(vm.getSnapshot()).toEqual({ state: RoomStatusBarState.NeedsConsent, }); vm.onTermsAndConditionsClicked(); expect(vm.getSnapshot()).toEqual({ state: RoomStatusBarState.UnsentMessages, isResending: false, }); }); it("should resolve state to ResourceLimited if a pending event has a M_RESOURCE_LIMIT_EXCEEDED error", () => { room.getPendingEvents.mockReturnValue([ mkEventWithError( new MatrixError({ errcode: "M_RESOURCE_LIMIT_EXCEEDED", limit_type: "hs_disabled", admin_contact: "https://example.org", }), ), ]); roomEmitFn(); expect(vm.getSnapshot()).toEqual({ state: RoomStatusBarState.ResourceLimited, adminContactHref: "https://example.org", resourceLimit: "hs_disabled", }); }); it("should resolve state to UnsentMessages if there are any other events", () => { room.getPendingEvents.mockReturnValue([mkEventWithError(new MatrixError({ errcode: "M_UNKNOWN" }))]); roomEmitFn(); expect(vm.getSnapshot()).toEqual({ state: RoomStatusBarState.UnsentMessages, isResending: false, }); }); it("should resolve state to isResending=true once onResendAllClick is called", async () => { room.getPendingEvents.mockReturnValue([mkEventWithError(new MatrixError({ errcode: "M_UNKNOWN" }))]); roomEmitFn(); expect(vm.getSnapshot()).toEqual({ state: RoomStatusBarState.UnsentMessages, isResending: false, }); const promise = vm.onResendAllClick(); expect(vm.getSnapshot()).toEqual({ state: RoomStatusBarState.UnsentMessages, isResending: true, }); room.getPendingEvents.mockReturnValue([]); await promise; expect(client.resendEvent).toHaveBeenCalledTimes(1); expect(vm.getSnapshot()).toEqual({ state: null, }); }); describe("Local rooms", () => { it("should resolve state to LocalRoomFailed if room fails to be created", () => { const localRoom = new LocalRoom("!example", client, userId); localRoom.state = LocalRoomState.ERROR; vm = new RoomStatusBarViewModel({ room: localRoom, }); expect(vm.getSnapshot()).toEqual({ state: RoomStatusBarState.LocalRoomFailed }); }); it("should resolve state to nothing for any other state for localroom", () => { const localRoom = new LocalRoom("!example", client, userId); localRoom.state = LocalRoomState.NEW; vm = new RoomStatusBarViewModel({ room: localRoom, }); expect(vm.getSnapshot()).toEqual({ state: null }); }); }); });