element-web/apps/web/test/viewmodels/room/RoomStatusBar-test.ts

167 lines
6.1 KiB
TypeScript

/*
* 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<MatrixClient>;
let vm: RoomStatusBarViewModel;
let room: MockedObject<Room>;
let roomEmitFn!: () => void;
beforeEach(() => {
client = stubClient() as MockedObject<MatrixClient>;
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 });
});
});
});