You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-09 10:22:46 +03:00
Merge branch 'develop' into robertlong/group-call
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
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");
|
||||
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
|
||||
* @constructor
|
||||
*/
|
||||
export function MockStorageApi() {
|
||||
this.data = {};
|
||||
this.keys = [];
|
||||
this.length = 0;
|
||||
}
|
||||
export class MockStorageApi {
|
||||
public data: Record<string, string> = {};
|
||||
public keys: string[] = [];
|
||||
public length = 0;
|
||||
|
||||
MockStorageApi.prototype = {
|
||||
setItem: function(k, v) {
|
||||
public setItem(k: string, v: string): void {
|
||||
this.data[k] = v;
|
||||
this._recalc();
|
||||
},
|
||||
getItem: function(k) {
|
||||
this.recalc();
|
||||
}
|
||||
|
||||
public getItem(k: string): string | null {
|
||||
return this.data[k] || null;
|
||||
},
|
||||
removeItem: function(k) {
|
||||
}
|
||||
|
||||
public removeItem(k: string): void {
|
||||
delete this.data[k];
|
||||
this._recalc();
|
||||
},
|
||||
key: function(index) {
|
||||
this.recalc();
|
||||
}
|
||||
|
||||
public key(index: number): string {
|
||||
return this.keys[index];
|
||||
},
|
||||
_recalc: function() {
|
||||
const keys = [];
|
||||
}
|
||||
|
||||
private recalc(): void {
|
||||
const keys: string[] = [];
|
||||
for (const k in this.data) {
|
||||
if (!this.data.hasOwnProperty(k)) {
|
||||
continue;
|
||||
@@ -50,6 +51,5 @@ MockStorageApi.prototype = {
|
||||
}
|
||||
this.keys = keys;
|
||||
this.length = keys.length;
|
||||
},
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@@ -50,7 +50,7 @@ export class TestClient {
|
||||
options?: Partial<ICreateClientOpts>,
|
||||
) {
|
||||
if (sessionStoreBackend === undefined) {
|
||||
sessionStoreBackend = new MockStorageApi();
|
||||
sessionStoreBackend = new MockStorageApi() as unknown as Storage;
|
||||
}
|
||||
|
||||
this.httpBackend = new MockHttpBackend();
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -168,6 +168,7 @@ describe("SlidingSyncSdk", () => {
|
||||
const roomD = "!d_with_notif_count:localhost";
|
||||
const roomE = "!e_with_invite:localhost";
|
||||
const roomF = "!f_calc_room_name:localhost";
|
||||
const roomG = "!g_join_invite_counts:localhost";
|
||||
const data: Record<string, MSC3575RoomData> = {
|
||||
[roomA]: {
|
||||
name: "A",
|
||||
@@ -261,12 +262,25 @@ describe("SlidingSyncSdk", () => {
|
||||
],
|
||||
initial: true,
|
||||
},
|
||||
[roomG]: {
|
||||
name: "G",
|
||||
required_state: [],
|
||||
timeline: [
|
||||
mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""),
|
||||
mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId),
|
||||
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
|
||||
],
|
||||
joined_count: 5,
|
||||
invited_count: 2,
|
||||
initial: true,
|
||||
},
|
||||
};
|
||||
|
||||
it("can be created with required_state and timeline", () => {
|
||||
mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomA, data[roomA]);
|
||||
const gotRoom = client.getRoom(roomA);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(gotRoom.name).toEqual(data[roomA].name);
|
||||
expect(gotRoom.getMyMembership()).toEqual("join");
|
||||
assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-2), data[roomA].timeline);
|
||||
@@ -276,6 +290,7 @@ describe("SlidingSyncSdk", () => {
|
||||
mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomB, data[roomB]);
|
||||
const gotRoom = client.getRoom(roomB);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(gotRoom.name).toEqual(data[roomB].name);
|
||||
expect(gotRoom.getMyMembership()).toEqual("join");
|
||||
assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-5), data[roomB].timeline);
|
||||
@@ -285,6 +300,7 @@ describe("SlidingSyncSdk", () => {
|
||||
mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomC, data[roomC]);
|
||||
const gotRoom = client.getRoom(roomC);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(
|
||||
gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight),
|
||||
).toEqual(data[roomC].highlight_count);
|
||||
@@ -294,15 +310,26 @@ describe("SlidingSyncSdk", () => {
|
||||
mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomD, data[roomD]);
|
||||
const gotRoom = client.getRoom(roomD);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(
|
||||
gotRoom.getUnreadNotificationCount(NotificationCountType.Total),
|
||||
).toEqual(data[roomD].notification_count);
|
||||
});
|
||||
|
||||
it("can be created with an invited/joined_count", () => {
|
||||
mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomG, data[roomG]);
|
||||
const gotRoom = client.getRoom(roomG);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(gotRoom.getInvitedMemberCount()).toEqual(data[roomG].invited_count);
|
||||
expect(gotRoom.getJoinedMemberCount()).toEqual(data[roomG].joined_count);
|
||||
});
|
||||
|
||||
it("can be created with invite_state", () => {
|
||||
mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomE, data[roomE]);
|
||||
const gotRoom = client.getRoom(roomE);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(gotRoom.getMyMembership()).toEqual("invite");
|
||||
expect(gotRoom.currentState.getJoinRule()).toEqual(JoinRule.Invite);
|
||||
});
|
||||
@@ -311,6 +338,7 @@ describe("SlidingSyncSdk", () => {
|
||||
mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomF, data[roomF]);
|
||||
const gotRoom = client.getRoom(roomF);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(
|
||||
gotRoom.name,
|
||||
).toEqual(data[roomF].name);
|
||||
@@ -326,6 +354,7 @@ describe("SlidingSyncSdk", () => {
|
||||
});
|
||||
const gotRoom = client.getRoom(roomA);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
const newTimeline = data[roomA].timeline;
|
||||
newTimeline.push(newEvent);
|
||||
assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-3), newTimeline);
|
||||
@@ -333,6 +362,8 @@ describe("SlidingSyncSdk", () => {
|
||||
|
||||
it("can update with a new required_state event", async () => {
|
||||
let gotRoom = client.getRoom(roomB);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(gotRoom.getJoinRule()).toEqual(JoinRule.Invite); // default
|
||||
mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomB, {
|
||||
required_state: [
|
||||
@@ -343,6 +374,7 @@ describe("SlidingSyncSdk", () => {
|
||||
});
|
||||
gotRoom = client.getRoom(roomB);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(gotRoom.getJoinRule()).toEqual(JoinRule.Restricted);
|
||||
});
|
||||
|
||||
@@ -355,6 +387,7 @@ describe("SlidingSyncSdk", () => {
|
||||
});
|
||||
const gotRoom = client.getRoom(roomC);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(
|
||||
gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight),
|
||||
).toEqual(1);
|
||||
@@ -369,11 +402,25 @@ describe("SlidingSyncSdk", () => {
|
||||
});
|
||||
const gotRoom = client.getRoom(roomD);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(
|
||||
gotRoom.getUnreadNotificationCount(NotificationCountType.Total),
|
||||
).toEqual(1);
|
||||
});
|
||||
|
||||
it("can update with a new joined_count", () => {
|
||||
mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomG, {
|
||||
name: data[roomD].name,
|
||||
required_state: [],
|
||||
timeline: [],
|
||||
joined_count: 1,
|
||||
});
|
||||
const gotRoom = client.getRoom(roomG);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
expect(gotRoom.getJoinedMemberCount()).toEqual(1);
|
||||
});
|
||||
|
||||
// Regression test for a bug which caused the timeline entries to be out-of-order
|
||||
// when the same room appears twice with different timeline limits. E.g appears in
|
||||
// the list with timeline_limit:1 then appears again as a room subscription with
|
||||
@@ -394,6 +441,7 @@ describe("SlidingSyncSdk", () => {
|
||||
});
|
||||
const gotRoom = client.getRoom(roomA);
|
||||
expect(gotRoom).toBeDefined();
|
||||
if (gotRoom == null) { return; }
|
||||
|
||||
logger.log("want:", oldTimeline.map((e) => (e.type + " : " + (e.content || {}).body)));
|
||||
logger.log("got:", gotRoom.getLiveTimeline().getEvents().map(
|
||||
|
@@ -558,6 +558,153 @@ describe("SlidingSync", () => {
|
||||
await httpBackend.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
});
|
||||
|
||||
// this refers to a set of operations where the end result is no change.
|
||||
it("should handle net zero operations correctly", async () => {
|
||||
const indexToRoomId = {
|
||||
0: roomB,
|
||||
1: roomC,
|
||||
};
|
||||
expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual(indexToRoomId);
|
||||
httpBackend.when("POST", syncUrl).respond(200, {
|
||||
pos: "f",
|
||||
// currently the list is [B,C] so we will insert D then immediately delete it
|
||||
lists: [{
|
||||
count: 500,
|
||||
ops: [
|
||||
{
|
||||
op: "DELETE", index: 2,
|
||||
},
|
||||
{
|
||||
op: "INSERT", index: 0, room_id: roomA,
|
||||
},
|
||||
{
|
||||
op: "DELETE", index: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
count: 50,
|
||||
}],
|
||||
});
|
||||
const listPromise = listenUntil(slidingSync, "SlidingSync.List",
|
||||
(listIndex, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listIndex).toEqual(0);
|
||||
expect(joinedCount).toEqual(500);
|
||||
expect(roomIndexToRoomId).toEqual(indexToRoomId);
|
||||
return true;
|
||||
});
|
||||
const responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
});
|
||||
|
||||
it("should handle deletions correctly", async () => {
|
||||
expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual({
|
||||
0: roomB,
|
||||
1: roomC,
|
||||
});
|
||||
httpBackend.when("POST", syncUrl).respond(200, {
|
||||
pos: "g",
|
||||
lists: [{
|
||||
count: 499,
|
||||
ops: [
|
||||
{
|
||||
op: "DELETE", index: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
count: 50,
|
||||
}],
|
||||
});
|
||||
const listPromise = listenUntil(slidingSync, "SlidingSync.List",
|
||||
(listIndex, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listIndex).toEqual(0);
|
||||
expect(joinedCount).toEqual(499);
|
||||
expect(roomIndexToRoomId).toEqual({
|
||||
0: roomC,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
const responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
});
|
||||
|
||||
it("should handle insertions correctly", async () => {
|
||||
expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual({
|
||||
0: roomC,
|
||||
});
|
||||
httpBackend.when("POST", syncUrl).respond(200, {
|
||||
pos: "h",
|
||||
lists: [{
|
||||
count: 500,
|
||||
ops: [
|
||||
{
|
||||
op: "INSERT", index: 1, room_id: roomA,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
count: 50,
|
||||
}],
|
||||
});
|
||||
let listPromise = listenUntil(slidingSync, "SlidingSync.List",
|
||||
(listIndex, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listIndex).toEqual(0);
|
||||
expect(joinedCount).toEqual(500);
|
||||
expect(roomIndexToRoomId).toEqual({
|
||||
0: roomC,
|
||||
1: roomA,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
let responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
|
||||
httpBackend.when("POST", syncUrl).respond(200, {
|
||||
pos: "h",
|
||||
lists: [{
|
||||
count: 501,
|
||||
ops: [
|
||||
{
|
||||
op: "INSERT", index: 1, room_id: roomB,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
count: 50,
|
||||
}],
|
||||
});
|
||||
listPromise = listenUntil(slidingSync, "SlidingSync.List",
|
||||
(listIndex, joinedCount, roomIndexToRoomId) => {
|
||||
expect(listIndex).toEqual(0);
|
||||
expect(joinedCount).toEqual(501);
|
||||
expect(roomIndexToRoomId).toEqual({
|
||||
0: roomC,
|
||||
1: roomB,
|
||||
2: roomA,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => {
|
||||
return state === SlidingSyncState.Complete;
|
||||
});
|
||||
await httpBackend.flushAllExpected();
|
||||
await responseProcessed;
|
||||
await listPromise;
|
||||
slidingSync.stop();
|
||||
});
|
||||
});
|
||||
|
@@ -20,6 +20,7 @@ import * as utils from "../src/utils";
|
||||
|
||||
// try to load the olm library.
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
global.Olm = require('@matrix-org/olm');
|
||||
logger.log('loaded libolm');
|
||||
} catch (e) {
|
||||
@@ -28,6 +29,7 @@ try {
|
||||
|
||||
// also try to set node crypto
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const crypto = require('crypto');
|
||||
utils.setCrypto(crypto);
|
||||
} catch (err) {
|
@@ -24,5 +24,5 @@ limitations under the License.
|
||||
* 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);
|
||||
|
@@ -147,9 +147,9 @@ export function mkEventCustom<T>(base: T): T & GeneratedMetadata {
|
||||
interface IPresenceOpts {
|
||||
user?: string;
|
||||
sender?: string;
|
||||
url: string;
|
||||
name: string;
|
||||
ago: number;
|
||||
url?: string;
|
||||
name?: string;
|
||||
ago?: number;
|
||||
presence?: string;
|
||||
event?: boolean;
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ import { CRYPTO_ENABLED } from "../../src/client";
|
||||
import { DeviceInfo } from "../../src/crypto/deviceinfo";
|
||||
import { logger } from '../../src/logger';
|
||||
import { MemoryStore } from "../../src";
|
||||
import { IStore } from '../../src/store';
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
@@ -158,8 +159,8 @@ describe("Crypto", function() {
|
||||
let fakeEmitter;
|
||||
|
||||
beforeEach(async function() {
|
||||
const mockStorage = new MockStorageApi();
|
||||
const clientStore = new MemoryStore({ localStorage: mockStorage });
|
||||
const mockStorage = new MockStorageApi() as unknown as Storage;
|
||||
const clientStore = new MemoryStore({ localStorage: mockStorage }) as unknown as IStore;
|
||||
const cryptoStore = new MemoryCryptoStore();
|
||||
|
||||
cryptoStore.storeEndToEndDeviceData({
|
||||
@@ -469,12 +470,12 @@ describe("Crypto", function() {
|
||||
jest.setTimeout(10000);
|
||||
const client = (new TestClient("@a:example.com", "dev")).client;
|
||||
await client.initCrypto();
|
||||
client.crypto.getSecretStorageKey = async () => null;
|
||||
client.crypto.getSecretStorageKey = jest.fn().mockResolvedValue(null);
|
||||
client.crypto.isCrossSigningReady = async () => false;
|
||||
client.crypto.baseApis.uploadDeviceSigningKeys = jest.fn().mockResolvedValue(null);
|
||||
client.crypto.baseApis.setAccountData = () => null;
|
||||
client.crypto.baseApis.uploadKeySignatures = () => null;
|
||||
client.crypto.baseApis.http.authedRequest = () => null;
|
||||
client.crypto.baseApis.setAccountData = jest.fn().mockResolvedValue(null);
|
||||
client.crypto.baseApis.uploadKeySignatures = jest.fn();
|
||||
client.crypto.baseApis.http.authedRequest = jest.fn();
|
||||
const createSecretStorageKey = async () => {
|
||||
return {
|
||||
keyInfo: undefined, // Returning undefined here used to cause a crash
|
||||
|
@@ -32,8 +32,8 @@ import { ClientEvent, MatrixClient, RoomMember } from '../../../../src';
|
||||
import { DeviceInfo, IDevice } from '../../../../src/crypto/deviceinfo';
|
||||
import { DeviceTrustLevel } from '../../../../src/crypto/CrossSigning';
|
||||
|
||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
||||
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2');
|
||||
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES.get('m.megolm.v1.aes-sha2');
|
||||
|
||||
const ROOM_ID = '!ROOM:ID';
|
||||
|
||||
|
@@ -34,7 +34,7 @@ import { IAbortablePromise, MatrixScheduler } from '../../../src';
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2');
|
||||
|
||||
const ROOM_ID = '!ROOM:ID';
|
||||
|
||||
|
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
IndexedDBCryptoStore,
|
||||
} from '../../../src/crypto/store/indexeddb-crypto-store';
|
||||
import { CryptoStore } from '../../../src/crypto/store/base';
|
||||
import { IndexedDBCryptoStore } from '../../../src/crypto/store/indexeddb-crypto-store';
|
||||
import { LocalStorageCryptoStore } from '../../../src/crypto/store/localStorage-crypto-store';
|
||||
import { MemoryCryptoStore } from '../../../src/crypto/store/memory-crypto-store';
|
||||
import { RoomKeyRequestState } from '../../../src/crypto/OutgoingRoomKeyRequestManager';
|
||||
|
||||
@@ -26,36 +26,39 @@ import 'jest-localstorage-mock';
|
||||
const requests = [
|
||||
{
|
||||
requestId: "A",
|
||||
requestBody: { session_id: "A", room_id: "A" },
|
||||
requestBody: { session_id: "A", room_id: "A", sender_key: "A", algorithm: "m.megolm.v1.aes-sha2" },
|
||||
state: RoomKeyRequestState.Sent,
|
||||
recipients: [
|
||||
{ userId: "@alice:example.com", deviceId: "*" },
|
||||
{ userId: "@becca:example.com", deviceId: "foobarbaz" },
|
||||
],
|
||||
},
|
||||
{
|
||||
requestId: "B",
|
||||
requestBody: { session_id: "B", room_id: "B" },
|
||||
requestBody: { session_id: "B", room_id: "B", sender_key: "B", algorithm: "m.megolm.v1.aes-sha2" },
|
||||
state: RoomKeyRequestState.Sent,
|
||||
recipients: [
|
||||
{ userId: "@alice:example.com", deviceId: "*" },
|
||||
{ userId: "@carrie:example.com", deviceId: "barbazquux" },
|
||||
],
|
||||
},
|
||||
{
|
||||
requestId: "C",
|
||||
requestBody: { session_id: "C", room_id: "C" },
|
||||
requestBody: { session_id: "C", room_id: "C", sender_key: "B", algorithm: "m.megolm.v1.aes-sha2" },
|
||||
state: RoomKeyRequestState.Unsent,
|
||||
recipients: [
|
||||
{ userId: "@becca:example.com", deviceId: "foobarbaz" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
describe.each([
|
||||
["IndexedDBCryptoStore",
|
||||
() => new IndexedDBCryptoStore(global.indexedDB, "tests")],
|
||||
["LocalStorageCryptoStore",
|
||||
() => new IndexedDBCryptoStore(undefined, "tests")],
|
||||
["MemoryCryptoStore", () => {
|
||||
const store = new IndexedDBCryptoStore(undefined, "tests");
|
||||
// @ts-ignore set private properties
|
||||
store.backend = new MemoryCryptoStore();
|
||||
// @ts-ignore
|
||||
store.backendPromise = Promise.resolve(store.backend);
|
||||
return store;
|
||||
}],
|
||||
["LocalStorageCryptoStore", () => new LocalStorageCryptoStore(localStorage)],
|
||||
["MemoryCryptoStore", () => new MemoryCryptoStore()],
|
||||
])("Outgoing room key requests [%s]", function(name, dbFactory) {
|
||||
let store;
|
||||
let store: CryptoStore;
|
||||
|
||||
beforeAll(async () => {
|
||||
store = dbFactory();
|
||||
@@ -75,6 +78,15 @@ describe.each([
|
||||
});
|
||||
});
|
||||
|
||||
it("getOutgoingRoomKeyRequestsByTarget retrieves all entries with a given target",
|
||||
async () => {
|
||||
const r = await store.getOutgoingRoomKeyRequestsByTarget(
|
||||
"@becca:example.com", "foobarbaz", [RoomKeyRequestState.Sent],
|
||||
);
|
||||
expect(r).toHaveLength(1);
|
||||
expect(r[0]).toEqual(requests[0]);
|
||||
});
|
||||
|
||||
test("getOutgoingRoomKeyRequestByState retrieves any entry in a given state",
|
||||
async () => {
|
||||
const r =
|
||||
|
@@ -16,14 +16,15 @@ limitations under the License.
|
||||
|
||||
import * as utils from "../test-utils/test-utils";
|
||||
import {
|
||||
DuplicateStrategy,
|
||||
EventTimeline,
|
||||
EventTimelineSet,
|
||||
EventType,
|
||||
Filter,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
MatrixEventEvent,
|
||||
Room,
|
||||
DuplicateStrategy,
|
||||
} from '../../src';
|
||||
import { Thread } from "../../src/models/thread";
|
||||
import { ReEmitter } from "../../src/ReEmitter";
|
||||
@@ -291,4 +292,34 @@ describe('EventTimelineSet', () => {
|
||||
expect(eventTimelineSet.canContain(event)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleRemoteEcho", () => {
|
||||
it("should add to liveTimeline only if the event matches the filter", () => {
|
||||
const filter = new Filter(client.getUserId()!, "test_filter");
|
||||
filter.setDefinition({
|
||||
room: {
|
||||
timeline: {
|
||||
types: [EventType.RoomMessage],
|
||||
},
|
||||
},
|
||||
});
|
||||
const eventTimelineSet = new EventTimelineSet(room, { filter }, client);
|
||||
|
||||
const roomMessageEvent = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
content: { body: "test" },
|
||||
event_id: "!test1:server",
|
||||
});
|
||||
eventTimelineSet.handleRemoteEcho(roomMessageEvent, "~!local-event-id:server", roomMessageEvent.getId());
|
||||
expect(eventTimelineSet.getLiveTimeline().getEvents()).toContain(roomMessageEvent);
|
||||
|
||||
const roomFilteredEvent = new MatrixEvent({
|
||||
type: "other_event_type",
|
||||
content: { body: "test" },
|
||||
event_id: "!test2:server",
|
||||
});
|
||||
eventTimelineSet.handleRemoteEcho(roomFilteredEvent, "~!local-event-id:server", roomFilteredEvent.getId());
|
||||
expect(eventTimelineSet.getLiveTimeline().getEvents()).not.toContain(roomFilteredEvent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -36,9 +36,14 @@ import { ReceiptType } from "../../src/@types/read_receipts";
|
||||
import * as testUtils from "../test-utils/test-utils";
|
||||
import { makeBeaconInfoContent } from "../../src/content-helpers";
|
||||
import { M_BEACON_INFO } from "../../src/@types/beacon";
|
||||
import { ContentHelpers, Room } from "../../src";
|
||||
import { ContentHelpers, EventTimeline, Room } from "../../src";
|
||||
import { supportsMatrixCall } from "../../src/webrtc/call";
|
||||
import { makeBeaconEvent } from "../test-utils/beacon";
|
||||
import {
|
||||
IGNORE_INVITES_ACCOUNT_EVENT_KEY,
|
||||
POLICIES_ACCOUNT_EVENT_TYPE,
|
||||
PolicyScope,
|
||||
} from "../../src/models/invites-ignorer";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
@@ -427,7 +432,7 @@ describe("MatrixClient", function() {
|
||||
}
|
||||
});
|
||||
});
|
||||
await client.startClient();
|
||||
await client.startClient({ filter });
|
||||
await syncPromise;
|
||||
});
|
||||
|
||||
@@ -1412,4 +1417,301 @@ describe("MatrixClient", function() {
|
||||
expect(client.crypto.encryptAndSendToDevices).toHaveBeenLastCalledWith(deviceInfos, payload);
|
||||
});
|
||||
});
|
||||
|
||||
describe("support for ignoring invites", () => {
|
||||
beforeEach(() => {
|
||||
// Mockup `getAccountData`/`setAccountData`.
|
||||
const dataStore = new Map();
|
||||
client.setAccountData = function(eventType, content) {
|
||||
dataStore.set(eventType, content);
|
||||
return Promise.resolve();
|
||||
};
|
||||
client.getAccountData = function(eventType) {
|
||||
const data = dataStore.get(eventType);
|
||||
return new MatrixEvent({
|
||||
content: data,
|
||||
});
|
||||
};
|
||||
|
||||
// Mockup `createRoom`/`getRoom`/`joinRoom`, including state.
|
||||
const rooms = new Map();
|
||||
client.createRoom = function(options = {}) {
|
||||
const roomId = options["_roomId"] || `!room-${rooms.size}:example.org`;
|
||||
const state = new Map();
|
||||
const room = {
|
||||
roomId,
|
||||
_options: options,
|
||||
_state: state,
|
||||
getUnfilteredTimelineSet: function() {
|
||||
return {
|
||||
getLiveTimeline: function() {
|
||||
return {
|
||||
getState: function(direction) {
|
||||
expect(direction).toBe(EventTimeline.FORWARDS);
|
||||
return {
|
||||
getStateEvents: function(type) {
|
||||
const store = state.get(type) || {};
|
||||
return Object.keys(store).map(key => store[key]);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
rooms.set(roomId, room);
|
||||
return Promise.resolve({ room_id: roomId });
|
||||
};
|
||||
client.getRoom = function(roomId) {
|
||||
return rooms.get(roomId);
|
||||
};
|
||||
client.joinRoom = function(roomId) {
|
||||
return this.getRoom(roomId) || this.createRoom({ _roomId: roomId });
|
||||
};
|
||||
|
||||
// Mockup state events
|
||||
client.sendStateEvent = function(roomId, type, content) {
|
||||
const room = this.getRoom(roomId);
|
||||
const state: Map<string, any> = room._state;
|
||||
let store = state.get(type);
|
||||
if (!store) {
|
||||
store = {};
|
||||
state.set(type, store);
|
||||
}
|
||||
const eventId = `$event-${Math.random()}:example.org`;
|
||||
store[eventId] = {
|
||||
getId: function() {
|
||||
return eventId;
|
||||
},
|
||||
getRoomId: function() {
|
||||
return roomId;
|
||||
},
|
||||
getContent: function() {
|
||||
return content;
|
||||
},
|
||||
};
|
||||
return { event_id: eventId };
|
||||
};
|
||||
client.redactEvent = function(roomId, eventId) {
|
||||
const room = this.getRoom(roomId);
|
||||
const state: Map<string, any> = room._state;
|
||||
for (const store of state.values()) {
|
||||
delete store[eventId];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it("should initialize and return the same `target` consistently", async () => {
|
||||
const target1 = await client.ignoredInvites.getOrCreateTargetRoom();
|
||||
const target2 = await client.ignoredInvites.getOrCreateTargetRoom();
|
||||
expect(target1).toBeTruthy();
|
||||
expect(target1).toBe(target2);
|
||||
});
|
||||
|
||||
it("should initialize and return the same `sources` consistently", async () => {
|
||||
const sources1 = await client.ignoredInvites.getOrCreateSourceRooms();
|
||||
const sources2 = await client.ignoredInvites.getOrCreateSourceRooms();
|
||||
expect(sources1).toBeTruthy();
|
||||
expect(sources1).toHaveLength(1);
|
||||
expect(sources1).toEqual(sources2);
|
||||
});
|
||||
|
||||
it("should initially not reject any invite", async () => {
|
||||
const rule = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:example.org",
|
||||
roomId: "!snafu:somewhere.org",
|
||||
});
|
||||
expect(rule).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should reject invites once we have added a matching rule in the target room (scope: user)", async () => {
|
||||
await client.ignoredInvites.addRule(PolicyScope.User, "*:example.org", "just a test");
|
||||
|
||||
// We should reject this invite.
|
||||
const ruleMatch = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:example.org",
|
||||
roomId: "!snafu:somewhere.org",
|
||||
});
|
||||
expect(ruleMatch).toBeTruthy();
|
||||
expect(ruleMatch.getContent()).toMatchObject({
|
||||
recommendation: "m.ban",
|
||||
reason: "just a test",
|
||||
});
|
||||
|
||||
// We should let these invites go through.
|
||||
const ruleWrongServer = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:somewhere.org",
|
||||
roomId: "!snafu:somewhere.org",
|
||||
});
|
||||
expect(ruleWrongServer).toBeFalsy();
|
||||
|
||||
const ruleWrongServerRoom = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:somewhere.org",
|
||||
roomId: "!snafu:example.org",
|
||||
});
|
||||
expect(ruleWrongServerRoom).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should reject invites once we have added a matching rule in the target room (scope: server)", async () => {
|
||||
const REASON = `Just a test ${Math.random()}`;
|
||||
await client.ignoredInvites.addRule(PolicyScope.Server, "example.org", REASON);
|
||||
|
||||
// We should reject these invites.
|
||||
const ruleSenderMatch = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:example.org",
|
||||
roomId: "!snafu:somewhere.org",
|
||||
});
|
||||
expect(ruleSenderMatch).toBeTruthy();
|
||||
expect(ruleSenderMatch.getContent()).toMatchObject({
|
||||
recommendation: "m.ban",
|
||||
reason: REASON,
|
||||
});
|
||||
|
||||
const ruleRoomMatch = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:somewhere.org",
|
||||
roomId: "!snafu:example.org",
|
||||
});
|
||||
expect(ruleRoomMatch).toBeTruthy();
|
||||
expect(ruleRoomMatch.getContent()).toMatchObject({
|
||||
recommendation: "m.ban",
|
||||
reason: REASON,
|
||||
});
|
||||
|
||||
// We should let these invites go through.
|
||||
const ruleWrongServer = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:somewhere.org",
|
||||
roomId: "!snafu:somewhere.org",
|
||||
});
|
||||
expect(ruleWrongServer).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should reject invites once we have added a matching rule in the target room (scope: room)", async () => {
|
||||
const REASON = `Just a test ${Math.random()}`;
|
||||
const BAD_ROOM_ID = "!bad:example.org";
|
||||
const GOOD_ROOM_ID = "!good:example.org";
|
||||
await client.ignoredInvites.addRule(PolicyScope.Room, BAD_ROOM_ID, REASON);
|
||||
|
||||
// We should reject this invite.
|
||||
const ruleSenderMatch = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:example.org",
|
||||
roomId: BAD_ROOM_ID,
|
||||
});
|
||||
expect(ruleSenderMatch).toBeTruthy();
|
||||
expect(ruleSenderMatch.getContent()).toMatchObject({
|
||||
recommendation: "m.ban",
|
||||
reason: REASON,
|
||||
});
|
||||
|
||||
// We should let these invites go through.
|
||||
const ruleWrongRoom = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: BAD_ROOM_ID,
|
||||
roomId: GOOD_ROOM_ID,
|
||||
});
|
||||
expect(ruleWrongRoom).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should reject invites once we have added a matching rule in a non-target source room", async () => {
|
||||
const NEW_SOURCE_ROOM_ID = "!another-source:example.org";
|
||||
|
||||
// Make sure that everything is initialized.
|
||||
await client.ignoredInvites.getOrCreateSourceRooms();
|
||||
await client.joinRoom(NEW_SOURCE_ROOM_ID);
|
||||
await client.ignoredInvites.addSource(NEW_SOURCE_ROOM_ID);
|
||||
|
||||
// Add a rule in the new source room.
|
||||
await client.sendStateEvent(NEW_SOURCE_ROOM_ID, PolicyScope.User, {
|
||||
entity: "*:example.org",
|
||||
reason: "just a test",
|
||||
recommendation: "m.ban",
|
||||
});
|
||||
|
||||
// We should reject this invite.
|
||||
const ruleMatch = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:example.org",
|
||||
roomId: "!snafu:somewhere.org",
|
||||
});
|
||||
expect(ruleMatch).toBeTruthy();
|
||||
expect(ruleMatch.getContent()).toMatchObject({
|
||||
recommendation: "m.ban",
|
||||
reason: "just a test",
|
||||
});
|
||||
|
||||
// We should let these invites go through.
|
||||
const ruleWrongServer = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:somewhere.org",
|
||||
roomId: "!snafu:somewhere.org",
|
||||
});
|
||||
expect(ruleWrongServer).toBeFalsy();
|
||||
|
||||
const ruleWrongServerRoom = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:somewhere.org",
|
||||
roomId: "!snafu:example.org",
|
||||
});
|
||||
expect(ruleWrongServerRoom).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should not reject invites anymore once we have removed a rule", async () => {
|
||||
await client.ignoredInvites.addRule(PolicyScope.User, "*:example.org", "just a test");
|
||||
|
||||
// We should reject this invite.
|
||||
const ruleMatch = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:example.org",
|
||||
roomId: "!snafu:somewhere.org",
|
||||
});
|
||||
expect(ruleMatch).toBeTruthy();
|
||||
expect(ruleMatch.getContent()).toMatchObject({
|
||||
recommendation: "m.ban",
|
||||
reason: "just a test",
|
||||
});
|
||||
|
||||
// After removing the invite, we shouldn't reject it anymore.
|
||||
await client.ignoredInvites.removeRule(ruleMatch);
|
||||
const ruleMatch2 = await client.ignoredInvites.getRuleForInvite({
|
||||
sender: "@foobar:example.org",
|
||||
roomId: "!snafu:somewhere.org",
|
||||
});
|
||||
expect(ruleMatch2).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should add new rules in the target room, rather than any other source room", async () => {
|
||||
const NEW_SOURCE_ROOM_ID = "!another-source:example.org";
|
||||
|
||||
// Make sure that everything is initialized.
|
||||
await client.ignoredInvites.getOrCreateSourceRooms();
|
||||
await client.joinRoom(NEW_SOURCE_ROOM_ID);
|
||||
const newSourceRoom = client.getRoom(NEW_SOURCE_ROOM_ID);
|
||||
|
||||
// Fetch the list of sources and check that we do not have the new room yet.
|
||||
const policies = await client.getAccountData(POLICIES_ACCOUNT_EVENT_TYPE.name).getContent();
|
||||
expect(policies).toBeTruthy();
|
||||
const ignoreInvites = policies[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name];
|
||||
expect(ignoreInvites).toBeTruthy();
|
||||
expect(ignoreInvites.sources).toBeTruthy();
|
||||
expect(ignoreInvites.sources).not.toContain(NEW_SOURCE_ROOM_ID);
|
||||
|
||||
// Add a source.
|
||||
const added = await client.ignoredInvites.addSource(NEW_SOURCE_ROOM_ID);
|
||||
expect(added).toBe(true);
|
||||
const added2 = await client.ignoredInvites.addSource(NEW_SOURCE_ROOM_ID);
|
||||
expect(added2).toBe(false);
|
||||
|
||||
// Fetch the list of sources and check that we have added the new room.
|
||||
const policies2 = await client.getAccountData(POLICIES_ACCOUNT_EVENT_TYPE.name).getContent();
|
||||
expect(policies2).toBeTruthy();
|
||||
const ignoreInvites2 = policies2[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name];
|
||||
expect(ignoreInvites2).toBeTruthy();
|
||||
expect(ignoreInvites2.sources).toBeTruthy();
|
||||
expect(ignoreInvites2.sources).toContain(NEW_SOURCE_ROOM_ID);
|
||||
|
||||
// Add a rule.
|
||||
const eventId = await client.ignoredInvites.addRule(PolicyScope.User, "*:example.org", "just a test");
|
||||
|
||||
// Check where it shows up.
|
||||
const targetRoomId = ignoreInvites2.target;
|
||||
const targetRoom = client.getRoom(targetRoomId);
|
||||
expect(targetRoom._state.get(PolicyScope.User)[eventId]).toBeTruthy();
|
||||
expect(newSourceRoom._state.get(PolicyScope.User)?.[eventId]).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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 { RoomMember } from "../../src/models/room-member";
|
||||
import { RoomMember, RoomMemberEvent } from "../../src/models/room-member";
|
||||
import { RoomState } from "../../src";
|
||||
|
||||
describe("RoomMember", function() {
|
||||
const roomId = "!foo:bar";
|
||||
const userA = "@alice:bar";
|
||||
const userB = "@bertha:bar";
|
||||
const userC = "@clarissa:bar";
|
||||
let member;
|
||||
let member = new RoomMember(roomId, userA);
|
||||
|
||||
beforeEach(function() {
|
||||
member = new RoomMember(roomId, userA);
|
||||
@@ -27,17 +44,17 @@ describe("RoomMember", function() {
|
||||
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
|
||||
// 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",
|
||||
function() {
|
||||
const url = member.getAvatarUrl(hsUrl, 64, 64, "crop", false);
|
||||
expect(url).toEqual(null);
|
||||
});
|
||||
function() {
|
||||
const url = member.getAvatarUrl(hsUrl, 64, 64, "crop", false, false);
|
||||
expect(url).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setPowerLevelEvent", function() {
|
||||
@@ -66,92 +83,92 @@ describe("RoomMember", function() {
|
||||
});
|
||||
|
||||
it("should emit 'RoomMember.powerLevel' if the power level changes.",
|
||||
function() {
|
||||
const event = utils.mkEvent({
|
||||
type: "m.room.power_levels",
|
||||
room: roomId,
|
||||
user: userA,
|
||||
content: {
|
||||
users_default: 20,
|
||||
users: {
|
||||
"@bertha:bar": 200,
|
||||
"@invalid:user": 10, // shouldn't barf on this.
|
||||
function() {
|
||||
const event = utils.mkEvent({
|
||||
type: "m.room.power_levels",
|
||||
room: roomId,
|
||||
user: userA,
|
||||
content: {
|
||||
users_default: 20,
|
||||
users: {
|
||||
"@bertha:bar": 200,
|
||||
"@invalid:user": 10, // shouldn't barf on this.
|
||||
},
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
let emitCount = 0;
|
||||
event: true,
|
||||
});
|
||||
let emitCount = 0;
|
||||
|
||||
member.on("RoomMember.powerLevel", function(emitEvent, emitMember) {
|
||||
emitCount += 1;
|
||||
expect(emitMember).toEqual(member);
|
||||
expect(emitEvent).toEqual(event);
|
||||
});
|
||||
member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
|
||||
emitCount += 1;
|
||||
expect(emitMember).toEqual(member);
|
||||
expect(emitEvent).toEqual(event);
|
||||
});
|
||||
|
||||
member.setPowerLevelEvent(event);
|
||||
expect(emitCount).toEqual(1);
|
||||
member.setPowerLevelEvent(event); // no-op
|
||||
expect(emitCount).toEqual(1);
|
||||
});
|
||||
member.setPowerLevelEvent(event);
|
||||
expect(emitCount).toEqual(1);
|
||||
member.setPowerLevelEvent(event); // no-op
|
||||
expect(emitCount).toEqual(1);
|
||||
});
|
||||
|
||||
it("should honour power levels of zero.",
|
||||
function() {
|
||||
const event = utils.mkEvent({
|
||||
type: "m.room.power_levels",
|
||||
room: roomId,
|
||||
user: userA,
|
||||
content: {
|
||||
users_default: 20,
|
||||
users: {
|
||||
"@alice:bar": 0,
|
||||
function() {
|
||||
const event = utils.mkEvent({
|
||||
type: "m.room.power_levels",
|
||||
room: roomId,
|
||||
user: userA,
|
||||
content: {
|
||||
users_default: 20,
|
||||
users: {
|
||||
"@alice:bar": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
let emitCount = 0;
|
||||
event: true,
|
||||
});
|
||||
let emitCount = 0;
|
||||
|
||||
// set the power level to something other than zero or we
|
||||
// won't get an event
|
||||
member.powerLevel = 1;
|
||||
member.on("RoomMember.powerLevel", function(emitEvent, emitMember) {
|
||||
emitCount += 1;
|
||||
expect(emitMember.userId).toEqual('@alice:bar');
|
||||
expect(emitMember.powerLevel).toEqual(0);
|
||||
expect(emitEvent).toEqual(event);
|
||||
});
|
||||
// set the power level to something other than zero or we
|
||||
// won't get an event
|
||||
member.powerLevel = 1;
|
||||
member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
|
||||
emitCount += 1;
|
||||
expect(emitMember.userId).toEqual('@alice:bar');
|
||||
expect(emitMember.powerLevel).toEqual(0);
|
||||
expect(emitEvent).toEqual(event);
|
||||
});
|
||||
|
||||
member.setPowerLevelEvent(event);
|
||||
expect(member.powerLevel).toEqual(0);
|
||||
expect(emitCount).toEqual(1);
|
||||
});
|
||||
member.setPowerLevelEvent(event);
|
||||
expect(member.powerLevel).toEqual(0);
|
||||
expect(emitCount).toEqual(1);
|
||||
});
|
||||
|
||||
it("should not honor string power levels.",
|
||||
function() {
|
||||
const event = utils.mkEvent({
|
||||
type: "m.room.power_levels",
|
||||
room: roomId,
|
||||
user: userA,
|
||||
content: {
|
||||
users_default: 20,
|
||||
users: {
|
||||
"@alice:bar": "5",
|
||||
function() {
|
||||
const event = utils.mkEvent({
|
||||
type: "m.room.power_levels",
|
||||
room: roomId,
|
||||
user: userA,
|
||||
content: {
|
||||
users_default: 20,
|
||||
users: {
|
||||
"@alice:bar": "5",
|
||||
},
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
let emitCount = 0;
|
||||
event: true,
|
||||
});
|
||||
let emitCount = 0;
|
||||
|
||||
member.on("RoomMember.powerLevel", function(emitEvent, emitMember) {
|
||||
emitCount += 1;
|
||||
expect(emitMember.userId).toEqual('@alice:bar');
|
||||
expect(emitMember.powerLevel).toEqual(20);
|
||||
expect(emitEvent).toEqual(event);
|
||||
});
|
||||
member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
|
||||
emitCount += 1;
|
||||
expect(emitMember.userId).toEqual('@alice:bar');
|
||||
expect(emitMember.powerLevel).toEqual(20);
|
||||
expect(emitEvent).toEqual(event);
|
||||
});
|
||||
|
||||
member.setPowerLevelEvent(event);
|
||||
expect(member.powerLevel).toEqual(20);
|
||||
expect(emitCount).toEqual(1);
|
||||
});
|
||||
member.setPowerLevelEvent(event);
|
||||
expect(member.powerLevel).toEqual(20);
|
||||
expect(emitCount).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setTypingEvent", function() {
|
||||
@@ -183,34 +200,34 @@ describe("RoomMember", function() {
|
||||
});
|
||||
|
||||
it("should emit 'RoomMember.typing' if the typing state changes",
|
||||
function() {
|
||||
const event = utils.mkEvent({
|
||||
type: "m.typing",
|
||||
room: roomId,
|
||||
content: {
|
||||
user_ids: [
|
||||
userA, userC,
|
||||
],
|
||||
},
|
||||
event: true,
|
||||
function() {
|
||||
const event = utils.mkEvent({
|
||||
type: "m.typing",
|
||||
room: roomId,
|
||||
content: {
|
||||
user_ids: [
|
||||
userA, userC,
|
||||
],
|
||||
},
|
||||
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() {
|
||||
it("should be set by markOutOfBand", function() {
|
||||
const member = new RoomMember();
|
||||
const member = new RoomMember(roomId, userA);
|
||||
expect(member.isOutOfBand()).toEqual(false);
|
||||
member.markOutOfBand();
|
||||
expect(member.isOutOfBand()).toEqual(true);
|
||||
@@ -235,50 +252,50 @@ describe("RoomMember", function() {
|
||||
});
|
||||
|
||||
it("should set 'membership' and assign the event to 'events.member'.",
|
||||
function() {
|
||||
member.setMembershipEvent(inviteEvent);
|
||||
expect(member.membership).toEqual("invite");
|
||||
expect(member.events.member).toEqual(inviteEvent);
|
||||
member.setMembershipEvent(joinEvent);
|
||||
expect(member.membership).toEqual("join");
|
||||
expect(member.events.member).toEqual(joinEvent);
|
||||
});
|
||||
function() {
|
||||
member.setMembershipEvent(inviteEvent);
|
||||
expect(member.membership).toEqual("invite");
|
||||
expect(member.events.member).toEqual(inviteEvent);
|
||||
member.setMembershipEvent(joinEvent);
|
||||
expect(member.membership).toEqual("join");
|
||||
expect(member.events.member).toEqual(joinEvent);
|
||||
});
|
||||
|
||||
it("should set 'name' based on user_id, displayname and room state",
|
||||
function() {
|
||||
const roomState = {
|
||||
getStateEvents: function(type) {
|
||||
if (type !== "m.room.member") {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
utils.mkMembership({
|
||||
event: true, mship: "join", room: roomId,
|
||||
user: userB,
|
||||
}),
|
||||
utils.mkMembership({
|
||||
event: true, mship: "join", room: roomId,
|
||||
user: userC, name: "Alice",
|
||||
}),
|
||||
joinEvent,
|
||||
];
|
||||
},
|
||||
getUserIdsWithDisplayName: function(displayName) {
|
||||
return [userA, userC];
|
||||
},
|
||||
};
|
||||
expect(member.name).toEqual(userA); // default = user_id
|
||||
member.setMembershipEvent(joinEvent);
|
||||
expect(member.name).toEqual("Alice"); // prefer displayname
|
||||
member.setMembershipEvent(joinEvent, roomState);
|
||||
expect(member.name).not.toEqual("Alice"); // it should disambig.
|
||||
// user_id should be there somewhere
|
||||
expect(member.name.indexOf(userA)).not.toEqual(-1);
|
||||
});
|
||||
function() {
|
||||
const roomState = {
|
||||
getStateEvents: function(type) {
|
||||
if (type !== "m.room.member") {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
utils.mkMembership({
|
||||
event: true, mship: "join", room: roomId,
|
||||
user: userB,
|
||||
}),
|
||||
utils.mkMembership({
|
||||
event: true, mship: "join", room: roomId,
|
||||
user: userC, name: "Alice",
|
||||
}),
|
||||
joinEvent,
|
||||
];
|
||||
},
|
||||
getUserIdsWithDisplayName: function(displayName) {
|
||||
return [userA, userC];
|
||||
},
|
||||
} as unknown as RoomState;
|
||||
expect(member.name).toEqual(userA); // default = user_id
|
||||
member.setMembershipEvent(joinEvent);
|
||||
expect(member.name).toEqual("Alice"); // prefer displayname
|
||||
member.setMembershipEvent(joinEvent, roomState);
|
||||
expect(member.name).not.toEqual("Alice"); // it should disambig.
|
||||
// user_id should be there somewhere
|
||||
expect(member.name.indexOf(userA)).not.toEqual(-1);
|
||||
});
|
||||
|
||||
it("should emit 'RoomMember.membership' if the membership changes", function() {
|
||||
let emitCount = 0;
|
||||
member.on("RoomMember.membership", function(ev, mem) {
|
||||
member.on(RoomMemberEvent.Membership, function(ev, mem) {
|
||||
emitCount += 1;
|
||||
expect(mem).toEqual(member);
|
||||
expect(ev).toEqual(inviteEvent);
|
||||
@@ -291,7 +308,7 @@ describe("RoomMember", function() {
|
||||
|
||||
it("should emit 'RoomMember.name' if the name changes", function() {
|
||||
let emitCount = 0;
|
||||
member.on("RoomMember.name", function(ev, mem) {
|
||||
member.on(RoomMemberEvent.Name, function(ev, mem) {
|
||||
emitCount += 1;
|
||||
expect(mem).toEqual(member);
|
||||
expect(ev).toEqual(joinEvent);
|
||||
@@ -341,7 +358,7 @@ describe("RoomMember", function() {
|
||||
getUserIdsWithDisplayName: function(displayName) {
|
||||
return [userA, userC];
|
||||
},
|
||||
};
|
||||
} as unknown as RoomState;
|
||||
expect(member.name).toEqual(userA); // default = user_id
|
||||
member.setMembershipEvent(joinEvent, roomState);
|
||||
expect(member.name).not.toEqual("Alíce"); // it should disambig.
|
@@ -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 { makeBeaconEvent, makeBeaconInfoEvent } from "../test-utils/beacon";
|
||||
import { filterEmitCallsByEventType } from "../test-utils/emitter";
|
||||
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 {
|
||||
MatrixEvent,
|
||||
MatrixEventEvent,
|
||||
} from "../../src/models/event";
|
||||
import { M_BEACON } from "../../src/@types/beacon";
|
||||
import { MatrixClient } from "../../src/client";
|
||||
|
||||
describe("RoomState", function() {
|
||||
const roomId = "!foo:bar";
|
||||
@@ -17,7 +40,7 @@ describe("RoomState", function() {
|
||||
const userC = "@cleo:bar";
|
||||
const userLazy = "@lazy:bar";
|
||||
|
||||
let state;
|
||||
let state = new RoomState(roomId);
|
||||
|
||||
beforeEach(function() {
|
||||
state = new RoomState(roomId);
|
||||
@@ -67,8 +90,8 @@ describe("RoomState", function() {
|
||||
|
||||
it("should return a member which changes as state changes", function() {
|
||||
const member = state.getMember(userB);
|
||||
expect(member.membership).toEqual("join");
|
||||
expect(member.name).toEqual(userB);
|
||||
expect(member?.membership).toEqual("join");
|
||||
expect(member?.name).toEqual(userB);
|
||||
|
||||
state.setStateEvents([
|
||||
utils.mkMembership({
|
||||
@@ -77,40 +100,40 @@ describe("RoomState", function() {
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(member.membership).toEqual("leave");
|
||||
expect(member.name).toEqual("BobGone");
|
||||
expect(member?.membership).toEqual("leave");
|
||||
expect(member?.name).toEqual("BobGone");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSentinelMember", 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",
|
||||
function() {
|
||||
const preLeaveUser = state.getSentinelMember(userA);
|
||||
state.setStateEvents([
|
||||
utils.mkMembership({
|
||||
room: roomId, user: userA, mship: "leave", event: true,
|
||||
name: "AliceIsGone",
|
||||
}),
|
||||
]);
|
||||
const postLeaveUser = state.getSentinelMember(userA);
|
||||
function() {
|
||||
const preLeaveUser = state.getSentinelMember(userA);
|
||||
state.setStateEvents([
|
||||
utils.mkMembership({
|
||||
room: roomId, user: userA, mship: "leave", event: true,
|
||||
name: "AliceIsGone",
|
||||
}),
|
||||
]);
|
||||
const postLeaveUser = state.getSentinelMember(userA);
|
||||
|
||||
expect(preLeaveUser.membership).toEqual("join");
|
||||
expect(preLeaveUser.name).toEqual(userA);
|
||||
expect(preLeaveUser?.membership).toEqual("join");
|
||||
expect(preLeaveUser?.name).toEqual(userA);
|
||||
|
||||
expect(postLeaveUser.membership).toEqual("leave");
|
||||
expect(postLeaveUser.name).toEqual("AliceIsGone");
|
||||
});
|
||||
expect(postLeaveUser?.membership).toEqual("leave");
|
||||
expect(postLeaveUser?.name).toEqual("AliceIsGone");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getStateEvents", function() {
|
||||
it("should return null if a state_key was specified and there was no match",
|
||||
function() {
|
||||
expect(state.getStateEvents("foo.bar.baz", "keyname")).toEqual(null);
|
||||
});
|
||||
function() {
|
||||
expect(state.getStateEvents("foo.bar.baz", "keyname")).toEqual(null);
|
||||
});
|
||||
|
||||
it("should return an empty list if a state_key was not specified and there" +
|
||||
" 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",
|
||||
function() {
|
||||
const events = state.getStateEvents("m.room.member");
|
||||
expect(events.length).toEqual(2);
|
||||
// ordering unimportant
|
||||
expect([userA, userB].indexOf(events[0].getStateKey())).not.toEqual(-1);
|
||||
expect([userA, userB].indexOf(events[1].getStateKey())).not.toEqual(-1);
|
||||
});
|
||||
function() {
|
||||
const events = state.getStateEvents("m.room.member");
|
||||
expect(events.length).toEqual(2);
|
||||
// ordering unimportant
|
||||
expect([userA, userB].indexOf(events[0].getStateKey() as string)).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",
|
||||
function() {
|
||||
const event = state.getStateEvents("m.room.member", userA);
|
||||
expect(event.getContent()).toMatchObject({
|
||||
membership: "join",
|
||||
function() {
|
||||
const event = state.getStateEvents("m.room.member", userA);
|
||||
expect(event.getContent()).toMatchObject({
|
||||
membership: "join",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("setStateEvents", function() {
|
||||
@@ -146,7 +169,7 @@ describe("RoomState", function() {
|
||||
}),
|
||||
];
|
||||
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(st).toEqual(state);
|
||||
expect(mem).toEqual(state.getMember(ev.getSender()));
|
||||
@@ -166,7 +189,7 @@ describe("RoomState", function() {
|
||||
}),
|
||||
];
|
||||
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(mem.userId).toEqual(memberEvents[emitCount].getSender());
|
||||
expect(mem.membership).toBeFalsy(); // not defined yet
|
||||
@@ -192,7 +215,7 @@ describe("RoomState", function() {
|
||||
}),
|
||||
];
|
||||
let emitCount = 0;
|
||||
state.on("RoomState.events", function(ev, st) {
|
||||
state.on(RoomStateEvent.Events, function(ev, st) {
|
||||
expect(ev).toEqual(events[emitCount]);
|
||||
expect(st).toEqual(state);
|
||||
emitCount += 1;
|
||||
@@ -272,7 +295,7 @@ describe("RoomState", function() {
|
||||
}),
|
||||
];
|
||||
let emitCount = 0;
|
||||
state.on("RoomState.Marker", function(markerEvent, markerFoundOptions) {
|
||||
state.on(RoomStateEvent.Marker, function(markerEvent, markerFoundOptions) {
|
||||
expect(markerEvent).toEqual(events[emitCount]);
|
||||
expect(markerFoundOptions).toEqual({ timelineWasEmpty: true });
|
||||
emitCount += 1;
|
||||
@@ -296,7 +319,7 @@ describe("RoomState", function() {
|
||||
|
||||
it('does not add redacted beacon info events to state', () => {
|
||||
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
|
||||
const redactionEvent = { event: { type: 'm.room.redaction' } };
|
||||
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction' });
|
||||
redactedBeaconEvent.makeRedacted(redactionEvent);
|
||||
const emitSpy = jest.spyOn(state, 'emit');
|
||||
|
||||
@@ -316,27 +339,27 @@ describe("RoomState", function() {
|
||||
|
||||
state.setStateEvents([beaconEvent]);
|
||||
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
|
||||
expect(beaconInstance.isLive).toEqual(true);
|
||||
expect(beaconInstance?.isLive).toEqual(true);
|
||||
|
||||
state.setStateEvents([updatedBeaconEvent]);
|
||||
|
||||
// same Beacon
|
||||
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance);
|
||||
// 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', () => {
|
||||
const beaconId = '$beacon1';
|
||||
const beaconEvent = 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);
|
||||
|
||||
state.setStateEvents([beaconEvent]);
|
||||
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
|
||||
const destroySpy = jest.spyOn(beaconInstance, 'destroy');
|
||||
expect(beaconInstance.isLive).toEqual(true);
|
||||
const destroySpy = jest.spyOn(beaconInstance as Beacon, 'destroy');
|
||||
expect(beaconInstance?.isLive).toEqual(true);
|
||||
|
||||
state.setStateEvents([redactedBeaconEvent]);
|
||||
|
||||
@@ -357,7 +380,7 @@ describe("RoomState", function() {
|
||||
|
||||
// live beacon is now not live
|
||||
const updatedLiveBeaconEvent = makeBeaconInfoEvent(
|
||||
userA, roomId, { isLive: false }, liveBeaconEvent.getId(), '$beacon1',
|
||||
userA, roomId, { isLive: false }, liveBeaconEvent.getId(),
|
||||
);
|
||||
|
||||
state.setStateEvents([updatedLiveBeaconEvent]);
|
||||
@@ -377,8 +400,8 @@ describe("RoomState", function() {
|
||||
state.markOutOfBandMembersStarted();
|
||||
state.setOutOfBandMembers([oobMemberEvent]);
|
||||
const member = state.getMember(userLazy);
|
||||
expect(member.userId).toEqual(userLazy);
|
||||
expect(member.isOutOfBand()).toEqual(true);
|
||||
expect(member?.userId).toEqual(userLazy);
|
||||
expect(member?.isOutOfBand()).toEqual(true);
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
let eventReceived = false;
|
||||
state.once('RoomState.newMember', (_, __, member) => {
|
||||
state.once(RoomStateEvent.NewMember, (_event, _state, member) => {
|
||||
expect(member.userId).toEqual(userLazy);
|
||||
eventReceived = true;
|
||||
});
|
||||
@@ -410,8 +433,8 @@ describe("RoomState", function() {
|
||||
state.markOutOfBandMembersStarted();
|
||||
state.setOutOfBandMembers([oobMemberEvent]);
|
||||
const memberA = state.getMember(userA);
|
||||
expect(memberA.events.member.getId()).not.toEqual(oobMemberEvent.getId());
|
||||
expect(memberA.isOutOfBand()).toEqual(false);
|
||||
expect(memberA?.events?.member?.getId()).not.toEqual(oobMemberEvent.getId());
|
||||
expect(memberA?.isOutOfBand()).toEqual(false);
|
||||
});
|
||||
|
||||
it("should emit members when updating a member", function() {
|
||||
@@ -420,7 +443,7 @@ describe("RoomState", function() {
|
||||
user: doesntExistYetUserId, mship: "join", room: roomId, event: true,
|
||||
});
|
||||
let eventReceived = false;
|
||||
state.once('RoomState.members', (_, __, member) => {
|
||||
state.once(RoomStateEvent.Members, (_event, _state, member) => {
|
||||
expect(member.userId).toEqual(doesntExistYetUserId);
|
||||
eventReceived = true;
|
||||
});
|
||||
@@ -443,8 +466,8 @@ describe("RoomState", function() {
|
||||
[userA, userB, userLazy].forEach((userId) => {
|
||||
const member = state.getMember(userId);
|
||||
const memberCopy = copy.getMember(userId);
|
||||
expect(member.name).toEqual(memberCopy.name);
|
||||
expect(member.isOutOfBand()).toEqual(memberCopy.isOutOfBand());
|
||||
expect(member?.name).toEqual(memberCopy?.name);
|
||||
expect(member?.isOutOfBand()).toEqual(memberCopy?.isOutOfBand());
|
||||
});
|
||||
// check member keys
|
||||
expect(Object.keys(state.members)).toEqual(Object.keys(copy.members));
|
||||
@@ -496,78 +519,80 @@ describe("RoomState", function() {
|
||||
|
||||
describe("maySendStateEvent", function() {
|
||||
it("should say any member may send state with no power level event",
|
||||
function() {
|
||||
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
|
||||
});
|
||||
function() {
|
||||
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
|
||||
});
|
||||
|
||||
it("should say members with power >=50 may send state with power level event " +
|
||||
"but no state default",
|
||||
function() {
|
||||
const powerLevelEvent = {
|
||||
type: "m.room.power_levels", room: roomId, user: userA, event: true,
|
||||
const powerLevelEvent = new MatrixEvent({
|
||||
type: "m.room.power_levels", room_id: roomId, sender: userA,
|
||||
state_key: "",
|
||||
content: {
|
||||
users_default: 10,
|
||||
// state_default: 50, "intentionally left blank"
|
||||
events_default: 25,
|
||||
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', userB)).toEqual(false);
|
||||
});
|
||||
|
||||
it("should obey state_default",
|
||||
function() {
|
||||
const powerLevelEvent = {
|
||||
type: "m.room.power_levels", room: roomId, user: userA, event: true,
|
||||
content: {
|
||||
users_default: 10,
|
||||
state_default: 30,
|
||||
events_default: 25,
|
||||
users: {
|
||||
function() {
|
||||
const powerLevelEvent = new MatrixEvent({
|
||||
type: "m.room.power_levels", room_id: roomId, sender: userA,
|
||||
state_key: "",
|
||||
content: {
|
||||
users_default: 10,
|
||||
state_default: 30,
|
||||
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', userB)).toEqual(false);
|
||||
});
|
||||
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
|
||||
expect(state.maySendStateEvent('m.room.name', userB)).toEqual(false);
|
||||
});
|
||||
|
||||
it("should honour explicit event power levels in the power_levels event",
|
||||
function() {
|
||||
const powerLevelEvent = {
|
||||
type: "m.room.power_levels", room: roomId, user: userA, event: true,
|
||||
content: {
|
||||
events: {
|
||||
"m.room.other_thing": 76,
|
||||
function() {
|
||||
const powerLevelEvent = new MatrixEvent({
|
||||
type: "m.room.power_levels", room_id: roomId, sender: userA,
|
||||
state_key: "", content: {
|
||||
events: {
|
||||
"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', userB)).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.other_thing', userA)).toEqual(true);
|
||||
expect(state.maySendStateEvent('m.room.other_thing', userB)).toEqual(false);
|
||||
});
|
||||
expect(state.maySendStateEvent('m.room.other_thing', userA)).toEqual(true);
|
||||
expect(state.maySendStateEvent('m.room.other_thing', userB)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getJoinedMemberCount", function() {
|
||||
@@ -682,71 +707,73 @@ describe("RoomState", function() {
|
||||
|
||||
describe("maySendEvent", function() {
|
||||
it("should say any member may send events with no power level event",
|
||||
function() {
|
||||
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
|
||||
expect(state.maySendMessage(userA)).toEqual(true);
|
||||
});
|
||||
function() {
|
||||
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
|
||||
expect(state.maySendMessage(userA)).toEqual(true);
|
||||
});
|
||||
|
||||
it("should obey events_default",
|
||||
function() {
|
||||
const powerLevelEvent = {
|
||||
type: "m.room.power_levels", room: roomId, user: userA, event: true,
|
||||
content: {
|
||||
users_default: 10,
|
||||
state_default: 30,
|
||||
events_default: 25,
|
||||
users: {
|
||||
function() {
|
||||
const powerLevelEvent = new MatrixEvent({
|
||||
type: "m.room.power_levels", room_id: roomId, sender: userA,
|
||||
state_key: "",
|
||||
content: {
|
||||
users_default: 10,
|
||||
state_default: 30,
|
||||
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', userB)).toEqual(false);
|
||||
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
|
||||
expect(state.maySendEvent('m.room.message', userB)).toEqual(false);
|
||||
|
||||
expect(state.maySendMessage(userA)).toEqual(true);
|
||||
expect(state.maySendMessage(userB)).toEqual(false);
|
||||
});
|
||||
expect(state.maySendMessage(userA)).toEqual(true);
|
||||
expect(state.maySendMessage(userB)).toEqual(false);
|
||||
});
|
||||
|
||||
it("should honour explicit event power levels in the power_levels event",
|
||||
function() {
|
||||
const powerLevelEvent = {
|
||||
type: "m.room.power_levels", room: roomId, user: userA, event: true,
|
||||
content: {
|
||||
events: {
|
||||
"m.room.other_thing": 33,
|
||||
function() {
|
||||
const powerLevelEvent = new MatrixEvent({
|
||||
type: "m.room.power_levels", room_id: roomId, sender: userA,
|
||||
state_key: "",
|
||||
content: {
|
||||
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', userB)).toEqual(true);
|
||||
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
|
||||
expect(state.maySendEvent('m.room.message', userB)).toEqual(true);
|
||||
|
||||
expect(state.maySendMessage(userA)).toEqual(true);
|
||||
expect(state.maySendMessage(userB)).toEqual(true);
|
||||
expect(state.maySendMessage(userA)).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', userB)).toEqual(false);
|
||||
});
|
||||
expect(state.maySendEvent('m.room.other_thing', userA)).toEqual(true);
|
||||
expect(state.maySendEvent('m.room.other_thing', userB)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('processBeaconEvents', () => {
|
||||
const beacon1 = makeBeaconInfoEvent(userA, roomId, {}, '$beacon1', '$beacon1');
|
||||
const beacon2 = makeBeaconInfoEvent(userB, roomId, {}, '$beacon2', '$beacon2');
|
||||
const beacon1 = makeBeaconInfoEvent(userA, roomId, {}, '$beacon1');
|
||||
const beacon2 = makeBeaconInfoEvent(userB, roomId, {}, '$beacon2');
|
||||
|
||||
const mockClient = { decryptEventIfNeeded: jest.fn() };
|
||||
const mockClient = { decryptEventIfNeeded: jest.fn() } as unknown as MockedObject<MatrixClient>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient.decryptEventIfNeeded.mockClear();
|
||||
@@ -816,11 +843,11 @@ describe("RoomState", function() {
|
||||
beaconInfoId: 'some-other-beacon',
|
||||
});
|
||||
|
||||
state.setStateEvents([beacon1, beacon2], mockClient);
|
||||
state.setStateEvents([beacon1, beacon2]);
|
||||
|
||||
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');
|
||||
|
||||
state.processBeaconEvents([location1, location2, location3], mockClient);
|
||||
@@ -885,7 +912,7 @@ describe("RoomState", function() {
|
||||
});
|
||||
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();
|
||||
state.processBeaconEvents([location, otherRelatedEvent], mockClient);
|
||||
expect(addLocationsSpy).not.toHaveBeenCalled();
|
||||
@@ -945,13 +972,13 @@ describe("RoomState", function() {
|
||||
});
|
||||
jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true);
|
||||
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();
|
||||
state.processBeaconEvents([decryptingRelatedEvent], mockClient);
|
||||
|
||||
// this event is a message after decryption
|
||||
decryptingRelatedEvent.type = EventType.RoomMessage;
|
||||
decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted);
|
||||
decryptingRelatedEvent.event.type = EventType.RoomMessage;
|
||||
decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted, decryptingRelatedEvent);
|
||||
|
||||
expect(addLocationsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -967,14 +994,14 @@ describe("RoomState", function() {
|
||||
});
|
||||
jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true);
|
||||
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();
|
||||
state.processBeaconEvents([decryptingRelatedEvent], mockClient);
|
||||
|
||||
// update type after '''decryption'''
|
||||
decryptingRelatedEvent.event.type = M_BEACON.name;
|
||||
decryptingRelatedEvent.event.content = locationEvent.content;
|
||||
decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted);
|
||||
decryptingRelatedEvent.event.content = locationEvent.event.content;
|
||||
decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted, decryptingRelatedEvent);
|
||||
|
||||
expect(addLocationsSpy).toHaveBeenCalledWith([decryptingRelatedEvent]);
|
||||
});
|
@@ -288,11 +288,11 @@ describe("Room", function() {
|
||||
room.addLiveEvents(events);
|
||||
expect(room.currentState.setStateEvents).toHaveBeenCalledWith(
|
||||
[events[0]],
|
||||
{ timelineWasEmpty: undefined },
|
||||
{ timelineWasEmpty: false },
|
||||
);
|
||||
expect(room.currentState.setStateEvents).toHaveBeenCalledWith(
|
||||
[events[1]],
|
||||
{ timelineWasEmpty: undefined },
|
||||
{ timelineWasEmpty: false },
|
||||
);
|
||||
expect(events[0].forwardLooking).toBe(true);
|
||||
expect(events[1].forwardLooking).toBe(true);
|
||||
@@ -426,6 +426,17 @@ describe("Room", function() {
|
||||
// but without the event ID matching we will still have the local event in pending events
|
||||
expect(room.getEventForTxnId(txnId)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should correctly handle remote echoes from other devices", () => {
|
||||
const remoteEvent = utils.mkMessage({
|
||||
room: roomId, user: userA, event: true,
|
||||
});
|
||||
remoteEvent.event.unsigned = { transaction_id: "TXN_ID" };
|
||||
|
||||
// add the remoteEvent
|
||||
room.addLiveEvents([remoteEvent]);
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addEphemeralEvents', () => {
|
||||
|
@@ -152,6 +152,9 @@ describe("utils", function() {
|
||||
assert.isTrue(utils.deepCompare({ a: 1, b: 2 }, { a: 1, b: 2 }));
|
||||
assert.isTrue(utils.deepCompare({ a: 1, b: 2 }, { b: 2, a: 1 }));
|
||||
assert.isFalse(utils.deepCompare({ a: 1, b: 2 }, { a: 1, b: 3 }));
|
||||
assert.isFalse(utils.deepCompare({ a: 1, b: 2 }, { a: 1 }));
|
||||
assert.isFalse(utils.deepCompare({ a: 1 }, { a: 1, b: 2 }));
|
||||
assert.isFalse(utils.deepCompare({ a: 1 }, { b: 1 }));
|
||||
|
||||
assert.isTrue(utils.deepCompare({
|
||||
1: { name: "mhc", age: 28 },
|
||||
|
Reference in New Issue
Block a user