Files
ThreadNet-Web/apps/web/src/events/EventTileFactory.test.ts
T
Michael TelatynskiandGitHub 67a20c57a3 Move more tests from Jest to Vitest (#34181)
* Move more tests from Jest to Vitest

* Iterate
2026-07-08 12:15:28 +00:00

414 lines
16 KiB
TypeScript

/*
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.
*/
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeAll, beforeEach, afterEach } from "vitest";
import { render } from "test-utils-rtl";
import { EventType, type MatrixClient, MatrixEvent, MsgType, Room, type RoomMember } from "matrix-js-sdk/src/matrix";
import { createTestClient, mkEvent, withClientContextRenderOptions } from "test-utils";
import {
JSONEventFactory,
MessageEventFactory,
pickFactory,
renderTile,
RoomCreateEventFactory,
} from "./EventTileFactory";
import SettingsStore from "../settings/SettingsStore";
import { TimelineRenderingType } from "../contexts/RoomContext";
import { ModuleApi } from "../modules/Api";
import DMRoomMap from "../utils/DMRoomMap";
import { MatrixClientPeg } from "../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());
vi.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(() => {
vi.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(() => {
vi.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;
vi.restoreAllMocks();
});
it("rendering a tile defers to the module API", () => {
ModuleApi.instance.customComponents.renderMessage = vi.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 = vi.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());
vi.spyOn(room, "getMember").mockImplementation((userId: string) => {
if (userId === sender) return { name: "Alice" } as RoomMember;
return null;
});
vi.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");
const { getByText } = render(tile, withClientContextRenderOptions(client));
expect(getByText("Alice wants to verify")).toBeVisible();
expect(getByText("Alice (@alice:example.com)")).toBeVisible();
});
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());
vi.spyOn(room, "getMember").mockImplementation((userId: string) => {
if (userId === recipient) return { name: "Alice" } as RoomMember;
return null;
});
vi.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");
const { getByText } = render(tile, withClientContextRenderOptions(client));
expect(getByText("You sent a verification request")).toBeInTheDocument();
expect(getByText("Alice (@alice:example.com)")).toBeInTheDocument();
});
it("throws when a key verification request tile is rendered without a client context", () => {
vi.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",
},
}),
]);
vi.mocked(client.getRoom).mockReturnValue(room);
vi.spyOn(DMRoomMap, "shared").mockReturnValue({
getUserIdForRoomId: vi.fn().mockReturnValue(null),
} as unknown as DMRoomMap);
vi.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");
const { getByText, getByRole } = render(tile, withClientContextRenderOptions(client));
expect(getByText("Alice changed the room avatar to")).toBeInTheDocument();
expect(getByRole("button", { name: "Alice changed the avatar for General" })).toBeInTheDocument();
});
});