Merge branch 'develop' into Bubble-bericht
This commit is contained in:
parent
f367d617c5
commit
c843387043
264 changed files with 7383 additions and 3268 deletions
214
test/CallHandler-test.ts
Normal file
214
test/CallHandler-test.ts
Normal file
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
Copyright 2021 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 './skinned-sdk';
|
||||
|
||||
import CallHandler, { PlaceCallType, CallHandlerEvent } from '../src/CallHandler';
|
||||
import { stubClient, mkStubRoom } from './test-utils';
|
||||
import { MatrixClientPeg } from '../src/MatrixClientPeg';
|
||||
import dis from '../src/dispatcher/dispatcher';
|
||||
import { CallEvent, CallState } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import DMRoomMap from '../src/utils/DMRoomMap';
|
||||
import EventEmitter from 'events';
|
||||
import { Action } from '../src/dispatcher/actions';
|
||||
import SdkConfig from '../src/SdkConfig';
|
||||
|
||||
const REAL_ROOM_ID = '$room1:example.org';
|
||||
const MAPPED_ROOM_ID = '$room2:example.org';
|
||||
const MAPPED_ROOM_ID_2 = '$room3:example.org';
|
||||
|
||||
function mkStubDM(roomId, userId) {
|
||||
const room = mkStubRoom(roomId);
|
||||
room.getJoinedMembers = jest.fn().mockReturnValue([
|
||||
{
|
||||
userId: '@me:example.org',
|
||||
name: 'Member',
|
||||
rawDisplayName: 'Member',
|
||||
roomId: roomId,
|
||||
membership: 'join',
|
||||
getAvatarUrl: () => 'mxc://avatar.url/image.png',
|
||||
getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
|
||||
},
|
||||
{
|
||||
userId: userId,
|
||||
name: 'Member',
|
||||
rawDisplayName: 'Member',
|
||||
roomId: roomId,
|
||||
membership: 'join',
|
||||
getAvatarUrl: () => 'mxc://avatar.url/image.png',
|
||||
getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
|
||||
},
|
||||
]);
|
||||
room.currentState.getMembers = room.getJoinedMembers;
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
class FakeCall extends EventEmitter {
|
||||
roomId: string;
|
||||
callId = "fake call id";
|
||||
|
||||
constructor(roomId) {
|
||||
super();
|
||||
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
setRemoteOnHold() {}
|
||||
setRemoteAudioElement() {}
|
||||
|
||||
placeVoiceCall() {
|
||||
this.emit(CallEvent.State, CallState.Connected, null);
|
||||
}
|
||||
}
|
||||
|
||||
describe('CallHandler', () => {
|
||||
let dmRoomMap;
|
||||
let callHandler;
|
||||
let audioElement;
|
||||
let fakeCall;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
MatrixClientPeg.get().createCall = roomId => {
|
||||
if (fakeCall && fakeCall.roomId !== roomId) {
|
||||
throw new Error("Only one call is supported!");
|
||||
}
|
||||
fakeCall = new FakeCall(roomId);
|
||||
return fakeCall;
|
||||
};
|
||||
|
||||
callHandler = new CallHandler();
|
||||
callHandler.start();
|
||||
|
||||
dmRoomMap = {
|
||||
getUserIdForRoomId: roomId => {
|
||||
if (roomId === REAL_ROOM_ID) {
|
||||
return '@user1:example.org';
|
||||
} else if (roomId === MAPPED_ROOM_ID) {
|
||||
return '@user2:example.org';
|
||||
} else if (roomId === MAPPED_ROOM_ID_2) {
|
||||
return '@user3:example.org';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getDMRoomsForUserId: userId => {
|
||||
if (userId === '@user2:example.org') {
|
||||
return [MAPPED_ROOM_ID];
|
||||
} else if (userId === '@user3:example.org') {
|
||||
return [MAPPED_ROOM_ID_2];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
};
|
||||
DMRoomMap.setShared(dmRoomMap);
|
||||
|
||||
audioElement = document.createElement('audio');
|
||||
audioElement.id = "remoteAudio";
|
||||
document.body.appendChild(audioElement);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
callHandler.stop();
|
||||
DMRoomMap.setShared(null);
|
||||
// @ts-ignore
|
||||
window.mxCallHandler = null;
|
||||
MatrixClientPeg.unset();
|
||||
|
||||
document.body.removeChild(audioElement);
|
||||
SdkConfig.unset();
|
||||
});
|
||||
|
||||
it('should move calls between rooms when remote asserted identity changes', async () => {
|
||||
const realRoom = mkStubDM(REAL_ROOM_ID, '@user1:example.org');
|
||||
const mappedRoom = mkStubDM(MAPPED_ROOM_ID, '@user2:example.org');
|
||||
const mappedRoom2 = mkStubDM(MAPPED_ROOM_ID_2, '@user3:example.org');
|
||||
|
||||
MatrixClientPeg.get().getRoom = roomId => {
|
||||
switch (roomId) {
|
||||
case REAL_ROOM_ID:
|
||||
return realRoom;
|
||||
case MAPPED_ROOM_ID:
|
||||
return mappedRoom;
|
||||
case MAPPED_ROOM_ID_2:
|
||||
return mappedRoom2;
|
||||
}
|
||||
};
|
||||
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: PlaceCallType.Voice,
|
||||
room_id: REAL_ROOM_ID,
|
||||
}, true);
|
||||
|
||||
let dispatchHandle;
|
||||
// wait for the call to be set up
|
||||
await new Promise<void>(resolve => {
|
||||
dispatchHandle = dis.register(payload => {
|
||||
if (payload.action === 'call_state') {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
dis.unregister(dispatchHandle);
|
||||
|
||||
// should start off in the actual room ID it's in at the protocol level
|
||||
expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBe(fakeCall);
|
||||
|
||||
let callRoomChangeEventCount = 0;
|
||||
const roomChangePromise = new Promise<void>(resolve => {
|
||||
callHandler.addListener(CallHandlerEvent.CallChangeRoom, () => {
|
||||
++callRoomChangeEventCount;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Now emit an asserted identity for user2: this should be ignored
|
||||
// because we haven't set the config option to obey asserted identity
|
||||
fakeCall.getRemoteAssertedIdentity = jest.fn().mockReturnValue({
|
||||
id: "@user2:example.org",
|
||||
});
|
||||
fakeCall.emit(CallEvent.AssertedIdentityChanged);
|
||||
|
||||
// Now set the config option
|
||||
SdkConfig.put({
|
||||
voip: {
|
||||
obeyAssertedIdentity: true,
|
||||
},
|
||||
});
|
||||
|
||||
// ...and send another asserted identity event for a different user
|
||||
fakeCall.getRemoteAssertedIdentity = jest.fn().mockReturnValue({
|
||||
id: "@user3:example.org",
|
||||
});
|
||||
fakeCall.emit(CallEvent.AssertedIdentityChanged);
|
||||
|
||||
await roomChangePromise;
|
||||
callHandler.removeAllListeners();
|
||||
|
||||
// If everything's gone well, we should have seen only one room change
|
||||
// event and the call should now be in user 3's room.
|
||||
// If it's not obeying any, the call will still be in REAL_ROOM_ID.
|
||||
// If it incorrectly obeyed both asserted identity changes, either it will
|
||||
// have just processed one and the call will be in the wrong room, or we'll
|
||||
// have seen two room change dispatches.
|
||||
expect(callRoomChangeEventCount).toEqual(1);
|
||||
expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBeNull();
|
||||
expect(callHandler.getCallForRoom(MAPPED_ROOM_ID_2)).toBe(fakeCall);
|
||||
});
|
||||
});
|
|
@ -29,7 +29,7 @@ describe('ScalarAuthClient', function() {
|
|||
it('should request a new token if the old one fails', async function() {
|
||||
const sac = new ScalarAuthClient();
|
||||
|
||||
sac._getAccountName = jest.fn((arg) => {
|
||||
sac.getAccountName = jest.fn((arg) => {
|
||||
switch (arg) {
|
||||
case "brokentoken":
|
||||
return Promise.reject({
|
||||
|
|
|
@ -177,7 +177,7 @@ describe('QueryMatcher', function() {
|
|||
const qm = new QueryMatcher(NONWORDOBJECTS, {
|
||||
keys: ["name"],
|
||||
shouldMatchWordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
const results = qm.match('bob');
|
||||
expect(results.length).toBe(1);
|
||||
|
|
|
@ -26,9 +26,9 @@ describe("AccessSecretStorageDialog", function() {
|
|||
it("Closes the dialog if _onRecoveryKeyNext is called with a valid key", (done) => {
|
||||
const testInstance = TestRenderer.create(
|
||||
<AccessSecretStorageDialog
|
||||
checkPrivateKey={(p) => p && p.recoveryKey && p.recoveryKey == "a"}
|
||||
onFinished={(v) => {
|
||||
if (v) { done(); }
|
||||
checkPrivateKey={(p) => p && p.recoveryKey && p.recoveryKey == "a"}
|
||||
onFinished={(v) => {
|
||||
if (v) { done(); }
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
@ -43,7 +43,7 @@ describe("AccessSecretStorageDialog", function() {
|
|||
it("Considers a valid key to be valid", async function() {
|
||||
const testInstance = TestRenderer.create(
|
||||
<AccessSecretStorageDialog
|
||||
checkPrivateKey={() => true}
|
||||
checkPrivateKey={() => true}
|
||||
/>,
|
||||
);
|
||||
const v = "asdf";
|
||||
|
@ -61,7 +61,7 @@ describe("AccessSecretStorageDialog", function() {
|
|||
it("Notifies the user if they input an invalid Security Key", async function(done) {
|
||||
const testInstance = TestRenderer.create(
|
||||
<AccessSecretStorageDialog
|
||||
checkPrivateKey={async () => false}
|
||||
checkPrivateKey={async () => false}
|
||||
/>,
|
||||
);
|
||||
const e = { target: { value: "a" } };
|
||||
|
@ -87,12 +87,14 @@ describe("AccessSecretStorageDialog", function() {
|
|||
it("Notifies the user if they input an invalid passphrase", async function(done) {
|
||||
const testInstance = TestRenderer.create(
|
||||
<AccessSecretStorageDialog
|
||||
checkPrivateKey={() => false}
|
||||
onFinished={() => {}}
|
||||
keyInfo={ { passphrase: {
|
||||
salt: 'nonempty',
|
||||
iterations: 2,
|
||||
} } }
|
||||
checkPrivateKey={() => false}
|
||||
onFinished={() => {}}
|
||||
keyInfo={{
|
||||
passphrase: {
|
||||
salt: 'nonempty',
|
||||
iterations: 2,
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
const e = { target: { value: "a" } };
|
||||
|
|
|
@ -245,8 +245,7 @@ describe('MemberEventListSummary', function() {
|
|||
);
|
||||
});
|
||||
|
||||
it('truncates multiple sequences of repetitions with other events between',
|
||||
function() {
|
||||
it('truncates multiple sequences of repetitions with other events between', function() {
|
||||
const events = generateEvents([
|
||||
{
|
||||
userId: "@user_1:some.domain",
|
||||
|
@ -395,8 +394,7 @@ describe('MemberEventListSummary', function() {
|
|||
);
|
||||
});
|
||||
|
||||
it('correctly orders sequences of transitions by the order of their first event',
|
||||
function() {
|
||||
it('correctly orders sequences of transitions by the order of their first event', function() {
|
||||
const events = generateEvents([
|
||||
{
|
||||
userId: "@user_2:some.domain",
|
||||
|
@ -568,8 +566,7 @@ describe('MemberEventListSummary', function() {
|
|||
);
|
||||
});
|
||||
|
||||
it('handles invitation plurals correctly when there are multiple invites',
|
||||
function() {
|
||||
it('handles invitation plurals correctly when there are multiple invites', function() {
|
||||
const events = generateEvents([
|
||||
{
|
||||
userId: "@user_1:some.domain",
|
||||
|
|
|
@ -100,7 +100,7 @@ describe('MemberList', () => {
|
|||
memberList = r;
|
||||
};
|
||||
root = ReactDOM.render(<WrappedMemberList roomId={memberListRoom.roomId}
|
||||
wrappedRef={gatherWrappedRef} />, parentDiv);
|
||||
wrappedRef={gatherWrappedRef} />, parentDiv);
|
||||
});
|
||||
|
||||
afterEach((done) => {
|
||||
|
|
|
@ -29,7 +29,10 @@ function waitForRoomListStoreUpdate() {
|
|||
|
||||
describe('RoomList', () => {
|
||||
function createRoom(opts) {
|
||||
const room = new Room(generateRoomId(), null, client.getUserId());
|
||||
const room = new Room(generateRoomId(), MatrixClientPeg.get(), client.getUserId(), {
|
||||
// The room list now uses getPendingEvents(), so we need a detached ordering.
|
||||
pendingEventOrdering: "detached",
|
||||
});
|
||||
if (opts) {
|
||||
Object.assign(room, opts);
|
||||
}
|
||||
|
@ -67,8 +70,9 @@ describe('RoomList', () => {
|
|||
root = ReactDOM.render(
|
||||
<DragDropContext>
|
||||
<WrappedRoomList searchFilter="" onResize={() => {}} />
|
||||
</DragDropContext>
|
||||
, parentDiv);
|
||||
</DragDropContext>,
|
||||
parentDiv,
|
||||
);
|
||||
ReactTestUtils.findRenderedComponentWithType(root, RoomList);
|
||||
|
||||
movingRoom = createRoom({name: 'Moving room'});
|
||||
|
|
|
@ -178,7 +178,7 @@ describe('editor/deserialize', function() {
|
|||
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||||
expect(parts.length).toBe(3);
|
||||
expect(parts[0]).toStrictEqual({type: "plain", text: "Try "});
|
||||
expect(parts[1]).toStrictEqual({type: "room-pill", text: "#room:hs.tld"});
|
||||
expect(parts[1]).toStrictEqual({type: "room-pill", text: "#room:hs.tld", resourceId: "#room:hs.tld"});
|
||||
expect(parts[2]).toStrictEqual({type: "plain", text: "?"});
|
||||
});
|
||||
it('@room pill', function() {
|
||||
|
|
|
@ -93,10 +93,10 @@ module.exports = class ElementSession {
|
|||
const type = req.resourceType();
|
||||
const response = await req.response();
|
||||
//if (type === 'xhr' || type === 'fetch') {
|
||||
buffer += `${type} ${response.status()} ${req.method()} ${req.url()} \n`;
|
||||
// if (req.method() === "POST") {
|
||||
// buffer += " Post data: " + req.postData();
|
||||
// }
|
||||
buffer += `${type} ${response.status()} ${req.method()} ${req.url()} \n`;
|
||||
// if (req.method() === "POST") {
|
||||
// buffer += " Post data: " + req.postData();
|
||||
// }
|
||||
//}
|
||||
});
|
||||
return {
|
||||
|
|
|
@ -435,9 +435,9 @@ jsprim@^1.2.2:
|
|||
verror "1.10.0"
|
||||
|
||||
lodash@^4.15.0, lodash@^4.17.11:
|
||||
version "4.17.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
mime-db@~1.38.0:
|
||||
version "1.38.0"
|
||||
|
|
714
test/stores/SpaceStore-test.ts
Normal file
714
test/stores/SpaceStore-test.ts
Normal file
|
@ -0,0 +1,714 @@
|
|||
/*
|
||||
Copyright 2021 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 { EventEmitter } from "events";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
import "../skinned-sdk"; // Must be first for skinning to work
|
||||
import SpaceStore, {
|
||||
UPDATE_INVITED_SPACES,
|
||||
UPDATE_SELECTED_SPACE,
|
||||
UPDATE_TOP_LEVEL_SPACES
|
||||
} from "../../src/stores/SpaceStore";
|
||||
import { resetAsyncStoreWithClient, setupAsyncStoreWithClient } from "../utils/test-utils";
|
||||
import { mkEvent, mkStubRoom, stubClient } from "../test-utils";
|
||||
import { EnhancedMap } from "../../src/utils/maps";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import DMRoomMap from "../../src/utils/DMRoomMap";
|
||||
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
||||
import defaultDispatcher from "../../src/dispatcher/dispatcher";
|
||||
|
||||
type MatrixEvent = any; // importing from js-sdk upsets things
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
const mockStateEventImplementation = (events: MatrixEvent[]) => {
|
||||
const stateMap = new EnhancedMap<string, Map<string, MatrixEvent>>();
|
||||
events.forEach(event => {
|
||||
stateMap.getOrCreate(event.getType(), new Map()).set(event.getStateKey(), event);
|
||||
});
|
||||
|
||||
return (eventType: string, stateKey?: string) => {
|
||||
if (stateKey || stateKey === "") {
|
||||
return stateMap.get(eventType)?.get(stateKey) || null;
|
||||
}
|
||||
return Array.from(stateMap.get(eventType)?.values() || []);
|
||||
};
|
||||
};
|
||||
|
||||
const emitPromise = (e: EventEmitter, k: string | symbol) => new Promise(r => e.once(k, r));
|
||||
|
||||
const testUserId = "@test:user";
|
||||
|
||||
let rooms = [];
|
||||
|
||||
const mkRoom = (roomId: string) => {
|
||||
const room = mkStubRoom(roomId);
|
||||
room.currentState.getStateEvents.mockImplementation(mockStateEventImplementation([]));
|
||||
rooms.push(room);
|
||||
return room;
|
||||
};
|
||||
|
||||
const mkSpace = (spaceId: string, children: string[] = []) => {
|
||||
const space = mkRoom(spaceId);
|
||||
space.isSpaceRoom.mockReturnValue(true);
|
||||
space.currentState.getStateEvents.mockImplementation(mockStateEventImplementation(children.map(roomId =>
|
||||
mkEvent({
|
||||
event: true,
|
||||
type: EventType.SpaceChild,
|
||||
room: spaceId,
|
||||
user: testUserId,
|
||||
skey: roomId,
|
||||
content: { via: [] },
|
||||
ts: Date.now(),
|
||||
}),
|
||||
)));
|
||||
return space;
|
||||
};
|
||||
|
||||
const getValue = jest.fn();
|
||||
SettingsStore.getValue = getValue;
|
||||
|
||||
const getUserIdForRoomId = jest.fn();
|
||||
// @ts-ignore
|
||||
DMRoomMap.sharedInstance = { getUserIdForRoomId };
|
||||
|
||||
const fav1 = "!fav1:server";
|
||||
const fav2 = "!fav2:server";
|
||||
const fav3 = "!fav3:server";
|
||||
const dm1 = "!dm1:server";
|
||||
const dm1Partner = "@dm1Partner:server";
|
||||
const dm2 = "!dm2:server";
|
||||
const dm2Partner = "@dm2Partner:server";
|
||||
const dm3 = "!dm3:server";
|
||||
const dm3Partner = "@dm3Partner:server";
|
||||
const orphan1 = "!orphan1:server";
|
||||
const orphan2 = "!orphan2:server";
|
||||
const invite1 = "!invite1:server";
|
||||
const invite2 = "!invite2:server";
|
||||
const room1 = "!room1:server";
|
||||
const room2 = "!room2:server";
|
||||
const space1 = "!space1:server";
|
||||
const space2 = "!space2:server";
|
||||
const space3 = "!space3:server";
|
||||
|
||||
describe("SpaceStore", () => {
|
||||
stubClient();
|
||||
const store = SpaceStore.instance;
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
const viewRoom = roomId => defaultDispatcher.dispatch({ action: "view_room", room_id: roomId }, true);
|
||||
|
||||
const run = async () => {
|
||||
client.getRoom.mockImplementation(roomId => rooms.find(room => room.roomId === roomId));
|
||||
await setupAsyncStoreWithClient(store, client);
|
||||
jest.runAllTimers();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.runAllTimers();
|
||||
client.getVisibleRooms.mockReturnValue(rooms = []);
|
||||
getValue.mockImplementation(settingName => {
|
||||
if (settingName === "feature_spaces") {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
afterEach(async () => {
|
||||
await resetAsyncStoreWithClient(store);
|
||||
});
|
||||
|
||||
describe("static hierarchy resolution tests", () => {
|
||||
it("handles no spaces", async () => {
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces).toStrictEqual([]);
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("handles 3 joined top level spaces", async () => {
|
||||
mkSpace("!space1:server");
|
||||
mkSpace("!space2:server");
|
||||
mkSpace("!space3:server");
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces.sort()).toStrictEqual(client.getVisibleRooms().sort());
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("handles a basic hierarchy", async () => {
|
||||
mkSpace("!space1:server");
|
||||
mkSpace("!space2:server");
|
||||
mkSpace("!company:server", [
|
||||
mkSpace("!company_dept1:server", [
|
||||
mkSpace("!company_dept1_group1:server").roomId,
|
||||
]).roomId,
|
||||
mkSpace("!company_dept2:server").roomId,
|
||||
]);
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces.map(r => r.roomId).sort()).toStrictEqual([
|
||||
"!space1:server",
|
||||
"!space2:server",
|
||||
"!company:server",
|
||||
].sort());
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
|
||||
expect(store.getChildRooms("!space1:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!space1:server")).toStrictEqual([]);
|
||||
expect(store.getChildRooms("!space2:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!space2:server")).toStrictEqual([]);
|
||||
expect(store.getChildRooms("!company:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!company:server")).toStrictEqual([
|
||||
client.getRoom("!company_dept1:server"),
|
||||
client.getRoom("!company_dept2:server"),
|
||||
]);
|
||||
expect(store.getChildRooms("!company_dept1:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!company_dept1:server")).toStrictEqual([
|
||||
client.getRoom("!company_dept1_group1:server"),
|
||||
]);
|
||||
expect(store.getChildRooms("!company_dept1_group1:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!company_dept1_group1:server")).toStrictEqual([]);
|
||||
expect(store.getChildRooms("!company_dept2:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!company_dept2:server")).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("handles a sub-space existing in multiple places in the space tree", async () => {
|
||||
const subspace = mkSpace("!subspace:server");
|
||||
mkSpace("!space1:server");
|
||||
mkSpace("!space2:server");
|
||||
mkSpace("!company:server", [
|
||||
mkSpace("!company_dept1:server", [
|
||||
mkSpace("!company_dept1_group1:server", [subspace.roomId]).roomId,
|
||||
]).roomId,
|
||||
mkSpace("!company_dept2:server", [subspace.roomId]).roomId,
|
||||
subspace.roomId,
|
||||
]);
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces.map(r => r.roomId).sort()).toStrictEqual([
|
||||
"!space1:server",
|
||||
"!space2:server",
|
||||
"!company:server",
|
||||
].sort());
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
|
||||
expect(store.getChildRooms("!space1:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!space1:server")).toStrictEqual([]);
|
||||
expect(store.getChildRooms("!space2:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!space2:server")).toStrictEqual([]);
|
||||
expect(store.getChildRooms("!company:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!company:server")).toStrictEqual([
|
||||
client.getRoom("!company_dept1:server"),
|
||||
client.getRoom("!company_dept2:server"),
|
||||
subspace,
|
||||
]);
|
||||
expect(store.getChildRooms("!company_dept1:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!company_dept1:server")).toStrictEqual([
|
||||
client.getRoom("!company_dept1_group1:server"),
|
||||
]);
|
||||
expect(store.getChildRooms("!company_dept1_group1:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!company_dept1_group1:server")).toStrictEqual([subspace]);
|
||||
expect(store.getChildRooms("!company_dept2:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!company_dept2:server")).toStrictEqual([subspace]);
|
||||
});
|
||||
|
||||
it("handles full cycles", async () => {
|
||||
mkSpace("!a:server", [
|
||||
mkSpace("!b:server", [
|
||||
mkSpace("!c:server", [
|
||||
"!a:server",
|
||||
]).roomId,
|
||||
]).roomId,
|
||||
]);
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces.map(r => r.roomId)).toStrictEqual(["!a:server"]);
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
|
||||
expect(store.getChildRooms("!a:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!a:server")).toStrictEqual([client.getRoom("!b:server")]);
|
||||
expect(store.getChildRooms("!b:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!b:server")).toStrictEqual([client.getRoom("!c:server")]);
|
||||
expect(store.getChildRooms("!c:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!c:server")).toStrictEqual([client.getRoom("!a:server")]);
|
||||
});
|
||||
|
||||
it("handles partial cycles", async () => {
|
||||
mkSpace("!b:server", [
|
||||
mkSpace("!a:server", [
|
||||
mkSpace("!c:server", [
|
||||
"!a:server",
|
||||
]).roomId,
|
||||
]).roomId,
|
||||
]);
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces.map(r => r.roomId)).toStrictEqual(["!b:server"]);
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
|
||||
expect(store.getChildRooms("!b:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!b:server")).toStrictEqual([client.getRoom("!a:server")]);
|
||||
expect(store.getChildRooms("!a:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!a:server")).toStrictEqual([client.getRoom("!c:server")]);
|
||||
expect(store.getChildRooms("!c:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!c:server")).toStrictEqual([client.getRoom("!a:server")]);
|
||||
});
|
||||
|
||||
it("handles partial cycles with additional spaces coming off them", async () => {
|
||||
// TODO this test should be failing right now
|
||||
mkSpace("!a:server", [
|
||||
mkSpace("!b:server", [
|
||||
mkSpace("!c:server", [
|
||||
"!a:server",
|
||||
mkSpace("!d:server").roomId,
|
||||
]).roomId,
|
||||
]).roomId,
|
||||
]);
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces.map(r => r.roomId)).toStrictEqual(["!a:server"]);
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
|
||||
expect(store.getChildRooms("!a:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!a:server")).toStrictEqual([client.getRoom("!b:server")]);
|
||||
expect(store.getChildRooms("!b:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!b:server")).toStrictEqual([client.getRoom("!c:server")]);
|
||||
expect(store.getChildRooms("!c:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!c:server")).toStrictEqual([
|
||||
client.getRoom("!a:server"),
|
||||
client.getRoom("!d:server"),
|
||||
]);
|
||||
expect(store.getChildRooms("!d:server")).toStrictEqual([]);
|
||||
expect(store.getChildSpaces("!d:server")).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("invite to a subspace is only shown at the top level", async () => {
|
||||
mkSpace(invite1).getMyMembership.mockReturnValue("invite");
|
||||
mkSpace(space1, [invite1]);
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces).toStrictEqual([client.getRoom(space1)]);
|
||||
expect(store.getChildSpaces(space1)).toStrictEqual([]);
|
||||
expect(store.getChildRooms(space1)).toStrictEqual([]);
|
||||
expect(store.invitedSpaces).toStrictEqual([client.getRoom(invite1)]);
|
||||
});
|
||||
|
||||
describe("test fixture 1", () => {
|
||||
beforeEach(async () => {
|
||||
[fav1, fav2, fav3, dm1, dm2, dm3, orphan1, orphan2, invite1, invite2, room1].forEach(mkRoom);
|
||||
mkSpace(space1, [fav1, room1]);
|
||||
mkSpace(space2, [fav1, fav2, fav3, room1]);
|
||||
mkSpace(space3, [invite2]);
|
||||
|
||||
[fav1, fav2, fav3].forEach(roomId => {
|
||||
client.getRoom(roomId).tags = {
|
||||
"m.favourite": {
|
||||
order: 0.5,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
[invite1, invite2].forEach(roomId => {
|
||||
client.getRoom(roomId).getMyMembership.mockReturnValue("invite");
|
||||
});
|
||||
|
||||
getUserIdForRoomId.mockImplementation(roomId => {
|
||||
return {
|
||||
[dm1]: dm1Partner,
|
||||
[dm2]: dm2Partner,
|
||||
[dm3]: dm3Partner,
|
||||
}[roomId];
|
||||
});
|
||||
await run();
|
||||
});
|
||||
|
||||
it("home space contains orphaned rooms", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(orphan1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(orphan2)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("home space contains favourites", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(fav1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(fav2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(fav3)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("home space contains dm rooms", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(dm1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(dm2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(dm3)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("home space contains invites", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("home space contains invites even if they are also shown in a space", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(invite2)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("home space does not contain rooms/low priority from rooms within spaces", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(room1)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("space contains child rooms", () => {
|
||||
const space = client.getRoom(space1);
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(fav1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(room1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("space contains child favourites", () => {
|
||||
const space = client.getRoom(space2);
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(fav1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(fav2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(fav3)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(room1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("space contains child invites", () => {
|
||||
const space = client.getRoom(space3);
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(invite2)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("hierarchy resolution update tests", () => {
|
||||
let emitter: EventEmitter;
|
||||
beforeEach(async () => {
|
||||
emitter = new EventEmitter();
|
||||
client.on.mockImplementation(emitter.on.bind(emitter));
|
||||
client.removeListener.mockImplementation(emitter.removeListener.bind(emitter));
|
||||
});
|
||||
afterEach(() => {
|
||||
client.on.mockReset();
|
||||
client.removeListener.mockReset();
|
||||
});
|
||||
|
||||
it("updates state when spaces are joined", async () => {
|
||||
await run();
|
||||
expect(store.spacePanelSpaces).toStrictEqual([]);
|
||||
const space = mkSpace(space1);
|
||||
const prom = emitPromise(store, UPDATE_TOP_LEVEL_SPACES);
|
||||
emitter.emit("Room", space);
|
||||
await prom;
|
||||
expect(store.spacePanelSpaces).toStrictEqual([space]);
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("updates state when spaces are left", async () => {
|
||||
const space = mkSpace(space1);
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces).toStrictEqual([space]);
|
||||
space.getMyMembership.mockReturnValue("leave");
|
||||
const prom = emitPromise(store, UPDATE_TOP_LEVEL_SPACES);
|
||||
emitter.emit("Room.myMembership", space, "leave", "join");
|
||||
await prom;
|
||||
expect(store.spacePanelSpaces).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("updates state when space invite comes in", async () => {
|
||||
await run();
|
||||
expect(store.spacePanelSpaces).toStrictEqual([]);
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
const space = mkSpace(space1);
|
||||
space.getMyMembership.mockReturnValue("invite");
|
||||
const prom = emitPromise(store, UPDATE_INVITED_SPACES);
|
||||
emitter.emit("Room", space);
|
||||
await prom;
|
||||
expect(store.spacePanelSpaces).toStrictEqual([]);
|
||||
expect(store.invitedSpaces).toStrictEqual([space]);
|
||||
});
|
||||
|
||||
it("updates state when space invite is accepted", async () => {
|
||||
const space = mkSpace(space1);
|
||||
space.getMyMembership.mockReturnValue("invite");
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces).toStrictEqual([]);
|
||||
expect(store.invitedSpaces).toStrictEqual([space]);
|
||||
space.getMyMembership.mockReturnValue("join");
|
||||
const prom = emitPromise(store, UPDATE_TOP_LEVEL_SPACES);
|
||||
emitter.emit("Room.myMembership", space, "join", "invite");
|
||||
await prom;
|
||||
expect(store.spacePanelSpaces).toStrictEqual([space]);
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("updates state when space invite is rejected", async () => {
|
||||
const space = mkSpace(space1);
|
||||
space.getMyMembership.mockReturnValue("invite");
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces).toStrictEqual([]);
|
||||
expect(store.invitedSpaces).toStrictEqual([space]);
|
||||
space.getMyMembership.mockReturnValue("leave");
|
||||
const prom = emitPromise(store, UPDATE_INVITED_SPACES);
|
||||
emitter.emit("Room.myMembership", space, "leave", "invite");
|
||||
await prom;
|
||||
expect(store.spacePanelSpaces).toStrictEqual([]);
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("room invite gets added to relevant space filters", async () => {
|
||||
const space = mkSpace(space1, [invite1]);
|
||||
await run();
|
||||
|
||||
expect(store.spacePanelSpaces).toStrictEqual([space]);
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
expect(store.getChildSpaces(space1)).toStrictEqual([]);
|
||||
expect(store.getChildRooms(space1)).toStrictEqual([]);
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(invite1)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeFalsy();
|
||||
|
||||
const invite = mkRoom(invite1);
|
||||
invite.getMyMembership.mockReturnValue("invite");
|
||||
const prom = emitPromise(store, space1);
|
||||
emitter.emit("Room", space);
|
||||
await prom;
|
||||
|
||||
expect(store.spacePanelSpaces).toStrictEqual([space]);
|
||||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
expect(store.getChildSpaces(space1)).toStrictEqual([]);
|
||||
expect(store.getChildRooms(space1)).toStrictEqual([invite]);
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(invite1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("active space switching tests", () => {
|
||||
const fn = jest.spyOn(store, "emit");
|
||||
|
||||
beforeEach(async () => {
|
||||
mkRoom(room1); // not a space
|
||||
mkSpace(space1, [
|
||||
mkSpace(space2).roomId,
|
||||
]);
|
||||
mkSpace(space3).getMyMembership.mockReturnValue("invite");
|
||||
await run();
|
||||
await store.setActiveSpace(null);
|
||||
expect(store.activeSpace).toBe(null);
|
||||
});
|
||||
afterEach(() => {
|
||||
fn.mockClear();
|
||||
});
|
||||
|
||||
it("switch to home space", async () => {
|
||||
await store.setActiveSpace(client.getRoom(space1));
|
||||
fn.mockClear();
|
||||
|
||||
await store.setActiveSpace(null);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, null);
|
||||
expect(store.activeSpace).toBe(null);
|
||||
});
|
||||
|
||||
it("switch to invited space", async () => {
|
||||
const space = client.getRoom(space3);
|
||||
await store.setActiveSpace(space);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||
expect(store.activeSpace).toBe(space);
|
||||
});
|
||||
|
||||
it("switch to top level space", async () => {
|
||||
const space = client.getRoom(space1);
|
||||
await store.setActiveSpace(space);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||
expect(store.activeSpace).toBe(space);
|
||||
});
|
||||
|
||||
it("switch to subspace", async () => {
|
||||
const space = client.getRoom(space2);
|
||||
await store.setActiveSpace(space);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||
expect(store.activeSpace).toBe(space);
|
||||
});
|
||||
|
||||
it("switch to unknown space is a nop", async () => {
|
||||
expect(store.activeSpace).toBe(null);
|
||||
const space = client.getRoom(room1); // not a space
|
||||
await store.setActiveSpace(space);
|
||||
expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||
expect(store.activeSpace).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("context switching tests", () => {
|
||||
const fn = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
|
||||
beforeEach(async () => {
|
||||
[room1, room2, orphan1].forEach(mkRoom);
|
||||
mkSpace(space1, [room1, room2]);
|
||||
mkSpace(space2, [room2]);
|
||||
await run();
|
||||
});
|
||||
afterEach(() => {
|
||||
fn.mockClear();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
const getCurrentRoom = () => fn.mock.calls.reverse().find(([p]) => p.action === "view_room")?.[0].room_id;
|
||||
|
||||
it("last viewed room in target space is the current viewed and in both spaces", async () => {
|
||||
await store.setActiveSpace(client.getRoom(space1));
|
||||
viewRoom(room2);
|
||||
await store.setActiveSpace(client.getRoom(space2));
|
||||
viewRoom(room2);
|
||||
await store.setActiveSpace(client.getRoom(space1));
|
||||
expect(getCurrentRoom()).toBe(room2);
|
||||
});
|
||||
|
||||
it("last viewed room in target space is in the current space", async () => {
|
||||
await store.setActiveSpace(client.getRoom(space1));
|
||||
viewRoom(room2);
|
||||
await store.setActiveSpace(client.getRoom(space2));
|
||||
expect(getCurrentRoom()).toBe(space2);
|
||||
await store.setActiveSpace(client.getRoom(space1));
|
||||
expect(getCurrentRoom()).toBe(room2);
|
||||
});
|
||||
|
||||
it("last viewed room in target space is not in the current space", async () => {
|
||||
await store.setActiveSpace(client.getRoom(space1));
|
||||
viewRoom(room1);
|
||||
await store.setActiveSpace(client.getRoom(space2));
|
||||
viewRoom(room2);
|
||||
await store.setActiveSpace(client.getRoom(space1));
|
||||
expect(getCurrentRoom()).toBe(room1);
|
||||
});
|
||||
|
||||
it("last viewed room is target space is not known", async () => {
|
||||
await store.setActiveSpace(client.getRoom(space1));
|
||||
viewRoom(room1);
|
||||
localStorage.setItem(`mx_space_context_${space2}`, orphan2);
|
||||
await store.setActiveSpace(client.getRoom(space2));
|
||||
expect(getCurrentRoom()).toBe(space2);
|
||||
});
|
||||
|
||||
it("no last viewed room in target space", async () => {
|
||||
await store.setActiveSpace(client.getRoom(space1));
|
||||
viewRoom(room1);
|
||||
await store.setActiveSpace(client.getRoom(space2));
|
||||
expect(getCurrentRoom()).toBe(space2);
|
||||
});
|
||||
|
||||
it("no last viewed room in home space", async () => {
|
||||
await store.setActiveSpace(client.getRoom(space1));
|
||||
viewRoom(room1);
|
||||
await store.setActiveSpace(null);
|
||||
expect(fn.mock.calls[fn.mock.calls.length - 1][0]).toStrictEqual({ action: "view_home_page" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("space auto switching tests", () => {
|
||||
beforeEach(async () => {
|
||||
[room1, room2, orphan1].forEach(mkRoom);
|
||||
mkSpace(space1, [room1, room2]);
|
||||
mkSpace(space2, [room1, room2]);
|
||||
|
||||
client.getRoom(room2).currentState.getStateEvents.mockImplementation(mockStateEventImplementation([
|
||||
mkEvent({
|
||||
event: true,
|
||||
type: EventType.SpaceParent,
|
||||
room: room2,
|
||||
user: testUserId,
|
||||
skey: space2,
|
||||
content: { via: [], canonical: true },
|
||||
ts: Date.now(),
|
||||
}),
|
||||
]));
|
||||
await run();
|
||||
});
|
||||
|
||||
it("no switch required, room is in current space", async () => {
|
||||
viewRoom(room1);
|
||||
await store.setActiveSpace(client.getRoom(space1), false);
|
||||
viewRoom(room2);
|
||||
expect(store.activeSpace).toBe(client.getRoom(space1));
|
||||
});
|
||||
|
||||
it("switch to canonical parent space for room", async () => {
|
||||
viewRoom(room1);
|
||||
await store.setActiveSpace(null, false);
|
||||
viewRoom(room2);
|
||||
expect(store.activeSpace).toBe(client.getRoom(space2));
|
||||
});
|
||||
|
||||
it("switch to first containing space for room", async () => {
|
||||
viewRoom(room2);
|
||||
await store.setActiveSpace(null, false);
|
||||
viewRoom(room1);
|
||||
expect(store.activeSpace).toBe(client.getRoom(space1));
|
||||
});
|
||||
|
||||
it("switch to home for orphaned room", async () => {
|
||||
viewRoom(room1);
|
||||
await store.setActiveSpace(client.getRoom(space1), false);
|
||||
viewRoom(orphan1);
|
||||
expect(store.activeSpace).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("traverseSpace", () => {
|
||||
beforeEach(() => {
|
||||
mkSpace("!a:server", [
|
||||
mkSpace("!b:server", [
|
||||
mkSpace("!c:server", [
|
||||
"!a:server",
|
||||
mkRoom("!c-child:server").roomId,
|
||||
mkRoom("!shared-child:server").roomId,
|
||||
]).roomId,
|
||||
mkRoom("!b-child:server").roomId,
|
||||
]).roomId,
|
||||
mkRoom("!a-child:server").roomId,
|
||||
"!shared-child:server",
|
||||
]);
|
||||
});
|
||||
|
||||
it("avoids cycles", () => {
|
||||
const fn = jest.fn();
|
||||
store.traverseSpace("!b:server", fn);
|
||||
|
||||
expect(fn).toBeCalledTimes(3);
|
||||
expect(fn).toBeCalledWith("!a:server");
|
||||
expect(fn).toBeCalledWith("!b:server");
|
||||
expect(fn).toBeCalledWith("!c:server");
|
||||
});
|
||||
|
||||
it("including rooms", () => {
|
||||
const fn = jest.fn();
|
||||
store.traverseSpace("!b:server", fn, true);
|
||||
|
||||
expect(fn).toBeCalledTimes(8); // twice for shared-child
|
||||
expect(fn).toBeCalledWith("!a:server");
|
||||
expect(fn).toBeCalledWith("!a-child:server");
|
||||
expect(fn).toBeCalledWith("!b:server");
|
||||
expect(fn).toBeCalledWith("!b-child:server");
|
||||
expect(fn).toBeCalledWith("!c:server");
|
||||
expect(fn).toBeCalledWith("!c-child:server");
|
||||
expect(fn).toBeCalledWith("!shared-child:server");
|
||||
});
|
||||
|
||||
it("excluding rooms", () => {
|
||||
const fn = jest.fn();
|
||||
store.traverseSpace("!b:server", fn, false);
|
||||
|
||||
expect(fn).toBeCalledTimes(3);
|
||||
expect(fn).toBeCalledWith("!a:server");
|
||||
expect(fn).toBeCalledWith("!b:server");
|
||||
expect(fn).toBeCalledWith("!c:server");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -64,6 +64,11 @@ export function createTestClient() {
|
|||
getRoomIdForAlias: jest.fn().mockResolvedValue(undefined),
|
||||
getRoomDirectoryVisibility: jest.fn().mockResolvedValue(undefined),
|
||||
getProfileInfo: jest.fn().mockResolvedValue({}),
|
||||
getThirdpartyProtocols: jest.fn().mockResolvedValue({}),
|
||||
getClientWellKnown: jest.fn().mockReturnValue(null),
|
||||
supportsVoip: jest.fn().mockReturnValue(true),
|
||||
getTurnServersExpiry: jest.fn().mockReturnValue(2^32),
|
||||
getThirdpartyUser: jest.fn().mockResolvedValue([]),
|
||||
getAccountData: (type) => {
|
||||
return mkEvent({
|
||||
type,
|
||||
|
@ -79,6 +84,17 @@ export function createTestClient() {
|
|||
generateClientSecret: () => "t35tcl1Ent5ECr3T",
|
||||
isGuest: () => false,
|
||||
isCryptoEnabled: () => false,
|
||||
getSpaceSummary: jest.fn().mockReturnValue({
|
||||
rooms: [],
|
||||
events: [],
|
||||
}),
|
||||
|
||||
// Used by various internal bits we aren't concerned with (yet)
|
||||
_sessionStore: {
|
||||
store: {
|
||||
getItem: jest.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -88,8 +104,8 @@ export function createTestClient() {
|
|||
* @param {string} opts.type The event.type
|
||||
* @param {string} opts.room The event.room_id
|
||||
* @param {string} opts.user The event.user_id
|
||||
* @param {string} opts.skey Optional. The state key (auto inserts empty string)
|
||||
* @param {Number} opts.ts Optional. Timestamp for the event
|
||||
* @param {string=} opts.skey Optional. The state key (auto inserts empty string)
|
||||
* @param {number=} opts.ts Optional. Timestamp for the event
|
||||
* @param {Object} opts.content The event.content
|
||||
* @param {boolean} opts.event True to make a MatrixEvent.
|
||||
* @return {Object} a JSON object representing this event.
|
||||
|
@ -224,7 +240,7 @@ export function mkStubRoom(roomId = null) {
|
|||
hasMembershipState: () => null,
|
||||
getVersion: () => '1',
|
||||
shouldUpgradeToVersion: () => null,
|
||||
getMyMembership: () => "join",
|
||||
getMyMembership: jest.fn().mockReturnValue("join"),
|
||||
maySendMessage: jest.fn().mockReturnValue(true),
|
||||
currentState: {
|
||||
getStateEvents: jest.fn(),
|
||||
|
@ -233,17 +249,17 @@ export function mkStubRoom(roomId = null) {
|
|||
maySendEvent: jest.fn().mockReturnValue(true),
|
||||
members: [],
|
||||
},
|
||||
tags: {
|
||||
"m.favourite": {
|
||||
order: 0.5,
|
||||
},
|
||||
},
|
||||
tags: {},
|
||||
setBlacklistUnverifiedDevices: jest.fn(),
|
||||
on: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
getDMInviter: jest.fn(),
|
||||
getAvatarUrl: () => 'mxc://avatar.url/room.png',
|
||||
getMxcAvatarUrl: () => 'mxc://avatar.url/room.png',
|
||||
isSpaceRoom: jest.fn(() => false),
|
||||
getUnreadNotificationCount: jest.fn(() => 0),
|
||||
getEventReadUpTo: jest.fn(() => null),
|
||||
timeline: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -84,22 +84,22 @@ describe('MegolmExportEncryption', function() {
|
|||
it('should handle missing header', function() {
|
||||
const input=stringToArray(`-----`);
|
||||
return MegolmExportEncryption.decryptMegolmKeyFile(input, '')
|
||||
.then((res) => {
|
||||
throw new Error('expected to throw');
|
||||
}, (error) => {
|
||||
expect(error.message).toEqual('Header line not found');
|
||||
});
|
||||
.then((res) => {
|
||||
throw new Error('expected to throw');
|
||||
}, (error) => {
|
||||
expect(error.message).toEqual('Header line not found');
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle missing trailer', function() {
|
||||
const input=stringToArray(`-----BEGIN MEGOLM SESSION DATA-----
|
||||
-----`);
|
||||
return MegolmExportEncryption.decryptMegolmKeyFile(input, '')
|
||||
.then((res) => {
|
||||
throw new Error('expected to throw');
|
||||
}, (error) => {
|
||||
expect(error.message).toEqual('Trailer line not found');
|
||||
});
|
||||
.then((res) => {
|
||||
throw new Error('expected to throw');
|
||||
}, (error) => {
|
||||
expect(error.message).toEqual('Trailer line not found');
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle a too-short body', function() {
|
||||
|
@ -109,11 +109,11 @@ cissyYBxjsfsAn
|
|||
-----END MEGOLM SESSION DATA-----
|
||||
`);
|
||||
return MegolmExportEncryption.decryptMegolmKeyFile(input, '')
|
||||
.then((res) => {
|
||||
throw new Error('expected to throw');
|
||||
}, (error) => {
|
||||
expect(error.message).toEqual('Invalid file: too short');
|
||||
});
|
||||
.then((res) => {
|
||||
throw new Error('expected to throw');
|
||||
}, (error) => {
|
||||
expect(error.message).toEqual('Invalid file: too short');
|
||||
});
|
||||
});
|
||||
|
||||
// TODO find a subtlecrypto shim which doesn't break this test
|
||||
|
|
|
@ -26,7 +26,7 @@ describe("mkClient self-test", function() {
|
|||
["@TF:h", true],
|
||||
["@FT:h", false],
|
||||
["@FF:h", false]],
|
||||
)("behaves well for user trust %s", (userId, trust) => {
|
||||
)("behaves well for user trust %s", (userId, trust) => {
|
||||
expect(mkClient().checkUserTrust(userId).isCrossSigningVerified()).toBe(trust);
|
||||
});
|
||||
|
||||
|
@ -35,7 +35,7 @@ describe("mkClient self-test", function() {
|
|||
["@TF:h", false],
|
||||
["@FT:h", true],
|
||||
["@FF:h", false]],
|
||||
)("behaves well for device trust %s", (userId, trust) => {
|
||||
)("behaves well for device trust %s", (userId, trust) => {
|
||||
expect(mkClient().checkDeviceTrust(userId, "device").isVerified()).toBe(trust);
|
||||
});
|
||||
});
|
||||
|
@ -128,7 +128,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() {
|
|||
|
||||
describe("shieldStatusForMembership other-trust behaviour", function() {
|
||||
beforeAll(() => {
|
||||
DMRoomMap._sharedInstance = {
|
||||
DMRoomMap.sharedInstance = {
|
||||
getUserIdForRoomId: (roomId) => roomId === "DM" ? "@any:h" : null,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
arrayHasOrderChange,
|
||||
arrayMerge,
|
||||
arraySeed,
|
||||
arrayTrimFill,
|
||||
arrayUnion,
|
||||
ArrayUtil,
|
||||
GroupedArray,
|
||||
|
@ -64,6 +65,38 @@ describe('arrays', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('arrayTrimFill', () => {
|
||||
it('should shrink arrays', () => {
|
||||
const input = [1, 2, 3];
|
||||
const output = [1, 2];
|
||||
const seed = [4, 5, 6];
|
||||
const result = arrayTrimFill(input, output.length, seed);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(output.length);
|
||||
expect(result).toEqual(output);
|
||||
});
|
||||
|
||||
it('should expand arrays', () => {
|
||||
const input = [1, 2, 3];
|
||||
const output = [1, 2, 3, 4, 5];
|
||||
const seed = [4, 5, 6];
|
||||
const result = arrayTrimFill(input, output.length, seed);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(output.length);
|
||||
expect(result).toEqual(output);
|
||||
});
|
||||
|
||||
it('should keep arrays the same', () => {
|
||||
const input = [1, 2, 3];
|
||||
const output = [1, 2, 3];
|
||||
const seed = [4, 5, 6];
|
||||
const result = arrayTrimFill(input, output.length, seed);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(output.length);
|
||||
expect(result).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('arraySeed', () => {
|
||||
it('should create an array of given length', () => {
|
||||
const val = 1;
|
||||
|
|
33
test/utils/test-utils.ts
Normal file
33
test/utils/test-utils.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2021 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 { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { AsyncStoreWithClient } from "../../src/stores/AsyncStoreWithClient";
|
||||
|
||||
// These methods make some use of some private methods on the AsyncStoreWithClient to simplify getting into a consistent
|
||||
// ready state without needing to wire up a dispatcher and pretend to be a js-sdk client.
|
||||
|
||||
export const setupAsyncStoreWithClient = async (store: AsyncStoreWithClient<any>, client: MatrixClient) => {
|
||||
// @ts-ignore
|
||||
store.readyStore.useUnitTestClient(client);
|
||||
// @ts-ignore
|
||||
await store.onReady();
|
||||
};
|
||||
|
||||
export const resetAsyncStoreWithClient = async (store: AsyncStoreWithClient<any>) => {
|
||||
// @ts-ignore
|
||||
await store.onNotReady();
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue