mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-11-05 22:25:36 +00:00
Merge remote-tracking branch 'origin/develop' into feat/matrix-wysisyg-integration
This commit is contained in:
+156
-17
@@ -14,28 +14,31 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { mocked, MockedObject } from "jest-mock";
|
||||
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 BasePlatform from "../src/BasePlatform";
|
||||
import { ElementCall } from "../src/models/Call";
|
||||
import Notifier from "../src/Notifier";
|
||||
import { getLocalNotificationAccountDataEventType } from "../src/utils/notifications";
|
||||
import SettingsStore from "../src/settings/SettingsStore";
|
||||
import ToastStore from "../src/stores/ToastStore";
|
||||
import {
|
||||
createLocalNotificationSettingsIfNeeded,
|
||||
getLocalNotificationAccountDataEventType,
|
||||
} from "../src/utils/notifications";
|
||||
import { getMockClientWithEventEmitter, mkEvent, mkRoom, mockPlatformPeg } from "./test-utils";
|
||||
import { IncomingCallToast } from "../src/toasts/IncomingCallToast";
|
||||
|
||||
jest.mock("../src/utils/notifications", () => ({
|
||||
// @ts-ignore
|
||||
...jest.requireActual("../src/utils/notifications"),
|
||||
createLocalNotificationSettingsIfNeeded: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("Notifier", () => {
|
||||
let MockPlatform;
|
||||
let accountDataStore = {};
|
||||
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
getUserId: jest.fn().mockReturnValue("@bob:example.org"),
|
||||
isGuest: jest.fn().mockReturnValue(false),
|
||||
getAccountData: jest.fn().mockImplementation(eventType => accountDataStore[eventType]),
|
||||
setAccountData: jest.fn().mockImplementation((eventType, content) => {
|
||||
accountDataStore[eventType] = new MatrixEvent({
|
||||
type: eventType,
|
||||
content,
|
||||
});
|
||||
}),
|
||||
});
|
||||
const accountDataEventKey = getLocalNotificationAccountDataEventType(mockClient.deviceId);
|
||||
const roomId = "!room1:server";
|
||||
const testEvent = mkEvent({
|
||||
event: true,
|
||||
@@ -44,10 +47,33 @@ describe("Notifier", () => {
|
||||
room: roomId,
|
||||
content: {},
|
||||
});
|
||||
const testRoom = mkRoom(mockClient, roomId);
|
||||
|
||||
let MockPlatform: MockedObject<BasePlatform>;
|
||||
let mockClient: MockedObject<MatrixClient>;
|
||||
let testRoom: MockedObject<Room>;
|
||||
let accountDataEventKey: string;
|
||||
let accountDataStore = {};
|
||||
|
||||
beforeEach(() => {
|
||||
accountDataStore = {};
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
getUserId: jest.fn().mockReturnValue("@bob:example.org"),
|
||||
isGuest: jest.fn().mockReturnValue(false),
|
||||
getAccountData: jest.fn().mockImplementation(eventType => accountDataStore[eventType]),
|
||||
setAccountData: jest.fn().mockImplementation((eventType, content) => {
|
||||
accountDataStore[eventType] = new MatrixEvent({
|
||||
type: eventType,
|
||||
content,
|
||||
});
|
||||
}),
|
||||
decryptEventIfNeeded: jest.fn(),
|
||||
getRoom: jest.fn(),
|
||||
getPushActionsForEvent: jest.fn(),
|
||||
});
|
||||
accountDataEventKey = getLocalNotificationAccountDataEventType(mockClient.deviceId);
|
||||
|
||||
testRoom = mkRoom(mockClient, roomId);
|
||||
|
||||
MockPlatform = mockPlatformPeg({
|
||||
supportsNotifications: jest.fn().mockReturnValue(true),
|
||||
maySendNotifications: jest.fn().mockReturnValue(true),
|
||||
@@ -55,6 +81,8 @@ describe("Notifier", () => {
|
||||
});
|
||||
|
||||
Notifier.isBodyEnabled = jest.fn().mockReturnValue(true);
|
||||
|
||||
mockClient.getRoom.mockReturnValue(testRoom);
|
||||
});
|
||||
|
||||
describe("_displayPopupNotification", () => {
|
||||
@@ -82,4 +110,115 @@ describe("Notifier", () => {
|
||||
expect(Notifier.getSoundForRoom).toHaveBeenCalledTimes(count);
|
||||
});
|
||||
});
|
||||
|
||||
describe("group call notifications", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
|
||||
jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast");
|
||||
|
||||
mockClient.getPushActionsForEvent.mockReturnValue({
|
||||
notify: true,
|
||||
tweaks: {},
|
||||
});
|
||||
|
||||
Notifier.onSyncStateChange(SyncState.Syncing);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
Notifier.onEvent(callEvent);
|
||||
return callEvent;
|
||||
};
|
||||
|
||||
const setGroupCallsEnabled = (val: boolean) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
if (name === "feature_group_calls") return val;
|
||||
});
|
||||
};
|
||||
|
||||
it("should show toast when group calls are supported", () => {
|
||||
setGroupCallsEnabled(true);
|
||||
|
||||
const callEvent = callOnEvent();
|
||||
|
||||
expect(ToastStore.sharedInstance().addOrReplaceToast).toHaveBeenCalledWith(expect.objectContaining({
|
||||
key: `call_${callEvent.getStateKey()}`,
|
||||
priority: 100,
|
||||
component: IncomingCallToast,
|
||||
bodyClassName: "mx_IncomingCallToast",
|
||||
props: { callEvent },
|
||||
}));
|
||||
});
|
||||
|
||||
it("should not show toast when group calls are not supported", () => {
|
||||
setGroupCallsEnabled(false);
|
||||
|
||||
callOnEvent();
|
||||
|
||||
expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not show toast when calling with non-group call event", () => {
|
||||
setGroupCallsEnabled(true);
|
||||
|
||||
callOnEvent("event_type");
|
||||
|
||||
expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('local notification settings', () => {
|
||||
const createLocalNotificationSettingsIfNeededMock = mocked(createLocalNotificationSettingsIfNeeded);
|
||||
let hasStartedNotiferBefore = false;
|
||||
beforeEach(() => {
|
||||
// notifier defines some listener functions in start
|
||||
// and references them in stop
|
||||
// so blows up if stopped before it was started
|
||||
if (hasStartedNotiferBefore) {
|
||||
Notifier.stop();
|
||||
}
|
||||
Notifier.start();
|
||||
hasStartedNotiferBefore = true;
|
||||
createLocalNotificationSettingsIfNeededMock.mockClear();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
Notifier.stop();
|
||||
});
|
||||
|
||||
it('does not create local notifications event after a sync error', () => {
|
||||
mockClient.emit(ClientEvent.Sync, SyncState.Error, SyncState.Syncing);
|
||||
expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not create local notifications event after sync stops', () => {
|
||||
mockClient.emit(ClientEvent.Sync, SyncState.Stopped, SyncState.Syncing);
|
||||
expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not create local notifications event after a cached sync', () => {
|
||||
mockClient.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Syncing, {
|
||||
fromCache: true,
|
||||
});
|
||||
expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates local notifications event after a non-cached sync', () => {
|
||||
mockClient.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Syncing, {});
|
||||
expect(createLocalNotificationSettingsIfNeededMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,15 +14,17 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventType, MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import TestRenderer from 'react-test-renderer';
|
||||
import { ReactElement } from "react";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import { getSenderName, textForEvent } from "../src/TextForEvent";
|
||||
import SettingsStore from "../src/settings/SettingsStore";
|
||||
import { createTestClient } from './test-utils';
|
||||
import { createTestClient, stubClient } from './test-utils';
|
||||
import { MatrixClientPeg } from '../src/MatrixClientPeg';
|
||||
import UserIdentifierCustomisations from '../src/customisations/UserIdentifier';
|
||||
import { ElementCall } from "../src/models/Call";
|
||||
|
||||
jest.mock("../src/settings/SettingsStore");
|
||||
jest.mock('../src/customisations/UserIdentifier', () => ({
|
||||
@@ -444,4 +446,42 @@ describe('TextForEvent', () => {
|
||||
expect(textForEvent(messageEvent)).toEqual('@a: test message');
|
||||
});
|
||||
});
|
||||
|
||||
describe("textForCallEvent()", () => {
|
||||
let mockClient: MatrixClient;
|
||||
let callEvent: MatrixEvent;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
mockClient = MatrixClientPeg.get();
|
||||
|
||||
mocked(mockClient.getRoom).mockReturnValue({
|
||||
name: "Test room",
|
||||
} as unknown as Room);
|
||||
|
||||
callEvent = {
|
||||
getRoomId: jest.fn(),
|
||||
getType: jest.fn(),
|
||||
isState: jest.fn().mockReturnValue(true),
|
||||
} as unknown as MatrixEvent;
|
||||
});
|
||||
|
||||
describe.each(ElementCall.CALL_EVENT_TYPE.names)("eventType=%s", (eventType: string) => {
|
||||
beforeEach(() => {
|
||||
mocked(callEvent).getType.mockReturnValue(eventType);
|
||||
});
|
||||
|
||||
it("returns correct message for call event when supported", () => {
|
||||
expect(textForEvent(callEvent)).toEqual('Video call started in Test room.');
|
||||
});
|
||||
|
||||
it("returns correct message for call event when supported", () => {
|
||||
mocked(mockClient).supportsVoip.mockReturnValue(false);
|
||||
|
||||
expect(textForEvent(callEvent)).toEqual(
|
||||
'Video call started in Test room. (not supported by this browser)',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`RoomView for a local room in state CREATING should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_RoomHeader_e2eIcon\\"><div class=\\"mx_E2EIcon mx_E2EIcon_normal\\"></div></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><div class=\\"mx_RoomView_body\\"><div class=\\"mx_LargeLoader\\"><div class=\\"mx_Spinner\\"><div class=\\"mx_Spinner_icon\\" style=\\"width: 45px; height: 45px;\\" aria-label=\\"Loading...\\" role=\\"progressbar\\"></div></div><div class=\\"mx_LargeLoader_text\\">We're creating a room with @user:example.com</div></div></div></div>"`;
|
||||
exports[`RoomView for a local room in state CREATING should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><div class=\\"mx_RoomView_body\\"><div class=\\"mx_LargeLoader\\"><div class=\\"mx_Spinner\\"><div class=\\"mx_Spinner_icon\\" style=\\"width: 45px; height: 45px;\\" aria-label=\\"Loading...\\" role=\\"progressbar\\"></div></div><div class=\\"mx_LargeLoader_text\\">We're creating a room with @user:example.com</div></div></div></div>"`;
|
||||
|
||||
exports[`RoomView for a local room in state ERROR should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_RoomHeader_e2eIcon\\"><div class=\\"mx_E2EIcon mx_E2EIcon_normal\\"></div></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><li class=\\"mx_NewRoomIntro\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning\\"><div class=\\"mx_EventTileBubble_title\\">End-to-end encryption isn't enabled</div><div class=\\"mx_EventTileBubble_subtitle\\"><span> Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. </span></div></div><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_RoomStatusBar mx_RoomStatusBar_unsentMessages\\"><div role=\\"alert\\"><div class=\\"mx_RoomStatusBar_unsentBadge\\"><div class=\\"mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_highlighted mx_NotificationBadge_2char\\"><span class=\\"mx_NotificationBadge_count\\">!</span></div></div><div><div class=\\"mx_RoomStatusBar_unsentTitle\\">Some of your messages have not been sent</div></div><div class=\\"mx_RoomStatusBar_unsentButtonBar\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_RoomStatusBar_unsentRetry\\">Retry</div></div></div></div></main></div>"`;
|
||||
exports[`RoomView for a local room in state ERROR should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><li class=\\"mx_NewRoomIntro\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning\\"><div class=\\"mx_EventTileBubble_title\\">End-to-end encryption isn't enabled</div><div class=\\"mx_EventTileBubble_subtitle\\"><span> Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. </span></div></div><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_RoomStatusBar mx_RoomStatusBar_unsentMessages\\"><div role=\\"alert\\"><div class=\\"mx_RoomStatusBar_unsentBadge\\"><div class=\\"mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_highlighted mx_NotificationBadge_2char\\"><span class=\\"mx_NotificationBadge_count\\">!</span></div></div><div><div class=\\"mx_RoomStatusBar_unsentTitle\\">Some of your messages have not been sent</div></div><div class=\\"mx_RoomStatusBar_unsentButtonBar\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_RoomStatusBar_unsentRetry\\">Retry</div></div></div></div></main></div>"`;
|
||||
|
||||
exports[`RoomView for a local room in state NEW should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_RoomHeader_e2eIcon\\"><div class=\\"mx_E2EIcon mx_E2EIcon_normal\\"></div></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><li class=\\"mx_NewRoomIntro\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning\\"><div class=\\"mx_EventTileBubble_title\\">End-to-end encryption isn't enabled</div><div class=\\"mx_EventTileBubble_subtitle\\"><span> Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. </span></div></div><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;
|
||||
exports[`RoomView for a local room in state NEW should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><li class=\\"mx_NewRoomIntro\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning\\"><div class=\\"mx_EventTileBubble_title\\">End-to-end encryption isn't enabled</div><div class=\\"mx_EventTileBubble_subtitle\\"><span> Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. </span></div></div><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;
|
||||
|
||||
exports[`RoomView for a local room in state NEW that is encrypted should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_RoomHeader_e2eIcon\\"><div class=\\"mx_E2EIcon mx_E2EIcon_normal\\"></div></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon\\"><div class=\\"mx_EventTileBubble_title\\">Encryption enabled</div><div class=\\"mx_EventTileBubble_subtitle\\">Messages in this chat will be end-to-end encrypted.</div></div><li class=\\"mx_NewRoomIntro\\"><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;
|
||||
exports[`RoomView for a local room in state NEW that is encrypted should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon\\"><div class=\\"mx_EventTileBubble_title\\">Encryption enabled</div><div class=\\"mx_EventTileBubble_subtitle\\">Messages in this chat will be end-to-end encrypted.</div></div><li class=\\"mx_NewRoomIntro\\"><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
EventType,
|
||||
} from 'matrix-js-sdk/src/matrix';
|
||||
import { ExtensibleEvent, MessageEvent, M_POLL_KIND_DISCLOSED, PollStartEvent } from 'matrix-events-sdk';
|
||||
import { Thread } from "matrix-js-sdk/src/models/thread";
|
||||
import { FeatureSupport, Thread } from "matrix-js-sdk/src/models/thread";
|
||||
import { mocked } from "jest-mock";
|
||||
import { act } from '@testing-library/react';
|
||||
|
||||
@@ -469,7 +469,7 @@ describe('MessageContextMenu', () => {
|
||||
const eventContent = MessageEvent.from("hello");
|
||||
const mxEvent = new MatrixEvent(eventContent.serialize());
|
||||
|
||||
Thread.hasServerSideSupport = true;
|
||||
Thread.hasServerSideSupport = FeatureSupport.Stable;
|
||||
const context = {
|
||||
canSendMessages: true,
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`<TooltipTarget /> displays Bottom aligned tooltip on mouseover 1`] = `
|
||||
<div
|
||||
class="mx_Tooltip test tooltipClassName mx_Tooltip_visible"
|
||||
role="tooltip"
|
||||
style="display: block; top: 6px; left: 0px; transform: translate(-50%);"
|
||||
style="display: block; top: 6px; transform: translate(max(10px, min(calc(0px - 50%), calc(100vw - 100% - 10px))));"
|
||||
>
|
||||
<div
|
||||
class="mx_Tooltip_chevron"
|
||||
@@ -17,7 +17,7 @@ exports[`<TooltipTarget /> displays InnerBottom aligned tooltip on mouseover 1`]
|
||||
<div
|
||||
class="mx_Tooltip test tooltipClassName mx_Tooltip_visible"
|
||||
role="tooltip"
|
||||
style="display: block; top: -50px; left: 0px; transform: translate(-50%);"
|
||||
style="display: block; top: -50px; transform: translate(max(10px, min(calc(0px - 50%), calc(100vw - 100% - 10px))));"
|
||||
>
|
||||
<div
|
||||
class="mx_Tooltip_chevron"
|
||||
@@ -69,7 +69,7 @@ exports[`<TooltipTarget /> displays Top aligned tooltip on mouseover 1`] = `
|
||||
<div
|
||||
class="mx_Tooltip test tooltipClassName mx_Tooltip_visible"
|
||||
role="tooltip"
|
||||
style="display: block; top: -6px; left: 0px; transform: translate(-50%, -100%);"
|
||||
style="display: block; top: -6px; transform: translate(max(10px, min(calc(0px - 50%), calc(100vw - 100% - 10px))), -100%);"
|
||||
>
|
||||
<div
|
||||
class="mx_Tooltip_chevron"
|
||||
|
||||
@@ -42,36 +42,53 @@ exports[`<LocationShareMenu /> with live location disabled goes to labs flag scr
|
||||
Enable live location sharing
|
||||
</span>
|
||||
<_default
|
||||
aria-label="Enable live location sharing"
|
||||
checked={false}
|
||||
onChange={[Function]}
|
||||
title="Enable live location sharing"
|
||||
>
|
||||
<AccessibleButton
|
||||
<AccessibleTooltipButton
|
||||
aria-checked={false}
|
||||
aria-disabled={false}
|
||||
aria-label="Enable live location sharing"
|
||||
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
role="switch"
|
||||
tabIndex={0}
|
||||
title="Enable live location sharing"
|
||||
>
|
||||
<div
|
||||
<AccessibleButton
|
||||
aria-checked={false}
|
||||
aria-disabled={false}
|
||||
aria-label="Enable live location sharing"
|
||||
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
element="div"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
role="switch"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
aria-checked={false}
|
||||
aria-disabled={false}
|
||||
aria-label="Enable live location sharing"
|
||||
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
role="switch"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</AccessibleTooltipButton>
|
||||
</_default>
|
||||
</div>
|
||||
</LabelledToggleSwitch>
|
||||
|
||||
@@ -82,7 +82,7 @@ describe("CallEvent", () => {
|
||||
));
|
||||
|
||||
MockedCall.create(room, "1");
|
||||
const maybeCall = CallStore.instance.get(room.roomId);
|
||||
const maybeCall = CallStore.instance.getCall(room.roomId);
|
||||
if (!(maybeCall instanceof MockedCall)) throw new Error("Failed to create call");
|
||||
call = maybeCall;
|
||||
|
||||
@@ -113,7 +113,7 @@ describe("CallEvent", () => {
|
||||
});
|
||||
|
||||
it("shows placeholder info if the call isn't loaded yet", () => {
|
||||
jest.spyOn(CallStore.instance, "get").mockReturnValue(null);
|
||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(null);
|
||||
jest.advanceTimersByTime(90000);
|
||||
renderEvent();
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
MsgType,
|
||||
Room,
|
||||
} from 'matrix-js-sdk/src/matrix';
|
||||
import { Thread } from 'matrix-js-sdk/src/models/thread';
|
||||
import { FeatureSupport, Thread } from 'matrix-js-sdk/src/models/thread';
|
||||
|
||||
import MessageActionBar from '../../../../src/components/views/messages/MessageActionBar';
|
||||
import {
|
||||
@@ -388,13 +388,13 @@ describe('<MessageActionBar />', () => {
|
||||
|
||||
describe('thread button', () => {
|
||||
beforeEach(() => {
|
||||
Thread.setServerSideSupport(true, false);
|
||||
Thread.setServerSideSupport(FeatureSupport.Stable);
|
||||
});
|
||||
|
||||
describe('when threads feature is not enabled', () => {
|
||||
it('does not render thread button when threads does not have server support', () => {
|
||||
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
|
||||
Thread.setServerSideSupport(false, false);
|
||||
Thread.setServerSideSupport(FeatureSupport.None);
|
||||
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
|
||||
expect(queryByLabelText('Reply in thread')).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -268,6 +268,7 @@ function createRoomState(room: Room, narrow: boolean): IRoomState {
|
||||
liveTimeline: undefined,
|
||||
resizing: false,
|
||||
narrow,
|
||||
activeCall: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { PendingEventOrdering } from "matrix-js-sdk/src/client";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { ClientWidgetApi, Widget } from "matrix-widget-api";
|
||||
import EventEmitter from "events";
|
||||
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import type { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
@@ -53,6 +55,10 @@ import LegacyCallHandler from "../../../../src/LegacyCallHandler";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import WidgetStore from "../../../../src/stores/WidgetStore";
|
||||
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
|
||||
import WidgetUtils from "../../../../src/utils/WidgetUtils";
|
||||
import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidgetActions";
|
||||
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler";
|
||||
|
||||
describe('RoomHeader (Enzyme)', () => {
|
||||
it('shows the room avatar in a room with only ourselves', () => {
|
||||
@@ -173,13 +179,13 @@ describe('RoomHeader (Enzyme)', () => {
|
||||
it("should render buttons if not passing showButtons (default true)", () => {
|
||||
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
|
||||
const wrapper = mountHeader(room);
|
||||
expect(wrapper.find(".mx_RoomHeader_buttons")).toHaveLength(1);
|
||||
expect(wrapper.find(".mx_RoomHeader_button")).not.toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should not render buttons if passing showButtons = false", () => {
|
||||
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
|
||||
const wrapper = mountHeader(room, { showButtons: false });
|
||||
expect(wrapper.find(".mx_RoomHeader_buttons")).toHaveLength(0);
|
||||
expect(wrapper.find(".mx_RoomHeader_button")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should render the room options context menu if not passing enableRoomOptionsMenu (default true)", () => {
|
||||
@@ -252,6 +258,8 @@ function mountHeader(room: Room, propsOverride = {}, roomContext?: Partial<IRoom
|
||||
searchScope: SearchScope.Room,
|
||||
searchCount: 0,
|
||||
},
|
||||
viewingCall: false,
|
||||
activeCall: null,
|
||||
...propsOverride,
|
||||
};
|
||||
|
||||
@@ -381,6 +389,12 @@ describe("RoomHeader (React Testing Library)", () => {
|
||||
await Promise.all([CallStore.instance, WidgetStore.instance].map(
|
||||
store => setupAsyncStoreWithClient(store, client),
|
||||
));
|
||||
|
||||
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
|
||||
[MediaDeviceKindEnum.AudioInput]: [],
|
||||
[MediaDeviceKindEnum.VideoInput]: [],
|
||||
[MediaDeviceKindEnum.AudioOutput]: [],
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -419,6 +433,32 @@ describe("RoomHeader (React Testing Library)", () => {
|
||||
const mockLegacyCall = () => {
|
||||
jest.spyOn(LegacyCallHandler.instance, "getCallForRoom").mockReturnValue({} as unknown as MatrixCall);
|
||||
};
|
||||
const withCall = async (fn: (call: ElementCall) => (void | Promise<void>)): Promise<void> => {
|
||||
await ElementCall.create(room);
|
||||
const call = CallStore.instance.getCall(room.roomId);
|
||||
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
|
||||
|
||||
const widget = new Widget(call.widget);
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
const messaging = {
|
||||
on: eventEmitter.on.bind(eventEmitter),
|
||||
off: eventEmitter.off.bind(eventEmitter),
|
||||
once: eventEmitter.once.bind(eventEmitter),
|
||||
emit: eventEmitter.emit.bind(eventEmitter),
|
||||
stop: jest.fn(),
|
||||
transport: {
|
||||
send: jest.fn(),
|
||||
reply: jest.fn(),
|
||||
},
|
||||
} as unknown as Mocked<ClientWidgetApi>;
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, call.roomId, messaging);
|
||||
|
||||
await fn(call);
|
||||
|
||||
call.destroy();
|
||||
WidgetMessagingStore.instance.stopMessaging(widget, call.roomId);
|
||||
};
|
||||
|
||||
const renderHeader = (props: Partial<RoomHeaderProps> = {}, roomContext: Partial<IRoomState> = {}) => {
|
||||
render(
|
||||
@@ -437,6 +477,8 @@ describe("RoomHeader (React Testing Library)", () => {
|
||||
searchScope: SearchScope.Room,
|
||||
searchCount: 0,
|
||||
}}
|
||||
viewingCall={false}
|
||||
activeCall={null}
|
||||
{...props}
|
||||
/>
|
||||
</RoomContext.Provider>,
|
||||
@@ -463,7 +505,9 @@ describe("RoomHeader (React Testing Library)", () => {
|
||||
+ "and there's an ongoing call",
|
||||
async () => {
|
||||
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
|
||||
SdkConfig.put({ element_call: { url: "https://call.element.io", use_exclusively: true } });
|
||||
SdkConfig.put(
|
||||
{ element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" } },
|
||||
);
|
||||
await ElementCall.create(room);
|
||||
|
||||
renderHeader();
|
||||
@@ -477,7 +521,9 @@ describe("RoomHeader (React Testing Library)", () => {
|
||||
+ "use Element Call exclusively",
|
||||
async () => {
|
||||
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
|
||||
SdkConfig.put({ element_call: { url: "https://call.element.io", use_exclusively: true } });
|
||||
SdkConfig.put(
|
||||
{ element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" } },
|
||||
);
|
||||
|
||||
renderHeader();
|
||||
expect(screen.queryByRole("button", { name: "Voice call" })).toBeNull();
|
||||
@@ -499,7 +545,9 @@ describe("RoomHeader (React Testing Library)", () => {
|
||||
+ "and the user lacks permission",
|
||||
() => {
|
||||
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
|
||||
SdkConfig.put({ element_call: { url: "https://call.element.io", use_exclusively: true } });
|
||||
SdkConfig.put(
|
||||
{ element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" } },
|
||||
);
|
||||
mockEventPowerLevels({ [ElementCall.CALL_EVENT_TYPE.name]: 100 });
|
||||
|
||||
renderHeader();
|
||||
@@ -724,4 +772,99 @@ describe("RoomHeader (React Testing Library)", () => {
|
||||
expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
|
||||
expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
|
||||
});
|
||||
|
||||
it("shows a close button when viewing a call lobby that returns to the timeline when pressed", async () => {
|
||||
mockEnabledSettings(["feature_group_calls"]);
|
||||
|
||||
renderHeader({ viewingCall: true });
|
||||
|
||||
const dispatcherSpy = jest.fn();
|
||||
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
|
||||
fireEvent.click(screen.getByRole("button", { name: /close/i }));
|
||||
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
view_call: false,
|
||||
}));
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
});
|
||||
|
||||
it("shows a reduce button when viewing a call that returns to the timeline when pressed", async () => {
|
||||
mockEnabledSettings(["feature_group_calls"]);
|
||||
|
||||
await withCall(async call => {
|
||||
renderHeader({ viewingCall: true, activeCall: call });
|
||||
|
||||
const dispatcherSpy = jest.fn();
|
||||
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
|
||||
fireEvent.click(screen.getByRole("button", { name: /timeline/i }));
|
||||
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
view_call: false,
|
||||
}));
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a layout button when viewing a call that shows a menu when pressed", async () => {
|
||||
mockEnabledSettings(["feature_group_calls"]);
|
||||
|
||||
await withCall(async call => {
|
||||
await call.connect();
|
||||
const messaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(call.widget));
|
||||
renderHeader({ viewingCall: true, activeCall: call });
|
||||
|
||||
// Should start with Freedom selected
|
||||
fireEvent.click(screen.getByRole("button", { name: /layout/i }));
|
||||
screen.getByRole("menuitemradio", { name: "Freedom", checked: true });
|
||||
|
||||
// Clicking Spotlight should tell the widget to switch and close the menu
|
||||
fireEvent.click(screen.getByRole("menuitemradio", { name: "Spotlight" }));
|
||||
expect(mocked(messaging.transport).send).toHaveBeenCalledWith(ElementWidgetActions.SpotlightLayout, {});
|
||||
expect(screen.queryByRole("menu")).toBeNull();
|
||||
|
||||
// When the widget responds and the user reopens the menu, they should see Spotlight selected
|
||||
act(() => {
|
||||
messaging.emit(
|
||||
`action:${ElementWidgetActions.SpotlightLayout}`,
|
||||
new CustomEvent("widgetapirequest", { detail: { data: {} } }),
|
||||
);
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: /layout/i }));
|
||||
screen.getByRole("menuitemradio", { name: "Spotlight", checked: true });
|
||||
|
||||
// Now try switching back to Freedom
|
||||
fireEvent.click(screen.getByRole("menuitemradio", { name: "Freedom" }));
|
||||
expect(mocked(messaging.transport).send).toHaveBeenCalledWith(ElementWidgetActions.TileLayout, {});
|
||||
expect(screen.queryByRole("menu")).toBeNull();
|
||||
|
||||
// When the widget responds and the user reopens the menu, they should see Freedom selected
|
||||
act(() => {
|
||||
messaging.emit(
|
||||
`action:${ElementWidgetActions.TileLayout}`,
|
||||
new CustomEvent("widgetapirequest", { detail: { data: {} } }),
|
||||
);
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: /layout/i }));
|
||||
screen.getByRole("menuitemradio", { name: "Freedom", checked: true });
|
||||
});
|
||||
});
|
||||
|
||||
it("shows an invite button in video rooms", () => {
|
||||
mockEnabledSettings(["feature_video_rooms", "feature_element_call_video_rooms"]);
|
||||
mockRoomType(RoomType.UnstableCall);
|
||||
|
||||
const onInviteClick = jest.fn();
|
||||
renderHeader({ onInviteClick, viewingCall: true });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /invite/i }));
|
||||
expect(onInviteClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("hides the invite button in non-video rooms when viewing a call", () => {
|
||||
renderHeader({ onInviteClick: () => {}, viewingCall: true });
|
||||
|
||||
expect(screen.queryByRole("button", { name: /invite/i })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,7 +77,7 @@ describe("RoomTile", () => {
|
||||
setupAsyncStoreWithClient(WidgetMessagingStore.instance, client);
|
||||
|
||||
MockedCall.create(room, "1");
|
||||
call = CallStore.instance.get(room.roomId) as MockedCall;
|
||||
call = CallStore.instance.getCall(room.roomId) as MockedCall;
|
||||
|
||||
widget = new Widget(call.widget);
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
|
||||
|
||||
@@ -91,6 +91,7 @@ describe('<SendMessageComposer/>', () => {
|
||||
canSelfRedact: false,
|
||||
resizing: false,
|
||||
narrow: false,
|
||||
activeCall: null,
|
||||
};
|
||||
describe("createMessageContent", () => {
|
||||
const permalinkCreator = jest.fn() as any;
|
||||
|
||||
@@ -112,16 +112,20 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
||||
data-testid="device-tile-device_1"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
@@ -235,16 +239,20 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
||||
data-testid="device-tile-device_2"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
@@ -317,16 +325,20 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
||||
data-testid="device-tile-device_3"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -18,37 +18,54 @@ exports[`<Notifications /> main notification switches email switches renders ema
|
||||
Enable email notifications for tester@test.com
|
||||
</span>
|
||||
<_default
|
||||
aria-label="Enable email notifications for tester@test.com"
|
||||
checked={false}
|
||||
disabled={false}
|
||||
onChange={[Function]}
|
||||
title="Enable email notifications for tester@test.com"
|
||||
>
|
||||
<AccessibleButton
|
||||
<AccessibleTooltipButton
|
||||
aria-checked={false}
|
||||
aria-disabled={false}
|
||||
aria-label="Enable email notifications for tester@test.com"
|
||||
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
role="switch"
|
||||
tabIndex={0}
|
||||
title="Enable email notifications for tester@test.com"
|
||||
>
|
||||
<div
|
||||
<AccessibleButton
|
||||
aria-checked={false}
|
||||
aria-disabled={false}
|
||||
aria-label="Enable email notifications for tester@test.com"
|
||||
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
element="div"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
role="switch"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
aria-checked={false}
|
||||
aria-disabled={false}
|
||||
aria-label="Enable email notifications for tester@test.com"
|
||||
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
role="switch"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</AccessibleTooltipButton>
|
||||
</_default>
|
||||
</div>
|
||||
</LabelledToggleSwitch>
|
||||
@@ -84,37 +101,54 @@ exports[`<Notifications /> main notification switches renders only enable notifi
|
||||
</Caption>
|
||||
</span>
|
||||
<_default
|
||||
aria-label="Enable notifications for this account"
|
||||
checked={false}
|
||||
disabled={false}
|
||||
onChange={[Function]}
|
||||
title="Enable notifications for this account"
|
||||
>
|
||||
<AccessibleButton
|
||||
<AccessibleTooltipButton
|
||||
aria-checked={false}
|
||||
aria-disabled={false}
|
||||
aria-label="Enable notifications for this account"
|
||||
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
role="switch"
|
||||
tabIndex={0}
|
||||
title="Enable notifications for this account"
|
||||
>
|
||||
<div
|
||||
<AccessibleButton
|
||||
aria-checked={false}
|
||||
aria-disabled={false}
|
||||
aria-label="Enable notifications for this account"
|
||||
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
element="div"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
role="switch"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
aria-checked={false}
|
||||
aria-disabled={false}
|
||||
aria-label="Enable notifications for this account"
|
||||
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
role="switch"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</AccessibleTooltipButton>
|
||||
</_default>
|
||||
</div>
|
||||
</LabelledToggleSwitch>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { fireEvent, render } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import CurrentDeviceSection from '../../../../../src/components/views/settings/devices/CurrentDeviceSection';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
describe('<CurrentDeviceSection />', () => {
|
||||
const deviceId = 'alices_device';
|
||||
@@ -26,10 +27,12 @@ describe('<CurrentDeviceSection />', () => {
|
||||
const alicesVerifiedDevice = {
|
||||
device_id: deviceId,
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const alicesUnverifiedDevice = {
|
||||
device_id: deviceId,
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
|
||||
@@ -19,6 +19,7 @@ import { fireEvent, render, RenderResult } from '@testing-library/react';
|
||||
|
||||
import { DeviceDetailHeading } from '../../../../../src/components/views/settings/devices/DeviceDetailHeading';
|
||||
import { flushPromisesWithFakeTimers } from '../../../../test-utils';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
@@ -27,6 +28,7 @@ describe('<DeviceDetailHeading />', () => {
|
||||
device_id: '123',
|
||||
display_name: 'My device',
|
||||
isVerified: true,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const defaultProps = {
|
||||
device,
|
||||
|
||||
@@ -20,11 +20,13 @@ import { PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event';
|
||||
|
||||
import DeviceDetails from '../../../../../src/components/views/settings/devices/DeviceDetails';
|
||||
import { mkPusher } from '../../../../test-utils/test-utils';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
describe('<DeviceDetails />', () => {
|
||||
const baseDevice = {
|
||||
device_id: 'my-device',
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const defaultProps = {
|
||||
device: baseDevice,
|
||||
@@ -58,7 +60,10 @@ describe('<DeviceDetails />', () => {
|
||||
display_name: 'My Device',
|
||||
last_seen_ip: '123.456.789',
|
||||
last_seen_ts: now - 60000000,
|
||||
clientName: 'Element Web',
|
||||
appName: 'Element Web',
|
||||
client: 'Firefox 100',
|
||||
deviceModel: 'Iphone X',
|
||||
deviceOperatingSystem: 'Windows 95',
|
||||
};
|
||||
const { container } = render(getComponent({ device }));
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
@@ -19,12 +19,14 @@ import { render } from '@testing-library/react';
|
||||
import { IMyDevice } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import DeviceTile from '../../../../../src/components/views/settings/devices/DeviceTile';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
describe('<DeviceTile />', () => {
|
||||
const defaultProps = {
|
||||
device: {
|
||||
device_id: '123',
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
},
|
||||
};
|
||||
const getComponent = (props = {}) => (
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
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 { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { DeviceType } from '../../../../../src/components/views/settings/devices/DeviceType';
|
||||
|
||||
describe('<DeviceType />', () => {
|
||||
const defaultProps = {
|
||||
isVerified: false,
|
||||
isSelected: false,
|
||||
};
|
||||
const getComponent = (props = {}) =>
|
||||
<DeviceType {...defaultProps} {...props} />;
|
||||
|
||||
it('renders an unverified device', () => {
|
||||
const { container } = render(getComponent());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a verified device', () => {
|
||||
const { container } = render(getComponent({ isVerified: true }));
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when selected', () => {
|
||||
const { container } = render(getComponent({ isSelected: true }));
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
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 { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { DeviceTypeIcon } from '../../../../../src/components/views/settings/devices/DeviceTypeIcon';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
describe('<DeviceTypeIcon />', () => {
|
||||
const defaultProps = {
|
||||
isVerified: false,
|
||||
isSelected: false,
|
||||
};
|
||||
const getComponent = (props = {}) =>
|
||||
<DeviceTypeIcon {...defaultProps} {...props} />;
|
||||
|
||||
it('renders an unverified device', () => {
|
||||
const { container } = render(getComponent());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a verified device', () => {
|
||||
const { container } = render(getComponent({ isVerified: true }));
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when selected', () => {
|
||||
const { container } = render(getComponent({ isSelected: true }));
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders an unknown device icon when no device type given', () => {
|
||||
const { getByLabelText } = render(getComponent());
|
||||
expect(getByLabelText('Unknown session type')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders a desktop device type', () => {
|
||||
const deviceType = DeviceType.Desktop;
|
||||
const { getByLabelText } = render(getComponent({ deviceType }));
|
||||
expect(getByLabelText('Desktop session')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders a web device type', () => {
|
||||
const deviceType = DeviceType.Web;
|
||||
const { getByLabelText } = render(getComponent({ deviceType }));
|
||||
expect(getByLabelText('Web session')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders a mobile device type', () => {
|
||||
const deviceType = DeviceType.Mobile;
|
||||
const { getByLabelText } = render(getComponent({ deviceType }));
|
||||
expect(getByLabelText('Mobile session')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders an unknown device type', () => {
|
||||
const deviceType = DeviceType.Unknown;
|
||||
const { getByLabelText } = render(getComponent({ deviceType }));
|
||||
expect(getByLabelText('Unknown session type')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -20,6 +20,7 @@ import { act, fireEvent, render } from '@testing-library/react';
|
||||
import { FilteredDeviceList } from '../../../../../src/components/views/settings/devices/FilteredDeviceList';
|
||||
import { DeviceSecurityVariation } from '../../../../../src/components/views/settings/devices/types';
|
||||
import { flushPromises, mockPlatformPeg } from '../../../../test-utils';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
mockPlatformPeg();
|
||||
|
||||
@@ -31,14 +32,26 @@ describe('<FilteredDeviceList />', () => {
|
||||
last_seen_ip: '123.456.789',
|
||||
display_name: 'My Device',
|
||||
isVerified: true,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const unverifiedNoMetadata = { device_id: 'unverified-no-metadata', isVerified: false };
|
||||
const verifiedNoMetadata = { device_id: 'verified-no-metadata', isVerified: true };
|
||||
const hundredDaysOld = { device_id: '100-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 100) };
|
||||
const unverifiedNoMetadata = {
|
||||
device_id: 'unverified-no-metadata',
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown };
|
||||
const verifiedNoMetadata = {
|
||||
device_id: 'verified-no-metadata',
|
||||
isVerified: true,
|
||||
deviceType: DeviceType.Unknown };
|
||||
const hundredDaysOld = {
|
||||
device_id: '100-days-old',
|
||||
isVerified: true,
|
||||
last_seen_ts: Date.now() - (MS_DAY * 100),
|
||||
deviceType: DeviceType.Unknown };
|
||||
const hundredDaysOldUnverified = {
|
||||
device_id: 'unverified-100-days-old',
|
||||
isVerified: false,
|
||||
last_seen_ts: Date.now() - (MS_DAY * 100),
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const defaultProps = {
|
||||
onFilterChange: jest.fn(),
|
||||
|
||||
@@ -19,6 +19,7 @@ import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import SelectableDeviceTile from '../../../../../src/components/views/settings/devices/SelectableDeviceTile';
|
||||
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||
|
||||
describe('<SelectableDeviceTile />', () => {
|
||||
const device = {
|
||||
@@ -26,6 +27,7 @@ describe('<SelectableDeviceTile />', () => {
|
||||
device_id: 'my-device',
|
||||
last_seen_ip: '123.456.789',
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const defaultProps = {
|
||||
onClick: jest.fn(),
|
||||
|
||||
+20
-12
@@ -151,16 +151,20 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
|
||||
data-testid="device-tile-alices_device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
@@ -267,16 +271,20 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
|
||||
data-testid="device-tile-alices_device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -181,6 +181,18 @@ exports[`<DeviceDetails /> renders device with metadata 1`] = `
|
||||
my-device
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
>
|
||||
Client
|
||||
</td>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataValue"
|
||||
>
|
||||
Firefox 100
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
@@ -233,6 +245,30 @@ exports[`<DeviceDetails /> renders device with metadata 1`] = `
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
>
|
||||
Model
|
||||
</td>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataValue"
|
||||
>
|
||||
Iphone X
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
>
|
||||
Operating system
|
||||
</td>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataValue"
|
||||
>
|
||||
Windows 95
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
|
||||
@@ -7,16 +7,20 @@ exports[`<DeviceTile /> renders a device with no metadata 1`] = `
|
||||
data-testid="device-tile-123"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
@@ -58,16 +62,20 @@ exports[`<DeviceTile /> renders a verified device with no metadata 1`] = `
|
||||
data-testid="device-tile-123"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
@@ -109,16 +117,20 @@ exports[`<DeviceTile /> renders display name with a tooltip 1`] = `
|
||||
data-testid="device-tile-123"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
@@ -160,16 +172,20 @@ exports[`<DeviceTile /> separates metadata with a dot 1`] = `
|
||||
data-testid="device-tile-123"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<DeviceType /> renders a verified device 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Verified"
|
||||
class="mx_DeviceType_verificationIcon verified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<DeviceType /> renders an unverified device 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<DeviceType /> renders correctly when selected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceType mx_DeviceType_selected"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,70 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<DeviceTypeIcon /> renders a verified device 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Verified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<DeviceTypeIcon /> renders an unverified device 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<DeviceTypeIcon /> renders correctly when selected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon mx_DeviceTypeIcon_selected"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
+10
-6
@@ -39,16 +39,20 @@ exports[`<SelectableDeviceTile /> renders unselected device tile with checkbox 1
|
||||
data-testid="device-tile-my-device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -20,18 +20,38 @@ import {
|
||||
import {
|
||||
DeviceSecurityVariation,
|
||||
} from "../../../../../src/components/views/settings/devices/types";
|
||||
import { DeviceType } from "../../../../../src/utils/device/parseUserAgent";
|
||||
|
||||
const MS_DAY = 86400000;
|
||||
describe('filterDevicesBySecurityRecommendation()', () => {
|
||||
const unverifiedNoMetadata = { device_id: 'unverified-no-metadata', isVerified: false };
|
||||
const verifiedNoMetadata = { device_id: 'verified-no-metadata', isVerified: true };
|
||||
const hundredDaysOld = { device_id: '100-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 100) };
|
||||
const unverifiedNoMetadata = {
|
||||
device_id: 'unverified-no-metadata',
|
||||
isVerified: false,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const verifiedNoMetadata = {
|
||||
device_id: 'verified-no-metadata',
|
||||
isVerified: true,
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const hundredDaysOld = {
|
||||
device_id: '100-days-old',
|
||||
isVerified: true,
|
||||
last_seen_ts: Date.now() - (MS_DAY * 100),
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const hundredDaysOldUnverified = {
|
||||
device_id: 'unverified-100-days-old',
|
||||
isVerified: false,
|
||||
last_seen_ts: Date.now() - (MS_DAY * 100),
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const fiftyDaysOld = {
|
||||
device_id: '50-days-old',
|
||||
isVerified: true,
|
||||
last_seen_ts: Date.now() - (MS_DAY * 50),
|
||||
deviceType: DeviceType.Unknown,
|
||||
};
|
||||
const fiftyDaysOld = { device_id: '50-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 50) };
|
||||
|
||||
const devices = [
|
||||
unverifiedNoMetadata,
|
||||
|
||||
@@ -16,30 +16,35 @@ limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, RenderResult } from "@testing-library/react";
|
||||
import { EventType, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
import RolesRoomSettingsTab from "../../../../../../src/components/views/settings/tabs/room/RolesRoomSettingsTab";
|
||||
import { mkStubRoom, stubClient } from "../../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||
import { VoiceBroadcastInfoEventType } from "../../../../../../src/voice-broadcast";
|
||||
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||
import { ElementCall } from "../../../../../../src/models/Call";
|
||||
|
||||
describe("RolesRoomSettingsTab", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let rolesRoomSettingsTab: RenderResult;
|
||||
let cli: MatrixClient;
|
||||
|
||||
const renderTab = (): RenderResult => {
|
||||
return render(<RolesRoomSettingsTab roomId={roomId} />);
|
||||
};
|
||||
|
||||
const getVoiceBroadcastsSelect = () => {
|
||||
return rolesRoomSettingsTab.container.querySelector("select[label='Voice broadcasts']");
|
||||
return renderTab().container.querySelector("select[label='Voice broadcasts']");
|
||||
};
|
||||
|
||||
const getVoiceBroadcastsSelectedOption = () => {
|
||||
return rolesRoomSettingsTab.container.querySelector("select[label='Voice broadcasts'] option:checked");
|
||||
return renderTab().container.querySelector("select[label='Voice broadcasts'] option:checked");
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.get();
|
||||
rolesRoomSettingsTab = render(<RolesRoomSettingsTab roomId={roomId} />);
|
||||
mkStubRoom(roomId, "test room", cli);
|
||||
});
|
||||
|
||||
@@ -66,4 +71,96 @@ describe("RolesRoomSettingsTab", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Element Call", () => {
|
||||
const setGroupCallsEnabled = (val: boolean): void => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
if (name === "feature_group_calls") return val;
|
||||
});
|
||||
};
|
||||
|
||||
const getStartCallSelect = (tab: RenderResult) => {
|
||||
return tab.container.querySelector("select[label='Start Element Call calls']");
|
||||
};
|
||||
|
||||
const getStartCallSelectedOption = (tab: RenderResult) => {
|
||||
return tab.container.querySelector("select[label='Start Element Call calls'] option:checked");
|
||||
};
|
||||
|
||||
const getJoinCallSelect = (tab: RenderResult) => {
|
||||
return tab.container.querySelector("select[label='Join Element Call calls']");
|
||||
};
|
||||
|
||||
const getJoinCallSelectedOption = (tab: RenderResult) => {
|
||||
return tab.container.querySelector("select[label='Join Element Call calls'] option:checked");
|
||||
};
|
||||
|
||||
describe("Element Call enabled", () => {
|
||||
beforeEach(() => {
|
||||
setGroupCallsEnabled(true);
|
||||
});
|
||||
|
||||
describe("Join Element calls", () => {
|
||||
it("defaults to moderator for joining calls", () => {
|
||||
expect(getJoinCallSelectedOption(renderTab())?.textContent).toBe("Moderator");
|
||||
});
|
||||
|
||||
it("can change joining calls power level", () => {
|
||||
const tab = renderTab();
|
||||
|
||||
fireEvent.change(getJoinCallSelect(tab), {
|
||||
target: { value: 0 },
|
||||
});
|
||||
|
||||
expect(getJoinCallSelectedOption(tab)?.textContent).toBe("Default");
|
||||
expect(cli.sendStateEvent).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
EventType.RoomPowerLevels,
|
||||
{
|
||||
events: {
|
||||
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Start Element calls", () => {
|
||||
it("defaults to moderator for starting calls", () => {
|
||||
expect(getStartCallSelectedOption(renderTab())?.textContent).toBe("Moderator");
|
||||
});
|
||||
|
||||
it("can change starting calls power level", () => {
|
||||
const tab = renderTab();
|
||||
|
||||
fireEvent.change(getStartCallSelect(tab), {
|
||||
target: { value: 0 },
|
||||
});
|
||||
|
||||
expect(getStartCallSelectedOption(tab)?.textContent).toBe("Default");
|
||||
expect(cli.sendStateEvent).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
EventType.RoomPowerLevels,
|
||||
{
|
||||
events: {
|
||||
[ElementCall.CALL_EVENT_TYPE.name]: 0,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("hides when group calls disabled", () => {
|
||||
setGroupCallsEnabled(false);
|
||||
|
||||
const tab = renderTab();
|
||||
|
||||
expect(getStartCallSelect(tab)).toBeFalsy();
|
||||
expect(getStartCallSelectedOption(tab)).toBeFalsy();
|
||||
|
||||
expect(getJoinCallSelect(tab)).toBeFalsy();
|
||||
expect(getJoinCallSelectedOption(tab)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
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, RenderResult, waitFor } from "@testing-library/react";
|
||||
import { 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 { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||
|
||||
import { mkStubRoom, stubClient } from "../../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||
import { VoipRoomSettingsTab } from "../../../../../../src/components/views/settings/tabs/room/VoipRoomSettingsTab";
|
||||
import { ElementCall } from "../../../../../../src/models/Call";
|
||||
|
||||
describe("RolesRoomSettingsTab", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let cli: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
const renderTab = (): RenderResult => {
|
||||
return render(<VoipRoomSettingsTab roomId={roomId} />);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.get();
|
||||
room = mkStubRoom(roomId, "test room", cli);
|
||||
|
||||
jest.spyOn(cli, "sendStateEvent");
|
||||
jest.spyOn(cli, "getRoom").mockReturnValue(room);
|
||||
});
|
||||
|
||||
describe("Element Call", () => {
|
||||
const mockPowerLevels = (events): void => {
|
||||
jest.spyOn(room.currentState, "getStateEvents").mockReturnValue({
|
||||
getContent: () => ({
|
||||
events,
|
||||
}),
|
||||
} as unknown as MatrixEvent);
|
||||
};
|
||||
|
||||
const getElementCallSwitch = (tab: RenderResult): HTMLElement => {
|
||||
return tab.container.querySelector("[data-testid='element-call-switch']");
|
||||
};
|
||||
|
||||
describe("correct state", () => {
|
||||
it("shows enabled when call member power level is 0", () => {
|
||||
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: 0 });
|
||||
|
||||
const tab = renderTab();
|
||||
|
||||
expect(getElementCallSwitch(tab).querySelector("[aria-checked='true']")).toBeTruthy();
|
||||
});
|
||||
|
||||
it.each([1, 50, 100])("shows disabled when call member power level is 0", (level: number) => {
|
||||
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: level });
|
||||
|
||||
const tab = renderTab();
|
||||
|
||||
expect(getElementCallSwitch(tab).querySelector("[aria-checked='false']")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("enabling/disabling", () => {
|
||||
describe("enabling Element calls", () => {
|
||||
beforeEach(() => {
|
||||
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: 100 });
|
||||
});
|
||||
|
||||
it("enables Element calls in public room", async () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
||||
|
||||
const tab = renderTab();
|
||||
|
||||
fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch"));
|
||||
await waitFor(() => expect(cli.sendStateEvent).toHaveBeenCalledWith(
|
||||
room.roomId,
|
||||
EventType.RoomPowerLevels,
|
||||
expect.objectContaining({
|
||||
events: {
|
||||
[ElementCall.CALL_EVENT_TYPE.name]: 50,
|
||||
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
|
||||
},
|
||||
}),
|
||||
));
|
||||
});
|
||||
|
||||
it("enables Element calls in private room", async () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Invite);
|
||||
|
||||
const tab = renderTab();
|
||||
|
||||
fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch"));
|
||||
await waitFor(() => expect(cli.sendStateEvent).toHaveBeenCalledWith(
|
||||
room.roomId,
|
||||
EventType.RoomPowerLevels,
|
||||
expect.objectContaining({
|
||||
events: {
|
||||
[ElementCall.CALL_EVENT_TYPE.name]: 0,
|
||||
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
|
||||
},
|
||||
}),
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
it("disables Element calls", async () => {
|
||||
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: 0 });
|
||||
|
||||
const tab = renderTab();
|
||||
|
||||
fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch"));
|
||||
await waitFor(() => expect(cli.sendStateEvent).toHaveBeenCalledWith(
|
||||
room.roomId,
|
||||
EventType.RoomPowerLevels,
|
||||
expect.objectContaining({
|
||||
events: {
|
||||
[ElementCall.CALL_EVENT_TYPE.name]: 100,
|
||||
[ElementCall.MEMBER_EVENT_TYPE.name]: 100,
|
||||
},
|
||||
}),
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
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, RenderResult, waitFor } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom";
|
||||
|
||||
import PreferencesUserSettingsTab from
|
||||
"../../../../../../src/components/views/settings/tabs/user/PreferencesUserSettingsTab";
|
||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||
import { mockPlatformPeg, stubClient } from "../../../../../test-utils";
|
||||
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
|
||||
|
||||
describe("PreferencesUserSettingsTab", () => {
|
||||
beforeEach(() => {
|
||||
mockPlatformPeg();
|
||||
});
|
||||
|
||||
const renderTab = (): RenderResult => {
|
||||
return render(<PreferencesUserSettingsTab closeSettingsFn={() => {}} />);
|
||||
};
|
||||
|
||||
describe("send read receipts", () => {
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
jest.spyOn(SettingsStore, "setValue");
|
||||
jest.spyOn(window, "matchMedia").mockReturnValue({ matches: false } as MediaQueryList);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const getToggle = () => renderTab().getByRole("switch", { name: "Send read receipts" });
|
||||
|
||||
const mockIsVersionSupported = (val: boolean) => {
|
||||
const client = MatrixClientPeg.get();
|
||||
jest.spyOn(client, "isVersionSupported").mockImplementation(async (version: string) => {
|
||||
if (version === "v1.4") return val;
|
||||
});
|
||||
};
|
||||
|
||||
const mockGetValue = (val: boolean) => {
|
||||
const copyOfGetValueAt = SettingsStore.getValueAt;
|
||||
|
||||
SettingsStore.getValueAt = (level: SettingLevel, name: string, roomId?: string, isExplicit?: boolean) => {
|
||||
if (name === "sendReadReceipts") return val;
|
||||
return copyOfGetValueAt(level, name, roomId, isExplicit);
|
||||
};
|
||||
};
|
||||
|
||||
const expectSetValueToHaveBeenCalled = (
|
||||
name: string,
|
||||
roomId: string,
|
||||
level: SettingLevel,
|
||||
value: boolean,
|
||||
) => expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value);
|
||||
|
||||
describe("with server support", () => {
|
||||
beforeEach(() => {
|
||||
mockIsVersionSupported(true);
|
||||
});
|
||||
|
||||
it("can be enabled", async () => {
|
||||
mockGetValue(false);
|
||||
const toggle = getToggle();
|
||||
|
||||
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false"));
|
||||
fireEvent.click(toggle);
|
||||
expectSetValueToHaveBeenCalled("sendReadReceipts", undefined, SettingLevel.ACCOUNT, true);
|
||||
});
|
||||
|
||||
it("can be disabled", async () => {
|
||||
mockGetValue(true);
|
||||
const toggle = getToggle();
|
||||
|
||||
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false"));
|
||||
fireEvent.click(toggle);
|
||||
expectSetValueToHaveBeenCalled("sendReadReceipts", undefined, SettingLevel.ACCOUNT, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("without server support", () => {
|
||||
beforeEach(() => {
|
||||
mockIsVersionSupported(false);
|
||||
});
|
||||
|
||||
it("can be enabled", async () => {
|
||||
mockGetValue(false);
|
||||
const toggle = getToggle();
|
||||
|
||||
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false"));
|
||||
fireEvent.click(toggle);
|
||||
expectSetValueToHaveBeenCalled("sendReadReceipts", undefined, SettingLevel.ACCOUNT, true);
|
||||
});
|
||||
|
||||
it("cannot be disabled", async () => {
|
||||
mockGetValue(true);
|
||||
const toggle = getToggle();
|
||||
|
||||
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "true"));
|
||||
fireEvent.click(toggle);
|
||||
expect(SettingsStore.setValue).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -44,7 +44,7 @@ import Modal from '../../../../../../src/Modal';
|
||||
import LogoutDialog from '../../../../../../src/components/views/dialogs/LogoutDialog';
|
||||
import {
|
||||
DeviceSecurityVariation,
|
||||
DeviceWithVerification,
|
||||
ExtendedDevice,
|
||||
} from '../../../../../../src/components/views/settings/devices/types';
|
||||
import { INACTIVE_DEVICE_AGE_MS } from '../../../../../../src/components/views/settings/devices/filter';
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('<SessionManagerTab />', () => {
|
||||
|
||||
const toggleDeviceDetails = (
|
||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||
deviceId: DeviceWithVerification['device_id'],
|
||||
deviceId: ExtendedDevice['device_id'],
|
||||
) => {
|
||||
// open device detail
|
||||
const tile = getByTestId(`device-tile-${deviceId}`);
|
||||
@@ -114,7 +114,7 @@ describe('<SessionManagerTab />', () => {
|
||||
|
||||
const toggleDeviceSelection = (
|
||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||
deviceId: DeviceWithVerification['device_id'],
|
||||
deviceId: ExtendedDevice['device_id'],
|
||||
) => {
|
||||
const checkbox = getByTestId(`device-tile-checkbox-${deviceId}`);
|
||||
fireEvent.click(checkbox);
|
||||
@@ -135,7 +135,7 @@ describe('<SessionManagerTab />', () => {
|
||||
|
||||
const isDeviceSelected = (
|
||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||
deviceId: DeviceWithVerification['device_id'],
|
||||
deviceId: ExtendedDevice['device_id'],
|
||||
): boolean => !!(getByTestId(`device-tile-checkbox-${deviceId}`) as HTMLInputElement).checked;
|
||||
|
||||
const isSelectAllChecked = (
|
||||
|
||||
+30
-18
@@ -94,16 +94,20 @@ exports[`<SessionManagerTab /> renders current session section with a verified s
|
||||
data-testid="device-tile-alices_device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Verified"
|
||||
class="mx_DeviceType_verificationIcon verified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
@@ -196,16 +200,20 @@ exports[`<SessionManagerTab /> renders current session section with an unverifie
|
||||
data-testid="device-tile-alices_device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
@@ -298,16 +306,20 @@ exports[`<SessionManagerTab /> sets device verification status correctly 1`] = `
|
||||
data-testid="device-tile-alices_device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceType"
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceType_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Verified"
|
||||
class="mx_DeviceType_verificationIcon verified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,7 @@ import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { Widget } from "matrix-widget-api";
|
||||
import "@testing-library/jest-dom";
|
||||
|
||||
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import type { ClientWidgetApi } from "matrix-widget-api";
|
||||
@@ -38,6 +39,7 @@ import { CallView as _CallView } from "../../../../src/components/views/voip/Cal
|
||||
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
|
||||
import { CallStore } from "../../../../src/stores/CallStore";
|
||||
import { Call, ConnectionState } from "../../../../src/models/Call";
|
||||
import SdkConfig from "../../../../src/SdkConfig";
|
||||
|
||||
const CallView = wrapInMatrixClientContext(_CallView);
|
||||
|
||||
@@ -90,7 +92,7 @@ describe("CallLobby", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
MockedCall.create(room, "1");
|
||||
const maybeCall = CallStore.instance.get(room.roomId);
|
||||
const maybeCall = CallStore.instance.getCall(room.roomId);
|
||||
if (!(maybeCall instanceof MockedCall)) throw new Error("Failed to create call");
|
||||
call = maybeCall;
|
||||
|
||||
@@ -163,6 +165,23 @@ describe("CallLobby", () => {
|
||||
fireEvent.click(screen.getByRole("button", { name: "Join" }));
|
||||
await waitFor(() => expect(connectSpy).toHaveBeenCalled(), { interval: 1 });
|
||||
});
|
||||
|
||||
it("disables join button when the participant limit has been exceeded", async () => {
|
||||
const bob = mkRoomMember(room.roomId, "@bob:example.org");
|
||||
const carol = mkRoomMember(room.roomId, "@carol:example.org");
|
||||
|
||||
SdkConfig.put({
|
||||
"element_call": { participant_limit: 2, url: "", use_exclusively: false, brand: "Element Call" },
|
||||
});
|
||||
call.participants = new Set([bob, carol]);
|
||||
|
||||
await renderView();
|
||||
const connectSpy = jest.spyOn(call, "connect");
|
||||
const joinButton = screen.getByRole("button", { name: "Join" });
|
||||
expect(joinButton).toHaveAttribute("aria-disabled", "true");
|
||||
fireEvent.click(joinButton);
|
||||
await waitFor(() => expect(connectSpy).not.toHaveBeenCalled(), { interval: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("without an existing call", () => {
|
||||
@@ -171,8 +190,8 @@ describe("CallLobby", () => {
|
||||
expect(Call.get(room)).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Join" }));
|
||||
await waitFor(() => expect(CallStore.instance.get(room.roomId)).not.toBeNull());
|
||||
const call = CallStore.instance.get(room.roomId)!;
|
||||
await waitFor(() => expect(CallStore.instance.getCall(room.roomId)).not.toBeNull());
|
||||
const call = CallStore.instance.getCall(room.roomId)!;
|
||||
|
||||
const widget = new Widget(call.widget);
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
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 { mocked, Mocked } from "jest-mock";
|
||||
import { screen, render, act, cleanup, fireEvent, waitFor } from "@testing-library/react";
|
||||
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { Widget, ClientWidgetApi } from "matrix-widget-api";
|
||||
|
||||
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import {
|
||||
useMockedCalls,
|
||||
MockedCall,
|
||||
mkRoomMember,
|
||||
stubClient,
|
||||
setupAsyncStoreWithClient,
|
||||
resetAsyncStoreWithClient,
|
||||
wrapInMatrixClientContext,
|
||||
} from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { CallStore } from "../../../../src/stores/CallStore";
|
||||
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
|
||||
import UnwrappedPipView from "../../../../src/components/views/voip/PipView";
|
||||
import ActiveWidgetStore from "../../../../src/stores/ActiveWidgetStore";
|
||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import { ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPayload";
|
||||
|
||||
const PipView = wrapInMatrixClientContext(UnwrappedPipView);
|
||||
|
||||
describe("PipView", () => {
|
||||
useMockedCalls();
|
||||
Object.defineProperty(navigator, "mediaDevices", { value: { enumerateDevices: () => [] } });
|
||||
jest.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(async () => {});
|
||||
|
||||
let client: Mocked<MatrixClient>;
|
||||
let room: Room;
|
||||
let alice: RoomMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
stubClient();
|
||||
client = mocked(MatrixClientPeg.get());
|
||||
DMRoomMap.makeShared();
|
||||
|
||||
room = new Room("!1:example.org", client, "@alice:example.org", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
client.getRoom.mockImplementation(roomId => roomId === room.roomId ? room : null);
|
||||
client.getRooms.mockReturnValue([room]);
|
||||
alice = mkRoomMember(room.roomId, "@alice:example.org");
|
||||
jest.spyOn(room, "getMember").mockImplementation(userId => userId === alice.userId ? alice : null);
|
||||
|
||||
client.getRoom.mockImplementation(roomId => roomId === room.roomId ? room : null);
|
||||
client.getRooms.mockReturnValue([room]);
|
||||
client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
|
||||
|
||||
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(
|
||||
store => setupAsyncStoreWithClient(store, client),
|
||||
));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
cleanup();
|
||||
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(resetAsyncStoreWithClient));
|
||||
client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]);
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const renderPip = () => { render(<PipView />); };
|
||||
|
||||
const viewRoom = (roomId: string) =>
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: roomId,
|
||||
metricsTrigger: undefined,
|
||||
}, true);
|
||||
|
||||
const withCall = async (fn: () => Promise<void>): Promise<void> => {
|
||||
MockedCall.create(room, "1");
|
||||
const call = CallStore.instance.getCall(room.roomId);
|
||||
if (!(call instanceof MockedCall)) throw new Error("Failed to create call");
|
||||
|
||||
const widget = new Widget(call.widget);
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
|
||||
stop: () => {},
|
||||
} as unknown as ClientWidgetApi);
|
||||
|
||||
await act(async () => {
|
||||
await call.connect();
|
||||
ActiveWidgetStore.instance.setWidgetPersistence(widget.id, room.roomId, true);
|
||||
});
|
||||
|
||||
await fn();
|
||||
|
||||
cleanup();
|
||||
call.destroy();
|
||||
ActiveWidgetStore.instance.destroyPersistentWidget(widget.id, room.roomId);
|
||||
};
|
||||
|
||||
const withWidget = (fn: () => void): void => {
|
||||
act(() => ActiveWidgetStore.instance.setWidgetPersistence("1", room.roomId, true));
|
||||
fn();
|
||||
cleanup();
|
||||
ActiveWidgetStore.instance.destroyPersistentWidget("1", room.roomId);
|
||||
};
|
||||
|
||||
it("hides if there's no content", () => {
|
||||
renderPip();
|
||||
expect(screen.queryByRole("complementary")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows an active call with a maximise button", async () => {
|
||||
renderPip();
|
||||
|
||||
await withCall(async () => {
|
||||
screen.getByRole("complementary");
|
||||
screen.getByText(room.roomId);
|
||||
expect(screen.queryByRole("button", { name: "Pin" })).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: /return/i })).toBeNull();
|
||||
|
||||
// The maximise button should jump to the call
|
||||
const dispatcherSpy = jest.fn();
|
||||
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Fill screen" }));
|
||||
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
view_call: true,
|
||||
}));
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a persistent widget with pin and maximise buttons when viewing the room", () => {
|
||||
viewRoom(room.roomId);
|
||||
renderPip();
|
||||
|
||||
withWidget(() => {
|
||||
screen.getByRole("complementary");
|
||||
screen.getByText(room.roomId);
|
||||
screen.getByRole("button", { name: "Pin" });
|
||||
screen.getByRole("button", { name: "Fill screen" });
|
||||
expect(screen.queryByRole("button", { name: /return/i })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a persistent widget with a return button when not viewing the room", () => {
|
||||
viewRoom("!2:example.org");
|
||||
renderPip();
|
||||
|
||||
withWidget(() => {
|
||||
screen.getByRole("complementary");
|
||||
screen.getByText(room.roomId);
|
||||
expect(screen.queryByRole("button", { name: "Pin" })).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "Fill screen" })).toBeNull();
|
||||
screen.getByRole("button", { name: /return/i });
|
||||
});
|
||||
});
|
||||
});
|
||||
+22
-1
@@ -25,6 +25,7 @@ import WidgetStore from "../src/stores/WidgetStore";
|
||||
import WidgetUtils from "../src/utils/WidgetUtils";
|
||||
import { JitsiCall, ElementCall } from "../src/models/Call";
|
||||
import createRoom, { canEncryptToAllUsers } from '../src/createRoom';
|
||||
import SettingsStore from "../src/settings/SettingsStore";
|
||||
|
||||
describe("createRoom", () => {
|
||||
mockPlatformPeg();
|
||||
@@ -85,7 +86,7 @@ describe("createRoom", () => {
|
||||
[ElementCall.MEMBER_EVENT_TYPE.name]: callMemberPower,
|
||||
},
|
||||
},
|
||||
}]] = client.createRoom.mock.calls as any; // no good type
|
||||
}]] = client.createRoom.mock.calls;
|
||||
|
||||
// We should have had enough power to be able to set up the call
|
||||
expect(userPower).toBeGreaterThanOrEqual(callPower);
|
||||
@@ -109,6 +110,26 @@ describe("createRoom", () => {
|
||||
expect(createJitsiCallSpy).not.toHaveBeenCalled();
|
||||
expect(createElementCallSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("correctly sets up MSC3401 power levels", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
if (name === "feature_group_calls") return true;
|
||||
});
|
||||
|
||||
await createRoom({});
|
||||
|
||||
const [[{
|
||||
power_level_content_override: {
|
||||
events: {
|
||||
[ElementCall.CALL_EVENT_TYPE.name]: callPower,
|
||||
[ElementCall.MEMBER_EVENT_TYPE.name]: callMemberPower,
|
||||
},
|
||||
},
|
||||
}]] = client.createRoom.mock.calls;
|
||||
|
||||
expect(callPower).toBe(100);
|
||||
expect(callMemberPower).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe("canEncryptToAllUsers", () => {
|
||||
|
||||
+79
-26
@@ -15,7 +15,6 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import EventEmitter from "events";
|
||||
import { isEqual } from "lodash";
|
||||
import { mocked } from "jest-mock";
|
||||
import { waitFor } from "@testing-library/react";
|
||||
import { RoomType } from "matrix-js-sdk/src/@types/event";
|
||||
@@ -28,7 +27,7 @@ import type { Mocked } from "jest-mock";
|
||||
import type { MatrixClient, IMyDevice } from "matrix-js-sdk/src/client";
|
||||
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import type { ClientWidgetApi } from "matrix-widget-api";
|
||||
import type { JitsiCallMemberContent, ElementCallMemberContent } from "../../src/models/Call";
|
||||
import { JitsiCallMemberContent, ElementCallMemberContent, Layout } from "../../src/models/Call";
|
||||
import { stubClient, mkEvent, mkRoomMember, setupAsyncStoreWithClient, mockPlatformPeg } from "../test-utils";
|
||||
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../src/MediaDeviceHandler";
|
||||
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
||||
@@ -404,30 +403,35 @@ describe("JitsiCall", () => {
|
||||
});
|
||||
|
||||
it("emits events when connection state changes", async () => {
|
||||
const events: ConnectionState[] = [];
|
||||
const onConnectionState = (state: ConnectionState) => events.push(state);
|
||||
const onConnectionState = jest.fn();
|
||||
call.on(CallEvent.ConnectionState, onConnectionState);
|
||||
|
||||
await call.connect();
|
||||
await call.disconnect();
|
||||
expect(events).toEqual([
|
||||
ConnectionState.Connecting,
|
||||
ConnectionState.Connected,
|
||||
ConnectionState.Disconnecting,
|
||||
ConnectionState.Disconnected,
|
||||
expect(onConnectionState.mock.calls).toEqual([
|
||||
[ConnectionState.Connecting, ConnectionState.Disconnected],
|
||||
[ConnectionState.Connected, ConnectionState.Connecting],
|
||||
[ConnectionState.Disconnecting, ConnectionState.Connected],
|
||||
[ConnectionState.Disconnected, ConnectionState.Disconnecting],
|
||||
]);
|
||||
|
||||
call.off(CallEvent.ConnectionState, onConnectionState);
|
||||
});
|
||||
|
||||
it("emits events when participants change", async () => {
|
||||
const events: Set<RoomMember>[] = [];
|
||||
const onParticipants = (participants: Set<RoomMember>) => {
|
||||
if (!isEqual(participants, events[events.length - 1])) events.push(participants);
|
||||
};
|
||||
const onParticipants = jest.fn();
|
||||
call.on(CallEvent.Participants, onParticipants);
|
||||
|
||||
await call.connect();
|
||||
await call.disconnect();
|
||||
expect(events).toEqual([new Set([alice]), new Set()]);
|
||||
expect(onParticipants.mock.calls).toEqual([
|
||||
[new Set([alice]), new Set()],
|
||||
[new Set([alice]), new Set([alice])],
|
||||
[new Set(), new Set([alice])],
|
||||
[new Set(), new Set()],
|
||||
]);
|
||||
|
||||
call.off(CallEvent.Participants, onParticipants);
|
||||
});
|
||||
|
||||
it("switches to spotlight layout when the widget becomes a PiP", async () => {
|
||||
@@ -725,31 +729,80 @@ describe("ElementCall", () => {
|
||||
expect([...call.participants]).toEqual([bob]);
|
||||
});
|
||||
|
||||
it("tracks layout", async () => {
|
||||
await call.connect();
|
||||
expect(call.layout).toBe(Layout.Tile);
|
||||
|
||||
messaging.emit(
|
||||
`action:${ElementWidgetActions.SpotlightLayout}`,
|
||||
new CustomEvent("widgetapirequest", { detail: {} }),
|
||||
);
|
||||
expect(call.layout).toBe(Layout.Spotlight);
|
||||
|
||||
messaging.emit(
|
||||
`action:${ElementWidgetActions.TileLayout}`,
|
||||
new CustomEvent("widgetapirequest", { detail: {} }),
|
||||
);
|
||||
expect(call.layout).toBe(Layout.Tile);
|
||||
});
|
||||
|
||||
it("sets layout", async () => {
|
||||
await call.connect();
|
||||
|
||||
await call.setLayout(Layout.Spotlight);
|
||||
expect(messaging.transport.send).toHaveBeenCalledWith(ElementWidgetActions.SpotlightLayout, {});
|
||||
|
||||
await call.setLayout(Layout.Tile);
|
||||
expect(messaging.transport.send).toHaveBeenCalledWith(ElementWidgetActions.TileLayout, {});
|
||||
});
|
||||
|
||||
it("emits events when connection state changes", async () => {
|
||||
const events: ConnectionState[] = [];
|
||||
const onConnectionState = (state: ConnectionState) => events.push(state);
|
||||
const onConnectionState = jest.fn();
|
||||
call.on(CallEvent.ConnectionState, onConnectionState);
|
||||
|
||||
await call.connect();
|
||||
await call.disconnect();
|
||||
expect(events).toEqual([
|
||||
ConnectionState.Connecting,
|
||||
ConnectionState.Connected,
|
||||
ConnectionState.Disconnecting,
|
||||
ConnectionState.Disconnected,
|
||||
expect(onConnectionState.mock.calls).toEqual([
|
||||
[ConnectionState.Connecting, ConnectionState.Disconnected],
|
||||
[ConnectionState.Connected, ConnectionState.Connecting],
|
||||
[ConnectionState.Disconnecting, ConnectionState.Connected],
|
||||
[ConnectionState.Disconnected, ConnectionState.Disconnecting],
|
||||
]);
|
||||
|
||||
call.off(CallEvent.ConnectionState, onConnectionState);
|
||||
});
|
||||
|
||||
it("emits events when participants change", async () => {
|
||||
const events: Set<RoomMember>[] = [];
|
||||
const onParticipants = (participants: Set<RoomMember>) => {
|
||||
if (!isEqual(participants, events[events.length - 1])) events.push(participants);
|
||||
};
|
||||
const onParticipants = jest.fn();
|
||||
call.on(CallEvent.Participants, onParticipants);
|
||||
|
||||
await call.connect();
|
||||
await call.disconnect();
|
||||
expect(events).toEqual([new Set([alice]), new Set()]);
|
||||
expect(onParticipants.mock.calls).toEqual([
|
||||
[new Set([alice]), new Set()],
|
||||
[new Set(), new Set()],
|
||||
[new Set(), new Set([alice])],
|
||||
]);
|
||||
|
||||
call.off(CallEvent.Participants, onParticipants);
|
||||
});
|
||||
|
||||
it("emits events when layout changes", async () => {
|
||||
await call.connect();
|
||||
const onLayout = jest.fn();
|
||||
call.on(CallEvent.Layout, onLayout);
|
||||
|
||||
messaging.emit(
|
||||
`action:${ElementWidgetActions.SpotlightLayout}`,
|
||||
new CustomEvent("widgetapirequest", { detail: {} }),
|
||||
);
|
||||
messaging.emit(
|
||||
`action:${ElementWidgetActions.TileLayout}`,
|
||||
new CustomEvent("widgetapirequest", { detail: {} }),
|
||||
);
|
||||
expect(onLayout.mock.calls).toEqual([[Layout.Spotlight], [Layout.Tile]]);
|
||||
|
||||
call.off(CallEvent.Layout, onLayout);
|
||||
});
|
||||
|
||||
it("ends the call immediately if we're the last participant to leave", async () => {
|
||||
|
||||
@@ -81,7 +81,7 @@ describe("Algorithm", () => {
|
||||
setupAsyncStoreWithClient(WidgetMessagingStore.instance, client);
|
||||
|
||||
MockedCall.create(roomWithCall, "1");
|
||||
const call = CallStore.instance.get(roomWithCall.roomId);
|
||||
const call = CallStore.instance.getCall(roomWithCall.roomId);
|
||||
if (call === null) throw new Error("Failed to create call");
|
||||
|
||||
const widget = new Widget(call.widget);
|
||||
|
||||
@@ -209,7 +209,6 @@ describe("StopGapWidgetDriver", () => {
|
||||
});
|
||||
|
||||
await expect(driver.readEventRelations('$event')).resolves.toEqual({
|
||||
originalEvent: expect.objectContaining({ content: {} }),
|
||||
chunk: [],
|
||||
nextBatch: undefined,
|
||||
prevBatch: undefined,
|
||||
@@ -218,24 +217,6 @@ describe("StopGapWidgetDriver", () => {
|
||||
expect(client.relations).toBeCalledWith('!this-room-id', '$event', null, null, {});
|
||||
});
|
||||
|
||||
it('reads related events if the original event is missing', async () => {
|
||||
client.relations.mockResolvedValue({
|
||||
// the relations function can return an undefined event, even
|
||||
// though the typings don't permit an undefined value.
|
||||
originalEvent: undefined as any,
|
||||
events: [],
|
||||
});
|
||||
|
||||
await expect(driver.readEventRelations('$event', '!room-id')).resolves.toEqual({
|
||||
originalEvent: undefined,
|
||||
chunk: [],
|
||||
nextBatch: undefined,
|
||||
prevBatch: undefined,
|
||||
});
|
||||
|
||||
expect(client.relations).toBeCalledWith('!room-id', '$event', null, null, {});
|
||||
});
|
||||
|
||||
it('reads related events from a selected room', async () => {
|
||||
client.relations.mockResolvedValue({
|
||||
originalEvent: new MatrixEvent(),
|
||||
@@ -244,7 +225,6 @@ describe("StopGapWidgetDriver", () => {
|
||||
});
|
||||
|
||||
await expect(driver.readEventRelations('$event', '!room-id')).resolves.toEqual({
|
||||
originalEvent: expect.objectContaining({ content: {} }),
|
||||
chunk: [
|
||||
expect.objectContaining({ content: {} }),
|
||||
expect.objectContaining({ content: {} }),
|
||||
@@ -272,7 +252,6 @@ describe("StopGapWidgetDriver", () => {
|
||||
25,
|
||||
'f',
|
||||
)).resolves.toEqual({
|
||||
originalEvent: expect.objectContaining({ content: {} }),
|
||||
chunk: [],
|
||||
nextBatch: undefined,
|
||||
prevBatch: undefined,
|
||||
|
||||
@@ -160,6 +160,7 @@ export function createTestClient(): MatrixClient {
|
||||
getIdentityAccount: jest.fn().mockResolvedValue({}),
|
||||
getTerms: jest.fn().mockResolvedValueOnce(undefined),
|
||||
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(undefined),
|
||||
isVersionSupported: jest.fn().mockResolvedValue(undefined),
|
||||
getPushRules: jest.fn().mockResolvedValue(undefined),
|
||||
getPushers: jest.fn().mockResolvedValue({ pushers: [] }),
|
||||
getThreePids: jest.fn().mockResolvedValue({ threepids: [] }),
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
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 { setTheme } from "../src/theme";
|
||||
|
||||
describe('theme', () => {
|
||||
describe('setTheme', () => {
|
||||
let lightTheme;
|
||||
let darkTheme;
|
||||
|
||||
let spyQuerySelectorAll: jest.MockInstance<NodeListOf<Element>, [selectors: string]>;
|
||||
|
||||
beforeEach(() => {
|
||||
const styles = [
|
||||
{
|
||||
attributes: {
|
||||
'data-mx-theme': {
|
||||
value: 'light',
|
||||
},
|
||||
},
|
||||
disabled: true,
|
||||
href: 'urlLight',
|
||||
onload: () => void 0,
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
'data-mx-theme': {
|
||||
value: 'dark',
|
||||
},
|
||||
},
|
||||
disabled: true,
|
||||
href: 'urlDark',
|
||||
onload: () => void 0,
|
||||
},
|
||||
];
|
||||
lightTheme = styles[0];
|
||||
darkTheme = styles[1];
|
||||
|
||||
jest.spyOn(document.body, 'style', 'get').mockReturnValue([] as any);
|
||||
spyQuerySelectorAll = jest.spyOn(document, 'querySelectorAll').mockReturnValue(styles as any);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should switch theme on onload call', async () => {
|
||||
// When
|
||||
await new Promise(resolve => {
|
||||
setTheme('light').then(resolve);
|
||||
lightTheme.onload();
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(spyQuerySelectorAll).toHaveBeenCalledWith('[data-mx-theme]');
|
||||
expect(spyQuerySelectorAll).toBeCalledTimes(1);
|
||||
expect(lightTheme.disabled).toBe(false);
|
||||
expect(darkTheme.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject promise on onerror call', () => {
|
||||
return expect(new Promise(resolve => {
|
||||
setTheme('light').catch(e => resolve(e));
|
||||
lightTheme.onerror('call onerror');
|
||||
})).resolves.toBe('call onerror');
|
||||
});
|
||||
|
||||
it('should switch theme if CSS are preloaded', async () => {
|
||||
// When
|
||||
jest.spyOn(document, 'styleSheets', 'get').mockReturnValue([lightTheme] as any);
|
||||
|
||||
await setTheme('light');
|
||||
|
||||
// Then
|
||||
expect(lightTheme.disabled).toBe(false);
|
||||
expect(darkTheme.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should switch theme if CSS is loaded during pooling', async () => {
|
||||
// When
|
||||
jest.useFakeTimers();
|
||||
await new Promise(resolve => {
|
||||
setTheme('light').then(resolve);
|
||||
jest.spyOn(document, 'styleSheets', 'get').mockReturnValue([lightTheme] as any);
|
||||
jest.advanceTimersByTime(200);
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(lightTheme.disabled).toBe(false);
|
||||
expect(darkTheme.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject promise if pooling maximum value is reached', () => {
|
||||
jest.useFakeTimers();
|
||||
return new Promise(resolve => {
|
||||
setTheme('light').catch(resolve);
|
||||
jest.advanceTimersByTime(200 * 10);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
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 { render, screen, cleanup, fireEvent, waitFor } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom";
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { ClientWidgetApi, Widget } from "matrix-widget-api";
|
||||
|
||||
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import {
|
||||
useMockedCalls,
|
||||
MockedCall,
|
||||
stubClient,
|
||||
mkRoomMember,
|
||||
setupAsyncStoreWithClient,
|
||||
resetAsyncStoreWithClient,
|
||||
} from "../test-utils";
|
||||
import defaultDispatcher from "../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../src/dispatcher/actions";
|
||||
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
||||
import { CallStore } from "../../src/stores/CallStore";
|
||||
import { WidgetMessagingStore } from "../../src/stores/widgets/WidgetMessagingStore";
|
||||
import DMRoomMap from "../../src/utils/DMRoomMap";
|
||||
import ToastStore from "../../src/stores/ToastStore";
|
||||
import { getIncomingCallToastKey, IncomingCallToast } from "../../src/toasts/IncomingCallToast";
|
||||
|
||||
describe("IncomingCallEvent", () => {
|
||||
useMockedCalls();
|
||||
Object.defineProperty(navigator, "mediaDevices", { value: { enumerateDevices: () => [] } });
|
||||
jest.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(async () => { });
|
||||
|
||||
let client: Mocked<MatrixClient>;
|
||||
let room: Room;
|
||||
let alice: RoomMember;
|
||||
let bob: RoomMember;
|
||||
let call: MockedCall;
|
||||
let widget: Widget;
|
||||
const dmRoomMap = {
|
||||
getUserIdForRoomId: jest.fn(),
|
||||
} as unknown as DMRoomMap;
|
||||
const toastStore = {
|
||||
dismissToast: jest.fn(),
|
||||
} as unknown as ToastStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
stubClient();
|
||||
client = mocked(MatrixClientPeg.get());
|
||||
|
||||
room = new Room("!1:example.org", client, "@alice:example.org");
|
||||
|
||||
alice = mkRoomMember(room.roomId, "@alice:example.org");
|
||||
bob = mkRoomMember(room.roomId, "@bob:example.org");
|
||||
|
||||
client.getRoom.mockImplementation(roomId => roomId === room.roomId ? room : null);
|
||||
client.getRooms.mockReturnValue([room]);
|
||||
client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
|
||||
|
||||
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(
|
||||
store => setupAsyncStoreWithClient(store, client),
|
||||
));
|
||||
|
||||
MockedCall.create(room, "1");
|
||||
const maybeCall = CallStore.instance.getCall(room.roomId);
|
||||
if (!(maybeCall instanceof MockedCall)) throw new Error("Failed to create call");
|
||||
call = maybeCall;
|
||||
|
||||
widget = new Widget(call.widget);
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
|
||||
stop: () => { },
|
||||
} as unknown as ClientWidgetApi);
|
||||
|
||||
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
|
||||
jest.spyOn(ToastStore, "sharedInstance").mockReturnValue(toastStore);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
cleanup(); // Unmount before we do any cleanup that might update the component
|
||||
call.destroy();
|
||||
WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
|
||||
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(resetAsyncStoreWithClient));
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const renderToast = () => { render(<IncomingCallToast callEvent={call.event} />); };
|
||||
|
||||
it("correctly shows all the information", () => {
|
||||
call.participants = new Set([alice, bob]);
|
||||
renderToast();
|
||||
|
||||
screen.getByText("Video call started");
|
||||
screen.getByText("Video");
|
||||
screen.getByLabelText("2 participants");
|
||||
|
||||
screen.getByRole("button", { name: "Join" });
|
||||
screen.getByRole("button", { name: "Close" });
|
||||
});
|
||||
|
||||
it("correctly renders toast without a call", () => {
|
||||
call.destroy();
|
||||
renderToast();
|
||||
|
||||
screen.getByText("Video call started");
|
||||
screen.getByText("Video");
|
||||
|
||||
screen.getByRole("button", { name: "Join" });
|
||||
screen.getByRole("button", { name: "Close" });
|
||||
});
|
||||
|
||||
it("joins the call and closes the toast", async () => {
|
||||
renderToast();
|
||||
|
||||
const dispatcherSpy = jest.fn();
|
||||
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Join" }));
|
||||
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
view_call: true,
|
||||
}));
|
||||
await waitFor(() => expect(toastStore.dismissToast).toHaveBeenCalledWith(
|
||||
getIncomingCallToastKey(call.event.getStateKey()!),
|
||||
));
|
||||
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
});
|
||||
|
||||
it("closes the toast", async () => {
|
||||
renderToast();
|
||||
|
||||
const dispatcherSpy = jest.fn();
|
||||
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Close" }));
|
||||
await waitFor(() => expect(toastStore.dismissToast).toHaveBeenCalledWith(
|
||||
getIncomingCallToastKey(call.event.getStateKey()!),
|
||||
));
|
||||
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
});
|
||||
|
||||
it("closes toast when the call lobby is viewed", async () => {
|
||||
renderToast();
|
||||
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
view_call: true,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(toastStore.dismissToast).toHaveBeenCalledWith(
|
||||
getIncomingCallToastKey(call.event.getStateKey()!),
|
||||
));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Copyright 2022 Oliver Sand
|
||||
Copyright 2022 Nordeck IT + Consulting GmbH.
|
||||
|
||||
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 WidgetUtils from "../../src/utils/WidgetUtils";
|
||||
import { mockPlatformPeg } from "../test-utils";
|
||||
|
||||
describe("getLocalJitsiWrapperUrl", () => {
|
||||
it('should generate jitsi URL (for defaults)', () => {
|
||||
mockPlatformPeg();
|
||||
|
||||
expect(WidgetUtils.getLocalJitsiWrapperUrl()).toEqual(
|
||||
'https://app.element.io/jitsi.html'
|
||||
+ '#conferenceDomain=$domain'
|
||||
+ '&conferenceId=$conferenceId'
|
||||
+ '&isAudioOnly=$isAudioOnly'
|
||||
+ '&isVideoChannel=$isVideoChannel'
|
||||
+ '&displayName=$matrix_display_name'
|
||||
+ '&avatarUrl=$matrix_avatar_url'
|
||||
+ '&userId=$matrix_user_id'
|
||||
+ '&roomId=$matrix_room_id'
|
||||
+ '&theme=$theme'
|
||||
+ '&roomName=$roomName'
|
||||
+ '&supportsScreensharing=true'
|
||||
+ '&language=$org.matrix.msc2873.client_language',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -36,7 +36,7 @@ describe('recordClientInformation()', () => {
|
||||
|
||||
const sdkConfig: IConfigOptions = {
|
||||
brand: 'Test Brand',
|
||||
element_call: { url: '', use_exclusively: false },
|
||||
element_call: { url: '', use_exclusively: false, brand: "Element Call" },
|
||||
};
|
||||
|
||||
const platform = {
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
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 { DeviceType, ExtendedDeviceInformation, parseUserAgent } from "../../../src/utils/device/parseUserAgent";
|
||||
|
||||
const makeDeviceExtendedInfo = (
|
||||
deviceType: DeviceType,
|
||||
deviceModel?: string,
|
||||
deviceOperatingSystem?: string,
|
||||
clientName?: string,
|
||||
clientVersion?: string,
|
||||
): ExtendedDeviceInformation => ({
|
||||
deviceType,
|
||||
deviceModel,
|
||||
deviceOperatingSystem,
|
||||
client: clientName && [clientName, clientVersion].filter(Boolean).join(' '),
|
||||
});
|
||||
|
||||
/* eslint-disable max-len */
|
||||
const ANDROID_UA = [
|
||||
// New User Agent Implementation
|
||||
"Element dbg/1.5.0-dev (Xiaomi Mi 9T; Android 11; RKQ1.200826.002 test-keys; Flavour GooglePlay; MatrixAndroidSdk2 1.5.2)",
|
||||
"Element/1.5.0 (Samsung SM-G960F; Android 6.0.1; RKQ1.200826.002; Flavour FDroid; MatrixAndroidSdk2 1.5.2)",
|
||||
"Element/1.5.0 (Google Nexus 5; Android 7.0; RKQ1.200826.002 test test; Flavour FDroid; MatrixAndroidSdk2 1.5.2)",
|
||||
"Element/1.5.0 (Google (Nexus) 5; Android 7.0; RKQ1.200826.002 test test; Flavour FDroid; MatrixAndroidSdk2 1.5.2)",
|
||||
"Element/1.5.0 (Google (Nexus) (5); Android 7.0; RKQ1.200826.002 test test; Flavour FDroid; MatrixAndroidSdk2 1.5.2)",
|
||||
// Legacy User Agent Implementation
|
||||
"Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSdk2 1.0)",
|
||||
"Element/1.0.0 (Linux; Android 7.0; SM-G610M Build/NRD90M; Flavour GPlay; MatrixAndroidSdk2 1.0)",
|
||||
];
|
||||
|
||||
const ANDROID_EXPECTED_RESULT = [
|
||||
makeDeviceExtendedInfo(DeviceType.Mobile, "Xiaomi Mi 9T", "Android 11"),
|
||||
makeDeviceExtendedInfo(DeviceType.Mobile, "Samsung SM-G960F", "Android 6.0.1"),
|
||||
makeDeviceExtendedInfo(DeviceType.Mobile, "LG Nexus 5", "Android 7.0"),
|
||||
makeDeviceExtendedInfo(DeviceType.Mobile, "Google (Nexus) 5", "Android 7.0"),
|
||||
makeDeviceExtendedInfo(DeviceType.Mobile, "Google (Nexus) (5)", "Android 7.0"),
|
||||
makeDeviceExtendedInfo(DeviceType.Mobile, "Samsung SM-A510F", "Android 6.0.1"),
|
||||
makeDeviceExtendedInfo(DeviceType.Mobile, "Samsung SM-G610M", "Android 7.0"),
|
||||
];
|
||||
|
||||
const IOS_UA = [
|
||||
"Element/1.8.21 (iPhone; iOS 15.2; Scale/3.00)",
|
||||
"Element/1.8.21 (iPhone XS Max; iOS 15.2; Scale/3.00)",
|
||||
"Element/1.8.21 (iPad Pro (11-inch); iOS 15.2; Scale/3.00)",
|
||||
"Element/1.8.21 (iPad Pro (12.9-inch) (3rd generation); iOS 15.2; Scale/3.00)",
|
||||
];
|
||||
const IOS_EXPECTED_RESULT = [
|
||||
makeDeviceExtendedInfo(DeviceType.Mobile, "Apple iPhone", "iOS 15.2"),
|
||||
makeDeviceExtendedInfo(DeviceType.Mobile, "Apple iPhone XS Max", "iOS 15.2"),
|
||||
makeDeviceExtendedInfo(DeviceType.Mobile, "iPad Pro (11-inch)", "iOS 15.2"),
|
||||
makeDeviceExtendedInfo(DeviceType.Mobile, "iPad Pro (12.9-inch) (3rd generation)", "iOS 15.2"),
|
||||
];
|
||||
const DESKTOP_UA = [
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2022091301 Chrome/104.0.5112.102" +
|
||||
" Electron/20.1.1 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2022091301 Chrome/104.0.5112.102 Electron/20.1.1 Safari/537.36",
|
||||
];
|
||||
const DESKTOP_EXPECTED_RESULT = [
|
||||
makeDeviceExtendedInfo(DeviceType.Desktop, undefined, "Mac OS 10.15.7", "Electron", "20"),
|
||||
makeDeviceExtendedInfo(DeviceType.Desktop, undefined, "Windows 10", "Electron", "20"),
|
||||
];
|
||||
|
||||
const WEB_UA = [
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:39.0) Gecko/20100101 Firefox/39.0",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/600.3.18 (KHTML, like Gecko) Version/8.0.3 Safari/600.3.18",
|
||||
"Mozilla/5.0 (Windows NT 6.0; rv:40.0) Gecko/20100101 Firefox/40.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246",
|
||||
// using mobile browser
|
||||
"Mozilla/5.0 (iPad; CPU OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H321 Safari/600.1.4",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H321 Safari/600.1.4",
|
||||
"Mozilla/5.0 (Linux; Android 9; SM-G973U Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
];
|
||||
|
||||
const WEB_EXPECTED_RESULT = [
|
||||
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Mac OS 10.15.7", "Chrome", "104"),
|
||||
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Windows 10", "Chrome", "104"),
|
||||
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Mac OS 10.10", "Firefox", "39"),
|
||||
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Mac OS 10.10.2", "Safari", "8"),
|
||||
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Windows Vista", "Firefox", "40"),
|
||||
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Windows 10", "Edge", "12"),
|
||||
// using mobile browser
|
||||
makeDeviceExtendedInfo(DeviceType.Web, "Apple iPad", "iOS 8.4.1", "Mobile Safari", "8"),
|
||||
makeDeviceExtendedInfo(DeviceType.Web, "Apple iPhone", "iOS 8.4.1", "Mobile Safari", "8"),
|
||||
makeDeviceExtendedInfo(DeviceType.Web, "Samsung SM-G973U", "Android 9", "Chrome", "69"),
|
||||
|
||||
];
|
||||
|
||||
const MISC_UA = [
|
||||
"AppleTV11,1/11.1",
|
||||
"Curl Client/1.0",
|
||||
"banana",
|
||||
"",
|
||||
];
|
||||
|
||||
const MISC_EXPECTED_RESULT = [
|
||||
makeDeviceExtendedInfo(DeviceType.Unknown, "Apple Apple TV", undefined, undefined, undefined),
|
||||
makeDeviceExtendedInfo(DeviceType.Unknown, undefined, undefined, undefined, undefined),
|
||||
makeDeviceExtendedInfo(DeviceType.Unknown, undefined, undefined, undefined, undefined),
|
||||
makeDeviceExtendedInfo(DeviceType.Unknown, undefined, undefined, undefined, undefined),
|
||||
];
|
||||
/* eslint-disable max-len */
|
||||
|
||||
describe('parseUserAgent()', () => {
|
||||
it('returns deviceType unknown when user agent is falsy', () => {
|
||||
expect(parseUserAgent(undefined)).toEqual({
|
||||
deviceType: DeviceType.Unknown,
|
||||
});
|
||||
});
|
||||
|
||||
type TestCase = [string, ExtendedDeviceInformation];
|
||||
|
||||
const testPlatform = (platform: string, userAgents: string[], results: ExtendedDeviceInformation[]): void => {
|
||||
const testCases: TestCase[] = userAgents.map((userAgent, index) => [userAgent, results[index]]);
|
||||
|
||||
describe(platform, () => {
|
||||
it.each(
|
||||
testCases,
|
||||
)('Parses user agent correctly - %s', (userAgent, expectedResult) => {
|
||||
expect(parseUserAgent(userAgent)).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
testPlatform('Android', ANDROID_UA, ANDROID_EXPECTED_RESULT);
|
||||
testPlatform('iOS', IOS_UA, IOS_EXPECTED_RESULT);
|
||||
testPlatform('Desktop', DESKTOP_UA, DESKTOP_EXPECTED_RESULT);
|
||||
testPlatform('Web', WEB_UA, WEB_EXPECTED_RESULT);
|
||||
testPlatform('Misc', MISC_UA, MISC_EXPECTED_RESULT);
|
||||
});
|
||||
@@ -20,6 +20,8 @@ import { mocked } from "jest-mock";
|
||||
import {
|
||||
localNotificationsAreSilenced,
|
||||
getLocalNotificationAccountDataEventType,
|
||||
createLocalNotificationSettingsIfNeeded,
|
||||
deviceNotificationSettingsKeys,
|
||||
} from "../../src/utils/notifications";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import { getMockClientWithEventEmitter } from "../test-utils/client";
|
||||
@@ -46,6 +48,38 @@ describe('notifications', () => {
|
||||
mocked(SettingsStore).getValue.mockReturnValue(false);
|
||||
});
|
||||
|
||||
describe('createLocalNotification', () => {
|
||||
it('creates account data event', async () => {
|
||||
await createLocalNotificationSettingsIfNeeded(mockClient);
|
||||
const event = mockClient.getAccountData(accountDataEventKey);
|
||||
expect(event?.getContent().is_silenced).toBe(true);
|
||||
});
|
||||
|
||||
it.each(deviceNotificationSettingsKeys)(
|
||||
'unsilenced for existing sessions when %s setting is truthy',
|
||||
async (settingKey) => {
|
||||
mocked(SettingsStore)
|
||||
.getValue
|
||||
.mockImplementation((key) => {
|
||||
return key === settingKey;
|
||||
});
|
||||
|
||||
await createLocalNotificationSettingsIfNeeded(mockClient);
|
||||
const event = mockClient.getAccountData(accountDataEventKey);
|
||||
expect(event?.getContent().is_silenced).toBe(false);
|
||||
});
|
||||
|
||||
it("does not override an existing account event data", async () => {
|
||||
mockClient.setAccountData(accountDataEventKey, {
|
||||
is_silenced: false,
|
||||
});
|
||||
|
||||
await createLocalNotificationSettingsIfNeeded(mockClient);
|
||||
const event = mockClient.getAccountData(accountDataEventKey);
|
||||
expect(event?.getContent().is_silenced).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('localNotificationsAreSilenced', () => {
|
||||
it('defaults to true when no setting exists', () => {
|
||||
expect(localNotificationsAreSilenced(mockClient)).toBeTruthy();
|
||||
|
||||
Reference in New Issue
Block a user