You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-08-07 21:23:00 +03:00
Merge branch 'develop' into feat/add-plain-text-mode
This commit is contained in:
@@ -19,6 +19,7 @@ import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { SyncState } from "matrix-js-sdk/src/sync";
|
||||
import { waitFor } from "@testing-library/react";
|
||||
|
||||
import BasePlatform from "../src/BasePlatform";
|
||||
import { ElementCall } from "../src/models/Call";
|
||||
@@ -29,8 +30,15 @@ import {
|
||||
createLocalNotificationSettingsIfNeeded,
|
||||
getLocalNotificationAccountDataEventType,
|
||||
} from "../src/utils/notifications";
|
||||
import { getMockClientWithEventEmitter, mkEvent, mkRoom, mockClientMethodsUser, mockPlatformPeg } from "./test-utils";
|
||||
import { getMockClientWithEventEmitter, mkEvent, mockClientMethodsUser, mockPlatformPeg } from "./test-utils";
|
||||
import { IncomingCallToast } from "../src/toasts/IncomingCallToast";
|
||||
import { SdkContextClass } from "../src/contexts/SDKContext";
|
||||
import UserActivity from "../src/UserActivity";
|
||||
import Modal from "../src/Modal";
|
||||
import { mkThread } from "./test-utils/threads";
|
||||
import dis from "../src/dispatcher/dispatcher";
|
||||
import { ThreadPayload } from "../src/dispatcher/payloads/ThreadPayload";
|
||||
import { Action } from "../src/dispatcher/actions";
|
||||
|
||||
jest.mock("../src/utils/notifications", () => ({
|
||||
// @ts-ignore
|
||||
@@ -50,10 +58,12 @@ describe("Notifier", () => {
|
||||
|
||||
let MockPlatform: MockedObject<BasePlatform>;
|
||||
let mockClient: MockedObject<MatrixClient>;
|
||||
let testRoom: MockedObject<Room>;
|
||||
let testRoom: Room;
|
||||
let accountDataEventKey: string;
|
||||
let accountDataStore = {};
|
||||
|
||||
let mockSettings: Record<string, boolean> = {};
|
||||
|
||||
const userId = "@bob:example.org";
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -78,7 +88,7 @@ describe("Notifier", () => {
|
||||
};
|
||||
accountDataEventKey = getLocalNotificationAccountDataEventType(mockClient.deviceId);
|
||||
|
||||
testRoom = mkRoom(mockClient, roomId);
|
||||
testRoom = new Room(roomId, mockClient, mockClient.getUserId());
|
||||
|
||||
MockPlatform = mockPlatformPeg({
|
||||
supportsNotifications: jest.fn().mockReturnValue(true),
|
||||
@@ -89,7 +99,9 @@ describe("Notifier", () => {
|
||||
|
||||
Notifier.isBodyEnabled = jest.fn().mockReturnValue(true);
|
||||
|
||||
mockClient.getRoom.mockReturnValue(testRoom);
|
||||
mockClient.getRoom.mockImplementation(id => {
|
||||
return id === roomId ? testRoom : new Room(id, mockClient, mockClient.getUserId());
|
||||
});
|
||||
});
|
||||
|
||||
describe('triggering notification from events', () => {
|
||||
@@ -121,13 +133,14 @@ describe("Notifier", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const enabledSettings = [
|
||||
'notificationsEnabled',
|
||||
'audioNotificationsEnabled',
|
||||
];
|
||||
mockSettings = {
|
||||
'notificationsEnabled': true,
|
||||
'audioNotificationsEnabled': true,
|
||||
};
|
||||
|
||||
// enable notifications by default
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
settingName => enabledSettings.includes(settingName),
|
||||
jest.spyOn(SettingsStore, "getValue").mockReset().mockImplementation(
|
||||
settingName => mockSettings[settingName] ?? false,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -253,16 +266,13 @@ describe("Notifier", () => {
|
||||
});
|
||||
|
||||
const callOnEvent = (type?: string) => {
|
||||
const callEvent = {
|
||||
getContent: () => { },
|
||||
getRoomId: () => roomId,
|
||||
isBeingDecrypted: () => false,
|
||||
isDecryptionFailure: () => false,
|
||||
getSender: () => "@alice:foo",
|
||||
getType: () => type ?? ElementCall.CALL_EVENT_TYPE.name,
|
||||
getStateKey: () => "state_key",
|
||||
} as unknown as MatrixEvent;
|
||||
|
||||
const callEvent = mkEvent({
|
||||
type: type ?? ElementCall.CALL_EVENT_TYPE.name,
|
||||
user: "@alice:foo",
|
||||
room: roomId,
|
||||
content: {},
|
||||
event: true,
|
||||
});
|
||||
Notifier.onEvent(callEvent);
|
||||
return callEvent;
|
||||
};
|
||||
@@ -345,4 +355,72 @@ describe("Notifier", () => {
|
||||
expect(createLocalNotificationSettingsIfNeededMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('_evaluateEvent', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId")
|
||||
.mockReturnValue(testRoom.roomId);
|
||||
|
||||
jest.spyOn(UserActivity.sharedInstance(), "userActiveRecently")
|
||||
.mockReturnValue(true);
|
||||
|
||||
jest.spyOn(Modal, "hasDialogs").mockReturnValue(false);
|
||||
|
||||
jest.spyOn(Notifier, "_displayPopupNotification").mockReset();
|
||||
jest.spyOn(Notifier, "isEnabled").mockReturnValue(true);
|
||||
|
||||
mockClient.getPushActionsForEvent.mockReturnValue({
|
||||
notify: true,
|
||||
tweaks: {
|
||||
sound: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should show a pop-up", () => {
|
||||
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0);
|
||||
Notifier._evaluateEvent(testEvent);
|
||||
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0);
|
||||
|
||||
const eventFromOtherRoom = mkEvent({
|
||||
event: true,
|
||||
type: "m.room.message",
|
||||
user: "@user1:server",
|
||||
room: "!otherroom:example.org",
|
||||
content: {},
|
||||
});
|
||||
|
||||
Notifier._evaluateEvent(eventFromOtherRoom);
|
||||
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should a pop-up for thread event", async () => {
|
||||
const { events, rootEvent } = mkThread({
|
||||
room: testRoom,
|
||||
client: mockClient,
|
||||
authorId: "@bob:example.org",
|
||||
participantUserIds: ["@bob:example.org"],
|
||||
});
|
||||
|
||||
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0);
|
||||
|
||||
Notifier._evaluateEvent(rootEvent);
|
||||
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0);
|
||||
|
||||
Notifier._evaluateEvent(events[1]);
|
||||
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1);
|
||||
|
||||
dis.dispatch<ThreadPayload>({
|
||||
action: Action.ViewThread,
|
||||
thread_id: rootEvent.getId(),
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBe(rootEvent.getId()),
|
||||
);
|
||||
|
||||
Notifier._evaluateEvent(events[1]);
|
||||
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -28,6 +28,7 @@ import { act } from "react-dom/test-utils";
|
||||
import ThreadView from "../../../src/components/structures/ThreadView";
|
||||
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../src/contexts/RoomContext";
|
||||
import { SdkContextClass } from "../../../src/contexts/SDKContext";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
import ResizeNotifier from "../../../src/utils/ResizeNotifier";
|
||||
@@ -155,4 +156,13 @@ describe("ThreadView", () => {
|
||||
ROOM_ID, rootEvent2.getId(), expectedMessageBody(rootEvent2, "yolo"),
|
||||
);
|
||||
});
|
||||
|
||||
it("sets the correct thread in the room view store", async () => {
|
||||
// expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBeNull();
|
||||
const { unmount } = await getComponent();
|
||||
expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBe(rootEvent.getId());
|
||||
|
||||
unmount();
|
||||
await waitFor(() => expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBeNull());
|
||||
});
|
||||
});
|
||||
|
57
test/components/views/elements/LearnMore-test.tsx
Normal file
57
test/components/views/elements/LearnMore-test.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
|
||||
import LearnMore from '../../../../src/components/views/elements/LearnMore';
|
||||
import Modal from '../../../../src/Modal';
|
||||
import InfoDialog from '../../../../src/components/views/dialogs/InfoDialog';
|
||||
|
||||
describe('<LearnMore />', () => {
|
||||
const defaultProps = {
|
||||
title: 'Test',
|
||||
description: 'test test test',
|
||||
['data-testid']: 'testid',
|
||||
};
|
||||
const getComponent = (props = {}) =>
|
||||
(<LearnMore {...defaultProps} {...props} />);
|
||||
|
||||
const modalSpy = jest.spyOn(Modal, 'createDialog').mockReturnValue(undefined);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders button', () => {
|
||||
const { container } = render(getComponent());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('opens modal on click', async () => {
|
||||
const { getByTestId } = render(getComponent());
|
||||
fireEvent.click(getByTestId('testid'));
|
||||
|
||||
expect(modalSpy).toHaveBeenCalledWith(
|
||||
InfoDialog,
|
||||
{
|
||||
button: 'Got it',
|
||||
description: defaultProps.description,
|
||||
hasCloseButton: true,
|
||||
title: defaultProps.title,
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,14 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<LearnMore /> renders button 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
data-testid="testid"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@@ -37,7 +37,16 @@ HTMLCollection [
|
||||
<p
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Consider signing out from old sessions (90 days or older) you don't use anymore
|
||||
<span>
|
||||
Consider signing out from old sessions (90 days or older) you don't use anymore.
|
||||
<div
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,7 +81,16 @@ HTMLCollection [
|
||||
<p
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.
|
||||
<span>
|
||||
Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.
|
||||
<div
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,7 +125,16 @@ HTMLCollection [
|
||||
<p
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
For best security, sign out from any session that you don't recognize or use anymore.
|
||||
<span>
|
||||
For best security, sign out from any session that you don't recognize or use anymore.
|
||||
<div
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -20,10 +20,6 @@ import * as languageHandler from "../../src/languageHandler";
|
||||
import en from "../../src/i18n/strings/en_EN.json";
|
||||
import de from "../../src/i18n/strings/de_DE.json";
|
||||
|
||||
fetchMock.config.overwriteRoutes = false;
|
||||
fetchMock.catch("");
|
||||
window.fetch = fetchMock.sandbox();
|
||||
|
||||
const lv = {
|
||||
"Save": "Saglabāt",
|
||||
"Uploading %(filename)s and %(count)s others|one": "Качване на %(filename)s и %(count)s друг",
|
||||
|
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
import { TextDecoder, TextEncoder } from "util";
|
||||
|
||||
// jest 27 removes setImmediate from jsdom
|
||||
@@ -54,3 +55,9 @@ global.TextDecoder = TextDecoder;
|
||||
|
||||
// prevent errors whenever a component tries to manually scroll.
|
||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||
|
||||
// set up fetch API mock
|
||||
fetchMock.config.overwriteRoutes = false;
|
||||
fetchMock.catch("");
|
||||
fetchMock.get("/image-file-stub", "image file stub");
|
||||
window.fetch = fetchMock.sandbox();
|
||||
|
319
test/stores/room-list/SlidingRoomListStore-test.ts
Normal file
319
test/stores/room-list/SlidingRoomListStore-test.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import { mocked } from 'jest-mock';
|
||||
import { SlidingSync, SlidingSyncEvent } from 'matrix-js-sdk/src/sliding-sync';
|
||||
import { Room } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import {
|
||||
LISTS_UPDATE_EVENT,
|
||||
SlidingRoomListStoreClass,
|
||||
SlidingSyncSortToFilter,
|
||||
} from "../../../src/stores/room-list/SlidingRoomListStore";
|
||||
import { SpaceStoreClass } from "../../../src/stores/spaces/SpaceStore";
|
||||
import { MockEventEmitter, stubClient, untilEmission } from "../../test-utils";
|
||||
import { TestSdkContext } from '../../TestSdkContext';
|
||||
import { SlidingSyncManager } from '../../../src/SlidingSyncManager';
|
||||
import { RoomViewStore } from '../../../src/stores/RoomViewStore';
|
||||
import { MatrixDispatcher } from '../../../src/dispatcher/dispatcher';
|
||||
import { SortAlgorithm } from '../../../src/stores/room-list/algorithms/models';
|
||||
import { DefaultTagID, TagID } from '../../../src/stores/room-list/models';
|
||||
import { UPDATE_SELECTED_SPACE } from '../../../src/stores/spaces';
|
||||
import { LISTS_LOADING_EVENT } from '../../../src/stores/room-list/RoomListStore';
|
||||
import { UPDATE_EVENT } from '../../../src/stores/AsyncStore';
|
||||
|
||||
jest.mock('../../../src/SlidingSyncManager');
|
||||
const MockSlidingSyncManager = <jest.Mock<SlidingSyncManager>><unknown>SlidingSyncManager;
|
||||
|
||||
describe("SlidingRoomListStore", () => {
|
||||
let store: SlidingRoomListStoreClass;
|
||||
let context: TestSdkContext;
|
||||
let dis: MatrixDispatcher;
|
||||
let activeSpace: string;
|
||||
let tagIdToIndex = {};
|
||||
|
||||
beforeEach(async () => {
|
||||
context = new TestSdkContext();
|
||||
context.client = stubClient();
|
||||
context._SpaceStore = new MockEventEmitter<SpaceStoreClass>({
|
||||
traverseSpace: jest.fn(),
|
||||
get activeSpace() {
|
||||
return activeSpace;
|
||||
},
|
||||
}) as SpaceStoreClass;
|
||||
context._SlidingSyncManager = new MockSlidingSyncManager();
|
||||
context._SlidingSyncManager.slidingSync = mocked(new MockEventEmitter({
|
||||
getListData: jest.fn(),
|
||||
}) as unknown as SlidingSync);
|
||||
context._RoomViewStore = mocked(new MockEventEmitter({
|
||||
getRoomId: jest.fn(),
|
||||
}) as unknown as RoomViewStore);
|
||||
|
||||
// mock implementations to allow the store to map tag IDs to sliding sync list indexes and vice versa
|
||||
let index = 0;
|
||||
tagIdToIndex = {};
|
||||
mocked(context._SlidingSyncManager.getOrAllocateListIndex).mockImplementation((listId: string): number => {
|
||||
if (tagIdToIndex[listId] != null) {
|
||||
return tagIdToIndex[listId];
|
||||
}
|
||||
tagIdToIndex[listId] = index;
|
||||
index++;
|
||||
return index;
|
||||
});
|
||||
mocked(context.slidingSyncManager.listIdForIndex).mockImplementation((i) => {
|
||||
for (const tagId in tagIdToIndex) {
|
||||
const j = tagIdToIndex[tagId];
|
||||
if (i === j) {
|
||||
return tagId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
mocked(context._SlidingSyncManager.ensureListRegistered).mockResolvedValue({
|
||||
ranges: [[0, 10]],
|
||||
});
|
||||
|
||||
dis = new MatrixDispatcher();
|
||||
store = new SlidingRoomListStoreClass(dis, context);
|
||||
});
|
||||
|
||||
describe("spaces", () => {
|
||||
it("alters 'filters.spaces' on the DefaultTagID.Untagged list when the selected space changes", async () => {
|
||||
await store.start(); // call onReady
|
||||
const spaceRoomId = "!foo:bar";
|
||||
|
||||
const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => {
|
||||
return listName === DefaultTagID.Untagged && !isLoading;
|
||||
});
|
||||
|
||||
// change the active space
|
||||
activeSpace = spaceRoomId;
|
||||
context._SpaceStore.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false);
|
||||
await p;
|
||||
|
||||
expect(context._SlidingSyncManager.ensureListRegistered).toHaveBeenCalledWith(
|
||||
tagIdToIndex[DefaultTagID.Untagged],
|
||||
{
|
||||
filters: expect.objectContaining({
|
||||
spaces: [spaceRoomId],
|
||||
}),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("alters 'filters.spaces' on the DefaultTagID.Untagged list if it loads with an active space", async () => {
|
||||
// change the active space before we are ready
|
||||
const spaceRoomId = "!foo2:bar";
|
||||
activeSpace = spaceRoomId;
|
||||
const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => {
|
||||
return listName === DefaultTagID.Untagged && !isLoading;
|
||||
});
|
||||
await store.start(); // call onReady
|
||||
await p;
|
||||
expect(context._SlidingSyncManager.ensureListRegistered).toHaveBeenCalledWith(
|
||||
tagIdToIndex[DefaultTagID.Untagged],
|
||||
expect.objectContaining({
|
||||
filters: expect.objectContaining({
|
||||
spaces: [spaceRoomId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("includes subspaces in 'filters.spaces' when the selected space has subspaces", async () => {
|
||||
await store.start(); // call onReady
|
||||
const spaceRoomId = "!foo:bar";
|
||||
const subSpace1 = "!ss1:bar";
|
||||
const subSpace2 = "!ss2:bar";
|
||||
|
||||
const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => {
|
||||
return listName === DefaultTagID.Untagged && !isLoading;
|
||||
});
|
||||
|
||||
mocked(context._SpaceStore.traverseSpace).mockImplementation(
|
||||
(spaceId: string, fn: (roomId: string) => void) => {
|
||||
if (spaceId === spaceRoomId) {
|
||||
fn(subSpace1);
|
||||
fn(subSpace2);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// change the active space
|
||||
activeSpace = spaceRoomId;
|
||||
context._SpaceStore.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false);
|
||||
await p;
|
||||
|
||||
expect(context._SlidingSyncManager.ensureListRegistered).toHaveBeenCalledWith(
|
||||
tagIdToIndex[DefaultTagID.Untagged],
|
||||
{
|
||||
filters: expect.objectContaining({
|
||||
spaces: [spaceRoomId, subSpace1, subSpace2],
|
||||
}),
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("setTagSorting alters the 'sort' option in the list", async () => {
|
||||
mocked(context._SlidingSyncManager.getOrAllocateListIndex).mockReturnValue(0);
|
||||
const tagId: TagID = "foo";
|
||||
await store.setTagSorting(tagId, SortAlgorithm.Alphabetic);
|
||||
expect(context._SlidingSyncManager.ensureListRegistered).toBeCalledWith(0, {
|
||||
sort: SlidingSyncSortToFilter[SortAlgorithm.Alphabetic],
|
||||
});
|
||||
expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Alphabetic);
|
||||
|
||||
await store.setTagSorting(tagId, SortAlgorithm.Recent);
|
||||
expect(context._SlidingSyncManager.ensureListRegistered).toBeCalledWith(0, {
|
||||
sort: SlidingSyncSortToFilter[SortAlgorithm.Recent],
|
||||
});
|
||||
expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Recent);
|
||||
});
|
||||
|
||||
it("getTagsForRoom gets the tags for the room", async () => {
|
||||
await store.start();
|
||||
const untaggedIndex = context._SlidingSyncManager.getOrAllocateListIndex(DefaultTagID.Untagged);
|
||||
const favIndex = context._SlidingSyncManager.getOrAllocateListIndex(DefaultTagID.Favourite);
|
||||
const roomA = "!a:localhost";
|
||||
const roomB = "!b:localhost";
|
||||
const indexToListData = {
|
||||
[untaggedIndex]: {
|
||||
joinedCount: 10,
|
||||
roomIndexToRoomId: {
|
||||
0: roomA,
|
||||
1: roomB,
|
||||
},
|
||||
},
|
||||
[favIndex]: {
|
||||
joinedCount: 2,
|
||||
roomIndexToRoomId: {
|
||||
0: roomB,
|
||||
},
|
||||
},
|
||||
};
|
||||
mocked(context._SlidingSyncManager.slidingSync.getListData).mockImplementation((i: number) => {
|
||||
return indexToListData[i] || null;
|
||||
});
|
||||
|
||||
expect(store.getTagsForRoom(new Room(roomA, context.client, context.client.getUserId()))).toEqual(
|
||||
[DefaultTagID.Untagged],
|
||||
);
|
||||
expect(store.getTagsForRoom(new Room(roomB, context.client, context.client.getUserId()))).toEqual(
|
||||
[DefaultTagID.Favourite, DefaultTagID.Untagged],
|
||||
);
|
||||
});
|
||||
|
||||
it("emits LISTS_UPDATE_EVENT when slidingSync lists update", async () => {
|
||||
await store.start();
|
||||
const roomA = "!a:localhost";
|
||||
const roomB = "!b:localhost";
|
||||
const roomC = "!c:localhost";
|
||||
const tagId = DefaultTagID.Favourite;
|
||||
const listIndex = context.slidingSyncManager.getOrAllocateListIndex(tagId);
|
||||
const joinCount = 10;
|
||||
const roomIndexToRoomId = { // mixed to ensure we sort
|
||||
1: roomB,
|
||||
2: roomC,
|
||||
0: roomA,
|
||||
};
|
||||
const rooms = [
|
||||
new Room(roomA, context.client, context.client.getUserId()),
|
||||
new Room(roomB, context.client, context.client.getUserId()),
|
||||
new Room(roomC, context.client, context.client.getUserId()),
|
||||
];
|
||||
mocked(context.client.getRoom).mockImplementation((roomId: string) => {
|
||||
switch (roomId) {
|
||||
case roomA:
|
||||
return rooms[0];
|
||||
case roomB:
|
||||
return rooms[1];
|
||||
case roomC:
|
||||
return rooms[2];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const p = untilEmission(store, LISTS_UPDATE_EVENT);
|
||||
context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, listIndex, joinCount, roomIndexToRoomId);
|
||||
await p;
|
||||
expect(store.getCount(tagId)).toEqual(joinCount);
|
||||
expect(store.orderedLists[tagId]).toEqual(rooms);
|
||||
});
|
||||
|
||||
it("sets the sticky room on the basis of the viewed room in RoomViewStore", async () => {
|
||||
await store.start();
|
||||
// seed the store with 3 rooms
|
||||
const roomIdA = "!a:localhost";
|
||||
const roomIdB = "!b:localhost";
|
||||
const roomIdC = "!c:localhost";
|
||||
const tagId = DefaultTagID.Favourite;
|
||||
const listIndex = context.slidingSyncManager.getOrAllocateListIndex(tagId);
|
||||
const joinCount = 10;
|
||||
const roomIndexToRoomId = { // mixed to ensure we sort
|
||||
1: roomIdB,
|
||||
2: roomIdC,
|
||||
0: roomIdA,
|
||||
};
|
||||
const roomA = new Room(roomIdA, context.client, context.client.getUserId());
|
||||
const roomB = new Room(roomIdB, context.client, context.client.getUserId());
|
||||
const roomC = new Room(roomIdC, context.client, context.client.getUserId());
|
||||
mocked(context.client.getRoom).mockImplementation((roomId: string) => {
|
||||
switch (roomId) {
|
||||
case roomIdA:
|
||||
return roomA;
|
||||
case roomIdB:
|
||||
return roomB;
|
||||
case roomIdC:
|
||||
return roomC;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
mocked(context._SlidingSyncManager.slidingSync.getListData).mockImplementation((i: number) => {
|
||||
if (i !== listIndex) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
roomIndexToRoomId: roomIndexToRoomId,
|
||||
joinedCount: joinCount,
|
||||
};
|
||||
});
|
||||
let p = untilEmission(store, LISTS_UPDATE_EVENT);
|
||||
context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, listIndex, joinCount, roomIndexToRoomId);
|
||||
await p;
|
||||
expect(store.orderedLists[tagId]).toEqual([roomA, roomB, roomC]);
|
||||
|
||||
// make roomB sticky and inform the store
|
||||
mocked(context.roomViewStore.getRoomId).mockReturnValue(roomIdB);
|
||||
context.roomViewStore.emit(UPDATE_EVENT);
|
||||
|
||||
// bump room C to the top, room B should not move from i=1 despite the list update saying to
|
||||
roomIndexToRoomId[0] = roomIdC;
|
||||
roomIndexToRoomId[1] = roomIdA;
|
||||
roomIndexToRoomId[2] = roomIdB;
|
||||
p = untilEmission(store, LISTS_UPDATE_EVENT);
|
||||
context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, listIndex, joinCount, roomIndexToRoomId);
|
||||
await p;
|
||||
|
||||
// check that B didn't move and that A was put below B
|
||||
expect(store.orderedLists[tagId]).toEqual([roomC, roomB, roomA]);
|
||||
|
||||
// make room C sticky: rooms should move as a result, without needing an additional list update
|
||||
mocked(context.roomViewStore.getRoomId).mockReturnValue(roomIdC);
|
||||
p = untilEmission(store, LISTS_UPDATE_EVENT);
|
||||
context.roomViewStore.emit(UPDATE_EVENT);
|
||||
await p;
|
||||
expect(store.orderedLists[tagId].map((r) => r.roomId)).toEqual([roomC, roomA, roomB].map((r) => r.roomId));
|
||||
});
|
||||
});
|
@@ -6,7 +6,7 @@ Array [
|
||||
Array [
|
||||
Object {
|
||||
"deviceInfo": DeviceInfo {
|
||||
"algorithms": undefined,
|
||||
"algorithms": Array [],
|
||||
"deviceId": "aliceWeb",
|
||||
"keys": Object {},
|
||||
"known": false,
|
||||
@@ -18,7 +18,7 @@ Array [
|
||||
},
|
||||
Object {
|
||||
"deviceInfo": DeviceInfo {
|
||||
"algorithms": undefined,
|
||||
"algorithms": Array [],
|
||||
"deviceId": "aliceMobile",
|
||||
"keys": Object {},
|
||||
"known": false,
|
||||
@@ -37,7 +37,7 @@ Array [
|
||||
Array [
|
||||
Object {
|
||||
"deviceInfo": DeviceInfo {
|
||||
"algorithms": undefined,
|
||||
"algorithms": Array [],
|
||||
"deviceId": "bobDesktop",
|
||||
"keys": Object {},
|
||||
"known": false,
|
||||
|
@@ -21,6 +21,26 @@ import { MatrixClient, User } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
||||
|
||||
/**
|
||||
* Mocked generic class with a real EventEmitter.
|
||||
* Useful for mocks which need event emitters.
|
||||
*/
|
||||
export class MockEventEmitter<T> extends EventEmitter {
|
||||
/**
|
||||
* Construct a new event emitter with additional properties/functions. The event emitter functions
|
||||
* like .emit and .on will be real.
|
||||
* @param mockProperties An object with the mock property or function implementations. 'getters'
|
||||
* are correctly cloned to this event emitter.
|
||||
*/
|
||||
constructor(mockProperties: Partial<Record<MethodKeysOf<T>|PropertyKeysOf<T>, unknown>> = {}) {
|
||||
super();
|
||||
// We must use defineProperties and not assign as the former clones getters correctly,
|
||||
// whereas the latter invokes the getter and sets the return value permanently on the
|
||||
// destination object.
|
||||
Object.defineProperties(this, Object.getOwnPropertyDescriptors(mockProperties));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock client with real event emitter
|
||||
* useful for testing code that listens
|
||||
|
@@ -28,7 +28,7 @@ import { isSelfLocation } from "../../../src/utils/location";
|
||||
|
||||
describe("isSelfLocation", () => {
|
||||
it("Returns true for a full m.asset event", () => {
|
||||
const content = makeLocationContent("", '0');
|
||||
const content = makeLocationContent("", '0', Date.now());
|
||||
expect(isSelfLocation(content)).toBe(true);
|
||||
});
|
||||
|
||||
|
@@ -50,7 +50,7 @@ describe("VoiceBroadcastRecordingBody", () => {
|
||||
room: roomId,
|
||||
user: userId,
|
||||
});
|
||||
recording = new VoiceBroadcastRecording(infoEvent, client, VoiceBroadcastInfoState.Running);
|
||||
recording = new VoiceBroadcastRecording(infoEvent, client, VoiceBroadcastInfoState.Resumed);
|
||||
});
|
||||
|
||||
describe("when rendering a live broadcast", () => {
|
||||
|
@@ -118,7 +118,7 @@ describe("VoiceBroadcastRecordingPip", () => {
|
||||
});
|
||||
|
||||
it("should resume the recording", () => {
|
||||
expect(recording.getState()).toBe(VoiceBroadcastInfoState.Running);
|
||||
expect(recording.getState()).toBe(VoiceBroadcastInfoState.Resumed);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -3,7 +3,7 @@
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a 0 broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastPlaybackBody"
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
@@ -51,7 +51,7 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 0 broadcast should render a
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastPlaybackBody_controls"
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
@@ -71,7 +71,7 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 0 broadcast should render a
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a 1 broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastPlaybackBody"
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
@@ -119,7 +119,7 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 1 broadcast should render a
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastPlaybackBody_controls"
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
@@ -139,7 +139,7 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 1 broadcast should render a
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastPlaybackBody"
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
@@ -187,7 +187,7 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastPlaybackBody_controls"
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
|
@@ -3,7 +3,7 @@
|
||||
exports[`VoiceBroadcastRecordingBody when rendering a live broadcast should render the expected HTML 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastRecordingBody"
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
|
@@ -3,7 +3,7 @@
|
||||
exports[`VoiceBroadcastRecordingPip when rendering a paused recording should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastRecordingPip"
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
@@ -43,10 +43,10 @@ exports[`VoiceBroadcastRecordingPip when rendering a paused recording should ren
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
class="mx_VoiceBroadcastRecordingPip_divider"
|
||||
class="mx_VoiceBroadcastBody_divider"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastRecordingPip_controls"
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
@@ -76,7 +76,7 @@ exports[`VoiceBroadcastRecordingPip when rendering a paused recording should ren
|
||||
exports[`VoiceBroadcastRecordingPip when rendering a started recording should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastRecordingPip"
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
@@ -116,10 +116,10 @@ exports[`VoiceBroadcastRecordingPip when rendering a started recording should re
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
class="mx_VoiceBroadcastRecordingPip_divider"
|
||||
class="mx_VoiceBroadcastBody_divider"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastRecordingPip_controls"
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
|
@@ -190,9 +190,9 @@ describe("VoiceBroadcastPlayback", () => {
|
||||
onStateChanged = jest.fn();
|
||||
});
|
||||
|
||||
describe("when there is a running broadcast without chunks yet", () => {
|
||||
describe(`when there is a ${VoiceBroadcastInfoState.Resumed} broadcast without chunks yet`, () => {
|
||||
beforeEach(() => {
|
||||
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Running);
|
||||
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed);
|
||||
playback = mkPlayback();
|
||||
setUpChunkEvents([]);
|
||||
});
|
||||
@@ -236,9 +236,9 @@ describe("VoiceBroadcastPlayback", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a running voice broadcast with some chunks", () => {
|
||||
describe(`when there is a ${VoiceBroadcastInfoState.Resumed} voice broadcast with some chunks`, () => {
|
||||
beforeEach(() => {
|
||||
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Running);
|
||||
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed);
|
||||
playback = mkPlayback();
|
||||
setUpChunkEvents([chunk2Event, chunk0Event, chunk1Event]);
|
||||
});
|
||||
|
@@ -423,15 +423,15 @@ describe("VoiceBroadcastRecording", () => {
|
||||
await action();
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Running);
|
||||
itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Running);
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Resumed);
|
||||
itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Resumed);
|
||||
|
||||
it("should start the recorder", () => {
|
||||
expect(mocked(voiceBroadcastRecorder.start)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should emit a running state changed event", () => {
|
||||
expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Running);
|
||||
it(`should emit a ${VoiceBroadcastInfoState.Resumed} state changed event`, () => {
|
||||
expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Resumed);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -121,7 +121,7 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
||||
// all there are kind of live states
|
||||
VoiceBroadcastInfoState.Started,
|
||||
VoiceBroadcastInfoState.Paused,
|
||||
VoiceBroadcastInfoState.Running,
|
||||
VoiceBroadcastInfoState.Resumed,
|
||||
])("when there is a live broadcast (%s) from the current user", (state: VoiceBroadcastInfoState) => {
|
||||
beforeEach(() => {
|
||||
addVoiceBroadcastInfoEvent(state, client.getUserId());
|
||||
@@ -132,7 +132,7 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
||||
|
||||
describe("when there was a live broadcast, that has been stopped", () => {
|
||||
beforeEach(() => {
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Running, client.getUserId());
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Resumed, client.getUserId());
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, client.getUserId());
|
||||
});
|
||||
|
||||
@@ -141,7 +141,7 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
||||
|
||||
describe("when there is a live broadcast from another user", () => {
|
||||
beforeEach(() => {
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Running, otherUserId);
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Resumed, otherUserId);
|
||||
});
|
||||
|
||||
itShouldReturnTrueFalse();
|
||||
|
@@ -40,7 +40,7 @@ const testCases = [
|
||||
[
|
||||
"@user1:example.com",
|
||||
"@user1:example.com",
|
||||
VoiceBroadcastInfoState.Running,
|
||||
VoiceBroadcastInfoState.Resumed,
|
||||
true,
|
||||
],
|
||||
[
|
||||
|
@@ -128,7 +128,7 @@ describe("shouldDisplayAsVoiceBroadcastTile", () => {
|
||||
describe.each(
|
||||
[
|
||||
VoiceBroadcastInfoState.Paused,
|
||||
VoiceBroadcastInfoState.Running,
|
||||
VoiceBroadcastInfoState.Resumed,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
],
|
||||
)("when a voice broadcast info event in state %s occurs", (state: VoiceBroadcastInfoState) => {
|
||||
|
@@ -161,7 +161,7 @@ describe("startNewVoiceBroadcastRecording", () => {
|
||||
room.currentState.setStateEvents([
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Running,
|
||||
VoiceBroadcastInfoState.Resumed,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
),
|
||||
@@ -184,7 +184,7 @@ describe("startNewVoiceBroadcastRecording", () => {
|
||||
room.currentState.setStateEvents([
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Running,
|
||||
VoiceBroadcastInfoState.Resumed,
|
||||
otherUserId,
|
||||
"ASD123",
|
||||
),
|
||||
|
Reference in New Issue
Block a user