Merge branch 'develop' into show-username
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
commit
025148d9fc
454 changed files with 20625 additions and 7587 deletions
238
test/CallHandler-test.ts
Normal file
238
test/CallHandler-test.ts
Normal file
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
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 SdkConfig from '../src/SdkConfig';
|
||||
import { ActionPayload } from '../src/dispatcher/payloads';
|
||||
import { Action } from '../src/dispatcher/actions';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function untilDispatch(waitForAction: string): Promise<ActionPayload> {
|
||||
let dispatchHandle;
|
||||
return new Promise<ActionPayload>(resolve => {
|
||||
dispatchHandle = dis.register(payload => {
|
||||
if (payload.action === waitForAction) {
|
||||
dis.unregister(dispatchHandle);
|
||||
resolve(payload);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
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 look up the correct user and open the room when a phone number is dialled', async () => {
|
||||
MatrixClientPeg.get().getThirdpartyUser = jest.fn().mockResolvedValue([{
|
||||
userid: '@user2:example.org',
|
||||
protocol: "im.vector.protocol.sip_native",
|
||||
fields: {
|
||||
is_native: true,
|
||||
lookup_success: true,
|
||||
},
|
||||
}]);
|
||||
|
||||
dis.dispatch({
|
||||
action: Action.DialNumber,
|
||||
number: '01818118181',
|
||||
}, true);
|
||||
|
||||
const viewRoomPayload = await untilDispatch('view_room');
|
||||
expect(viewRoomPayload.room_id).toEqual(MAPPED_ROOM_ID);
|
||||
});
|
||||
|
||||
it('should move calls between rooms when remote asserted identity changes', async () => {
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: PlaceCallType.Voice,
|
||||
room_id: REAL_ROOM_ID,
|
||||
}, true);
|
||||
|
||||
// wait for the call to be set up
|
||||
await untilDispatch('call_state');
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
|
@ -15,7 +15,6 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { isKeyComboMatch, KeyCombo } from '../src/KeyBindingsManager';
|
||||
const assert = require('assert');
|
||||
|
||||
function mockKeyEvent(key: string, modifiers?: {
|
||||
ctrlKey?: boolean,
|
||||
|
@ -28,7 +27,7 @@ function mockKeyEvent(key: string, modifiers?: {
|
|||
ctrlKey: modifiers?.ctrlKey ?? false,
|
||||
altKey: modifiers?.altKey ?? false,
|
||||
shiftKey: modifiers?.shiftKey ?? false,
|
||||
metaKey: modifiers?.metaKey ?? false
|
||||
metaKey: modifiers?.metaKey ?? false,
|
||||
} as KeyboardEvent;
|
||||
}
|
||||
|
||||
|
@ -37,9 +36,8 @@ describe('KeyBindingsManager', () => {
|
|||
const combo1: KeyCombo = {
|
||||
key: 'k',
|
||||
};
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo1, false), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('n'), combo1, false), false);
|
||||
|
||||
expect(isKeyComboMatch(mockKeyEvent('k'), combo1, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('n'), combo1, false)).toBe(false);
|
||||
});
|
||||
|
||||
it('should match key + modifier key combo', () => {
|
||||
|
@ -47,38 +45,38 @@ describe('KeyBindingsManager', () => {
|
|||
key: 'k',
|
||||
ctrlKey: true,
|
||||
};
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, metaKey: true }), combo, false), false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k'), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, metaKey: true }), combo, false)).toBe(false);
|
||||
|
||||
const combo2: KeyCombo = {
|
||||
key: 'k',
|
||||
metaKey: true,
|
||||
};
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo2, false), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { metaKey: true }), combo2, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo2, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { altKey: true, metaKey: true }), combo2, false), false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo2, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('n', { metaKey: true }), combo2, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k'), combo2, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { altKey: true, metaKey: true }), combo2, false)).toBe(false);
|
||||
|
||||
const combo3: KeyCombo = {
|
||||
key: 'k',
|
||||
altKey: true,
|
||||
};
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { altKey: true }), combo3, false), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { altKey: true }), combo3, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo3, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo3, false), false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { altKey: true }), combo3, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('n', { altKey: true }), combo3, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k'), combo3, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo3, false)).toBe(false);
|
||||
|
||||
const combo4: KeyCombo = {
|
||||
key: 'k',
|
||||
shiftKey: true,
|
||||
};
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo4, false), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { shiftKey: true }), combo4, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo4, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, ctrlKey: true }), combo4, false), false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo4, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('n', { shiftKey: true }), combo4, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k'), combo4, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, ctrlKey: true }), combo4, false)).toBe(false);
|
||||
});
|
||||
|
||||
it('should match key + multiple modifiers key combo', () => {
|
||||
|
@ -87,11 +85,11 @@ describe('KeyBindingsManager', () => {
|
|||
ctrlKey: true,
|
||||
altKey: true,
|
||||
};
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, altKey: true }), combo, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true, shiftKey: true }), combo,
|
||||
false), false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, altKey: true }), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true, shiftKey: true }), combo,
|
||||
false)).toBe(false);
|
||||
|
||||
const combo2: KeyCombo = {
|
||||
key: 'k',
|
||||
|
@ -99,13 +97,13 @@ describe('KeyBindingsManager', () => {
|
|||
shiftKey: true,
|
||||
altKey: true,
|
||||
};
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, shiftKey: true, altKey: true }), combo2,
|
||||
false), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, shiftKey: true, altKey: true }), combo2,
|
||||
false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo2, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k',
|
||||
{ ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo2, false), false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, shiftKey: true, altKey: true }), combo2,
|
||||
false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, shiftKey: true, altKey: true }), combo2,
|
||||
false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo2, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k',
|
||||
{ ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo2, false)).toBe(false);
|
||||
|
||||
const combo3: KeyCombo = {
|
||||
key: 'k',
|
||||
|
@ -114,12 +112,12 @@ describe('KeyBindingsManager', () => {
|
|||
altKey: true,
|
||||
metaKey: true,
|
||||
};
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k',
|
||||
{ ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('n',
|
||||
{ ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k',
|
||||
{ ctrlKey: true, shiftKey: true, altKey: true }), combo3, false), false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k',
|
||||
{ ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('n',
|
||||
{ ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k',
|
||||
{ ctrlKey: true, shiftKey: true, altKey: true }), combo3, false)).toBe(false);
|
||||
});
|
||||
|
||||
it('should match ctrlOrMeta key combo', () => {
|
||||
|
@ -128,13 +126,13 @@ describe('KeyBindingsManager', () => {
|
|||
ctrlOrCmd: true,
|
||||
};
|
||||
// PC:
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, false), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false), false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, false)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false)).toBe(false);
|
||||
// MAC:
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, true), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, true), false);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, true), false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, true)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, true)).toBe(false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, true)).toBe(false);
|
||||
});
|
||||
|
||||
it('should match advanced ctrlOrMeta key combo', () => {
|
||||
|
@ -144,10 +142,10 @@ describe('KeyBindingsManager', () => {
|
|||
altKey: true,
|
||||
};
|
||||
// PC:
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, false), false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, false)).toBe(false);
|
||||
// MAC:
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, true), true);
|
||||
assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, true), false);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, true)).toBe(true);
|
||||
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, true)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -77,7 +77,7 @@ describe('MessagePanel', function() {
|
|||
DMRoomMap.makeShared();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
clock.uninstall();
|
||||
});
|
||||
|
||||
|
@ -88,7 +88,21 @@ describe('MessagePanel', function() {
|
|||
events.push(test_utils.mkMessage(
|
||||
{
|
||||
event: true, room: "!room:id", user: "@user:id",
|
||||
ts: ts0 + i*1000,
|
||||
ts: ts0 + i * 1000,
|
||||
}));
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
// Just to avoid breaking Dateseparator tests that might run at 00hrs
|
||||
function mkOneDayEvents() {
|
||||
const events = [];
|
||||
const ts0 = Date.parse('09 May 2004 00:12:00 GMT');
|
||||
for (let i = 0; i < 10; i++) {
|
||||
events.push(test_utils.mkMessage(
|
||||
{
|
||||
event: true, room: "!room:id", user: "@user:id",
|
||||
ts: ts0 + i * 1000,
|
||||
}));
|
||||
}
|
||||
return events;
|
||||
|
@ -104,7 +118,7 @@ describe('MessagePanel', function() {
|
|||
let i = 0;
|
||||
events.push(test_utils.mkMessage({
|
||||
event: true, room: "!room:id", user: "@user:id",
|
||||
ts: ts0 + ++i*1000,
|
||||
ts: ts0 + ++i * 1000,
|
||||
}));
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
|
@ -151,7 +165,7 @@ describe('MessagePanel', function() {
|
|||
},
|
||||
getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
|
||||
},
|
||||
ts: ts0 + i*1000,
|
||||
ts: ts0 + i * 1000,
|
||||
mship: 'join',
|
||||
prevMship: 'join',
|
||||
name: 'A user',
|
||||
|
@ -250,7 +264,6 @@ describe('MessagePanel', function() {
|
|||
}),
|
||||
];
|
||||
}
|
||||
|
||||
function isReadMarkerVisible(rmContainer) {
|
||||
return rmContainer && rmContainer.children.length > 0;
|
||||
}
|
||||
|
@ -296,7 +309,7 @@ describe('MessagePanel', function() {
|
|||
const rm = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_RoomView_myReadMarker_container');
|
||||
|
||||
// it should follow the <li> which wraps the event tile for event 4
|
||||
const eventContainer = ReactDOM.findDOMNode(tiles[4]).parentNode;
|
||||
const eventContainer = ReactDOM.findDOMNode(tiles[4]);
|
||||
expect(rm.previousSibling).toEqual(eventContainer);
|
||||
});
|
||||
|
||||
|
@ -352,7 +365,7 @@ describe('MessagePanel', function() {
|
|||
const tiles = TestUtils.scryRenderedComponentsWithType(
|
||||
mp, sdk.getComponent('rooms.EventTile'));
|
||||
const tileContainers = tiles.map(function(t) {
|
||||
return ReactDOM.findDOMNode(t).parentNode;
|
||||
return ReactDOM.findDOMNode(t);
|
||||
});
|
||||
|
||||
// find the <li> which wraps the read marker
|
||||
|
@ -437,4 +450,17 @@ describe('MessagePanel', function() {
|
|||
// read marker should be hidden given props and at the last event
|
||||
expect(isReadMarkerVisible(rm)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should render Date separators for the events', function () {
|
||||
const events = mkOneDayEvents();
|
||||
const res = mount(
|
||||
<WrappedMessagePanel
|
||||
className="cls"
|
||||
events={events}
|
||||
/>,
|
||||
);
|
||||
const Dates = res.find(sdk.getComponent('messages.DateSeparator'));
|
||||
|
||||
expect(Dates.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -133,7 +133,7 @@ describe('Login', function() {
|
|||
root.setState({
|
||||
flows: [{
|
||||
"type": "m.login.sso",
|
||||
"org.matrix.msc2858.identity_providers": [{
|
||||
"identity_providers": [{
|
||||
id: "a",
|
||||
name: "Provider 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",
|
||||
|
|
|
@ -9,6 +9,8 @@ import sdk from '../../../skinned-sdk';
|
|||
|
||||
import {Room, RoomMember, User} from 'matrix-js-sdk';
|
||||
|
||||
import { compare } from "../../../../src/utils/strings";
|
||||
|
||||
function generateRoomId() {
|
||||
return '!' + Math.random().toString().slice(2, 10) + ':domain';
|
||||
}
|
||||
|
@ -88,6 +90,7 @@ describe('MemberList', () => {
|
|||
};
|
||||
memberListRoom.currentState = {
|
||||
members: {},
|
||||
getMember: jest.fn(),
|
||||
getStateEvents: (eventType, stateKey) => stateKey === undefined ? [] : null, // ignore 3pid invites
|
||||
};
|
||||
for (const member of [...adminUsers, ...moderatorUsers, ...defaultUsers]) {
|
||||
|
@ -100,7 +103,7 @@ describe('MemberList', () => {
|
|||
memberList = r;
|
||||
};
|
||||
root = ReactDOM.render(<WrappedMemberList roomId={memberListRoom.roomId}
|
||||
wrappedRef={gatherWrappedRef} />, parentDiv);
|
||||
wrappedRef={gatherWrappedRef} />, parentDiv);
|
||||
});
|
||||
|
||||
afterEach((done) => {
|
||||
|
@ -172,7 +175,7 @@ describe('MemberList', () => {
|
|||
if (!groupChange) {
|
||||
const nameA = memberA.name[0] === '@' ? memberA.name.substr(1) : memberA.name;
|
||||
const nameB = memberB.name[0] === '@' ? memberB.name.substr(1) : memberB.name;
|
||||
const nameCompare = nameB.localeCompare(nameA);
|
||||
const nameCompare = compare(nameB, nameA);
|
||||
console.log("Comparing name");
|
||||
expect(nameCompare).toBeGreaterThanOrEqual(0);
|
||||
} else {
|
||||
|
|
|
@ -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() {
|
||||
|
|
1
test/end-to-end-tests/.gitignore
vendored
1
test/end-to-end-tests/.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
node_modules
|
||||
*.png
|
||||
element/env
|
||||
performance-entries.json
|
||||
|
|
|
@ -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 {
|
||||
|
@ -208,7 +208,7 @@ module.exports = class ElementSession {
|
|||
this.log.done();
|
||||
}
|
||||
|
||||
close() {
|
||||
async close() {
|
||||
return this.browser.close();
|
||||
}
|
||||
|
||||
|
|
|
@ -79,8 +79,26 @@ async function runTests() {
|
|||
await new Promise((resolve) => setTimeout(resolve, 5 * 60 * 1000));
|
||||
}
|
||||
|
||||
await Promise.all(sessions.map((session) => session.close()));
|
||||
const performanceEntries = {};
|
||||
|
||||
await Promise.all(sessions.map(async (session) => {
|
||||
// Collecting all performance monitoring data before closing the session
|
||||
const measurements = await session.page.evaluate(() => {
|
||||
let measurements = [];
|
||||
window.mxPerformanceMonitor.addPerformanceDataCallback({
|
||||
entryNames: [
|
||||
window.mxPerformanceEntryNames.REGISTER,
|
||||
],
|
||||
callback: (events) => {
|
||||
measurements = JSON.stringify(events);
|
||||
},
|
||||
}, true);
|
||||
return measurements;
|
||||
});
|
||||
performanceEntries[session.username] = JSON.parse(measurements);
|
||||
return session.close();
|
||||
}));
|
||||
fs.writeFileSync(`performance-entries.json`, JSON.stringify(performanceEntries));
|
||||
if (failure) {
|
||||
process.exit(-1);
|
||||
} else {
|
||||
|
|
|
@ -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"
|
||||
|
|
722
test/stores/SpaceStore-test.ts
Normal file
722
test/stores/SpaceStore-test.ts
Normal file
|
@ -0,0 +1,722 @@
|
|||
/*
|
||||
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 room3 = "!room3: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 contain rooms/low priority even if they are also shown in a space", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(room1)).toBeTruthy();
|
||||
});
|
||||
|
||||
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, room3, orphan1].forEach(mkRoom);
|
||||
mkSpace(space1, [room1, room2, room3]);
|
||||
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(client.getRoom(space2), false);
|
||||
viewRoom(room2);
|
||||
expect(store.activeSpace).toBe(client.getRoom(space2));
|
||||
});
|
||||
|
||||
it("switch to first containing space for room", async () => {
|
||||
viewRoom(room2);
|
||||
await store.setActiveSpace(client.getRoom(space2), false);
|
||||
viewRoom(room3);
|
||||
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();
|
||||
});
|
||||
|
||||
it("when switching rooms in the all rooms home space don't switch to related space", async () => {
|
||||
viewRoom(room2);
|
||||
await store.setActiveSpace(null, false);
|
||||
viewRoom(room1);
|
||||
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,18 @@ 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(),
|
||||
},
|
||||
},
|
||||
decryptEventIfNeeded: () => Promise.resolve(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -88,8 +105,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.
|
||||
|
@ -217,6 +234,7 @@ export function mkStubRoom(roomId = null) {
|
|||
}),
|
||||
getMembersWithMembership: jest.fn().mockReturnValue([]),
|
||||
getJoinedMembers: jest.fn().mockReturnValue([]),
|
||||
getMembers: jest.fn().mockReturnValue([]),
|
||||
getPendingEvents: () => [],
|
||||
getLiveTimeline: () => stubTimeline,
|
||||
getUnfilteredTimelineSet: () => null,
|
||||
|
@ -224,26 +242,27 @@ 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(),
|
||||
getMember: jest.fn(),
|
||||
mayClientSendStateEvent: jest.fn().mockReturnValue(true),
|
||||
maySendStateEvent: jest.fn().mockReturnValue(true),
|
||||
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,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {Singleflight} from "../src/utils/Singleflight";
|
||||
import {Singleflight} from "../../src/utils/Singleflight";
|
||||
|
||||
describe('Singleflight', () => {
|
||||
afterEach(() => {
|
370
test/utils/arrays-test.ts
Normal file
370
test/utils/arrays-test.ts
Normal file
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
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 {
|
||||
arrayDiff,
|
||||
arrayFastClone,
|
||||
arrayFastResample,
|
||||
arrayHasDiff,
|
||||
arrayHasOrderChange,
|
||||
arrayMerge,
|
||||
arrayRescale,
|
||||
arraySeed,
|
||||
arraySmoothingResample,
|
||||
arrayTrimFill,
|
||||
arrayUnion,
|
||||
ArrayUtil,
|
||||
GroupedArray,
|
||||
} from "../../src/utils/arrays";
|
||||
import {objectFromEntries} from "../../src/utils/objects";
|
||||
|
||||
function expectSample(i: number, input: number[], expected: number[], smooth = false) {
|
||||
console.log(`Resample case index: ${i}`); // for debugging test failures
|
||||
const result = (smooth ? arraySmoothingResample : arrayFastResample)(input, expected.length);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(expected.length);
|
||||
expect(result).toEqual(expected);
|
||||
}
|
||||
|
||||
describe('arrays', () => {
|
||||
describe('arrayFastResample', () => {
|
||||
it('should downsample', () => {
|
||||
[
|
||||
{input: [1, 2, 3, 4, 5], output: [1, 4]}, // Odd -> Even
|
||||
{input: [1, 2, 3, 4, 5], output: [1, 3, 5]}, // Odd -> Odd
|
||||
{input: [1, 2, 3, 4], output: [1, 2, 3]}, // Even -> Odd
|
||||
{input: [1, 2, 3, 4], output: [1, 3]}, // Even -> Even
|
||||
].forEach((c, i) => expectSample(i, c.input, c.output));
|
||||
});
|
||||
|
||||
it('should upsample', () => {
|
||||
[
|
||||
{input: [1, 2, 3], output: [1, 1, 2, 2, 3, 3]}, // Odd -> Even
|
||||
{input: [1, 2, 3], output: [1, 1, 2, 2, 3]}, // Odd -> Odd
|
||||
{input: [1, 2], output: [1, 1, 1, 2, 2]}, // Even -> Odd
|
||||
{input: [1, 2], output: [1, 1, 1, 2, 2, 2]}, // Even -> Even
|
||||
].forEach((c, i) => expectSample(i, c.input, c.output));
|
||||
});
|
||||
|
||||
it('should maintain sample', () => {
|
||||
[
|
||||
{input: [1, 2, 3], output: [1, 2, 3]}, // Odd
|
||||
{input: [1, 2], output: [1, 2]}, // Even
|
||||
].forEach((c, i) => expectSample(i, c.input, c.output));
|
||||
});
|
||||
});
|
||||
|
||||
describe('arraySmoothingResample', () => {
|
||||
it('should downsample', () => {
|
||||
// Dev note: these aren't great samples, but they demonstrate the bare minimum. Ideally
|
||||
// we'd be feeding a thousand values in and seeing what a curve of 250 values looks like,
|
||||
// but that's not really feasible to manually verify accuracy.
|
||||
[
|
||||
{input: [4, 4, 1, 4, 4, 1, 4, 4, 1], output: [3, 3, 3, 3]}, // Odd -> Even
|
||||
{input: [4, 4, 1, 4, 4, 1, 4, 4, 1], output: [3, 3, 3]}, // Odd -> Odd
|
||||
{input: [4, 4, 1, 4, 4, 1, 4, 4], output: [3, 3, 3]}, // Even -> Odd
|
||||
{input: [4, 4, 1, 4, 4, 1, 4, 4], output: [3, 3]}, // Even -> Even
|
||||
].forEach((c, i) => expectSample(i, c.input, c.output, true));
|
||||
});
|
||||
|
||||
it('should upsample', () => {
|
||||
[
|
||||
{input: [2, 0, 2], output: [2, 2, 0, 0, 2, 2]}, // Odd -> Even
|
||||
{input: [2, 0, 2], output: [2, 2, 0, 0, 2]}, // Odd -> Odd
|
||||
{input: [2, 0], output: [2, 2, 2, 0, 0]}, // Even -> Odd
|
||||
{input: [2, 0], output: [2, 2, 2, 0, 0, 0]}, // Even -> Even
|
||||
].forEach((c, i) => expectSample(i, c.input, c.output, true));
|
||||
});
|
||||
|
||||
it('should maintain sample', () => {
|
||||
[
|
||||
{input: [2, 0, 2], output: [2, 0, 2]}, // Odd
|
||||
{input: [2, 0], output: [2, 0]}, // Even
|
||||
].forEach((c, i) => expectSample(i, c.input, c.output, true));
|
||||
});
|
||||
});
|
||||
|
||||
describe('arrayRescale', () => {
|
||||
it('should rescale', () => {
|
||||
const input = [8, 9, 1, 0, 2, 7, 10];
|
||||
const output = [80, 90, 10, 0, 20, 70, 100];
|
||||
const result = arrayRescale(input, 0, 100);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(output.length);
|
||||
expect(result).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
const output = [val, val, val];
|
||||
const result = arraySeed(val, output.length);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(output.length);
|
||||
expect(result).toEqual(output);
|
||||
});
|
||||
it('should maintain pointers', () => {
|
||||
const val = {}; // this works because `{} !== {}`, which is what toEqual checks
|
||||
const output = [val, val, val];
|
||||
const result = arraySeed(val, output.length);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(output.length);
|
||||
expect(result).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('arrayFastClone', () => {
|
||||
it('should break pointer reference on source array', () => {
|
||||
const val = {}; // we'll test to make sure the values maintain pointers too
|
||||
const input = [val, val, val];
|
||||
const result = arrayFastClone(input);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(input.length);
|
||||
expect(result).toEqual(input); // we want the array contents to match...
|
||||
expect(result).not.toBe(input); // ... but be a different reference
|
||||
});
|
||||
});
|
||||
|
||||
describe('arrayHasOrderChange', () => {
|
||||
it('should flag true on B ordering difference', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [3, 2, 1];
|
||||
const result = arrayHasOrderChange(a, b);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should flag false on no ordering difference', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 3];
|
||||
const result = arrayHasOrderChange(a, b);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should flag true on A length > B length', () => {
|
||||
const a = [1, 2, 3, 4];
|
||||
const b = [1, 2, 3];
|
||||
const result = arrayHasOrderChange(a, b);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should flag true on A length < B length', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 3, 4];
|
||||
const result = arrayHasOrderChange(a, b);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('arrayHasDiff', () => {
|
||||
it('should flag true on A length > B length', () => {
|
||||
const a = [1, 2, 3, 4];
|
||||
const b = [1, 2, 3];
|
||||
const result = arrayHasDiff(a, b);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should flag true on A length < B length', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 3, 4];
|
||||
const result = arrayHasDiff(a, b);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should flag true on element differences', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [4, 5, 6];
|
||||
const result = arrayHasDiff(a, b);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should flag false if same but order different', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [3, 1, 2];
|
||||
const result = arrayHasDiff(a, b);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should flag false if same', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 3];
|
||||
const result = arrayHasDiff(a, b);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('arrayDiff', () => {
|
||||
it('should see added from A->B', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 3, 4];
|
||||
const result = arrayDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.added).toHaveLength(1);
|
||||
expect(result.removed).toHaveLength(0);
|
||||
expect(result.added).toEqual([4]);
|
||||
});
|
||||
|
||||
it('should see removed from A->B', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2];
|
||||
const result = arrayDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.added).toHaveLength(0);
|
||||
expect(result.removed).toHaveLength(1);
|
||||
expect(result.removed).toEqual([3]);
|
||||
});
|
||||
|
||||
it('should see added and removed in the same set', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 4]; // note diff
|
||||
const result = arrayDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.added).toHaveLength(1);
|
||||
expect(result.removed).toHaveLength(1);
|
||||
expect(result.added).toEqual([4]);
|
||||
expect(result.removed).toEqual([3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('arrayUnion', () => {
|
||||
it('should return a union', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 4]; // note diff
|
||||
const result = arrayUnion(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('should return an empty array on no matches', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [4, 5, 6];
|
||||
const result = arrayUnion(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('arrayMerge', () => {
|
||||
it('should merge 3 arrays with deduplication', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 4, 5]; // note missing 3
|
||||
const c = [6, 7, 8, 9];
|
||||
const result = arrayMerge(a, b, c);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(9);
|
||||
expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
});
|
||||
|
||||
it('should deduplicate a single array', () => {
|
||||
// dev note: this is technically an edge case, but it is described behaviour if the
|
||||
// function is only provided one function (it'll merge the array against itself)
|
||||
const a = [1, 1, 2, 2, 3, 3];
|
||||
const result = arrayMerge(a);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result).toEqual([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ArrayUtil', () => {
|
||||
it('should maintain the pointer to the given array', () => {
|
||||
const input = [1, 2, 3];
|
||||
const result = new ArrayUtil(input);
|
||||
expect(result.value).toBe(input);
|
||||
});
|
||||
|
||||
it('should group appropriately', () => {
|
||||
const input = [['a', 1], ['b', 2], ['c', 3], ['a', 4], ['a', 5], ['b', 6]];
|
||||
const output = {
|
||||
'a': [['a', 1], ['a', 4], ['a', 5]],
|
||||
'b': [['b', 2], ['b', 6]],
|
||||
'c': [['c', 3]],
|
||||
};
|
||||
const result = new ArrayUtil(input).groupBy(p => p[0]);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.value).toBeDefined();
|
||||
|
||||
const asObject = objectFromEntries(result.value.entries());
|
||||
expect(asObject).toMatchObject(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GroupedArray', () => {
|
||||
it('should maintain the pointer to the given map', () => {
|
||||
const input = new Map([
|
||||
['a', [1, 2, 3]],
|
||||
['b', [7, 8, 9]],
|
||||
['c', [4, 5, 6]],
|
||||
]);
|
||||
const result = new GroupedArray(input);
|
||||
expect(result.value).toBe(input);
|
||||
});
|
||||
|
||||
it('should ordering by the provided key order', () => {
|
||||
const input = new Map([
|
||||
['a', [1, 2, 3]],
|
||||
['b', [7, 8, 9]], // note counting diff
|
||||
['c', [4, 5, 6]],
|
||||
]);
|
||||
const output = [4, 5, 6, 1, 2, 3, 7, 8, 9];
|
||||
const keyOrder = ['c', 'a', 'b']; // note weird order to cause the `output` to be strange
|
||||
const result = new GroupedArray(input).orderBy(keyOrder);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.value).toBeDefined();
|
||||
expect(result.value).toEqual(output);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
67
test/utils/enums-test.ts
Normal file
67
test/utils/enums-test.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
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 {getEnumValues, isEnumValue} from "../../src/utils/enums";
|
||||
|
||||
enum TestStringEnum {
|
||||
First = "__first__",
|
||||
Second = "__second__",
|
||||
}
|
||||
|
||||
enum TestNumberEnum {
|
||||
FirstKey = 10,
|
||||
SecondKey = 20,
|
||||
}
|
||||
|
||||
describe('enums', () => {
|
||||
describe('getEnumValues', () => {
|
||||
it('should work on string enums', () => {
|
||||
const result = getEnumValues(TestStringEnum);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toEqual(['__first__', '__second__']);
|
||||
});
|
||||
|
||||
it('should work on number enums', () => {
|
||||
const result = getEnumValues(TestNumberEnum);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toEqual([10, 20]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isEnumValue', () => {
|
||||
it('should return true on values in a string enum', () => {
|
||||
const result = isEnumValue(TestStringEnum, '__first__');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false on values not in a string enum', () => {
|
||||
const result = isEnumValue(TestStringEnum, 'not a value');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true on values in a number enum', () => {
|
||||
const result = isEnumValue(TestNumberEnum, 10);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false on values not in a number enum', () => {
|
||||
const result = isEnumValue(TestStringEnum, 99);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
77
test/utils/iterables-test.ts
Normal file
77
test/utils/iterables-test.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
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 {iterableDiff, iterableUnion} from "../../src/utils/iterables";
|
||||
|
||||
describe('iterables', () => {
|
||||
describe('iterableUnion', () => {
|
||||
it('should return a union', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 4]; // note diff
|
||||
const result = iterableUnion(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('should return an empty array on no matches', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [4, 5, 6];
|
||||
const result = iterableUnion(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('iterableDiff', () => {
|
||||
it('should see added from A->B', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 3, 4];
|
||||
const result = iterableDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.added).toHaveLength(1);
|
||||
expect(result.removed).toHaveLength(0);
|
||||
expect(result.added).toEqual([4]);
|
||||
});
|
||||
|
||||
it('should see removed from A->B', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2];
|
||||
const result = iterableDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.added).toHaveLength(0);
|
||||
expect(result.removed).toHaveLength(1);
|
||||
expect(result.removed).toEqual([3]);
|
||||
});
|
||||
|
||||
it('should see added and removed in the same set', () => {
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 4]; // note diff
|
||||
const result = iterableDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.added).toHaveLength(1);
|
||||
expect(result.removed).toHaveLength(1);
|
||||
expect(result.added).toEqual([4]);
|
||||
expect(result.removed).toEqual([3]);
|
||||
});
|
||||
});
|
||||
});
|
245
test/utils/maps-test.ts
Normal file
245
test/utils/maps-test.ts
Normal file
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
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 {EnhancedMap, mapDiff, mapKeyChanges} from "../../src/utils/maps";
|
||||
|
||||
describe('maps', () => {
|
||||
describe('mapDiff', () => {
|
||||
it('should indicate no differences when the pointers are the same', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const result = mapDiff(a, a);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toHaveLength(0);
|
||||
expect(result.removed).toHaveLength(0);
|
||||
expect(result.changed).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should indicate no differences when there are none', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const b = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const result = mapDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toHaveLength(0);
|
||||
expect(result.removed).toHaveLength(0);
|
||||
expect(result.changed).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should indicate added properties', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const b = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]);
|
||||
const result = mapDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toHaveLength(1);
|
||||
expect(result.removed).toHaveLength(0);
|
||||
expect(result.changed).toHaveLength(0);
|
||||
expect(result.added).toEqual([4]);
|
||||
});
|
||||
|
||||
it('should indicate removed properties', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const b = new Map([[1, 1], [2, 2]]);
|
||||
const result = mapDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toHaveLength(0);
|
||||
expect(result.removed).toHaveLength(1);
|
||||
expect(result.changed).toHaveLength(0);
|
||||
expect(result.removed).toEqual([3]);
|
||||
});
|
||||
|
||||
it('should indicate changed properties', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const b = new Map([[1, 1], [2, 2], [3, 4]]); // note change
|
||||
const result = mapDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toHaveLength(0);
|
||||
expect(result.removed).toHaveLength(0);
|
||||
expect(result.changed).toHaveLength(1);
|
||||
expect(result.changed).toEqual([3]);
|
||||
});
|
||||
|
||||
it('should indicate changed, added, and removed properties', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const b = new Map([[1, 1], [2, 8], [4, 4]]); // note change
|
||||
const result = mapDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toHaveLength(1);
|
||||
expect(result.removed).toHaveLength(1);
|
||||
expect(result.changed).toHaveLength(1);
|
||||
expect(result.added).toEqual([4]);
|
||||
expect(result.removed).toEqual([3]);
|
||||
expect(result.changed).toEqual([2]);
|
||||
});
|
||||
|
||||
it('should indicate changes for difference in pointers', () => {
|
||||
const a = new Map([[1, {}]]); // {} always creates a new object
|
||||
const b = new Map([[1, {}]]);
|
||||
const result = mapDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toHaveLength(0);
|
||||
expect(result.removed).toHaveLength(0);
|
||||
expect(result.changed).toHaveLength(1);
|
||||
expect(result.changed).toEqual([1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapKeyChanges', () => {
|
||||
it('should indicate no changes for unchanged pointers', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const result = mapKeyChanges(a, a);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should indicate no changes for unchanged maps with different pointers', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const b = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const result = mapKeyChanges(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should indicate changes for added properties', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const b = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]);
|
||||
const result = mapKeyChanges(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toEqual([4]);
|
||||
});
|
||||
|
||||
it('should indicate changes for removed properties', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]);
|
||||
const b = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const result = mapKeyChanges(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toEqual([4]);
|
||||
});
|
||||
|
||||
it('should indicate changes for changed properties', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]);
|
||||
const b = new Map([[1, 1], [2, 2], [3, 3], [4, 55]]);
|
||||
const result = mapKeyChanges(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toEqual([4]);
|
||||
});
|
||||
|
||||
it('should indicate changes for properties with different pointers', () => {
|
||||
const a = new Map([[1, {}]]); // {} always creates a new object
|
||||
const b = new Map([[1, {}]]);
|
||||
const result = mapKeyChanges(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toEqual([1]);
|
||||
});
|
||||
|
||||
it('should indicate changes for changed, added, and removed properties', () => {
|
||||
const a = new Map([[1, 1], [2, 2], [3, 3]]);
|
||||
const b = new Map([[1, 1], [2, 8], [4, 4]]); // note change
|
||||
const result = mapKeyChanges(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result).toEqual([3, 4, 2]); // order irrelevant, but the test cares
|
||||
});
|
||||
});
|
||||
|
||||
describe('EnhancedMap', () => {
|
||||
// Most of these tests will make sure it implements the Map<K, V> class
|
||||
|
||||
it('should be empty by default', () => {
|
||||
const result = new EnhancedMap();
|
||||
expect(result.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should use the provided entries', () => {
|
||||
const obj = {a: 1, b: 2};
|
||||
const result = new EnhancedMap(Object.entries(obj));
|
||||
expect(result.size).toBe(2);
|
||||
expect(result.get('a')).toBe(1);
|
||||
expect(result.get('b')).toBe(2);
|
||||
});
|
||||
|
||||
it('should create keys if they do not exist', () => {
|
||||
const key = 'a';
|
||||
const val = {}; // we'll check pointers
|
||||
|
||||
const result = new EnhancedMap<string, any>();
|
||||
expect(result.size).toBe(0);
|
||||
|
||||
let get = result.getOrCreate(key, val);
|
||||
expect(get).toBeDefined();
|
||||
expect(get).toBe(val);
|
||||
expect(result.size).toBe(1);
|
||||
|
||||
get = result.getOrCreate(key, 44); // specifically change `val`
|
||||
expect(get).toBeDefined();
|
||||
expect(get).toBe(val);
|
||||
expect(result.size).toBe(1);
|
||||
|
||||
get = result.get(key); // use the base class function
|
||||
expect(get).toBeDefined();
|
||||
expect(get).toBe(val);
|
||||
expect(result.size).toBe(1);
|
||||
});
|
||||
|
||||
it('should proxy remove to delete and return it', () => {
|
||||
const val = {};
|
||||
const result = new EnhancedMap<string, any>();
|
||||
result.set('a', val);
|
||||
|
||||
expect(result.size).toBe(1);
|
||||
|
||||
const removed = result.remove('a');
|
||||
expect(result.size).toBe(0);
|
||||
expect(removed).toBeDefined();
|
||||
expect(removed).toBe(val);
|
||||
});
|
||||
|
||||
it('should support removing unknown keys', () => {
|
||||
const val = {};
|
||||
const result = new EnhancedMap<string, any>();
|
||||
result.set('a', val);
|
||||
|
||||
expect(result.size).toBe(1);
|
||||
|
||||
const removed = result.remove('not-a');
|
||||
expect(result.size).toBe(1);
|
||||
expect(removed).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
163
test/utils/numbers-test.ts
Normal file
163
test/utils/numbers-test.ts
Normal file
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
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 {clamp, defaultNumber, percentageOf, percentageWithin, sum} from "../../src/utils/numbers";
|
||||
|
||||
describe('numbers', () => {
|
||||
describe('defaultNumber', () => {
|
||||
it('should use the default when the input is not a number', () => {
|
||||
const def = 42;
|
||||
|
||||
let result = defaultNumber(null, def);
|
||||
expect(result).toBe(def);
|
||||
|
||||
result = defaultNumber(undefined, def);
|
||||
expect(result).toBe(def);
|
||||
|
||||
result = defaultNumber(Number.NaN, def);
|
||||
expect(result).toBe(def);
|
||||
});
|
||||
|
||||
it('should use the number when it is a number', () => {
|
||||
const input = 24;
|
||||
const def = 42;
|
||||
const result = defaultNumber(input, def);
|
||||
expect(result).toBe(input);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clamp', () => {
|
||||
it('should clamp high numbers', () => {
|
||||
const input = 101;
|
||||
const min = 0;
|
||||
const max = 100;
|
||||
const result = clamp(input, min, max);
|
||||
expect(result).toBe(max);
|
||||
});
|
||||
|
||||
it('should clamp low numbers', () => {
|
||||
const input = -1;
|
||||
const min = 0;
|
||||
const max = 100;
|
||||
const result = clamp(input, min, max);
|
||||
expect(result).toBe(min);
|
||||
});
|
||||
|
||||
it('should not clamp numbers in range', () => {
|
||||
const input = 50;
|
||||
const min = 0;
|
||||
const max = 100;
|
||||
const result = clamp(input, min, max);
|
||||
expect(result).toBe(input);
|
||||
});
|
||||
|
||||
it('should clamp floats', () => {
|
||||
const min = -0.10;
|
||||
const max = +0.10;
|
||||
|
||||
let result = clamp(-1.2, min, max);
|
||||
expect(result).toBe(min);
|
||||
|
||||
result = clamp(1.2, min, max);
|
||||
expect(result).toBe(max);
|
||||
|
||||
result = clamp(0.02, min, max);
|
||||
expect(result).toBe(0.02);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sum', () => {
|
||||
it('should sum', () => { // duh
|
||||
const result = sum(1, 2, 1, 4);
|
||||
expect(result).toBe(8);
|
||||
});
|
||||
});
|
||||
|
||||
describe('percentageWithin', () => {
|
||||
it('should work within 0-100', () => {
|
||||
const result = percentageWithin(0.4, 0, 100);
|
||||
expect(result).toBe(40);
|
||||
});
|
||||
|
||||
it('should work within 0-100 when pct > 1', () => {
|
||||
const result = percentageWithin(1.4, 0, 100);
|
||||
expect(result).toBe(140);
|
||||
});
|
||||
|
||||
it('should work within 0-100 when pct < 0', () => {
|
||||
const result = percentageWithin(-1.4, 0, 100);
|
||||
expect(result).toBe(-140);
|
||||
});
|
||||
|
||||
it('should work with ranges other than 0-100', () => {
|
||||
const result = percentageWithin(0.4, 10, 20);
|
||||
expect(result).toBe(14);
|
||||
});
|
||||
|
||||
it('should work with ranges other than 0-100 when pct > 1', () => {
|
||||
const result = percentageWithin(1.4, 10, 20);
|
||||
expect(result).toBe(24);
|
||||
});
|
||||
|
||||
it('should work with ranges other than 0-100 when pct < 0', () => {
|
||||
const result = percentageWithin(-1.4, 10, 20);
|
||||
expect(result).toBe(-4);
|
||||
});
|
||||
|
||||
it('should work with floats', () => {
|
||||
const result = percentageWithin(0.4, 10.2, 20.4);
|
||||
expect(result).toBe(14.28);
|
||||
});
|
||||
});
|
||||
|
||||
// These are the inverse of percentageWithin
|
||||
describe('percentageOf', () => {
|
||||
it('should work within 0-100', () => {
|
||||
const result = percentageOf(40, 0, 100);
|
||||
expect(result).toBe(0.4);
|
||||
});
|
||||
|
||||
it('should work within 0-100 when val > 100', () => {
|
||||
const result = percentageOf(140, 0, 100);
|
||||
expect(result).toBe(1.40);
|
||||
});
|
||||
|
||||
it('should work within 0-100 when val < 0', () => {
|
||||
const result = percentageOf(-140, 0, 100);
|
||||
expect(result).toBe(-1.40);
|
||||
});
|
||||
|
||||
it('should work with ranges other than 0-100', () => {
|
||||
const result = percentageOf(14, 10, 20);
|
||||
expect(result).toBe(0.4);
|
||||
});
|
||||
|
||||
it('should work with ranges other than 0-100 when val > 100', () => {
|
||||
const result = percentageOf(24, 10, 20);
|
||||
expect(result).toBe(1.4);
|
||||
});
|
||||
|
||||
it('should work with ranges other than 0-100 when val < 0', () => {
|
||||
const result = percentageOf(-4, 10, 20);
|
||||
expect(result).toBe(-1.4);
|
||||
});
|
||||
|
||||
it('should work with floats', () => {
|
||||
const result = percentageOf(14.28, 10.2, 20.4);
|
||||
expect(result).toBe(0.4);
|
||||
});
|
||||
});
|
||||
});
|
262
test/utils/objects-test.ts
Normal file
262
test/utils/objects-test.ts
Normal file
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
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 {
|
||||
objectClone,
|
||||
objectDiff,
|
||||
objectExcluding,
|
||||
objectFromEntries,
|
||||
objectHasDiff,
|
||||
objectKeyChanges,
|
||||
objectShallowClone,
|
||||
objectWithOnly,
|
||||
} from "../../src/utils/objects";
|
||||
|
||||
describe('objects', () => {
|
||||
describe('objectExcluding', () => {
|
||||
it('should exclude the given properties', () => {
|
||||
const input = {hello: "world", test: true};
|
||||
const output = {hello: "world"};
|
||||
const props = ["test", "doesnotexist"]; // we also make sure it doesn't explode on missing props
|
||||
const result = objectExcluding(input, <any>props); // any is to test the missing prop
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toMatchObject(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('objectWithOnly', () => {
|
||||
it('should exclusively use the given properties', () => {
|
||||
const input = {hello: "world", test: true};
|
||||
const output = {hello: "world"};
|
||||
const props = ["hello", "doesnotexist"]; // we also make sure it doesn't explode on missing props
|
||||
const result = objectWithOnly(input, <any>props); // any is to test the missing prop
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toMatchObject(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('objectShallowClone', () => {
|
||||
it('should create a new object', () => {
|
||||
const input = {test: 1};
|
||||
const result = objectShallowClone(input);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).not.toBe(input);
|
||||
expect(result).toMatchObject(input);
|
||||
});
|
||||
|
||||
it('should only clone the top level properties', () => {
|
||||
const input = {a: 1, b: {c: 2}};
|
||||
const result = objectShallowClone(input);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toMatchObject(input);
|
||||
expect(result.b).toBe(input.b);
|
||||
});
|
||||
|
||||
it('should support custom clone functions', () => {
|
||||
const input = {a: 1, b: 2};
|
||||
const output = {a: 4, b: 8};
|
||||
const result = objectShallowClone(input, (k, v) => {
|
||||
// XXX: inverted expectation for ease of assertion
|
||||
expect(Object.keys(input)).toContain(k);
|
||||
|
||||
return v * 4;
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toMatchObject(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('objectHasDiff', () => {
|
||||
it('should return false for the same pointer', () => {
|
||||
const a = {};
|
||||
const result = objectHasDiff(a, a);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if keys for A > keys for B', () => {
|
||||
const a = {a: 1, b: 2};
|
||||
const b = {a: 1};
|
||||
const result = objectHasDiff(a, b);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if keys for A < keys for B', () => {
|
||||
const a = {a: 1};
|
||||
const b = {a: 1, b: 2};
|
||||
const result = objectHasDiff(a, b);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the objects are the same but different pointers', () => {
|
||||
const a = {a: 1, b: 2};
|
||||
const b = {a: 1, b: 2};
|
||||
const result = objectHasDiff(a, b);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should consider pointers when testing values', () => {
|
||||
const a = {a: {}, b: 2}; // `{}` is shorthand for `new Object()`
|
||||
const b = {a: {}, b: 2};
|
||||
const result = objectHasDiff(a, b);
|
||||
expect(result).toBe(true); // even though the keys are the same, the value pointers vary
|
||||
});
|
||||
});
|
||||
|
||||
describe('objectDiff', () => {
|
||||
it('should return empty sets for the same object', () => {
|
||||
const a = {a: 1, b: 2};
|
||||
const b = {a: 1, b: 2};
|
||||
const result = objectDiff(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toHaveLength(0);
|
||||
expect(result.added).toHaveLength(0);
|
||||
expect(result.removed).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should return empty sets for the same object pointer', () => {
|
||||
const a = {a: 1, b: 2};
|
||||
const result = objectDiff(a, a);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toHaveLength(0);
|
||||
expect(result.added).toHaveLength(0);
|
||||
expect(result.removed).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should indicate when property changes are made', () => {
|
||||
const a = {a: 1, b: 2};
|
||||
const b = {a: 11, b: 2};
|
||||
const result = objectDiff(a, b);
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toHaveLength(1);
|
||||
expect(result.added).toHaveLength(0);
|
||||
expect(result.removed).toHaveLength(0);
|
||||
expect(result.changed).toEqual(['a']);
|
||||
});
|
||||
|
||||
it('should indicate when properties are added', () => {
|
||||
const a = {a: 1, b: 2};
|
||||
const b = {a: 1, b: 2, c: 3};
|
||||
const result = objectDiff(a, b);
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toHaveLength(0);
|
||||
expect(result.added).toHaveLength(1);
|
||||
expect(result.removed).toHaveLength(0);
|
||||
expect(result.added).toEqual(['c']);
|
||||
});
|
||||
|
||||
it('should indicate when properties are removed', () => {
|
||||
const a = {a: 1, b: 2};
|
||||
const b = {a: 1};
|
||||
const result = objectDiff(a, b);
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toHaveLength(0);
|
||||
expect(result.added).toHaveLength(0);
|
||||
expect(result.removed).toHaveLength(1);
|
||||
expect(result.removed).toEqual(['b']);
|
||||
});
|
||||
|
||||
it('should indicate when multiple aspects change', () => {
|
||||
const a = {a: 1, b: 2, c: 3};
|
||||
const b: (typeof a | {d: number}) = {a: 1, b: 22, d: 4};
|
||||
const result = objectDiff(a, b);
|
||||
expect(result.changed).toBeDefined();
|
||||
expect(result.added).toBeDefined();
|
||||
expect(result.removed).toBeDefined();
|
||||
expect(result.changed).toHaveLength(1);
|
||||
expect(result.added).toHaveLength(1);
|
||||
expect(result.removed).toHaveLength(1);
|
||||
expect(result.changed).toEqual(['b']);
|
||||
expect(result.removed).toEqual(['c']);
|
||||
expect(result.added).toEqual(['d']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('objectKeyChanges', () => {
|
||||
it('should return an empty set if no properties changed', () => {
|
||||
const a = {a: 1, b: 2};
|
||||
const b = {a: 1, b: 2};
|
||||
const result = objectKeyChanges(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should return an empty set if no properties changed for the same pointer', () => {
|
||||
const a = {a: 1, b: 2};
|
||||
const result = objectKeyChanges(a, a);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should return properties which were changed, added, or removed', () => {
|
||||
const a = {a: 1, b: 2, c: 3};
|
||||
const b: (typeof a | {d: number}) = {a: 1, b: 22, d: 4};
|
||||
const result = objectKeyChanges(a, b);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result).toEqual(['c', 'd', 'b']); // order isn't important, but the test cares
|
||||
});
|
||||
});
|
||||
|
||||
describe('objectClone', () => {
|
||||
it('should deep clone an object', () => {
|
||||
const a = {
|
||||
hello: "world",
|
||||
test: {
|
||||
another: "property",
|
||||
test: 42,
|
||||
third: {
|
||||
prop: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = objectClone(a);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).not.toBe(a);
|
||||
expect(result).toMatchObject(a);
|
||||
expect(result.test).not.toBe(a.test);
|
||||
expect(result.test.third).not.toBe(a.test.third);
|
||||
});
|
||||
});
|
||||
|
||||
describe('objectFromEntries', () => {
|
||||
it('should create an object from an array of entries', () => {
|
||||
const output = {a: 1, b: 2, c: 3};
|
||||
const result = objectFromEntries(Object.entries(output));
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toMatchObject(output);
|
||||
});
|
||||
|
||||
it('should maintain pointers in values', () => {
|
||||
const output = {a: {}, b: 2, c: 3};
|
||||
const result = objectFromEntries(Object.entries(output));
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toMatchObject(output);
|
||||
expect(result['a']).toBe(output.a);
|
||||
});
|
||||
});
|
||||
});
|
56
test/utils/sets-test.ts
Normal file
56
test/utils/sets-test.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
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 {setHasDiff} from "../../src/utils/sets";
|
||||
|
||||
describe('sets', () => {
|
||||
describe('setHasDiff', () => {
|
||||
it('should flag true on A length > B length', () => {
|
||||
const a = new Set([1, 2, 3, 4]);
|
||||
const b = new Set([1, 2, 3]);
|
||||
const result = setHasDiff(a, b);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should flag true on A length < B length', () => {
|
||||
const a = new Set([1, 2, 3]);
|
||||
const b = new Set([1, 2, 3, 4]);
|
||||
const result = setHasDiff(a, b);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should flag true on element differences', () => {
|
||||
const a = new Set([1, 2, 3]);
|
||||
const b = new Set([4, 5, 6]);
|
||||
const result = setHasDiff(a, b);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should flag false if same but order different', () => {
|
||||
const a = new Set([1, 2, 3]);
|
||||
const b = new Set([3, 1, 2]);
|
||||
const result = setHasDiff(a, b);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should flag false if same', () => {
|
||||
const a = new Set([1, 2, 3]);
|
||||
const b = new Set([1, 2, 3]);
|
||||
const result = setHasDiff(a, b);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
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