Move more tests from Jest to Vitest (#34181)
* Move more tests from Jest to Vitest * Iterate
This commit is contained in:
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 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 { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import RoomListActions from "../../../src/actions/RoomListActions";
|
||||
import { DefaultTagID } from "../../../src/stores/room-list-v3/skip-list/tag";
|
||||
import Modal from "../../../src/Modal";
|
||||
import * as Rooms from "../../../src/Rooms";
|
||||
import { createTestClient, flushPromises, mkRoom } from "../../test-utils";
|
||||
|
||||
jest.mock("../../../src/Modal");
|
||||
jest.mock("../../../src/Rooms");
|
||||
|
||||
describe("RoomListActions", () => {
|
||||
const ROOM_ID = "!room:example.org";
|
||||
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
const dispatch = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
client = createTestClient();
|
||||
room = mkRoom(client, ROOM_ID);
|
||||
mocked(Rooms.guessAndSetDMRoom).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("tagRoom", () => {
|
||||
/**
|
||||
* Invoke the async payload returned by tagRoom and wait for all promises to settle.
|
||||
*/
|
||||
async function invokeTagRoom(
|
||||
oldTag: Parameters<typeof RoomListActions.tagRoom>[2],
|
||||
newTag: Parameters<typeof RoomListActions.tagRoom>[3],
|
||||
): Promise<void> {
|
||||
const payload = RoomListActions.tagRoom(client, room, oldTag, newTag);
|
||||
|
||||
// Execute the async function embedded in the payload.
|
||||
payload.fn(dispatch);
|
||||
|
||||
// Flush all microtasks / pending promises.
|
||||
await flushPromises();
|
||||
}
|
||||
|
||||
it("dispatches a pending action immediately with the optimistic update data", () => {
|
||||
const payload = RoomListActions.tagRoom(client, room, DefaultTagID.Favourite, DefaultTagID.LowPriority);
|
||||
|
||||
payload.fn(dispatch);
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "RoomListActions.tagRoom.pending",
|
||||
request: { room, oldTag: DefaultTagID.Favourite, newTag: DefaultTagID.LowPriority },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe("DM tag handling", () => {
|
||||
it.each([
|
||||
[undefined, DefaultTagID.DM],
|
||||
[DefaultTagID.DM, undefined],
|
||||
])(
|
||||
"treats oldTag=%s and newTag=%s as a DM tag change and does not call setRoomTag or deleteRoomTag",
|
||||
async (oldTag, newTag) => {
|
||||
await invokeTagRoom(oldTag, newTag as unknown as null);
|
||||
|
||||
expect(Rooms.guessAndSetDMRoom).toHaveBeenCalledWith(room, newTag === DefaultTagID.DM);
|
||||
expect(client.deleteRoomTag).not.toHaveBeenCalled();
|
||||
expect(client.setRoomTag).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it("opens an ErrorDialog and swallows the error if guessAndSetDMRoom rejects", async () => {
|
||||
const error = new Error("DM tag error");
|
||||
mocked(Rooms.guessAndSetDMRoom).mockRejectedValue(error);
|
||||
|
||||
await invokeTagRoom(undefined, DefaultTagID.DM);
|
||||
|
||||
expect(Modal.createDialog).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({ description: error.message }),
|
||||
);
|
||||
// Error is swallowed — success is still dispatched.
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ action: "RoomListActions.tagRoom.success" }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("regular tag changes (non-DM)", () => {
|
||||
it("deletes the old tag and adds the new tag when moving between two non-DM tags", async () => {
|
||||
await invokeTagRoom(DefaultTagID.Favourite, DefaultTagID.LowPriority);
|
||||
|
||||
expect(client.deleteRoomTag).toHaveBeenCalledWith(ROOM_ID, DefaultTagID.Favourite);
|
||||
expect(client.setRoomTag).toHaveBeenCalledWith(ROOM_ID, DefaultTagID.LowPriority);
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ action: "RoomListActions.tagRoom.success" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("only calls setRoomTag when there was no previous tag", async () => {
|
||||
await invokeTagRoom(null, DefaultTagID.Favourite);
|
||||
|
||||
expect(client.deleteRoomTag).not.toHaveBeenCalled();
|
||||
expect(client.setRoomTag).toHaveBeenCalledWith(ROOM_ID, DefaultTagID.Favourite);
|
||||
});
|
||||
|
||||
it.each([null, DefaultTagID.DM])(
|
||||
"only calls deleteRoomTag when moving from %s to another non-DM tag",
|
||||
async (newTag) => {
|
||||
await invokeTagRoom(DefaultTagID.Favourite, newTag);
|
||||
|
||||
expect(client.deleteRoomTag).toHaveBeenCalledWith(ROOM_ID, DefaultTagID.Favourite);
|
||||
expect(client.setRoomTag).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it("makes no API calls when oldTag equals newTag", async () => {
|
||||
await invokeTagRoom(DefaultTagID.Favourite, DefaultTagID.Favourite);
|
||||
|
||||
expect(client.deleteRoomTag).not.toHaveBeenCalled();
|
||||
expect(client.setRoomTag).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips deleteRoomTag for the DM tag but still sets the new tag", async () => {
|
||||
await invokeTagRoom(DefaultTagID.DM, DefaultTagID.Favourite);
|
||||
|
||||
expect(client.deleteRoomTag).not.toHaveBeenCalled();
|
||||
expect(client.setRoomTag).toHaveBeenCalledWith(ROOM_ID, DefaultTagID.Favourite);
|
||||
});
|
||||
|
||||
it("shows an ErrorDialog but still dispatches success when deleteRoomTag fails", async () => {
|
||||
const error = new Error("delete failed");
|
||||
jest.spyOn(client, "deleteRoomTag").mockRejectedValue(error);
|
||||
|
||||
await invokeTagRoom(DefaultTagID.Favourite, DefaultTagID.LowPriority);
|
||||
|
||||
expect(Modal.createDialog).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({ description: error.message }),
|
||||
);
|
||||
// deleteRoomTag swallows the error, so Promise.all still resolves.
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ action: "RoomListActions.tagRoom.success" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("shows an ErrorDialog and dispatches failure when setRoomTag fails", async () => {
|
||||
const error = new Error("set failed");
|
||||
jest.spyOn(client, "setRoomTag").mockRejectedValue(error);
|
||||
|
||||
await invokeTagRoom(DefaultTagID.Favourite, DefaultTagID.LowPriority);
|
||||
|
||||
expect(Modal.createDialog).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({ description: error.message }),
|
||||
);
|
||||
// setRoomTag rethrows, so Promise.all rejects → failure dispatched.
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ action: "RoomListActions.tagRoom.failure" }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { SDKContextClass } from "../../../src/contexts/SDKContextClass";
|
||||
import { UserProfilesStore } from "../../../src/stores/UserProfilesStore";
|
||||
import { createTestClient } from "../../test-utils";
|
||||
import { TestSDKContext } from "../TestSDKContext.ts";
|
||||
|
||||
describe("SDKContextClass", () => {
|
||||
let sdkContext: TestSDKContext;
|
||||
let client: MatrixClient;
|
||||
|
||||
beforeAll(() => {
|
||||
client = createTestClient();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sdkContext = new TestSDKContext();
|
||||
});
|
||||
|
||||
it("instance should always return the same instance", () => {
|
||||
const globalInstance = SDKContextClass.instance;
|
||||
expect(SDKContextClass.instance).toBe(globalInstance);
|
||||
});
|
||||
|
||||
it("userProfilesStore should raise an error without a client", () => {
|
||||
expect(() => sdkContext.userProfilesStore).toThrow("Unable to create UserProfilesStore without a client");
|
||||
});
|
||||
|
||||
describe("when SDKContext has a client", () => {
|
||||
beforeEach(() => {
|
||||
sdkContext._client = client;
|
||||
});
|
||||
|
||||
it("userProfilesStore should return a UserProfilesStore", () => {
|
||||
const store = sdkContext.userProfilesStore;
|
||||
expect(store).toBeInstanceOf(UserProfilesStore);
|
||||
// it should return the same instance
|
||||
expect(sdkContext.userProfilesStore).toBe(store);
|
||||
});
|
||||
|
||||
it("onLoggedOut should clear the UserProfilesStore", () => {
|
||||
const store = sdkContext.userProfilesStore;
|
||||
sdkContext.onLoggedOut();
|
||||
sdkContext._client = client;
|
||||
expect(sdkContext.userProfilesStore).not.toBe(store);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,416 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { EventType, type MatrixClient, MatrixEvent, MsgType, Room, type RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
JSONEventFactory,
|
||||
MessageEventFactory,
|
||||
pickFactory,
|
||||
renderTile,
|
||||
RoomCreateEventFactory,
|
||||
} from "../../../src/events/EventTileFactory";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { createTestClient, mkEvent } from "../../test-utils";
|
||||
import { TimelineRenderingType } from "../../../src/contexts/RoomContext";
|
||||
import { ModuleApi } from "../../../src/modules/Api";
|
||||
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
|
||||
const roomId = "!room:example.com";
|
||||
|
||||
function makeVerificationRequestEvent({ sender, to }: { sender: string; to: string }): MatrixEvent {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
user: sender,
|
||||
room: roomId,
|
||||
content: {
|
||||
msgtype: MsgType.KeyVerificationRequest,
|
||||
from_device: "DEVICE",
|
||||
methods: ["m.sas.v1"],
|
||||
to,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function makeRoomAvatarEvent(url = "mxc://example.com/avatar"): MatrixEvent {
|
||||
return new MatrixEvent({
|
||||
type: EventType.RoomAvatar,
|
||||
state_key: "",
|
||||
room_id: roomId,
|
||||
sender: "@alice:example.com",
|
||||
content: {
|
||||
url,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe("pickFactory", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
let createEventWithPredecessor: MatrixEvent;
|
||||
let createEventWithoutPredecessor: MatrixEvent;
|
||||
let dynamicPredecessorEvent: MatrixEvent;
|
||||
|
||||
let utdEvent: MatrixEvent;
|
||||
let audioMessageEvent: MatrixEvent;
|
||||
|
||||
beforeAll(() => {
|
||||
client = createTestClient();
|
||||
|
||||
room = new Room(roomId, client, client.getSafeUserId());
|
||||
mocked(client.getRoom).mockImplementation((getRoomId: string): Room | null => {
|
||||
if (getRoomId === room.roomId) return room;
|
||||
return null;
|
||||
});
|
||||
|
||||
createEventWithoutPredecessor = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomCreate,
|
||||
user: client.getUserId()!,
|
||||
room: roomId,
|
||||
content: {
|
||||
creator: client.getUserId()!,
|
||||
room_version: "9",
|
||||
},
|
||||
});
|
||||
createEventWithPredecessor = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomCreate,
|
||||
user: client.getUserId()!,
|
||||
room: roomId,
|
||||
content: {
|
||||
creator: client.getUserId()!,
|
||||
room_version: "9",
|
||||
predecessor: {
|
||||
room_id: "roomid1",
|
||||
event_id: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
dynamicPredecessorEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomPredecessor,
|
||||
user: client.getUserId()!,
|
||||
room: roomId,
|
||||
skey: "",
|
||||
content: {
|
||||
predecessor_room_id: "roomid2",
|
||||
last_known_event_id: null,
|
||||
},
|
||||
});
|
||||
audioMessageEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
user: client.getUserId()!,
|
||||
room: roomId,
|
||||
content: {
|
||||
msgtype: MsgType.Audio,
|
||||
},
|
||||
});
|
||||
utdEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
user: client.getUserId()!,
|
||||
room: roomId,
|
||||
content: {
|
||||
msgtype: "m.bad.encrypted",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return JSONEventFactory for a no-op m.room.power_levels event", () => {
|
||||
const event = new MatrixEvent({
|
||||
type: EventType.RoomPowerLevels,
|
||||
state_key: "",
|
||||
content: {},
|
||||
sender: client.getUserId()!,
|
||||
room_id: roomId,
|
||||
});
|
||||
expect(pickFactory(event, client, true)).toBe(JSONEventFactory);
|
||||
});
|
||||
|
||||
describe("when showing hidden events", () => {
|
||||
it("should return a JSONEventFactory for a room create event without predecessor", () => {
|
||||
room.currentState.events.set(
|
||||
EventType.RoomCreate,
|
||||
new Map([[createEventWithoutPredecessor.getStateKey()!, createEventWithoutPredecessor]]),
|
||||
);
|
||||
room.currentState.events.set(EventType.RoomPredecessor, new Map());
|
||||
expect(pickFactory(createEventWithoutPredecessor, client, true)).toBe(JSONEventFactory);
|
||||
});
|
||||
|
||||
it("should return a MessageEventFactory for an audio message event", () => {
|
||||
expect(pickFactory(audioMessageEvent, client, true)).toBe(MessageEventFactory);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when not showing hidden events", () => {
|
||||
describe("without dynamic predecessor support", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReset();
|
||||
});
|
||||
|
||||
it("should return undefined for a room without predecessor", () => {
|
||||
room.currentState.events.set(
|
||||
EventType.RoomCreate,
|
||||
new Map([[createEventWithoutPredecessor.getStateKey()!, createEventWithoutPredecessor]]),
|
||||
);
|
||||
room.currentState.events.set(EventType.RoomPredecessor, new Map());
|
||||
expect(pickFactory(createEventWithoutPredecessor, client, false)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return a RoomCreateFactory for a room with fixed predecessor", () => {
|
||||
room.currentState.events.set(
|
||||
EventType.RoomCreate,
|
||||
new Map([[createEventWithPredecessor.getStateKey()!, createEventWithPredecessor]]),
|
||||
);
|
||||
room.currentState.events.set(EventType.RoomPredecessor, new Map());
|
||||
expect(pickFactory(createEventWithPredecessor, client, false)).toBe(RoomCreateEventFactory);
|
||||
});
|
||||
|
||||
it("should return undefined for a room with dynamic predecessor", () => {
|
||||
room.currentState.events.set(
|
||||
EventType.RoomCreate,
|
||||
new Map([[createEventWithoutPredecessor.getStateKey()!, createEventWithoutPredecessor]]),
|
||||
);
|
||||
room.currentState.events.set(
|
||||
EventType.RoomPredecessor,
|
||||
new Map([[dynamicPredecessorEvent.getStateKey()!, dynamicPredecessorEvent]]),
|
||||
);
|
||||
expect(pickFactory(createEventWithoutPredecessor, client, false)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("with dynamic predecessor support", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue")
|
||||
.mockReset()
|
||||
.mockImplementation((settingName) => settingName === "feature_dynamic_room_predecessors");
|
||||
});
|
||||
|
||||
it("should return undefined for a room without predecessor", () => {
|
||||
room.currentState.events.set(
|
||||
EventType.RoomCreate,
|
||||
new Map([[createEventWithoutPredecessor.getStateKey()!, createEventWithoutPredecessor]]),
|
||||
);
|
||||
room.currentState.events.set(EventType.RoomPredecessor, new Map());
|
||||
expect(pickFactory(createEventWithoutPredecessor, client, false)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return a RoomCreateFactory for a room with fixed predecessor", () => {
|
||||
room.currentState.events.set(
|
||||
EventType.RoomCreate,
|
||||
new Map([[createEventWithPredecessor.getStateKey()!, createEventWithPredecessor]]),
|
||||
);
|
||||
room.currentState.events.set(EventType.RoomPredecessor, new Map());
|
||||
expect(pickFactory(createEventWithPredecessor, client, false)).toBe(RoomCreateEventFactory);
|
||||
});
|
||||
|
||||
it("should return a RoomCreateFactory for a room with dynamic predecessor", () => {
|
||||
room.currentState.events.set(
|
||||
EventType.RoomCreate,
|
||||
new Map([[createEventWithoutPredecessor.getStateKey()!, createEventWithoutPredecessor]]),
|
||||
);
|
||||
room.currentState.events.set(
|
||||
EventType.RoomPredecessor,
|
||||
new Map([[dynamicPredecessorEvent.getStateKey()!, dynamicPredecessorEvent]]),
|
||||
);
|
||||
expect(pickFactory(createEventWithoutPredecessor, client, false)).toBe(RoomCreateEventFactory);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return a MessageEventFactory for an audio message event", () => {
|
||||
expect(pickFactory(audioMessageEvent, client, false)).toBe(MessageEventFactory);
|
||||
});
|
||||
|
||||
it("should return a MessageEventFactory for a UTD event", () => {
|
||||
expect(pickFactory(utdEvent, client, false)).toBe(MessageEventFactory);
|
||||
});
|
||||
|
||||
it("should not render key verification requests which do not involve the current user", () => {
|
||||
const event = makeVerificationRequestEvent({
|
||||
sender: "@alice:example.com",
|
||||
to: "@bob:example.com",
|
||||
});
|
||||
|
||||
expect(pickFactory(event, client, false)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("renderTile", () => {
|
||||
let client: MatrixClient;
|
||||
let originalRenderMessage: typeof ModuleApi.instance.customComponents.renderMessage;
|
||||
|
||||
beforeEach(() => {
|
||||
client = createTestClient();
|
||||
originalRenderMessage = ModuleApi.instance.customComponents.renderMessage;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
ModuleApi.instance.customComponents.renderMessage = originalRenderMessage;
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("rendering a tile defers to the module API", () => {
|
||||
ModuleApi.instance.customComponents.renderMessage = jest.fn();
|
||||
|
||||
const messageEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
user: client.getUserId()!,
|
||||
room: roomId,
|
||||
content: {
|
||||
msgtype: MsgType.Text,
|
||||
},
|
||||
});
|
||||
|
||||
renderTile(TimelineRenderingType.Room, { mxEvent: messageEvent, showHiddenEvents: false }, client);
|
||||
|
||||
expect(ModuleApi.instance.customComponents.renderMessage).toHaveBeenCalledWith(
|
||||
{
|
||||
mxEvent: messageEvent,
|
||||
},
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it("rendering a tile for a message of unknown type defers to the module API", () => {
|
||||
ModuleApi.instance.customComponents.renderMessage = jest.fn();
|
||||
|
||||
const messageEvent = mkEvent({
|
||||
event: true,
|
||||
type: "weird.type",
|
||||
user: client.getUserId()!,
|
||||
room: roomId,
|
||||
content: {
|
||||
msgtype: MsgType.Text,
|
||||
},
|
||||
});
|
||||
|
||||
renderTile(TimelineRenderingType.Room, { mxEvent: messageEvent, showHiddenEvents: false }, client);
|
||||
|
||||
expect(ModuleApi.instance.customComponents.renderMessage).toHaveBeenCalledWith({
|
||||
mxEvent: messageEvent,
|
||||
});
|
||||
});
|
||||
|
||||
it("renders an incoming key verification request with the wrapped shared-components view", () => {
|
||||
const sender = "@alice:example.com";
|
||||
const room = new Room(roomId, client, client.getSafeUserId());
|
||||
jest.spyOn(room, "getMember").mockImplementation((userId: string) => {
|
||||
if (userId === sender) return { name: "Alice" } as RoomMember;
|
||||
return null;
|
||||
});
|
||||
mocked(client.getRoom).mockReturnValue(room);
|
||||
|
||||
const verificationRequestEvent = makeVerificationRequestEvent({
|
||||
sender,
|
||||
to: client.getUserId()!,
|
||||
});
|
||||
|
||||
const tile = renderTile(
|
||||
TimelineRenderingType.Room,
|
||||
{ mxEvent: verificationRequestEvent, showHiddenEvents: false },
|
||||
client,
|
||||
);
|
||||
if (!tile) throw new Error("Expected a key verification request tile");
|
||||
|
||||
render(React.createElement(MatrixClientContext.Provider, { value: client }, tile));
|
||||
|
||||
expect(screen.getByText("Alice wants to verify")).toBeInTheDocument();
|
||||
expect(screen.getByText("Alice (@alice:example.com)")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders an outgoing key verification request with the wrapped shared-components view", () => {
|
||||
const recipient = "@alice:example.com";
|
||||
const room = new Room(roomId, client, client.getSafeUserId());
|
||||
jest.spyOn(room, "getMember").mockImplementation((userId: string) => {
|
||||
if (userId === recipient) return { name: "Alice" } as RoomMember;
|
||||
return null;
|
||||
});
|
||||
mocked(client.getRoom).mockReturnValue(room);
|
||||
|
||||
const verificationRequestEvent = makeVerificationRequestEvent({
|
||||
sender: client.getUserId()!,
|
||||
to: recipient,
|
||||
});
|
||||
|
||||
const tile = renderTile(
|
||||
TimelineRenderingType.Room,
|
||||
{ mxEvent: verificationRequestEvent, showHiddenEvents: false },
|
||||
client,
|
||||
);
|
||||
if (!tile) throw new Error("Expected a key verification request tile");
|
||||
|
||||
render(React.createElement(MatrixClientContext.Provider, { value: client }, tile));
|
||||
|
||||
expect(screen.getByText("You sent a verification request")).toBeInTheDocument();
|
||||
expect(screen.getByText("Alice (@alice:example.com)")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("throws when a key verification request tile is rendered without a client context", () => {
|
||||
jest.spyOn(console, "error").mockImplementation(() => {});
|
||||
const verificationRequestEvent = makeVerificationRequestEvent({
|
||||
sender: client.getUserId()!,
|
||||
to: "@alice:example.com",
|
||||
});
|
||||
|
||||
const tile = renderTile(
|
||||
TimelineRenderingType.Room,
|
||||
{ mxEvent: verificationRequestEvent, showHiddenEvents: false },
|
||||
client,
|
||||
);
|
||||
if (!tile) throw new Error("Expected a key verification request tile");
|
||||
|
||||
expect(() => render(tile)).toThrow("Attempting to render verification request without a client context!");
|
||||
});
|
||||
|
||||
it("renders room avatar events with the wrapped shared-components view", () => {
|
||||
const room = new Room(roomId, client, client.getSafeUserId());
|
||||
room.name = "General";
|
||||
room.currentState.setStateEvents([
|
||||
new MatrixEvent({
|
||||
type: EventType.RoomCreate,
|
||||
state_key: "",
|
||||
room_id: room.roomId,
|
||||
sender: client.getUserId()!,
|
||||
content: {
|
||||
creator: client.getUserId()!,
|
||||
room_version: "9",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
mocked(client.getRoom).mockReturnValue(room);
|
||||
jest.spyOn(DMRoomMap, "shared").mockReturnValue({
|
||||
getUserIdForRoomId: jest.fn().mockReturnValue(null),
|
||||
} as unknown as DMRoomMap);
|
||||
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(client);
|
||||
const roomAvatarEvent = makeRoomAvatarEvent();
|
||||
roomAvatarEvent.sender = { name: "Alice" } as MatrixEvent["sender"];
|
||||
|
||||
const tile = renderTile(
|
||||
TimelineRenderingType.Room,
|
||||
{ mxEvent: roomAvatarEvent, showHiddenEvents: false },
|
||||
client,
|
||||
);
|
||||
if (!tile) throw new Error("Expected a room avatar event tile");
|
||||
|
||||
render(React.createElement(MatrixClientContext.Provider, { value: client }, tile));
|
||||
|
||||
expect(screen.getByText("Alice changed the room avatar to")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Alice changed the avatar for General" })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { EventType, MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { getForwardableEvent } from "../../../../src/events";
|
||||
import {
|
||||
getMockClientWithEventEmitter,
|
||||
makeBeaconEvent,
|
||||
makeBeaconInfoEvent,
|
||||
makePollStartEvent,
|
||||
makeRoomWithBeacons,
|
||||
} from "../../../test-utils";
|
||||
|
||||
describe("getForwardableEvent()", () => {
|
||||
const userId = "@alice:server.org";
|
||||
const roomId = "!room:server.org";
|
||||
const client = getMockClientWithEventEmitter({
|
||||
getRoom: jest.fn(),
|
||||
});
|
||||
|
||||
it("returns the event for a room message", () => {
|
||||
const alicesMessageEvent = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
room_id: roomId,
|
||||
content: {
|
||||
msgtype: MsgType.Text,
|
||||
body: "Hello",
|
||||
},
|
||||
});
|
||||
|
||||
expect(getForwardableEvent(alicesMessageEvent, client)).toBe(alicesMessageEvent);
|
||||
});
|
||||
|
||||
it("returns null for a poll start event", () => {
|
||||
const pollStartEvent = makePollStartEvent("test?", userId);
|
||||
|
||||
expect(getForwardableEvent(pollStartEvent, client)).toBe(null);
|
||||
});
|
||||
|
||||
describe("beacons", () => {
|
||||
it("returns null for a beacon that is not live", () => {
|
||||
const notLiveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: false });
|
||||
makeRoomWithBeacons(roomId, client, [notLiveBeacon]);
|
||||
|
||||
expect(getForwardableEvent(notLiveBeacon, client)).toBe(null);
|
||||
});
|
||||
|
||||
it("returns null for a live beacon that does not have a location", () => {
|
||||
const liveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: true });
|
||||
makeRoomWithBeacons(roomId, client, [liveBeacon]);
|
||||
|
||||
expect(getForwardableEvent(liveBeacon, client)).toBe(null);
|
||||
});
|
||||
|
||||
it("returns the latest location event for a live beacon with location", () => {
|
||||
const liveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: true }, "id");
|
||||
const locationEvent = makeBeaconEvent(userId, {
|
||||
beaconInfoId: liveBeacon.getId(),
|
||||
geoUri: "geo:52,42",
|
||||
// make sure its in live period
|
||||
timestamp: Date.now() + 1,
|
||||
});
|
||||
makeRoomWithBeacons(roomId, client, [liveBeacon], [locationEvent]);
|
||||
|
||||
expect(getForwardableEvent(liveBeacon, client)).toBe(locationEvent);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { EventType, MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { getShareableLocationEvent } from "../../../../src/events";
|
||||
import {
|
||||
getMockClientWithEventEmitter,
|
||||
makeBeaconEvent,
|
||||
makeBeaconInfoEvent,
|
||||
makeLocationEvent,
|
||||
makeRoomWithBeacons,
|
||||
} from "../../../test-utils";
|
||||
|
||||
describe("getShareableLocationEvent()", () => {
|
||||
const userId = "@alice:server.org";
|
||||
const roomId = "!room:server.org";
|
||||
const client = getMockClientWithEventEmitter({
|
||||
getRoom: jest.fn(),
|
||||
});
|
||||
|
||||
it("returns null for a non-location event", () => {
|
||||
const alicesMessageEvent = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
room_id: roomId,
|
||||
content: {
|
||||
msgtype: MsgType.Text,
|
||||
body: "Hello",
|
||||
},
|
||||
});
|
||||
|
||||
expect(getShareableLocationEvent(alicesMessageEvent, client)).toBe(null);
|
||||
});
|
||||
|
||||
it("returns the event for a location event", () => {
|
||||
const locationEvent = makeLocationEvent("geo:52,42");
|
||||
|
||||
expect(getShareableLocationEvent(locationEvent, client)).toBe(locationEvent);
|
||||
});
|
||||
|
||||
describe("beacons", () => {
|
||||
it("returns null for a beacon that is not live", () => {
|
||||
const notLiveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: false });
|
||||
makeRoomWithBeacons(roomId, client, [notLiveBeacon]);
|
||||
|
||||
expect(getShareableLocationEvent(notLiveBeacon, client)).toBe(null);
|
||||
});
|
||||
|
||||
it("returns null for a live beacon that does not have a location", () => {
|
||||
const liveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: true });
|
||||
makeRoomWithBeacons(roomId, client, [liveBeacon]);
|
||||
|
||||
expect(getShareableLocationEvent(liveBeacon, client)).toBe(null);
|
||||
});
|
||||
|
||||
it("returns the latest location event for a live beacon with location", () => {
|
||||
const liveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: true }, "id");
|
||||
const locationEvent = makeBeaconEvent(userId, {
|
||||
beaconInfoId: liveBeacon.getId(),
|
||||
geoUri: "geo:52,42",
|
||||
// make sure its in live period
|
||||
timestamp: Date.now() + 1,
|
||||
});
|
||||
makeRoomWithBeacons(roomId, client, [liveBeacon], [locationEvent]);
|
||||
|
||||
expect(getShareableLocationEvent(liveBeacon, client)).toBe(locationEvent);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 { renderHook } from "jest-matrix-react";
|
||||
import { type MatrixClient, NotificationCountType, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useRoomThreadNotifications } from "../../../../src/hooks/room/useRoomThreadNotifications";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { NotificationLevel } from "../../../../src/stores/notifications/NotificationLevel";
|
||||
import { populateThread } from "../../../test-utils/threads";
|
||||
|
||||
function render(room: Room) {
|
||||
return renderHook(() => useRoomThreadNotifications(room));
|
||||
}
|
||||
|
||||
describe("useRoomThreadNotifications", () => {
|
||||
let cli: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
cli.supportsThreads = () => true;
|
||||
room = new Room("!room:server", cli, cli.getSafeUserId());
|
||||
});
|
||||
|
||||
it("returns none if no thread in the room has notifications", async () => {
|
||||
const { result } = render(room);
|
||||
|
||||
expect(result.current).toBe(NotificationLevel.None);
|
||||
});
|
||||
|
||||
it("returns none if the thread hasn't a notification anymore", async () => {
|
||||
room.setThreadUnreadNotificationCount("flooble", NotificationCountType.Highlight, 0);
|
||||
const { result } = render(room);
|
||||
|
||||
expect(result.current).toBe(NotificationLevel.None);
|
||||
});
|
||||
|
||||
it("returns red if a thread in the room has a highlight notification", async () => {
|
||||
room.setThreadUnreadNotificationCount("flooble", NotificationCountType.Highlight, 1);
|
||||
const { result } = render(room);
|
||||
|
||||
expect(result.current).toBe(NotificationLevel.Highlight);
|
||||
});
|
||||
|
||||
it("returns grey if a thread in the room has a normal notification", async () => {
|
||||
room.setThreadUnreadNotificationCount("flooble", NotificationCountType.Total, 1);
|
||||
const { result } = render(room);
|
||||
|
||||
expect(result.current).toBe(NotificationLevel.Notification);
|
||||
});
|
||||
|
||||
it("returns activity if a thread in the room unread messages", async () => {
|
||||
await populateThread({
|
||||
room,
|
||||
client: cli,
|
||||
authorId: cli.getSafeUserId(),
|
||||
participantUserIds: ["@alice:server.org"],
|
||||
});
|
||||
|
||||
const { result } = render(room);
|
||||
|
||||
expect(result.current).toBe(NotificationLevel.Activity);
|
||||
});
|
||||
});
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 New Vector 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 { act, renderHook, waitFor } from "jest-matrix-react";
|
||||
import { JoinRule, MatrixEvent, type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useMediaVisible } from "../../../src/hooks/useMediaVisible";
|
||||
import { createTestClient, mkStubRoom, withClientContextRenderOptions } from "../../test-utils";
|
||||
import { type MediaPreviewConfig, MediaPreviewValue } from "../../../src/@types/media_preview";
|
||||
import MediaPreviewConfigController from "../../../src/settings/controllers/MediaPreviewConfigController";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
|
||||
const EVENT_ID = "$fibble:example.org";
|
||||
const ROOM_ID = "!foobar:example.org";
|
||||
|
||||
describe("useMediaVisible", () => {
|
||||
let matrixClient: MatrixClient;
|
||||
let room: Room;
|
||||
const mediaPreviewConfig: MediaPreviewConfig = MediaPreviewConfigController.default;
|
||||
|
||||
function render({ sender }: { sender?: string } = {}) {
|
||||
return renderHook(
|
||||
() =>
|
||||
useMediaVisible(
|
||||
new MatrixEvent({
|
||||
event_id: EVENT_ID,
|
||||
room_id: ROOM_ID,
|
||||
sender,
|
||||
}),
|
||||
),
|
||||
withClientContextRenderOptions(matrixClient),
|
||||
);
|
||||
}
|
||||
|
||||
function renderWithoutEvent() {
|
||||
return renderHook(() => useMediaVisible(), withClientContextRenderOptions(matrixClient));
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
matrixClient = createTestClient();
|
||||
room = mkStubRoom(ROOM_ID, undefined, matrixClient);
|
||||
matrixClient.getRoom = jest.fn().mockReturnValue(room);
|
||||
const origFn = SettingsStore.getValue;
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting, ...args) => {
|
||||
if (setting === "mediaPreviewConfig") {
|
||||
return mediaPreviewConfig;
|
||||
}
|
||||
return origFn(setting, ...args);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should display media by default", () => {
|
||||
const [visible] = render().result.current;
|
||||
expect(visible).toEqual(true);
|
||||
});
|
||||
|
||||
it("should use the global rule when no event is provided", () => {
|
||||
mediaPreviewConfig.media_previews = MediaPreviewValue.Off;
|
||||
expect(renderWithoutEvent().result.current[0]).toEqual(false);
|
||||
|
||||
mediaPreviewConfig.media_previews = MediaPreviewValue.On;
|
||||
expect(renderWithoutEvent().result.current[0]).toEqual(true);
|
||||
});
|
||||
|
||||
it("should hide media when media previews are Off", () => {
|
||||
mediaPreviewConfig.media_previews = MediaPreviewValue.Off;
|
||||
const [visible] = render().result.current;
|
||||
expect(visible).toEqual(false);
|
||||
});
|
||||
|
||||
it("should always show media sent by us", () => {
|
||||
mediaPreviewConfig.media_previews = MediaPreviewValue.Off;
|
||||
const [visible] = render({ sender: matrixClient.getUserId()! }).result.current;
|
||||
expect(visible).toEqual(true);
|
||||
});
|
||||
|
||||
it.each([[JoinRule.Invite], [JoinRule.Knock], [JoinRule.Restricted]])(
|
||||
"should display media when media previews are Private and the join rule is %s",
|
||||
(rule) => {
|
||||
mediaPreviewConfig.media_previews = MediaPreviewValue.Private;
|
||||
room.currentState.getJoinRule = jest.fn().mockReturnValue(rule);
|
||||
const [visible] = render().result.current;
|
||||
expect(visible).toEqual(true);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([[JoinRule.Public], ["anything_else"]])(
|
||||
"should hide media when media previews are Private and the join rule is %s",
|
||||
(rule) => {
|
||||
mediaPreviewConfig.media_previews = MediaPreviewValue.Private;
|
||||
room.currentState.getJoinRule = jest.fn().mockReturnValue(rule);
|
||||
const [visible] = render().result.current;
|
||||
expect(visible).toEqual(false);
|
||||
},
|
||||
);
|
||||
|
||||
it("should hide media after function is called", async () => {
|
||||
const { result } = render();
|
||||
expect(result.current[0]).toEqual(true);
|
||||
expect(result.current[1]).toBeDefined();
|
||||
act(() => {
|
||||
result.current[1]!(false);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(result.current[0]).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
it("should show media after function is called", async () => {
|
||||
mediaPreviewConfig.media_previews = MediaPreviewValue.Off;
|
||||
const { result } = render();
|
||||
expect(result.current[0]).toEqual(false);
|
||||
expect(result.current[1]).toBeDefined();
|
||||
act(() => {
|
||||
result.current[1]!(true);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(result.current[0]).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 { waitFor, renderHook } from "jest-matrix-react";
|
||||
import { type IPushRules, type MatrixClient, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useNotificationSettings } from "../../../src/hooks/useNotificationSettings";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import {
|
||||
DefaultNotificationSettings,
|
||||
type NotificationSettings,
|
||||
} from "../../../src/models/notificationsettings/NotificationSettings";
|
||||
import { StandardActions } from "../../../src/notifications/StandardActions";
|
||||
import { RoomNotifState } from "../../../src/RoomNotifs";
|
||||
import { stubClient } from "../../test-utils";
|
||||
|
||||
const expectedModel: NotificationSettings = {
|
||||
globalMute: false,
|
||||
defaultLevels: {
|
||||
dm: RoomNotifState.AllMessages,
|
||||
room: RoomNotifState.MentionsOnly,
|
||||
},
|
||||
sound: {
|
||||
calls: "ring",
|
||||
mentions: "default",
|
||||
people: undefined,
|
||||
},
|
||||
activity: {
|
||||
bot_notices: false,
|
||||
invite: true,
|
||||
status_event: false,
|
||||
},
|
||||
mentions: {
|
||||
user: true,
|
||||
room: true,
|
||||
keywords: true,
|
||||
},
|
||||
keywords: ["justjann3", "justj4nn3", "justj4nne", "Janne", "J4nne", "Jann3", "jann3", "j4nne", "janne"],
|
||||
};
|
||||
|
||||
describe("useNotificationSettings", () => {
|
||||
let cli: MatrixClient;
|
||||
let pushRules: IPushRules;
|
||||
|
||||
beforeAll(async () => {
|
||||
pushRules = (await import("../models/notificationsettings/pushrules_sample.json")) as IPushRules;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
cli.getPushRules = jest.fn(cli.getPushRules).mockResolvedValue(pushRules);
|
||||
cli.supportsIntentionalMentions = jest.fn(cli.supportsIntentionalMentions).mockReturnValue(false);
|
||||
});
|
||||
|
||||
it("correctly parses model", async () => {
|
||||
const { result } = renderHook(() => useNotificationSettings(cli));
|
||||
expect(result.current.model).toEqual(null);
|
||||
await waitFor(() => expect(result.current.model).toEqual(expectedModel));
|
||||
expect(result.current.hasPendingChanges).toBeFalsy();
|
||||
});
|
||||
|
||||
it("correctly generates change calls", async () => {
|
||||
const addPushRule = jest.fn(cli.addPushRule);
|
||||
cli.addPushRule = addPushRule;
|
||||
const deletePushRule = jest.fn(cli.deletePushRule);
|
||||
cli.deletePushRule = deletePushRule;
|
||||
const setPushRuleEnabled = jest.fn(cli.setPushRuleEnabled);
|
||||
cli.setPushRuleEnabled = setPushRuleEnabled;
|
||||
const setPushRuleActions = jest.fn(cli.setPushRuleActions);
|
||||
cli.setPushRuleActions = setPushRuleActions;
|
||||
|
||||
const { result } = renderHook(() => useNotificationSettings(cli));
|
||||
expect(result.current.model).toEqual(null);
|
||||
await waitFor(() => expect(result.current.model).toEqual(expectedModel));
|
||||
expect(result.current.hasPendingChanges).toBeFalsy();
|
||||
await result.current.reconcile(DefaultNotificationSettings);
|
||||
await waitFor(() => expect(result.current.hasPendingChanges).toBeFalsy());
|
||||
expect(addPushRule).toHaveBeenCalledTimes(0);
|
||||
expect(deletePushRule).toHaveBeenCalledTimes(9);
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justjann3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justj4nn3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justj4nne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "Janne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "J4nne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "Jann3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "jann3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "j4nne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "janne");
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledTimes(6);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.EncryptedMessage,
|
||||
true,
|
||||
);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.Message, true);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.EncryptedDM, true);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.DM, true);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Override, RuleId.SuppressNotices, false);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Override, RuleId.InviteToSelf, true);
|
||||
expect(setPushRuleActions).toHaveBeenCalledTimes(6);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.EncryptedMessage,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.Message,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.EncryptedDM,
|
||||
StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.DM,
|
||||
StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Override,
|
||||
RuleId.SuppressNotices,
|
||||
StandardActions.ACTION_DONT_NOTIFY,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Override,
|
||||
RuleId.InviteToSelf,
|
||||
StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { waitFor, renderHook, act } from "jest-matrix-react";
|
||||
import { type EmptyObject, type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useProfileInfo } from "../../../src/hooks/useProfileInfo";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import { stubClient } from "../../test-utils/test-utils";
|
||||
|
||||
function render() {
|
||||
return renderHook(() => useProfileInfo());
|
||||
}
|
||||
|
||||
describe("useProfileInfo", () => {
|
||||
let cli: MatrixClient;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
cli.getProfileInfo = (query) => {
|
||||
return Promise.resolve({
|
||||
avatar_url: undefined,
|
||||
displayname: query,
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
it("should display user profile when searching", async () => {
|
||||
const query = "@user:home.server";
|
||||
|
||||
const { result } = render();
|
||||
|
||||
act(() => {
|
||||
result.current.search({ query });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.ready).toBe(true);
|
||||
expect(result.current.profile?.display_name).toBe(query);
|
||||
});
|
||||
});
|
||||
|
||||
it("should work with empty queries", async () => {
|
||||
const query = "";
|
||||
|
||||
const { result } = render();
|
||||
|
||||
act(() => {
|
||||
result.current.search({ query });
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current.ready).toBe(true));
|
||||
|
||||
expect(result.current.profile).toBeNull();
|
||||
});
|
||||
|
||||
it("should treat invalid mxids as empty queries", async () => {
|
||||
const queries = ["@user", "user@home.server"];
|
||||
|
||||
for (const query of queries) {
|
||||
const { result } = render();
|
||||
|
||||
act(() => {
|
||||
result.current.search({ query });
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current.ready).toBe(true));
|
||||
|
||||
expect(result.current.profile).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
it("should recover from a server exception", async () => {
|
||||
cli.getProfileInfo = () => {
|
||||
throw new Error("Oops");
|
||||
};
|
||||
const query = "@user:home.server";
|
||||
|
||||
const { result } = render();
|
||||
|
||||
act(() => {
|
||||
result.current.search({ query });
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current.ready).toBe(true));
|
||||
|
||||
expect(result.current.profile).toBeNull();
|
||||
});
|
||||
|
||||
it("should be able to handle an empty result", async () => {
|
||||
cli.getProfileInfo = () => null as unknown as Promise<EmptyObject>;
|
||||
const query = "@user:home.server";
|
||||
|
||||
const { result } = render();
|
||||
|
||||
act(() => {
|
||||
result.current.search({ query });
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current.ready).toBe(true));
|
||||
|
||||
expect(result.current.profile?.display_name).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { waitFor, renderHook, act } from "jest-matrix-react";
|
||||
import { type IRoomDirectoryOptions, type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { usePublicRoomDirectory } from "../../../src/hooks/usePublicRoomDirectory";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import { stubClient } from "../../test-utils/test-utils";
|
||||
|
||||
function render() {
|
||||
return renderHook(() => usePublicRoomDirectory());
|
||||
}
|
||||
|
||||
describe("usePublicRoomDirectory", () => {
|
||||
let cli: MatrixClient;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
|
||||
cli.getDomain = () => "matrix.org";
|
||||
cli.getThirdpartyProtocols = () => Promise.resolve({});
|
||||
cli.publicRooms = ({ filter }: IRoomDirectoryOptions) => {
|
||||
const chunk = [
|
||||
{
|
||||
room_id: "hello world!",
|
||||
name: filter?.generic_search_term ?? "", // If the query is "" no filter is applied(an is undefined here), in keeping with the pattern let's call the room ""
|
||||
world_readable: true,
|
||||
guest_can_join: true,
|
||||
num_joined_members: 1,
|
||||
},
|
||||
];
|
||||
return Promise.resolve({
|
||||
chunk,
|
||||
total_room_count_estimate: 1,
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
it("should display public rooms when searching", async () => {
|
||||
const query = "ROOM NAME";
|
||||
const { result } = render();
|
||||
|
||||
expect(result.current.ready).toBe(false);
|
||||
expect(result.current.loading).toBe(false);
|
||||
|
||||
act(() => {
|
||||
result.current.search({
|
||||
limit: 1,
|
||||
query,
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.ready).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.publicRooms[0].name).toBe(query);
|
||||
});
|
||||
|
||||
it("should work with empty queries", async () => {
|
||||
const query = "";
|
||||
const { result } = render();
|
||||
|
||||
act(() => {
|
||||
result.current.search({
|
||||
limit: 1,
|
||||
query,
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.ready).toBe(true);
|
||||
expect(result.current.publicRooms[0]?.name).toEqual(query);
|
||||
});
|
||||
});
|
||||
|
||||
it("should recover from a server exception", async () => {
|
||||
cli.publicRooms = () => {
|
||||
throw new Error("Oops");
|
||||
};
|
||||
const query = "ROOM NAME";
|
||||
|
||||
const { result } = render();
|
||||
|
||||
act(() => {
|
||||
result.current.search({
|
||||
limit: 1,
|
||||
query,
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.ready).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.publicRooms).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
Copyright 2026 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 { renderHook, waitFor } from "jest-matrix-react";
|
||||
import React from "react";
|
||||
|
||||
import { PlatformCallType, useRoomCall } from "../../../src/hooks/room/useRoomCall";
|
||||
import {
|
||||
getMockClientWithEventEmitter,
|
||||
mkRoom,
|
||||
mockClientMethodsRooms,
|
||||
mockClientMethodsServer,
|
||||
mockClientMethodsUser,
|
||||
MockEventEmitter,
|
||||
setupAsyncStoreWithClient,
|
||||
} from "../../test-utils";
|
||||
import { ScopedRoomContextProvider } from "../../../src/contexts/ScopedRoomContext";
|
||||
import RoomContext, { type RoomContextType } from "../../../src/contexts/RoomContext";
|
||||
import { MatrixClientContextProvider } from "../../../src/components/structures/MatrixClientContextProvider";
|
||||
import type LegacyCallHandler from "../../../src/LegacyCallHandler";
|
||||
import { CallStore } from "../../../src/stores/CallStore";
|
||||
import { SDKContextClass } from "../../../src/contexts/SDKContextClass";
|
||||
|
||||
describe("useRoomCall", () => {
|
||||
const client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(),
|
||||
...mockClientMethodsServer(),
|
||||
...mockClientMethodsRooms(),
|
||||
matrixRTC: new MockEventEmitter(),
|
||||
_unstable_getRTCTransports: jest.fn().mockResolvedValue([]),
|
||||
getCrypto: () => null,
|
||||
});
|
||||
const room = mkRoom(client, "!test-room");
|
||||
// Create a stable room context for this test
|
||||
const mockRoomViewStore = {
|
||||
isViewingCall: jest.fn().mockReturnValue(false),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
const roomContext = {
|
||||
...RoomContext,
|
||||
roomId: room.roomId,
|
||||
roomViewStore: mockRoomViewStore,
|
||||
} as unknown as RoomContextType;
|
||||
|
||||
beforeEach(() => {
|
||||
const callHandler = {
|
||||
getCallForRoom: jest.fn().mockReturnValue(null),
|
||||
isCallSidebarShown: jest.fn().mockReturnValue(true),
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
};
|
||||
jest.spyOn(SDKContextClass.instance, "legacyCallHandler", "get").mockReturnValue(
|
||||
callHandler as unknown as LegacyCallHandler,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
function render() {
|
||||
return renderHook(() => useRoomCall(room), {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContextProvider client={client}>
|
||||
<ScopedRoomContextProvider {...roomContext}>{children}</ScopedRoomContextProvider>
|
||||
</MatrixClientContextProvider>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
describe("Element Call focus detection", () => {
|
||||
it("Blocks Element Call if required foci are not configured", async () => {
|
||||
await setupAsyncStoreWithClient(CallStore.instance, client);
|
||||
const { result } = render();
|
||||
await waitFor(() => expect(result.current.callOptions).toEqual([PlatformCallType.LegacyCall]));
|
||||
});
|
||||
it("Blocks Element Call if transport foci are the wrong type", async () => {
|
||||
client._unstable_getRTCTransports.mockResolvedValue([{ type: "anything-else" }]);
|
||||
await setupAsyncStoreWithClient(CallStore.instance, client);
|
||||
const { result } = render();
|
||||
await waitFor(() => expect(result.current.callOptions).toEqual([PlatformCallType.LegacyCall]));
|
||||
});
|
||||
it("Blocks Element Call if well-known foci are the wrong type", async () => {
|
||||
client.getClientWellKnown.mockReturnValue({
|
||||
"org.matrix.msc4143.rtc_foci": {
|
||||
type: "anything-else",
|
||||
},
|
||||
});
|
||||
await setupAsyncStoreWithClient(CallStore.instance, client);
|
||||
const { result } = render();
|
||||
await waitFor(() => expect(result.current.callOptions).toEqual([PlatformCallType.LegacyCall]));
|
||||
});
|
||||
it("Allows Element Call if foci is provided via getRTCTransports", async () => {
|
||||
client._unstable_getRTCTransports.mockResolvedValue([
|
||||
{ type: "livekit", livekit_service_url: "https://example.org" },
|
||||
]);
|
||||
await setupAsyncStoreWithClient(CallStore.instance, client);
|
||||
|
||||
const { result } = render();
|
||||
await waitFor(() =>
|
||||
expect(result.current.callOptions).toEqual([PlatformCallType.ElementCall, PlatformCallType.LegacyCall]),
|
||||
);
|
||||
});
|
||||
it("Allows Element Call if foci is provided via .well-known", async () => {
|
||||
client.getClientWellKnown.mockReturnValue({
|
||||
"org.matrix.msc4143.rtc_foci": {
|
||||
type: "livekit",
|
||||
livekit_service_url: "https://example.org",
|
||||
},
|
||||
});
|
||||
await setupAsyncStoreWithClient(CallStore.instance, client);
|
||||
const { result } = render();
|
||||
await waitFor(() =>
|
||||
expect(result.current.callOptions).toEqual([PlatformCallType.ElementCall, PlatformCallType.LegacyCall]),
|
||||
);
|
||||
});
|
||||
it("Ensure handler reacts to transport changes", async () => {
|
||||
// Clear all transports
|
||||
client._unstable_getRTCTransports.mockResolvedValue([]);
|
||||
client.getClientWellKnown.mockReturnValue({});
|
||||
|
||||
await setupAsyncStoreWithClient(CallStore.instance, client);
|
||||
const { result } = render();
|
||||
|
||||
// Ensure Element Call is not a call option.
|
||||
expect(result.current.callOptions).toEqual([PlatformCallType.LegacyCall]);
|
||||
|
||||
// Now enable a transport and ensure that useRoomCall picks it up reactively.
|
||||
client._unstable_getRTCTransports.mockResolvedValue([
|
||||
{ type: "livekit", livekit_service_url: "https://example.org" },
|
||||
]);
|
||||
await setupAsyncStoreWithClient(CallStore.instance, client);
|
||||
await waitFor(() =>
|
||||
expect(result.current.callOptions).toEqual([PlatformCallType.ElementCall, PlatformCallType.LegacyCall]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 { waitFor, renderHook, act } from "jest-matrix-react";
|
||||
import { type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import { stubClient } from "../../test-utils";
|
||||
import { useMyRoomMembership, useRoomMemberCount, useRoomMembers } from "../../../src/hooks/useRoomMembers";
|
||||
|
||||
describe("useRoomMembers", () => {
|
||||
function render(room: Room) {
|
||||
return renderHook(() => useRoomMembers(room));
|
||||
}
|
||||
|
||||
let cli: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
room = new Room("!room:server", cli, cli.getSafeUserId());
|
||||
});
|
||||
|
||||
it("should update on RoomState.Members events", async () => {
|
||||
const { result } = render(room);
|
||||
|
||||
expect(result.current).toHaveLength(0);
|
||||
|
||||
act(() => {
|
||||
room.currentState.markOutOfBandMembersStarted();
|
||||
room.currentState.setOutOfBandMembers([
|
||||
new MatrixEvent({
|
||||
type: "m.room.member",
|
||||
state_key: "!user:server",
|
||||
room_id: room.roomId,
|
||||
content: {
|
||||
membership: KnownMembership.Join,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
await waitFor(() => expect(result.current).toHaveLength(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe("useRoomMemberCount", () => {
|
||||
function render(room: Room) {
|
||||
return renderHook(() => useRoomMemberCount(room));
|
||||
}
|
||||
|
||||
let cli: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
room = new Room("!room:server", cli, cli.getSafeUserId());
|
||||
});
|
||||
|
||||
it("should update on RoomState.Members events", async () => {
|
||||
const { result } = render(room);
|
||||
|
||||
expect(result.current).toBe(0);
|
||||
|
||||
act(() => {
|
||||
room.currentState.markOutOfBandMembersStarted();
|
||||
room.currentState.setOutOfBandMembers([
|
||||
new MatrixEvent({
|
||||
type: "m.room.member",
|
||||
state_key: "!user:server",
|
||||
room_id: room.roomId,
|
||||
content: {
|
||||
membership: KnownMembership.Join,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
await waitFor(() => expect(result.current).toBe(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe("useMyRoomMembership", () => {
|
||||
function render(room: Room) {
|
||||
return renderHook(() => useMyRoomMembership(room));
|
||||
}
|
||||
|
||||
let cli: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
room = new Room("!room:server", cli, cli.getSafeUserId());
|
||||
});
|
||||
|
||||
it("should update on RoomState.Members events", async () => {
|
||||
room.updateMyMembership(KnownMembership.Join);
|
||||
const { result } = render(room);
|
||||
|
||||
expect(result.current).toBe(KnownMembership.Join);
|
||||
|
||||
act(() => {
|
||||
room.updateMyMembership(KnownMembership.Leave);
|
||||
});
|
||||
await waitFor(() => expect(result.current).toBe(KnownMembership.Leave));
|
||||
});
|
||||
});
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 { renderHook } from "jest-matrix-react";
|
||||
import { EventStatus, NotificationCountType, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { useUnreadNotifications } from "../../../src/hooks/useUnreadNotifications";
|
||||
import { NotificationLevel } from "../../../src/stores/notifications/NotificationLevel";
|
||||
import { mkEvent, muteRoom, stubClient } from "../../test-utils";
|
||||
|
||||
describe("useUnreadNotifications", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = new Room("!room:example.org", client, "@user:example.org", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
});
|
||||
|
||||
function setUnreads(greys: number, reds: number): void {
|
||||
room.setUnreadNotificationCount(NotificationCountType.Highlight, reds);
|
||||
room.setUnreadNotificationCount(NotificationCountType.Total, greys);
|
||||
}
|
||||
|
||||
it("shows nothing by default", async () => {
|
||||
const { result } = renderHook(() => useUnreadNotifications(room));
|
||||
const { level, symbol, count } = result.current;
|
||||
|
||||
expect(symbol).toBe(null);
|
||||
expect(level).toBe(NotificationLevel.None);
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
|
||||
it("indicates if there are unsent messages", async () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
type: "m.message",
|
||||
user: "@user:example.org",
|
||||
content: {},
|
||||
});
|
||||
event.status = EventStatus.NOT_SENT;
|
||||
room.addPendingEvent(event, "txn");
|
||||
|
||||
const { result } = renderHook(() => useUnreadNotifications(room));
|
||||
const { level, symbol, count } = result.current;
|
||||
|
||||
expect(symbol).toBe("!");
|
||||
expect(level).toBe(NotificationLevel.Unsent);
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("indicates the user has been invited to a channel", async () => {
|
||||
room.updateMyMembership(KnownMembership.Invite);
|
||||
|
||||
const { result } = renderHook(() => useUnreadNotifications(room));
|
||||
const { level, symbol, count } = result.current;
|
||||
|
||||
expect(symbol).toBe("!");
|
||||
expect(level).toBe(NotificationLevel.Highlight);
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("shows nothing for muted channels", async () => {
|
||||
setUnreads(999, 999);
|
||||
muteRoom(room);
|
||||
|
||||
const { result } = renderHook(() => useUnreadNotifications(room));
|
||||
const { level, count } = result.current;
|
||||
|
||||
expect(level).toBe(NotificationLevel.None);
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
|
||||
it("uses the correct number of unreads", async () => {
|
||||
setUnreads(999, 0);
|
||||
|
||||
const { result } = renderHook(() => useUnreadNotifications(room));
|
||||
const { level, count } = result.current;
|
||||
|
||||
expect(level).toBe(NotificationLevel.Notification);
|
||||
expect(count).toBe(999);
|
||||
});
|
||||
|
||||
it("uses the correct number of highlights", async () => {
|
||||
setUnreads(0, 888);
|
||||
|
||||
const { result } = renderHook(() => useUnreadNotifications(room));
|
||||
const { level, count } = result.current;
|
||||
|
||||
expect(level).toBe(NotificationLevel.Highlight);
|
||||
expect(count).toBe(888);
|
||||
});
|
||||
});
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { waitFor, renderHook, act } from "jest-matrix-react";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useUserDirectory } from "../../../src/hooks/useUserDirectory";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import { stubClient } from "../../test-utils";
|
||||
|
||||
function render() {
|
||||
return renderHook(() => useUserDirectory());
|
||||
}
|
||||
|
||||
describe("useUserDirectory", () => {
|
||||
let cli: MatrixClient;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
|
||||
cli.getDomain = () => "matrix.org";
|
||||
cli.getThirdpartyProtocols = () => Promise.resolve({});
|
||||
cli.searchUserDirectory = ({ term: query }) =>
|
||||
Promise.resolve({
|
||||
results: [
|
||||
{
|
||||
user_id: "@bob:matrix.org",
|
||||
display_name: query,
|
||||
},
|
||||
],
|
||||
limited: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("search for users in the identity server", async () => {
|
||||
const query = "Bob";
|
||||
const { result } = render();
|
||||
|
||||
act(() => {
|
||||
result.current.search({ limit: 1, query });
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(result.current.ready).toBe(true);
|
||||
expect(result.current.loading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.users[0].name).toBe(query);
|
||||
});
|
||||
|
||||
it("should work with empty queries", async () => {
|
||||
const query = "";
|
||||
const { result } = render();
|
||||
|
||||
act(() => {
|
||||
result.current.search({ limit: 1, query });
|
||||
});
|
||||
await waitFor(() => expect(result.current.ready).toBe(true));
|
||||
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.users).toEqual([]);
|
||||
});
|
||||
|
||||
it("should recover from a server exception", async () => {
|
||||
cli.searchUserDirectory = () => {
|
||||
throw new Error("Oops");
|
||||
};
|
||||
const query = "Bob";
|
||||
|
||||
const { result } = render();
|
||||
|
||||
act(() => {
|
||||
result.current.search({ limit: 1, query });
|
||||
});
|
||||
await waitFor(() => expect(result.current.ready).toBe(true));
|
||||
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.users).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
Copyright 2026 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 React from "react";
|
||||
import { renderHook, waitFor } from "jest-matrix-react";
|
||||
import { ClientEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useUserStatus } from "../../../src/hooks/useUserStatus";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsUser, mockClientMethodsServer } from "../../test-utils";
|
||||
import { MatrixClientContextProvider } from "../../../src/components/structures/MatrixClientContextProvider";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { userStatusTextWithinMaxLength } from "../../../src/utils/userStatus";
|
||||
|
||||
const userId = "@alice:example.com";
|
||||
|
||||
const client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(),
|
||||
...mockClientMethodsServer(),
|
||||
getCrypto: jest.fn().mockReturnValue(null),
|
||||
doesServerSupportExtendedProfiles: jest.fn().mockResolvedValue(true),
|
||||
getExtendedProfileProperty: jest.fn().mockResolvedValue(undefined),
|
||||
});
|
||||
|
||||
function render(uid: string | undefined = userId) {
|
||||
return renderHook(() => useUserStatus(uid), {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContextProvider client={client}>{children}</MatrixClientContextProvider>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
describe("userStatusTextWithinMaxLength", () => {
|
||||
it("returns true for short text", () => {
|
||||
expect(userStatusTextWithinMaxLength("on a horse")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for text exceeding 256 bytes", () => {
|
||||
expect(userStatusTextWithinMaxLength("a".repeat(257))).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true for text exactly 256 bytes", () => {
|
||||
expect(userStatusTextWithinMaxLength("a".repeat(256))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("useUserStatus", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name): any => {
|
||||
if (name === "feature_user_status") return true;
|
||||
});
|
||||
client.doesServerSupportExtendedProfiles.mockResolvedValue(true);
|
||||
client.getExtendedProfileProperty.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns undefined when feature is disabled", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
const { result } = render();
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when userId is undefined", async () => {
|
||||
const { result } = render(undefined);
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when server does not support extended profiles", async () => {
|
||||
client.doesServerSupportExtendedProfiles.mockResolvedValue(false);
|
||||
const { result } = render();
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when status property is not set", async () => {
|
||||
client.getExtendedProfileProperty.mockResolvedValue(undefined);
|
||||
const { result } = render();
|
||||
await waitFor(() =>
|
||||
expect(client.getExtendedProfileProperty).toHaveBeenCalledWith(userId, "org.matrix.msc4426.status"),
|
||||
);
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when status is not an object", async () => {
|
||||
client.getExtendedProfileProperty.mockResolvedValue("not an object");
|
||||
const { result } = render();
|
||||
await waitFor(() => expect(client.getExtendedProfileProperty).toHaveBeenCalled());
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when emoji is missing", async () => {
|
||||
client.getExtendedProfileProperty.mockResolvedValue({ text: "on a horse" });
|
||||
const { result } = render();
|
||||
await waitFor(() => expect(client.getExtendedProfileProperty).toHaveBeenCalled());
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when text is missing", async () => {
|
||||
client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🐎" });
|
||||
const { result } = render();
|
||||
await waitFor(() => expect(client.getExtendedProfileProperty).toHaveBeenCalled());
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns the user status when valid", async () => {
|
||||
client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🐎", text: "on a horse" });
|
||||
const { result } = render();
|
||||
await waitFor(() => expect(result.current).toEqual({ emoji: "🐎", text: "on a horse" }));
|
||||
});
|
||||
|
||||
it("truncates text that exceeds 256 bytes", async () => {
|
||||
const longText = "a".repeat(257);
|
||||
client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🐎", text: longText });
|
||||
const { result } = render();
|
||||
await waitFor(() => expect(result.current).toEqual({ emoji: "🐎", text: `${"a".repeat(256)}…` }));
|
||||
});
|
||||
|
||||
it("returns undefined when M_NOT_FOUND error is thrown", async () => {
|
||||
const error = new Error();
|
||||
client.getExtendedProfileProperty.mockRejectedValue(error);
|
||||
const { result } = render();
|
||||
await waitFor(() => expect(client.getExtendedProfileProperty).toHaveBeenCalled());
|
||||
expect(result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it("updates status when UserProfileUpdate event is emitted", async () => {
|
||||
client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🐎", text: "on a horse" });
|
||||
const { result } = render();
|
||||
await waitFor(() => expect(result.current).toEqual({ emoji: "🐎", text: "on a horse" }));
|
||||
|
||||
// Simulate a profile update event
|
||||
client.emit(ClientEvent.UserProfileUpdate, userId, {
|
||||
"org.matrix.msc4426.status": { emoji: "😵", text: "off a horse" },
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current).toEqual({ emoji: "😵", text: "off a horse" }));
|
||||
});
|
||||
|
||||
it("ignores UserProfileUpdate events for different users", async () => {
|
||||
client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🐎", text: "on a horse" });
|
||||
const { result } = render();
|
||||
await waitFor(() => expect(result.current).toEqual({ emoji: "🐎", text: "on a horse" }));
|
||||
|
||||
client.emit(ClientEvent.UserProfileUpdate, "@bob:example.com", {
|
||||
"org.matrix.msc4426.status": { emoji: "🤷", text: "unrelated status" },
|
||||
});
|
||||
|
||||
// Should still have original status
|
||||
expect(result.current).toEqual({ emoji: "🐎", text: "on a horse" });
|
||||
});
|
||||
});
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { act, render, screen } from "jest-matrix-react";
|
||||
|
||||
import { useTopic } from "../../src/hooks/room/useTopic";
|
||||
import { mkEvent, stubClient } from "../test-utils";
|
||||
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
||||
|
||||
describe("useTopic", () => {
|
||||
it("should display the room topic", () => {
|
||||
stubClient();
|
||||
const room = new Room("!TESTROOM", MatrixClientPeg.safeGet(), "@alice:example.org");
|
||||
const topic = mkEvent({
|
||||
type: "m.room.topic",
|
||||
room: "!TESTROOM",
|
||||
user: "@alice:example.org",
|
||||
content: {
|
||||
topic: "Test topic",
|
||||
},
|
||||
ts: 123,
|
||||
event: true,
|
||||
});
|
||||
|
||||
room.addLiveEvents([topic], { addToState: true });
|
||||
|
||||
function RoomTopic() {
|
||||
const topic = useTopic(room);
|
||||
return <p>{topic!.text}</p>;
|
||||
}
|
||||
|
||||
render(<RoomTopic />);
|
||||
|
||||
expect(screen.queryByText("Test topic")).toBeInTheDocument();
|
||||
|
||||
const updatedTopic = mkEvent({
|
||||
type: "m.room.topic",
|
||||
room: "!TESTROOM",
|
||||
user: "@alice:example.org",
|
||||
content: {
|
||||
topic: "New topic",
|
||||
},
|
||||
ts: 666,
|
||||
event: true,
|
||||
});
|
||||
|
||||
act(() => {
|
||||
room.addLiveEvents([updatedTopic], { addToState: true });
|
||||
});
|
||||
|
||||
expect(screen.queryByText("New topic")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user