1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-31 15:24:23 +03:00

Test typescriptification - room-member and room-state (#2601)

* renamed:    spec/MockStorageApi.js -> spec/MockStorageApi.ts

* renamed:    spec/olm-loader.js -> spec/olm-loader.t

* renamed:    spec/unit/room-state.spec.js -> spec/unit/room-state.spec.ts

* ts fixes in room-state.spec

* renamed:    spec/unit/room-member.spec.js -> spec/unit/room-member.spec.ts

* ts fixes in room-member.spec

* strict mode fixes for MockStorageApi

* strict ts fixes in room-state

* strict errors
This commit is contained in:
Kerry
2022-09-05 10:38:05 +02:00
committed by GitHub
parent e87ce873b0
commit 37187ef347
7 changed files with 380 additions and 333 deletions

View File

@ -1,6 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C. Copyright 2019, 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -17,31 +17,32 @@ limitations under the License.
/** /**
* A mock implementation of the webstorage api * A mock implementation of the webstorage api
* @constructor
*/ */
export function MockStorageApi() { export class MockStorageApi {
this.data = {}; public data: Record<string, string> = {};
this.keys = []; public keys: string[] = [];
this.length = 0; public length = 0;
}
MockStorageApi.prototype = { public setItem(k: string, v: string): void {
setItem: function(k, v) {
this.data[k] = v; this.data[k] = v;
this._recalc(); this.recalc();
}, }
getItem: function(k) {
public getItem(k: string): string | null {
return this.data[k] || null; return this.data[k] || null;
}, }
removeItem: function(k) {
public removeItem(k: string): void {
delete this.data[k]; delete this.data[k];
this._recalc(); this.recalc();
}, }
key: function(index) {
public key(index: number): string {
return this.keys[index]; return this.keys[index];
}, }
_recalc: function() {
const keys = []; private recalc(): void {
const keys: string[] = [];
for (const k in this.data) { for (const k in this.data) {
if (!this.data.hasOwnProperty(k)) { if (!this.data.hasOwnProperty(k)) {
continue; continue;
@ -50,6 +51,5 @@ MockStorageApi.prototype = {
} }
this.keys = keys; this.keys = keys;
this.length = keys.length; this.length = keys.length;
}, }
}; }

View File

@ -50,7 +50,7 @@ export class TestClient {
options?: Partial<ICreateClientOpts>, options?: Partial<ICreateClientOpts>,
) { ) {
if (sessionStoreBackend === undefined) { if (sessionStoreBackend === undefined) {
sessionStoreBackend = new MockStorageApi(); sessionStoreBackend = new MockStorageApi() as unknown as Storage;
} }
this.httpBackend = new MockHttpBackend(); this.httpBackend = new MockHttpBackend();

View File

@ -20,6 +20,7 @@ import * as utils from "../src/utils";
// try to load the olm library. // try to load the olm library.
try { try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
global.Olm = require('@matrix-org/olm'); global.Olm = require('@matrix-org/olm');
logger.log('loaded libolm'); logger.log('loaded libolm');
} catch (e) { } catch (e) {
@ -28,6 +29,7 @@ try {
// also try to set node crypto // also try to set node crypto
try { try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const crypto = require('crypto'); const crypto = require('crypto');
utils.setCrypto(crypto); utils.setCrypto(crypto);
} catch (err) { } catch (err) {

View File

@ -24,5 +24,5 @@ limitations under the License.
* expect(beaconLivenessEmits.length).toBe(1); * expect(beaconLivenessEmits.length).toBe(1);
* ``` * ```
*/ */
export const filterEmitCallsByEventType = (eventType: string, spy: jest.SpyInstance<any, unknown[]>) => export const filterEmitCallsByEventType = (eventType: string, spy: jest.SpyInstance<any, any[]>) =>
spy.mock.calls.filter((args) => args[0] === eventType); spy.mock.calls.filter((args) => args[0] === eventType);

View File

@ -15,6 +15,7 @@ import { CRYPTO_ENABLED } from "../../src/client";
import { DeviceInfo } from "../../src/crypto/deviceinfo"; import { DeviceInfo } from "../../src/crypto/deviceinfo";
import { logger } from '../../src/logger'; import { logger } from '../../src/logger';
import { MemoryStore } from "../../src"; import { MemoryStore } from "../../src";
import { IStore } from '../../src/store';
const Olm = global.Olm; const Olm = global.Olm;
@ -158,8 +159,8 @@ describe("Crypto", function() {
let fakeEmitter; let fakeEmitter;
beforeEach(async function() { beforeEach(async function() {
const mockStorage = new MockStorageApi(); const mockStorage = new MockStorageApi() as unknown as Storage;
const clientStore = new MemoryStore({ localStorage: mockStorage }); const clientStore = new MemoryStore({ localStorage: mockStorage }) as unknown as IStore;
const cryptoStore = new MemoryCryptoStore(); const cryptoStore = new MemoryCryptoStore();
cryptoStore.storeEndToEndDeviceData({ cryptoStore.storeEndToEndDeviceData({
@ -469,12 +470,12 @@ describe("Crypto", function() {
jest.setTimeout(10000); jest.setTimeout(10000);
const client = (new TestClient("@a:example.com", "dev")).client; const client = (new TestClient("@a:example.com", "dev")).client;
await client.initCrypto(); await client.initCrypto();
client.crypto.getSecretStorageKey = async () => null; client.crypto.getSecretStorageKey = jest.fn().mockResolvedValue(null);
client.crypto.isCrossSigningReady = async () => false; client.crypto.isCrossSigningReady = async () => false;
client.crypto.baseApis.uploadDeviceSigningKeys = jest.fn().mockResolvedValue(null); client.crypto.baseApis.uploadDeviceSigningKeys = jest.fn().mockResolvedValue(null);
client.crypto.baseApis.setAccountData = () => null; client.crypto.baseApis.setAccountData = jest.fn().mockResolvedValue(null);
client.crypto.baseApis.uploadKeySignatures = () => null; client.crypto.baseApis.uploadKeySignatures = jest.fn();
client.crypto.baseApis.http.authedRequest = () => null; client.crypto.baseApis.http.authedRequest = jest.fn();
const createSecretStorageKey = async () => { const createSecretStorageKey = async () => {
return { return {
keyInfo: undefined, // Returning undefined here used to cause a crash keyInfo: undefined, // Returning undefined here used to cause a crash

View File

@ -1,12 +1,29 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as utils from "../test-utils/test-utils"; import * as utils from "../test-utils/test-utils";
import { RoomMember } from "../../src/models/room-member"; import { RoomMember, RoomMemberEvent } from "../../src/models/room-member";
import { RoomState } from "../../src";
describe("RoomMember", function() { describe("RoomMember", function() {
const roomId = "!foo:bar"; const roomId = "!foo:bar";
const userA = "@alice:bar"; const userA = "@alice:bar";
const userB = "@bertha:bar"; const userB = "@bertha:bar";
const userC = "@clarissa:bar"; const userC = "@clarissa:bar";
let member; let member = new RoomMember(roomId, userA);
beforeEach(function() { beforeEach(function() {
member = new RoomMember(roomId, userA); member = new RoomMember(roomId, userA);
@ -27,17 +44,17 @@ describe("RoomMember", function() {
avatar_url: "mxc://flibble/wibble", avatar_url: "mxc://flibble/wibble",
}, },
}); });
const url = member.getAvatarUrl(hsUrl); const url = member.getAvatarUrl(hsUrl, 1, 1, '', false, false);
// we don't care about how the mxc->http conversion is done, other // we don't care about how the mxc->http conversion is done, other
// than it contains the mxc body. // than it contains the mxc body.
expect(url.indexOf("flibble/wibble")).not.toEqual(-1); expect(url?.indexOf("flibble/wibble")).not.toEqual(-1);
}); });
it("should return nothing if there is no m.room.member and allowDefault=false", it("should return nothing if there is no m.room.member and allowDefault=false",
function() { function() {
const url = member.getAvatarUrl(hsUrl, 64, 64, "crop", false); const url = member.getAvatarUrl(hsUrl, 64, 64, "crop", false, false);
expect(url).toEqual(null); expect(url).toEqual(null);
}); });
}); });
describe("setPowerLevelEvent", function() { describe("setPowerLevelEvent", function() {
@ -66,92 +83,92 @@ describe("RoomMember", function() {
}); });
it("should emit 'RoomMember.powerLevel' if the power level changes.", it("should emit 'RoomMember.powerLevel' if the power level changes.",
function() { function() {
const event = utils.mkEvent({ const event = utils.mkEvent({
type: "m.room.power_levels", type: "m.room.power_levels",
room: roomId, room: roomId,
user: userA, user: userA,
content: { content: {
users_default: 20, users_default: 20,
users: { users: {
"@bertha:bar": 200, "@bertha:bar": 200,
"@invalid:user": 10, // shouldn't barf on this. "@invalid:user": 10, // shouldn't barf on this.
},
}, },
}, event: true,
event: true, });
}); let emitCount = 0;
let emitCount = 0;
member.on("RoomMember.powerLevel", function(emitEvent, emitMember) { member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
emitCount += 1; emitCount += 1;
expect(emitMember).toEqual(member); expect(emitMember).toEqual(member);
expect(emitEvent).toEqual(event); expect(emitEvent).toEqual(event);
}); });
member.setPowerLevelEvent(event); member.setPowerLevelEvent(event);
expect(emitCount).toEqual(1); expect(emitCount).toEqual(1);
member.setPowerLevelEvent(event); // no-op member.setPowerLevelEvent(event); // no-op
expect(emitCount).toEqual(1); expect(emitCount).toEqual(1);
}); });
it("should honour power levels of zero.", it("should honour power levels of zero.",
function() { function() {
const event = utils.mkEvent({ const event = utils.mkEvent({
type: "m.room.power_levels", type: "m.room.power_levels",
room: roomId, room: roomId,
user: userA, user: userA,
content: { content: {
users_default: 20, users_default: 20,
users: { users: {
"@alice:bar": 0, "@alice:bar": 0,
},
}, },
}, event: true,
event: true, });
}); let emitCount = 0;
let emitCount = 0;
// set the power level to something other than zero or we // set the power level to something other than zero or we
// won't get an event // won't get an event
member.powerLevel = 1; member.powerLevel = 1;
member.on("RoomMember.powerLevel", function(emitEvent, emitMember) { member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
emitCount += 1; emitCount += 1;
expect(emitMember.userId).toEqual('@alice:bar'); expect(emitMember.userId).toEqual('@alice:bar');
expect(emitMember.powerLevel).toEqual(0); expect(emitMember.powerLevel).toEqual(0);
expect(emitEvent).toEqual(event); expect(emitEvent).toEqual(event);
}); });
member.setPowerLevelEvent(event); member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(0); expect(member.powerLevel).toEqual(0);
expect(emitCount).toEqual(1); expect(emitCount).toEqual(1);
}); });
it("should not honor string power levels.", it("should not honor string power levels.",
function() { function() {
const event = utils.mkEvent({ const event = utils.mkEvent({
type: "m.room.power_levels", type: "m.room.power_levels",
room: roomId, room: roomId,
user: userA, user: userA,
content: { content: {
users_default: 20, users_default: 20,
users: { users: {
"@alice:bar": "5", "@alice:bar": "5",
},
}, },
}, event: true,
event: true, });
}); let emitCount = 0;
let emitCount = 0;
member.on("RoomMember.powerLevel", function(emitEvent, emitMember) { member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
emitCount += 1; emitCount += 1;
expect(emitMember.userId).toEqual('@alice:bar'); expect(emitMember.userId).toEqual('@alice:bar');
expect(emitMember.powerLevel).toEqual(20); expect(emitMember.powerLevel).toEqual(20);
expect(emitEvent).toEqual(event); expect(emitEvent).toEqual(event);
}); });
member.setPowerLevelEvent(event); member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(20); expect(member.powerLevel).toEqual(20);
expect(emitCount).toEqual(1); expect(emitCount).toEqual(1);
}); });
}); });
describe("setTypingEvent", function() { describe("setTypingEvent", function() {
@ -183,34 +200,34 @@ describe("RoomMember", function() {
}); });
it("should emit 'RoomMember.typing' if the typing state changes", it("should emit 'RoomMember.typing' if the typing state changes",
function() { function() {
const event = utils.mkEvent({ const event = utils.mkEvent({
type: "m.typing", type: "m.typing",
room: roomId, room: roomId,
content: { content: {
user_ids: [ user_ids: [
userA, userC, userA, userC,
], ],
}, },
event: true, event: true,
});
let emitCount = 0;
member.on(RoomMemberEvent.Typing, function(ev, mem) {
expect(mem).toEqual(member);
expect(ev).toEqual(event);
emitCount += 1;
});
member.typing = false;
member.setTypingEvent(event);
expect(emitCount).toEqual(1);
member.setTypingEvent(event); // no-op
expect(emitCount).toEqual(1);
}); });
let emitCount = 0;
member.on("RoomMember.typing", function(ev, mem) {
expect(mem).toEqual(member);
expect(ev).toEqual(event);
emitCount += 1;
});
member.typing = false;
member.setTypingEvent(event);
expect(emitCount).toEqual(1);
member.setTypingEvent(event); // no-op
expect(emitCount).toEqual(1);
});
}); });
describe("isOutOfBand", function() { describe("isOutOfBand", function() {
it("should be set by markOutOfBand", function() { it("should be set by markOutOfBand", function() {
const member = new RoomMember(); const member = new RoomMember(roomId, userA);
expect(member.isOutOfBand()).toEqual(false); expect(member.isOutOfBand()).toEqual(false);
member.markOutOfBand(); member.markOutOfBand();
expect(member.isOutOfBand()).toEqual(true); expect(member.isOutOfBand()).toEqual(true);
@ -235,50 +252,50 @@ describe("RoomMember", function() {
}); });
it("should set 'membership' and assign the event to 'events.member'.", it("should set 'membership' and assign the event to 'events.member'.",
function() { function() {
member.setMembershipEvent(inviteEvent); member.setMembershipEvent(inviteEvent);
expect(member.membership).toEqual("invite"); expect(member.membership).toEqual("invite");
expect(member.events.member).toEqual(inviteEvent); expect(member.events.member).toEqual(inviteEvent);
member.setMembershipEvent(joinEvent); member.setMembershipEvent(joinEvent);
expect(member.membership).toEqual("join"); expect(member.membership).toEqual("join");
expect(member.events.member).toEqual(joinEvent); expect(member.events.member).toEqual(joinEvent);
}); });
it("should set 'name' based on user_id, displayname and room state", it("should set 'name' based on user_id, displayname and room state",
function() { function() {
const roomState = { const roomState = {
getStateEvents: function(type) { getStateEvents: function(type) {
if (type !== "m.room.member") { if (type !== "m.room.member") {
return []; return [];
} }
return [ return [
utils.mkMembership({ utils.mkMembership({
event: true, mship: "join", room: roomId, event: true, mship: "join", room: roomId,
user: userB, user: userB,
}), }),
utils.mkMembership({ utils.mkMembership({
event: true, mship: "join", room: roomId, event: true, mship: "join", room: roomId,
user: userC, name: "Alice", user: userC, name: "Alice",
}), }),
joinEvent, joinEvent,
]; ];
}, },
getUserIdsWithDisplayName: function(displayName) { getUserIdsWithDisplayName: function(displayName) {
return [userA, userC]; return [userA, userC];
}, },
}; } as unknown as RoomState;
expect(member.name).toEqual(userA); // default = user_id expect(member.name).toEqual(userA); // default = user_id
member.setMembershipEvent(joinEvent); member.setMembershipEvent(joinEvent);
expect(member.name).toEqual("Alice"); // prefer displayname expect(member.name).toEqual("Alice"); // prefer displayname
member.setMembershipEvent(joinEvent, roomState); member.setMembershipEvent(joinEvent, roomState);
expect(member.name).not.toEqual("Alice"); // it should disambig. expect(member.name).not.toEqual("Alice"); // it should disambig.
// user_id should be there somewhere // user_id should be there somewhere
expect(member.name.indexOf(userA)).not.toEqual(-1); expect(member.name.indexOf(userA)).not.toEqual(-1);
}); });
it("should emit 'RoomMember.membership' if the membership changes", function() { it("should emit 'RoomMember.membership' if the membership changes", function() {
let emitCount = 0; let emitCount = 0;
member.on("RoomMember.membership", function(ev, mem) { member.on(RoomMemberEvent.Membership, function(ev, mem) {
emitCount += 1; emitCount += 1;
expect(mem).toEqual(member); expect(mem).toEqual(member);
expect(ev).toEqual(inviteEvent); expect(ev).toEqual(inviteEvent);
@ -291,7 +308,7 @@ describe("RoomMember", function() {
it("should emit 'RoomMember.name' if the name changes", function() { it("should emit 'RoomMember.name' if the name changes", function() {
let emitCount = 0; let emitCount = 0;
member.on("RoomMember.name", function(ev, mem) { member.on(RoomMemberEvent.Name, function(ev, mem) {
emitCount += 1; emitCount += 1;
expect(mem).toEqual(member); expect(mem).toEqual(member);
expect(ev).toEqual(joinEvent); expect(ev).toEqual(joinEvent);
@ -341,7 +358,7 @@ describe("RoomMember", function() {
getUserIdsWithDisplayName: function(displayName) { getUserIdsWithDisplayName: function(displayName) {
return [userA, userC]; return [userA, userC];
}, },
}; } as unknown as RoomState;
expect(member.name).toEqual(userA); // default = user_id expect(member.name).toEqual(userA); // default = user_id
member.setMembershipEvent(joinEvent, roomState); member.setMembershipEvent(joinEvent, roomState);
expect(member.name).not.toEqual("Alíce"); // it should disambig. expect(member.name).not.toEqual("Alíce"); // it should disambig.

View File

@ -1,14 +1,37 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MockedObject } from 'jest-mock';
import * as utils from "../test-utils/test-utils"; import * as utils from "../test-utils/test-utils";
import { makeBeaconEvent, makeBeaconInfoEvent } from "../test-utils/beacon"; import { makeBeaconEvent, makeBeaconInfoEvent } from "../test-utils/beacon";
import { filterEmitCallsByEventType } from "../test-utils/emitter"; import { filterEmitCallsByEventType } from "../test-utils/emitter";
import { RoomState, RoomStateEvent } from "../../src/models/room-state"; import { RoomState, RoomStateEvent } from "../../src/models/room-state";
import { BeaconEvent, getBeaconInfoIdentifier } from "../../src/models/beacon"; import {
Beacon,
BeaconEvent,
getBeaconInfoIdentifier,
} from "../../src/models/beacon";
import { EventType, RelationType, UNSTABLE_MSC2716_MARKER } from "../../src/@types/event"; import { EventType, RelationType, UNSTABLE_MSC2716_MARKER } from "../../src/@types/event";
import { import {
MatrixEvent, MatrixEvent,
MatrixEventEvent, MatrixEventEvent,
} from "../../src/models/event"; } from "../../src/models/event";
import { M_BEACON } from "../../src/@types/beacon"; import { M_BEACON } from "../../src/@types/beacon";
import { MatrixClient } from "../../src/client";
describe("RoomState", function() { describe("RoomState", function() {
const roomId = "!foo:bar"; const roomId = "!foo:bar";
@ -17,7 +40,7 @@ describe("RoomState", function() {
const userC = "@cleo:bar"; const userC = "@cleo:bar";
const userLazy = "@lazy:bar"; const userLazy = "@lazy:bar";
let state; let state = new RoomState(roomId);
beforeEach(function() { beforeEach(function() {
state = new RoomState(roomId); state = new RoomState(roomId);
@ -67,8 +90,8 @@ describe("RoomState", function() {
it("should return a member which changes as state changes", function() { it("should return a member which changes as state changes", function() {
const member = state.getMember(userB); const member = state.getMember(userB);
expect(member.membership).toEqual("join"); expect(member?.membership).toEqual("join");
expect(member.name).toEqual(userB); expect(member?.name).toEqual(userB);
state.setStateEvents([ state.setStateEvents([
utils.mkMembership({ utils.mkMembership({
@ -77,40 +100,40 @@ describe("RoomState", function() {
}), }),
]); ]);
expect(member.membership).toEqual("leave"); expect(member?.membership).toEqual("leave");
expect(member.name).toEqual("BobGone"); expect(member?.name).toEqual("BobGone");
}); });
}); });
describe("getSentinelMember", function() { describe("getSentinelMember", function() {
it("should return a member with the user id as name", function() { it("should return a member with the user id as name", function() {
expect(state.getSentinelMember("@no-one:here").name).toEqual("@no-one:here"); expect(state.getSentinelMember("@no-one:here")?.name).toEqual("@no-one:here");
}); });
it("should return a member which doesn't change when the state is updated", it("should return a member which doesn't change when the state is updated",
function() { function() {
const preLeaveUser = state.getSentinelMember(userA); const preLeaveUser = state.getSentinelMember(userA);
state.setStateEvents([ state.setStateEvents([
utils.mkMembership({ utils.mkMembership({
room: roomId, user: userA, mship: "leave", event: true, room: roomId, user: userA, mship: "leave", event: true,
name: "AliceIsGone", name: "AliceIsGone",
}), }),
]); ]);
const postLeaveUser = state.getSentinelMember(userA); const postLeaveUser = state.getSentinelMember(userA);
expect(preLeaveUser.membership).toEqual("join"); expect(preLeaveUser?.membership).toEqual("join");
expect(preLeaveUser.name).toEqual(userA); expect(preLeaveUser?.name).toEqual(userA);
expect(postLeaveUser.membership).toEqual("leave"); expect(postLeaveUser?.membership).toEqual("leave");
expect(postLeaveUser.name).toEqual("AliceIsGone"); expect(postLeaveUser?.name).toEqual("AliceIsGone");
}); });
}); });
describe("getStateEvents", function() { describe("getStateEvents", function() {
it("should return null if a state_key was specified and there was no match", it("should return null if a state_key was specified and there was no match",
function() { function() {
expect(state.getStateEvents("foo.bar.baz", "keyname")).toEqual(null); expect(state.getStateEvents("foo.bar.baz", "keyname")).toEqual(null);
}); });
it("should return an empty list if a state_key was not specified and there" + it("should return an empty list if a state_key was not specified and there" +
" was no match", function() { " was no match", function() {
@ -118,21 +141,21 @@ describe("RoomState", function() {
}); });
it("should return a list of matching events if no state_key was specified", it("should return a list of matching events if no state_key was specified",
function() { function() {
const events = state.getStateEvents("m.room.member"); const events = state.getStateEvents("m.room.member");
expect(events.length).toEqual(2); expect(events.length).toEqual(2);
// ordering unimportant // ordering unimportant
expect([userA, userB].indexOf(events[0].getStateKey())).not.toEqual(-1); expect([userA, userB].indexOf(events[0].getStateKey() as string)).not.toEqual(-1);
expect([userA, userB].indexOf(events[1].getStateKey())).not.toEqual(-1); expect([userA, userB].indexOf(events[1].getStateKey() as string)).not.toEqual(-1);
}); });
it("should return a single MatrixEvent if a state_key was specified", it("should return a single MatrixEvent if a state_key was specified",
function() { function() {
const event = state.getStateEvents("m.room.member", userA); const event = state.getStateEvents("m.room.member", userA);
expect(event.getContent()).toMatchObject({ expect(event.getContent()).toMatchObject({
membership: "join", membership: "join",
});
}); });
});
}); });
describe("setStateEvents", function() { describe("setStateEvents", function() {
@ -146,7 +169,7 @@ describe("RoomState", function() {
}), }),
]; ];
let emitCount = 0; let emitCount = 0;
state.on("RoomState.members", function(ev, st, mem) { state.on(RoomStateEvent.Members, function(ev, st, mem) {
expect(ev).toEqual(memberEvents[emitCount]); expect(ev).toEqual(memberEvents[emitCount]);
expect(st).toEqual(state); expect(st).toEqual(state);
expect(mem).toEqual(state.getMember(ev.getSender())); expect(mem).toEqual(state.getMember(ev.getSender()));
@ -166,7 +189,7 @@ describe("RoomState", function() {
}), }),
]; ];
let emitCount = 0; let emitCount = 0;
state.on("RoomState.newMember", function(ev, st, mem) { state.on(RoomStateEvent.NewMember, function(ev, st, mem) {
expect(state.getMember(mem.userId)).toEqual(mem); expect(state.getMember(mem.userId)).toEqual(mem);
expect(mem.userId).toEqual(memberEvents[emitCount].getSender()); expect(mem.userId).toEqual(memberEvents[emitCount].getSender());
expect(mem.membership).toBeFalsy(); // not defined yet expect(mem.membership).toBeFalsy(); // not defined yet
@ -192,7 +215,7 @@ describe("RoomState", function() {
}), }),
]; ];
let emitCount = 0; let emitCount = 0;
state.on("RoomState.events", function(ev, st) { state.on(RoomStateEvent.Events, function(ev, st) {
expect(ev).toEqual(events[emitCount]); expect(ev).toEqual(events[emitCount]);
expect(st).toEqual(state); expect(st).toEqual(state);
emitCount += 1; emitCount += 1;
@ -272,7 +295,7 @@ describe("RoomState", function() {
}), }),
]; ];
let emitCount = 0; let emitCount = 0;
state.on("RoomState.Marker", function(markerEvent, markerFoundOptions) { state.on(RoomStateEvent.Marker, function(markerEvent, markerFoundOptions) {
expect(markerEvent).toEqual(events[emitCount]); expect(markerEvent).toEqual(events[emitCount]);
expect(markerFoundOptions).toEqual({ timelineWasEmpty: true }); expect(markerFoundOptions).toEqual({ timelineWasEmpty: true });
emitCount += 1; emitCount += 1;
@ -296,7 +319,7 @@ describe("RoomState", function() {
it('does not add redacted beacon info events to state', () => { it('does not add redacted beacon info events to state', () => {
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId); const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
const redactionEvent = { event: { type: 'm.room.redaction' } }; const redactionEvent = new MatrixEvent({ type: 'm.room.redaction' });
redactedBeaconEvent.makeRedacted(redactionEvent); redactedBeaconEvent.makeRedacted(redactionEvent);
const emitSpy = jest.spyOn(state, 'emit'); const emitSpy = jest.spyOn(state, 'emit');
@ -316,27 +339,27 @@ describe("RoomState", function() {
state.setStateEvents([beaconEvent]); state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent)); const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
expect(beaconInstance.isLive).toEqual(true); expect(beaconInstance?.isLive).toEqual(true);
state.setStateEvents([updatedBeaconEvent]); state.setStateEvents([updatedBeaconEvent]);
// same Beacon // same Beacon
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance); expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance);
// updated liveness // updated liveness
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent)).isLive).toEqual(false); expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))?.isLive).toEqual(false);
}); });
it('destroys and removes redacted beacon events', () => { it('destroys and removes redacted beacon events', () => {
const beaconId = '$beacon1'; const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId); const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId); const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactionEvent = { event: { type: 'm.room.redaction', redacts: beaconEvent.getId() } }; const redactionEvent = new MatrixEvent({ type: 'm.room.redaction', redacts: beaconEvent.getId() });
redactedBeaconEvent.makeRedacted(redactionEvent); redactedBeaconEvent.makeRedacted(redactionEvent);
state.setStateEvents([beaconEvent]); state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent)); const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
const destroySpy = jest.spyOn(beaconInstance, 'destroy'); const destroySpy = jest.spyOn(beaconInstance as Beacon, 'destroy');
expect(beaconInstance.isLive).toEqual(true); expect(beaconInstance?.isLive).toEqual(true);
state.setStateEvents([redactedBeaconEvent]); state.setStateEvents([redactedBeaconEvent]);
@ -357,7 +380,7 @@ describe("RoomState", function() {
// live beacon is now not live // live beacon is now not live
const updatedLiveBeaconEvent = makeBeaconInfoEvent( const updatedLiveBeaconEvent = makeBeaconInfoEvent(
userA, roomId, { isLive: false }, liveBeaconEvent.getId(), '$beacon1', userA, roomId, { isLive: false }, liveBeaconEvent.getId(),
); );
state.setStateEvents([updatedLiveBeaconEvent]); state.setStateEvents([updatedLiveBeaconEvent]);
@ -377,8 +400,8 @@ describe("RoomState", function() {
state.markOutOfBandMembersStarted(); state.markOutOfBandMembersStarted();
state.setOutOfBandMembers([oobMemberEvent]); state.setOutOfBandMembers([oobMemberEvent]);
const member = state.getMember(userLazy); const member = state.getMember(userLazy);
expect(member.userId).toEqual(userLazy); expect(member?.userId).toEqual(userLazy);
expect(member.isOutOfBand()).toEqual(true); expect(member?.isOutOfBand()).toEqual(true);
}); });
it("should have no effect when not in correct status", function() { it("should have no effect when not in correct status", function() {
@ -394,7 +417,7 @@ describe("RoomState", function() {
user: userLazy, mship: "join", room: roomId, event: true, user: userLazy, mship: "join", room: roomId, event: true,
}); });
let eventReceived = false; let eventReceived = false;
state.once('RoomState.newMember', (_, __, member) => { state.once(RoomStateEvent.NewMember, (_event, _state, member) => {
expect(member.userId).toEqual(userLazy); expect(member.userId).toEqual(userLazy);
eventReceived = true; eventReceived = true;
}); });
@ -410,8 +433,8 @@ describe("RoomState", function() {
state.markOutOfBandMembersStarted(); state.markOutOfBandMembersStarted();
state.setOutOfBandMembers([oobMemberEvent]); state.setOutOfBandMembers([oobMemberEvent]);
const memberA = state.getMember(userA); const memberA = state.getMember(userA);
expect(memberA.events.member.getId()).not.toEqual(oobMemberEvent.getId()); expect(memberA?.events?.member?.getId()).not.toEqual(oobMemberEvent.getId());
expect(memberA.isOutOfBand()).toEqual(false); expect(memberA?.isOutOfBand()).toEqual(false);
}); });
it("should emit members when updating a member", function() { it("should emit members when updating a member", function() {
@ -420,7 +443,7 @@ describe("RoomState", function() {
user: doesntExistYetUserId, mship: "join", room: roomId, event: true, user: doesntExistYetUserId, mship: "join", room: roomId, event: true,
}); });
let eventReceived = false; let eventReceived = false;
state.once('RoomState.members', (_, __, member) => { state.once(RoomStateEvent.Members, (_event, _state, member) => {
expect(member.userId).toEqual(doesntExistYetUserId); expect(member.userId).toEqual(doesntExistYetUserId);
eventReceived = true; eventReceived = true;
}); });
@ -443,8 +466,8 @@ describe("RoomState", function() {
[userA, userB, userLazy].forEach((userId) => { [userA, userB, userLazy].forEach((userId) => {
const member = state.getMember(userId); const member = state.getMember(userId);
const memberCopy = copy.getMember(userId); const memberCopy = copy.getMember(userId);
expect(member.name).toEqual(memberCopy.name); expect(member?.name).toEqual(memberCopy?.name);
expect(member.isOutOfBand()).toEqual(memberCopy.isOutOfBand()); expect(member?.isOutOfBand()).toEqual(memberCopy?.isOutOfBand());
}); });
// check member keys // check member keys
expect(Object.keys(state.members)).toEqual(Object.keys(copy.members)); expect(Object.keys(state.members)).toEqual(Object.keys(copy.members));
@ -496,78 +519,80 @@ describe("RoomState", function() {
describe("maySendStateEvent", function() { describe("maySendStateEvent", function() {
it("should say any member may send state with no power level event", it("should say any member may send state with no power level event",
function() { function() {
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true); expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
}); });
it("should say members with power >=50 may send state with power level event " + it("should say members with power >=50 may send state with power level event " +
"but no state default", "but no state default",
function() { function() {
const powerLevelEvent = { const powerLevelEvent = new MatrixEvent({
type: "m.room.power_levels", room: roomId, user: userA, event: true, type: "m.room.power_levels", room_id: roomId, sender: userA,
state_key: "",
content: { content: {
users_default: 10, users_default: 10,
// state_default: 50, "intentionally left blank" // state_default: 50, "intentionally left blank"
events_default: 25, events_default: 25,
users: { users: {
[userA]: 50,
}, },
}, },
}; });
powerLevelEvent.content.users[userA] = 50;
state.setStateEvents([utils.mkEvent(powerLevelEvent)]); state.setStateEvents([powerLevelEvent]);
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true); expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
expect(state.maySendStateEvent('m.room.name', userB)).toEqual(false); expect(state.maySendStateEvent('m.room.name', userB)).toEqual(false);
}); });
it("should obey state_default", it("should obey state_default",
function() { function() {
const powerLevelEvent = { const powerLevelEvent = new MatrixEvent({
type: "m.room.power_levels", room: roomId, user: userA, event: true, type: "m.room.power_levels", room_id: roomId, sender: userA,
content: { state_key: "",
users_default: 10, content: {
state_default: 30, users_default: 10,
events_default: 25, state_default: 30,
users: { events_default: 25,
users: {
[userA]: 30,
[userB]: 29,
},
}, },
}, });
};
powerLevelEvent.content.users[userA] = 30;
powerLevelEvent.content.users[userB] = 29;
state.setStateEvents([utils.mkEvent(powerLevelEvent)]); state.setStateEvents([powerLevelEvent]);
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true); expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
expect(state.maySendStateEvent('m.room.name', userB)).toEqual(false); expect(state.maySendStateEvent('m.room.name', userB)).toEqual(false);
}); });
it("should honour explicit event power levels in the power_levels event", it("should honour explicit event power levels in the power_levels event",
function() { function() {
const powerLevelEvent = { const powerLevelEvent = new MatrixEvent({
type: "m.room.power_levels", room: roomId, user: userA, event: true, type: "m.room.power_levels", room_id: roomId, sender: userA,
content: { state_key: "", content: {
events: { events: {
"m.room.other_thing": 76, "m.room.other_thing": 76,
},
users_default: 10,
state_default: 50,
events_default: 25,
users: {
[userA]: 80,
[userB]: 50,
},
}, },
users_default: 10, });
state_default: 50,
events_default: 25,
users: {
},
},
};
powerLevelEvent.content.users[userA] = 80;
powerLevelEvent.content.users[userB] = 50;
state.setStateEvents([utils.mkEvent(powerLevelEvent)]); state.setStateEvents([powerLevelEvent]);
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true); expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
expect(state.maySendStateEvent('m.room.name', userB)).toEqual(true); expect(state.maySendStateEvent('m.room.name', userB)).toEqual(true);
expect(state.maySendStateEvent('m.room.other_thing', userA)).toEqual(true); expect(state.maySendStateEvent('m.room.other_thing', userA)).toEqual(true);
expect(state.maySendStateEvent('m.room.other_thing', userB)).toEqual(false); expect(state.maySendStateEvent('m.room.other_thing', userB)).toEqual(false);
}); });
}); });
describe("getJoinedMemberCount", function() { describe("getJoinedMemberCount", function() {
@ -682,71 +707,73 @@ describe("RoomState", function() {
describe("maySendEvent", function() { describe("maySendEvent", function() {
it("should say any member may send events with no power level event", it("should say any member may send events with no power level event",
function() { function() {
expect(state.maySendEvent('m.room.message', userA)).toEqual(true); expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
expect(state.maySendMessage(userA)).toEqual(true); expect(state.maySendMessage(userA)).toEqual(true);
}); });
it("should obey events_default", it("should obey events_default",
function() { function() {
const powerLevelEvent = { const powerLevelEvent = new MatrixEvent({
type: "m.room.power_levels", room: roomId, user: userA, event: true, type: "m.room.power_levels", room_id: roomId, sender: userA,
content: { state_key: "",
users_default: 10, content: {
state_default: 30, users_default: 10,
events_default: 25, state_default: 30,
users: { events_default: 25,
users: {
[userA]: 26,
[userB]: 24,
},
}, },
}, });
};
powerLevelEvent.content.users[userA] = 26;
powerLevelEvent.content.users[userB] = 24;
state.setStateEvents([utils.mkEvent(powerLevelEvent)]); state.setStateEvents([powerLevelEvent]);
expect(state.maySendEvent('m.room.message', userA)).toEqual(true); expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
expect(state.maySendEvent('m.room.message', userB)).toEqual(false); expect(state.maySendEvent('m.room.message', userB)).toEqual(false);
expect(state.maySendMessage(userA)).toEqual(true); expect(state.maySendMessage(userA)).toEqual(true);
expect(state.maySendMessage(userB)).toEqual(false); expect(state.maySendMessage(userB)).toEqual(false);
}); });
it("should honour explicit event power levels in the power_levels event", it("should honour explicit event power levels in the power_levels event",
function() { function() {
const powerLevelEvent = { const powerLevelEvent = new MatrixEvent({
type: "m.room.power_levels", room: roomId, user: userA, event: true, type: "m.room.power_levels", room_id: roomId, sender: userA,
content: { state_key: "",
events: { content: {
"m.room.other_thing": 33, events: {
"m.room.other_thing": 33,
},
users_default: 10,
state_default: 50,
events_default: 25,
users: {
[userA]: 40,
[userB]: 30,
},
}, },
users_default: 10, });
state_default: 50,
events_default: 25,
users: {
},
},
};
powerLevelEvent.content.users[userA] = 40;
powerLevelEvent.content.users[userB] = 30;
state.setStateEvents([utils.mkEvent(powerLevelEvent)]); state.setStateEvents([powerLevelEvent]);
expect(state.maySendEvent('m.room.message', userA)).toEqual(true); expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
expect(state.maySendEvent('m.room.message', userB)).toEqual(true); expect(state.maySendEvent('m.room.message', userB)).toEqual(true);
expect(state.maySendMessage(userA)).toEqual(true); expect(state.maySendMessage(userA)).toEqual(true);
expect(state.maySendMessage(userB)).toEqual(true); expect(state.maySendMessage(userB)).toEqual(true);
expect(state.maySendEvent('m.room.other_thing', userA)).toEqual(true); expect(state.maySendEvent('m.room.other_thing', userA)).toEqual(true);
expect(state.maySendEvent('m.room.other_thing', userB)).toEqual(false); expect(state.maySendEvent('m.room.other_thing', userB)).toEqual(false);
}); });
}); });
describe('processBeaconEvents', () => { describe('processBeaconEvents', () => {
const beacon1 = makeBeaconInfoEvent(userA, roomId, {}, '$beacon1', '$beacon1'); const beacon1 = makeBeaconInfoEvent(userA, roomId, {}, '$beacon1');
const beacon2 = makeBeaconInfoEvent(userB, roomId, {}, '$beacon2', '$beacon2'); const beacon2 = makeBeaconInfoEvent(userB, roomId, {}, '$beacon2');
const mockClient = { decryptEventIfNeeded: jest.fn() }; const mockClient = { decryptEventIfNeeded: jest.fn() } as unknown as MockedObject<MatrixClient>;
beforeEach(() => { beforeEach(() => {
mockClient.decryptEventIfNeeded.mockClear(); mockClient.decryptEventIfNeeded.mockClear();
@ -816,11 +843,11 @@ describe("RoomState", function() {
beaconInfoId: 'some-other-beacon', beaconInfoId: 'some-other-beacon',
}); });
state.setStateEvents([beacon1, beacon2], mockClient); state.setStateEvents([beacon1, beacon2]);
expect(state.beacons.size).toEqual(2); expect(state.beacons.size).toEqual(2);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beacon1)); const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
const addLocationsSpy = jest.spyOn(beaconInstance, 'addLocations'); const addLocationsSpy = jest.spyOn(beaconInstance, 'addLocations');
state.processBeaconEvents([location1, location2, location3], mockClient); state.processBeaconEvents([location1, location2, location3], mockClient);
@ -885,7 +912,7 @@ describe("RoomState", function() {
}); });
state.setStateEvents([beacon1, beacon2]); state.setStateEvents([beacon1, beacon2]);
const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)); const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear(); const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear();
state.processBeaconEvents([location, otherRelatedEvent], mockClient); state.processBeaconEvents([location, otherRelatedEvent], mockClient);
expect(addLocationsSpy).not.toHaveBeenCalled(); expect(addLocationsSpy).not.toHaveBeenCalled();
@ -945,13 +972,13 @@ describe("RoomState", function() {
}); });
jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true); jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true);
state.setStateEvents([beacon1, beacon2]); state.setStateEvents([beacon1, beacon2]);
const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)); const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear(); const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear();
state.processBeaconEvents([decryptingRelatedEvent], mockClient); state.processBeaconEvents([decryptingRelatedEvent], mockClient);
// this event is a message after decryption // this event is a message after decryption
decryptingRelatedEvent.type = EventType.RoomMessage; decryptingRelatedEvent.event.type = EventType.RoomMessage;
decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted); decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted, decryptingRelatedEvent);
expect(addLocationsSpy).not.toHaveBeenCalled(); expect(addLocationsSpy).not.toHaveBeenCalled();
}); });
@ -967,14 +994,14 @@ describe("RoomState", function() {
}); });
jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true); jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true);
state.setStateEvents([beacon1, beacon2]); state.setStateEvents([beacon1, beacon2]);
const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)); const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear(); const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear();
state.processBeaconEvents([decryptingRelatedEvent], mockClient); state.processBeaconEvents([decryptingRelatedEvent], mockClient);
// update type after '''decryption''' // update type after '''decryption'''
decryptingRelatedEvent.event.type = M_BEACON.name; decryptingRelatedEvent.event.type = M_BEACON.name;
decryptingRelatedEvent.event.content = locationEvent.content; decryptingRelatedEvent.event.content = locationEvent.event.content;
decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted); decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted, decryptingRelatedEvent);
expect(addLocationsSpy).toHaveBeenCalledWith([decryptingRelatedEvent]); expect(addLocationsSpy).toHaveBeenCalledWith([decryptingRelatedEvent]);
}); });