Files
element-web/test/test-utils/test-utils.ts
T

453 lines
16 KiB
TypeScript
Raw Normal View History

import EventEmitter from "events";
2022-02-23 12:21:11 +01:00
import { mocked } from 'jest-mock';
2021-10-22 17:23:32 -05:00
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { JoinRule } from 'matrix-js-sdk/src/@types/partials';
2022-02-23 12:21:11 +01:00
import {
Room,
User,
IContent,
IEvent,
RoomMember,
MatrixClient,
EventTimeline,
RoomState,
EventType,
2022-02-23 17:12:48 +01:00
IEventRelation,
2022-02-23 12:21:11 +01:00
} from 'matrix-js-sdk';
2021-10-22 17:23:32 -05:00
2022-02-23 12:21:11 +01:00
import { MatrixClientPeg as peg } from '../../src/MatrixClientPeg';
import dis from '../../src/dispatcher/dispatcher';
import { makeType } from "../../src/utils/TypeUtils";
import { ValidatedServerConfig } from "../../src/utils/AutoDiscoveryUtils";
import { EnhancedMap } from "../../src/utils/maps";
2017-03-16 17:26:42 +00:00
2016-03-28 22:59:34 +01:00
/**
* Stub out the MatrixClient, and configure the MatrixClientPeg object to
* return it when get() is called.
*
* TODO: once the components are updated to get their MatrixClients from
* the react context, we can get rid of this and just inject a test client
* via the context instead.
2016-03-28 22:59:34 +01:00
*/
2016-09-09 18:07:42 +05:30
export function stubClient() {
2017-10-11 17:56:17 +01:00
const client = createTestClient();
// stub out the methods in MatrixClientPeg
//
// 'sandbox.restore()' doesn't work correctly on inherited methods,
// so we do this for each method
2017-10-11 17:56:17 +01:00
const methods = ['get', 'unset', 'replaceUsingCreds'];
for (let i = 0; i < methods.length; i++) {
2022-02-23 12:21:11 +01:00
const methodName = methods[i];
peg[methods[i]] = jest.spyOn(peg, methodName);
}
// MatrixClientPeg.get() is called a /lot/, so implement it with our own
// fast stub function rather than a sinon stub
peg.get = function() { return client; };
}
/**
* Create a stubbed-out MatrixClient
*
* @returns {object} MatrixClient stub
*/
export function createTestClient() {
const eventEmitter = new EventEmitter();
return {
2019-12-16 11:12:48 +00:00
getHomeserverUrl: jest.fn(),
getIdentityServerUrl: jest.fn(),
getDomain: jest.fn().mockReturnValue("matrix.rog"),
getUserId: jest.fn().mockReturnValue("@userId:matrix.rog"),
getUser: jest.fn().mockReturnValue({ on: jest.fn() }),
credentials: { userId: "@userId:matrix.rog" },
2019-12-16 11:12:48 +00:00
getPushActionsForEvent: jest.fn(),
2020-02-13 17:25:54 -05:00
getRoom: jest.fn().mockImplementation(mkStubRoom),
2019-12-16 11:12:48 +00:00
getRooms: jest.fn().mockReturnValue([]),
getVisibleRooms: jest.fn().mockReturnValue([]),
getGroups: jest.fn().mockReturnValue([]),
loginFlows: jest.fn(),
on: eventEmitter.on.bind(eventEmitter),
off: eventEmitter.off.bind(eventEmitter),
removeListener: eventEmitter.removeListener.bind(eventEmitter),
emit: eventEmitter.emit.bind(eventEmitter),
2019-12-16 11:12:48 +00:00
isRoomEncrypted: jest.fn().mockReturnValue(false),
peekInRoom: jest.fn().mockResolvedValue(mkStubRoom()),
2019-12-16 11:12:48 +00:00
paginateEventTimeline: jest.fn().mockResolvedValue(undefined),
sendReadReceipt: jest.fn().mockResolvedValue(undefined),
getRoomIdForAlias: jest.fn().mockResolvedValue(undefined),
getRoomDirectoryVisibility: jest.fn().mockResolvedValue(undefined),
getProfileInfo: jest.fn().mockResolvedValue({}),
2021-04-23 14:39:39 +01:00
getThirdpartyProtocols: jest.fn().mockResolvedValue({}),
getClientWellKnown: jest.fn().mockReturnValue(null),
supportsVoip: jest.fn().mockReturnValue(true),
getTurnServersExpiry: jest.fn().mockReturnValue(2^32),
getThirdpartyUser: jest.fn().mockResolvedValue([]),
2016-09-09 18:07:42 +05:30
getAccountData: (type) => {
return mkEvent({
type,
event: true,
content: {},
});
},
mxcUrlToHttp: (mxc) => `http://this.is.a.url/${mxc.substring(6)}`,
2019-12-16 11:12:48 +00:00
setAccountData: jest.fn(),
setRoomAccountData: jest.fn(),
2019-12-16 11:12:48 +00:00
sendTyping: jest.fn().mockResolvedValue({}),
sendMessage: () => jest.fn().mockResolvedValue({}),
sendStateEvent: jest.fn().mockResolvedValue(),
2016-10-10 17:51:26 +01:00
getSyncState: () => "SYNCING",
generateClientSecret: () => "t35tcl1Ent5ECr3T",
2017-05-02 10:14:54 +01:00
isGuest: () => false,
2020-10-07 00:09:48 +01:00
isCryptoEnabled: () => false,
getRoomHierarchy: jest.fn().mockReturnValue({
2021-04-23 14:45:22 +01:00
rooms: [],
}),
2021-04-21 16:45:21 -06:00
// Used by various internal bits we aren't concerned with (yet)
sessionStore: {
2021-04-21 16:45:21 -06:00
store: {
getItem: jest.fn(),
setItem: jest.fn(),
2021-04-21 16:45:21 -06:00
},
},
2021-08-03 15:43:56 +02:00
pushRules: {},
2021-05-18 13:46:47 +01:00
decryptEventIfNeeded: () => Promise.resolve(),
isUserIgnored: jest.fn().mockReturnValue(false),
2021-07-06 10:34:50 +01:00
getCapabilities: jest.fn().mockResolvedValue({}),
supportsExperimentalThreads: () => false,
getRoomUpgradeHistory: jest.fn().mockReturnValue([]),
getOpenIdToken: jest.fn().mockResolvedValue(),
registerWithIdentityServer: jest.fn().mockResolvedValue({}),
getIdentityAccount: jest.fn().mockResolvedValue({}),
getTerms: jest.fn().mockResolvedValueOnce(),
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(),
2022-01-06 10:47:03 +01:00
getPushRules: jest.fn().mockResolvedValue(),
getPushers: jest.fn().mockResolvedValue({ pushers: [] }),
getThreePids: jest.fn().mockResolvedValue({ threepids: [] }),
setPusher: jest.fn().mockResolvedValue(),
setPushRuleEnabled: jest.fn().mockResolvedValue(),
setPushRuleActions: jest.fn().mockResolvedValue(),
};
2016-03-28 22:59:34 +01:00
}
2022-02-23 12:21:11 +01:00
type MakeEventPassThruProps = {
user: User["userId"];
event?: boolean;
ts?: number;
skey?: string;
};
type MakeEventProps = MakeEventPassThruProps & {
type: string;
content: IContent;
room: Room["roomId"];
// eslint-disable-next-line camelcase
prev_content?: IContent;
};
2016-03-31 00:48:46 +01:00
/**
* Create an Event.
* @param {Object} opts Values for the event.
* @param {string} opts.type The event.type
* @param {string} opts.room The event.room_id
* @param {string} opts.user The event.user_id
2021-04-22 14:45:13 +01:00
* @param {string=} opts.skey Optional. The state key (auto inserts empty string)
* @param {number=} opts.ts Optional. Timestamp for the event
2016-03-31 00:48:46 +01:00
* @param {Object} opts.content The event.content
* @param {boolean} opts.event True to make a MatrixEvent.
2021-08-10 12:25:11 +05:30
* @param {unsigned=} opts.unsigned
2016-03-31 00:48:46 +01:00
* @return {Object} a JSON object representing this event.
*/
2022-02-23 12:21:11 +01:00
export function mkEvent(opts: MakeEventProps): MatrixEvent {
2016-03-31 00:48:46 +01:00
if (!opts.type || !opts.content) {
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
}
2022-02-23 12:21:11 +01:00
const event: Partial<IEvent> = {
2016-03-31 00:48:46 +01:00
type: opts.type,
room_id: opts.room,
sender: opts.user,
content: opts.content,
2017-01-18 11:53:17 +01:00
prev_content: opts.prev_content,
2016-03-31 00:48:46 +01:00
event_id: "$" + Math.random() + "-" + Math.random(),
origin_server_ts: opts.ts,
};
if (opts.skey) {
event.state_key = opts.skey;
} else if ([
"m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
"m.room.power_levels", "m.room.topic", "m.room.history_visibility",
"m.room.encryption", "m.room.member", "com.example.state",
"m.room.guest_access",
].indexOf(opts.type) !== -1) {
2016-03-31 00:48:46 +01:00
event.state_key = "";
}
2022-02-23 12:21:11 +01:00
return opts.event ? new MatrixEvent(event) : event as unknown as MatrixEvent;
2017-10-11 17:56:17 +01:00
}
2016-03-31 00:48:46 +01:00
/**
* Create an m.presence event.
* @param {Object} opts Values for the presence.
* @return {Object|MatrixEvent} The event
*/
2016-09-09 18:07:42 +05:30
export function mkPresence(opts) {
2016-03-31 00:48:46 +01:00
if (!opts.user) {
throw new Error("Missing user");
}
2017-10-11 17:56:17 +01:00
const event = {
2016-03-31 00:48:46 +01:00
event_id: "$" + Math.random() + "-" + Math.random(),
type: "m.presence",
sender: opts.user,
content: {
avatar_url: opts.url,
displayname: opts.name,
last_active_ago: opts.ago,
2017-10-11 17:56:17 +01:00
presence: opts.presence || "offline",
},
2016-03-31 00:48:46 +01:00
};
return opts.event ? new MatrixEvent(event) : event;
2017-10-11 17:56:17 +01:00
}
2016-03-31 00:48:46 +01:00
/**
* Create an m.room.member event.
* @param {Object} opts Values for the membership.
* @param {string} opts.room The room ID for the event.
* @param {string} opts.mship The content.membership for the event.
2017-01-18 11:53:17 +01:00
* @param {string} opts.prevMship The prev_content.membership for the event.
2021-08-10 12:25:11 +05:30
* @param {number=} opts.ts Optional. Timestamp for the event
2016-03-31 00:48:46 +01:00
* @param {string} opts.user The user ID for the event.
2017-01-18 11:53:17 +01:00
* @param {RoomMember} opts.target The target of the event.
2021-08-10 12:25:11 +05:30
* @param {string=} opts.skey The other user ID for the event if applicable
2016-03-31 00:48:46 +01:00
* e.g. for invites/bans.
* @param {string} opts.name The content.displayname for the event.
2021-08-10 12:25:11 +05:30
* @param {string=} opts.url The content.avatar_url for the event.
2016-03-31 00:48:46 +01:00
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event
*/
2022-02-23 12:21:11 +01:00
export function mkMembership(opts: MakeEventPassThruProps & {
room: Room["roomId"];
mship: string;
prevMship?: string;
name?: string;
url?: string;
skey?: string;
target?: RoomMember;
}): MatrixEvent {
const event: MakeEventProps = {
...opts,
type: "m.room.member",
content: {
membership: opts.mship,
},
};
2016-03-31 00:48:46 +01:00
if (!opts.skey) {
2022-02-23 12:21:11 +01:00
event.skey = opts.user;
2016-03-31 00:48:46 +01:00
}
if (!opts.mship) {
throw new Error("Missing .mship => " + JSON.stringify(opts));
}
2022-02-23 12:21:11 +01:00
2017-01-18 11:53:17 +01:00
if (opts.prevMship) {
2022-02-23 12:21:11 +01:00
event.prev_content = { membership: opts.prevMship };
2017-01-18 11:53:17 +01:00
}
2022-02-23 12:21:11 +01:00
if (opts.name) { event.content.displayname = opts.name; }
if (opts.url) { event.content.avatar_url = opts.url; }
const e = mkEvent(event);
2017-01-18 11:53:17 +01:00
if (opts.target) {
e.target = opts.target;
}
return e;
2017-10-11 17:56:17 +01:00
}
2016-03-31 00:48:46 +01:00
2022-02-23 17:12:48 +01:00
export type MessageEventProps = MakeEventPassThruProps & {
room: Room["roomId"];
relatesTo?: IEventRelation;
msg?: string;
};
2016-03-31 00:48:46 +01:00
/**
* Create an m.room.message event.
* @param {Object} opts Values for the message
* @param {string} opts.room The room ID for the event.
* @param {string} opts.user The user ID for the event.
2021-08-03 14:36:21 +05:30
* @param {number} opts.ts The timestamp for the event.
2016-03-31 00:48:46 +01:00
* @param {boolean} opts.event True to make a MatrixEvent.
2021-08-03 14:36:21 +05:30
* @param {string=} opts.msg Optional. The content.body for the event.
2016-03-31 00:48:46 +01:00
* @return {Object|MatrixEvent} The event
*/
2022-02-23 17:12:48 +01:00
export function mkMessage({
msg, relatesTo, ...opts
}: MessageEventProps): MatrixEvent {
2016-03-31 00:48:46 +01:00
if (!opts.room || !opts.user) {
2022-02-23 12:21:11 +01:00
throw new Error("Missing .room or .user from options");
2016-03-31 00:48:46 +01:00
}
2022-02-23 17:12:48 +01:00
const message = msg ?? "Random->" + Math.random();
2022-02-23 12:21:11 +01:00
const event: MakeEventProps = {
...opts,
type: "m.room.message",
content: {
msgtype: "m.text",
body: message,
2022-02-23 17:12:48 +01:00
['m.relates_to']: relatesTo,
2022-02-23 12:21:11 +01:00
},
2016-03-31 00:48:46 +01:00
};
2022-02-23 12:21:11 +01:00
return mkEvent(event);
2016-09-09 18:07:42 +05:30
}
2016-06-17 12:20:26 +01:00
2022-02-23 12:21:11 +01:00
export function mkStubRoom(roomId = null, name: string, client: MatrixClient): Room {
const stubTimeline = { getEvents: () => [] } as unknown as EventTimeline;
2016-06-17 12:20:26 +01:00
return {
2016-09-09 18:07:42 +05:30
roomId,
2019-12-16 11:12:48 +00:00
getReceiptsForEvent: jest.fn().mockReturnValue([]),
getMember: jest.fn().mockReturnValue({
userId: '@member:domain.bla',
name: 'Member',
2020-01-06 13:28:29 +00:00
rawDisplayName: 'Member',
roomId: roomId,
getAvatarUrl: () => 'mxc://avatar.url/image.png',
2021-03-11 09:42:55 -07:00
getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
}),
2019-12-16 11:12:48 +00:00
getMembersWithMembership: jest.fn().mockReturnValue([]),
getJoinedMembers: jest.fn().mockReturnValue([]),
getJoinedMemberCount: jest.fn().mockReturnValue(1),
2021-05-19 12:34:27 +01:00
getMembers: jest.fn().mockReturnValue([]),
2016-10-10 17:51:26 +01:00
getPendingEvents: () => [],
getLiveTimeline: () => stubTimeline,
getUnfilteredTimelineSet: () => null,
2021-05-10 00:54:00 -04:00
findEventById: () => null,
2016-10-10 17:51:26 +01:00
getAccountData: () => null,
hasMembershipState: () => null,
getVersion: () => '1',
2018-08-17 15:15:53 +01:00
shouldUpgradeToVersion: () => null,
2021-04-23 12:19:08 +01:00
getMyMembership: jest.fn().mockReturnValue("join"),
2019-12-16 11:12:48 +00:00
maySendMessage: jest.fn().mockReturnValue(true),
2016-06-17 12:20:26 +01:00
currentState: {
2019-12-16 11:12:48 +00:00
getStateEvents: jest.fn(),
2021-05-19 15:01:05 +05:30
getMember: jest.fn(),
2019-12-16 11:12:48 +00:00
mayClientSendStateEvent: jest.fn().mockReturnValue(true),
maySendStateEvent: jest.fn().mockReturnValue(true),
maySendEvent: jest.fn().mockReturnValue(true),
2022-02-23 12:21:11 +01:00
members: {},
getJoinRule: jest.fn().mockReturnValue(JoinRule.Invite),
on: jest.fn(),
2022-02-23 12:21:11 +01:00
} as unknown as RoomState,
2021-04-23 12:19:08 +01:00
tags: {},
2019-12-16 11:12:48 +00:00
setBlacklistUnverifiedDevices: jest.fn(),
on: jest.fn(),
2021-05-16 08:39:22 -04:00
off: jest.fn(),
2019-12-16 11:12:48 +00:00
removeListener: jest.fn(),
2020-11-05 16:27:41 +00:00
getDMInviter: jest.fn(),
2021-05-08 19:51:51 -04:00
name,
2021-02-03 15:18:19 +00:00
getAvatarUrl: () => 'mxc://avatar.url/room.png',
2021-03-11 09:42:55 -07:00
getMxcAvatarUrl: () => 'mxc://avatar.url/room.png',
2021-04-22 14:45:13 +01:00
isSpaceRoom: jest.fn(() => false),
2021-04-23 12:19:08 +01:00
getUnreadNotificationCount: jest.fn(() => 0),
getEventReadUpTo: jest.fn(() => null),
2021-06-01 20:36:28 -04:00
getCanonicalAlias: jest.fn(),
getAltAliases: jest.fn().mockReturnValue([]),
2021-04-23 12:19:08 +01:00
timeline: [],
2021-07-06 10:44:09 +01:00
getJoinRule: jest.fn().mockReturnValue("invite"),
loadMembersIfNeeded: jest.fn(),
client,
2022-02-23 12:21:11 +01:00
} as unknown as Room;
2016-09-09 18:07:42 +05:30
}
2017-05-24 16:56:13 +01:00
2019-05-02 23:46:43 -06:00
export function mkServerConfig(hsUrl, isUrl) {
return makeType(ValidatedServerConfig, {
hsUrl,
hsName: "TEST_ENVIRONMENT",
hsNameIsDifferent: false, // yes, we lie
isUrl,
});
}
2017-05-24 16:56:13 +01:00
export function getDispatchForStore(store) {
// Mock the dispatcher by gut-wrenching. Stores can only __emitChange whilst a
// dispatcher `_isDispatching` is true.
return (payload) => {
2022-02-23 12:21:11 +01:00
// these are private properties in flux dispatcher
// fool ts
(dis as any)._isDispatching = true;
(dis as any)._callbacks[store._dispatchToken](payload);
(dis as any)._isDispatching = false;
2017-05-24 16:56:13 +01:00
};
}
2018-02-06 17:50:53 +00:00
2022-02-23 12:21:11 +01:00
// These methods make some use of some private methods on the AsyncStoreWithClient to simplify getting into a consistent
// ready state without needing to wire up a dispatcher and pretend to be a js-sdk client.
2018-02-06 17:50:53 +00:00
2022-02-23 12:21:11 +01:00
export const setupAsyncStoreWithClient = async (store: AsyncStoreWithClient<any>, client: MatrixClient) => {
// @ts-ignore
store.readyStore.useUnitTestClient(client);
// @ts-ignore
await store.onReady();
};
export const resetAsyncStoreWithClient = async (store: AsyncStoreWithClient<any>) => {
// @ts-ignore
await store.onNotReady();
};
export const mockStateEventImplementation = (events: MatrixEvent[]): typeof RoomState['getStateEvents'] => {
const stateMap = new EnhancedMap<string, Map<string, MatrixEvent>>();
events.forEach(event => {
stateMap.getOrCreate(event.getType(), new Map()).set(event.getStateKey(), event);
});
2018-02-06 17:50:53 +00:00
2022-02-23 12:21:11 +01:00
return (eventType: string, stateKey?: string) => {
if (stateKey || stateKey === "") {
return stateMap.get(eventType)?.get(stateKey) || null;
2018-02-06 17:50:53 +00:00
}
2022-02-23 12:21:11 +01:00
return Array.from(stateMap.get(eventType)?.values() || []);
};
};
export const mkRoom = (client: MatrixClient, roomId: string, rooms?: ReturnType<typeof mkStubRoom>[]) => {
const room = mkStubRoom(roomId, roomId, client);
mocked(room.currentState).getStateEvents.mockImplementation(mockStateEventImplementation([]));
rooms?.push(room);
return room;
};
2018-05-02 11:19:01 +01:00
/**
2022-02-23 12:21:11 +01:00
* Upserts given events into room.currentState
* @param room
* @param events
2018-05-02 11:19:01 +01:00
*/
2022-02-23 12:21:11 +01:00
export const upsertRoomStateEvents = (room: Room, events: MatrixEvent[]): void => {
const eventsMap = events.reduce((acc, event) => {
const eventType = event.getType();
if (!acc.has(eventType)) {
acc.set(eventType, new Map());
}
acc.get(eventType).set(event.getStateKey(), event);
return acc;
}, room.currentState.events || new Map<string, Map<string, MatrixEvent>>());
2018-05-02 11:19:01 +01:00
2022-02-23 12:21:11 +01:00
room.currentState.events = eventsMap;
};
2018-05-02 11:19:01 +01:00
2022-02-23 12:21:11 +01:00
export const mkSpace = (
client: MatrixClient,
spaceId: string,
rooms?: ReturnType<typeof mkStubRoom>[],
children: string[] = [],
) => {
const space = mkRoom(client, spaceId, rooms);
mocked(space).isSpaceRoom.mockReturnValue(true);
mocked(space.currentState).getStateEvents.mockImplementation(mockStateEventImplementation(children.map(roomId =>
mkEvent({
event: true,
type: EventType.SpaceChild,
room: spaceId,
user: "@user:server",
skey: roomId,
content: { via: [] },
ts: Date.now(),
}),
)));
return space;
};