Merge remote-tracking branch 'origin/develop' into florianduros/fix-white-black-theme-switch
This commit is contained in:
commit
ba783b8441
131 changed files with 4015 additions and 1215 deletions
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
import { EventEmitter } from "events";
|
||||
import { mocked } from "jest-mock";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import DeviceListener from "../src/DeviceListener";
|
||||
import { MatrixClientPeg } from "../src/MatrixClientPeg";
|
||||
|
@ -27,6 +28,9 @@ import * as BulkUnverifiedSessionsToast from "../src/toasts/BulkUnverifiedSessio
|
|||
import { isSecretStorageBeingAccessed } from "../src/SecurityManager";
|
||||
import dis from "../src/dispatcher/dispatcher";
|
||||
import { Action } from "../src/dispatcher/actions";
|
||||
import SettingsStore from "../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../src/settings/SettingLevel";
|
||||
import { mockPlatformPeg } from "./test-utils";
|
||||
|
||||
// don't litter test console with logs
|
||||
jest.mock("matrix-js-sdk/src/logger");
|
||||
|
@ -40,7 +44,10 @@ jest.mock("../src/SecurityManager", () => ({
|
|||
isSecretStorageBeingAccessed: jest.fn(), accessSecretStorage: jest.fn(),
|
||||
}));
|
||||
|
||||
const deviceId = 'my-device-id';
|
||||
|
||||
class MockClient extends EventEmitter {
|
||||
isGuest = jest.fn();
|
||||
getUserId = jest.fn();
|
||||
getKeyBackupVersion = jest.fn().mockResolvedValue(undefined);
|
||||
getRooms = jest.fn().mockReturnValue([]);
|
||||
|
@ -57,6 +64,8 @@ class MockClient extends EventEmitter {
|
|||
downloadKeys = jest.fn();
|
||||
isRoomEncrypted = jest.fn();
|
||||
getClientWellKnown = jest.fn();
|
||||
getDeviceId = jest.fn().mockReturnValue(deviceId);
|
||||
setAccountData = jest.fn();
|
||||
}
|
||||
const mockDispatcher = mocked(dis);
|
||||
const flushPromises = async () => await new Promise(process.nextTick);
|
||||
|
@ -75,8 +84,12 @@ describe('DeviceListener', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
mockPlatformPeg({
|
||||
getAppVersion: jest.fn().mockResolvedValue('1.2.3'),
|
||||
});
|
||||
mockClient = new MockClient();
|
||||
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient);
|
||||
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
|
||||
});
|
||||
|
||||
const createAndStart = async (): Promise<DeviceListener> => {
|
||||
|
@ -86,6 +99,115 @@ describe('DeviceListener', () => {
|
|||
return instance;
|
||||
};
|
||||
|
||||
describe('client information', () => {
|
||||
it('watches device client information setting', async () => {
|
||||
const watchSettingSpy = jest.spyOn(SettingsStore, 'watchSetting');
|
||||
const unwatchSettingSpy = jest.spyOn(SettingsStore, 'unwatchSetting');
|
||||
const deviceListener = await createAndStart();
|
||||
|
||||
expect(watchSettingSpy).toHaveBeenCalledWith(
|
||||
'deviceClientInformationOptIn', null, expect.any(Function),
|
||||
);
|
||||
|
||||
deviceListener.stop();
|
||||
|
||||
expect(unwatchSettingSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('when device client information feature is enabled', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, 'getValue').mockImplementation(
|
||||
settingName => settingName === 'deviceClientInformationOptIn',
|
||||
);
|
||||
});
|
||||
it('saves client information on start', async () => {
|
||||
await createAndStart();
|
||||
|
||||
expect(mockClient.setAccountData).toHaveBeenCalledWith(
|
||||
`io.element.matrix_client_information.${deviceId}`,
|
||||
{ name: 'Element', url: 'localhost', version: '1.2.3' },
|
||||
);
|
||||
});
|
||||
|
||||
it('catches error and logs when saving client information fails', async () => {
|
||||
const errorLogSpy = jest.spyOn(logger, 'error');
|
||||
const error = new Error('oups');
|
||||
mockClient.setAccountData.mockRejectedValue(error);
|
||||
|
||||
// doesn't throw
|
||||
await createAndStart();
|
||||
|
||||
expect(errorLogSpy).toHaveBeenCalledWith(
|
||||
'Failed to record client information',
|
||||
error,
|
||||
);
|
||||
});
|
||||
|
||||
it('saves client information on logged in action', async () => {
|
||||
const instance = await createAndStart();
|
||||
|
||||
mockClient.setAccountData.mockClear();
|
||||
|
||||
// @ts-ignore calling private function
|
||||
instance.onAction({ action: Action.OnLoggedIn });
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(mockClient.setAccountData).toHaveBeenCalledWith(
|
||||
`io.element.matrix_client_information.${deviceId}`,
|
||||
{ name: 'Element', url: 'localhost', version: '1.2.3' },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when device client information feature is disabled', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
|
||||
});
|
||||
|
||||
it('does not save client information on start', async () => {
|
||||
await createAndStart();
|
||||
|
||||
expect(mockClient.setAccountData).not.toHaveBeenCalledWith(
|
||||
`io.element.matrix_client_information.${deviceId}`,
|
||||
{ name: 'Element', url: 'localhost', version: '1.2.3' },
|
||||
);
|
||||
});
|
||||
|
||||
it('does not save client information on logged in action', async () => {
|
||||
const instance = await createAndStart();
|
||||
|
||||
// @ts-ignore calling private function
|
||||
instance.onAction({ action: Action.OnLoggedIn });
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(mockClient.setAccountData).not.toHaveBeenCalledWith(
|
||||
`io.element.matrix_client_information.${deviceId}`,
|
||||
{ name: 'Element', url: 'localhost', version: '1.2.3' },
|
||||
);
|
||||
});
|
||||
|
||||
it('saves client information after setting is enabled', async () => {
|
||||
const watchSettingSpy = jest.spyOn(SettingsStore, 'watchSetting');
|
||||
await createAndStart();
|
||||
|
||||
const [settingName, roomId, callback] = watchSettingSpy.mock.calls[0];
|
||||
expect(settingName).toEqual('deviceClientInformationOptIn');
|
||||
expect(roomId).toBeNull();
|
||||
|
||||
callback('deviceClientInformationOptIn', null, SettingLevel.DEVICE, SettingLevel.DEVICE, true);
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(mockClient.setAccountData).toHaveBeenCalledWith(
|
||||
`io.element.matrix_client_information.${deviceId}`,
|
||||
{ name: 'Element', url: 'localhost', version: '1.2.3' },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('recheck', () => {
|
||||
it('does nothing when cross signing feature is not supported', async () => {
|
||||
mockClient.doesServerSupportUnstableFeature.mockResolvedValue(false);
|
||||
|
|
85
test/Notifier-test.ts
Normal file
85
test/Notifier-test.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
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 { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import Notifier from "../src/Notifier";
|
||||
import { getLocalNotificationAccountDataEventType } from "../src/utils/notifications";
|
||||
import { getMockClientWithEventEmitter, mkEvent, mkRoom, mockPlatformPeg } from "./test-utils";
|
||||
|
||||
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,
|
||||
type: "m.room.message",
|
||||
user: "@user1:server",
|
||||
room: roomId,
|
||||
content: {},
|
||||
});
|
||||
const testRoom = mkRoom(mockClient, roomId);
|
||||
|
||||
beforeEach(() => {
|
||||
accountDataStore = {};
|
||||
MockPlatform = mockPlatformPeg({
|
||||
supportsNotifications: jest.fn().mockReturnValue(true),
|
||||
maySendNotifications: jest.fn().mockReturnValue(true),
|
||||
displayNotification: jest.fn(),
|
||||
});
|
||||
|
||||
Notifier.isBodyEnabled = jest.fn().mockReturnValue(true);
|
||||
});
|
||||
|
||||
describe("_displayPopupNotification", () => {
|
||||
it.each([
|
||||
{ silenced: true, count: 0 },
|
||||
{ silenced: false, count: 1 },
|
||||
])("does not dispatch when notifications are silenced", ({ silenced, count }) => {
|
||||
mockClient.setAccountData(accountDataEventKey, { is_silenced: silenced });
|
||||
Notifier._displayPopupNotification(testEvent, testRoom);
|
||||
expect(MockPlatform.displayNotification).toHaveBeenCalledTimes(count);
|
||||
});
|
||||
});
|
||||
|
||||
describe("_playAudioNotification", () => {
|
||||
it.each([
|
||||
{ silenced: true, count: 0 },
|
||||
{ silenced: false, count: 1 },
|
||||
])("does not dispatch when notifications are silenced", ({ silenced, count }) => {
|
||||
// It's not ideal to only look at whether this function has been called
|
||||
// but avoids starting to look into DOM stuff
|
||||
Notifier.getSoundForRoom = jest.fn();
|
||||
|
||||
mockClient.setAccountData(accountDataEventKey, { is_silenced: silenced });
|
||||
Notifier._playAudioNotification(testEvent, testRoom);
|
||||
expect(Notifier.getSoundForRoom).toHaveBeenCalledTimes(count);
|
||||
});
|
||||
});
|
||||
});
|
150
test/components/views/messages/CallEvent-test.tsx
Normal file
150
test/components/views/messages/CallEvent-test.tsx
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
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, act, 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, PendingEventOrdering } 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,
|
||||
wrapInMatrixClientContext,
|
||||
} from "../../../test-utils";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import { CallEvent as UnwrappedCallEvent } from "../../../../src/components/views/messages/CallEvent";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { CallStore } from "../../../../src/stores/CallStore";
|
||||
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
|
||||
import { ConnectionState } from "../../../../src/models/Call";
|
||||
|
||||
const CallEvent = wrapInMatrixClientContext(UnwrappedCallEvent);
|
||||
|
||||
describe("CallEvent", () => {
|
||||
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;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
|
||||
stubClient();
|
||||
client = mocked(MatrixClientPeg.get());
|
||||
client.getUserId.mockReturnValue("@alice:example.org");
|
||||
|
||||
room = new Room("!1:example.org", client, "@alice:example.org", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
||||
alice = mkRoomMember(room.roomId, "@alice:example.org");
|
||||
bob = mkRoomMember(room.roomId, "@bob:example.org");
|
||||
jest.spyOn(room, "getMember").mockImplementation(
|
||||
userId => [alice, bob].find(member => member.userId === userId) ?? 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),
|
||||
));
|
||||
|
||||
MockedCall.create(room, "1");
|
||||
const maybeCall = CallStore.instance.get(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);
|
||||
});
|
||||
|
||||
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));
|
||||
client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]);
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const renderEvent = () => { render(<CallEvent mxEvent={call.event} />); };
|
||||
|
||||
it("shows a message and duration if the call was ended", () => {
|
||||
jest.advanceTimersByTime(90000);
|
||||
call.destroy();
|
||||
renderEvent();
|
||||
|
||||
screen.getByText("Video call ended");
|
||||
screen.getByText("1m 30s");
|
||||
});
|
||||
|
||||
it("shows placeholder info if the call isn't loaded yet", () => {
|
||||
jest.spyOn(CallStore.instance, "get").mockReturnValue(null);
|
||||
jest.advanceTimersByTime(90000);
|
||||
renderEvent();
|
||||
|
||||
screen.getByText("@alice:example.org started a video call");
|
||||
expect(screen.getByRole("button", { name: "Join" })).toHaveAttribute("aria-disabled", "true");
|
||||
});
|
||||
|
||||
it("shows call details and connection controls if the call is loaded", async () => {
|
||||
jest.advanceTimersByTime(90000);
|
||||
call.participants = new Set([alice, bob]);
|
||||
renderEvent();
|
||||
|
||||
screen.getByText("@alice:example.org started a video call");
|
||||
screen.getByLabelText("2 participants");
|
||||
screen.getByText("1m 30s");
|
||||
|
||||
// Test that the join button works
|
||||
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,
|
||||
}));
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
await act(() => call.connect());
|
||||
|
||||
// Test that the leave button works
|
||||
fireEvent.click(screen.getByRole("button", { name: "Leave" }));
|
||||
await waitFor(() => screen.getByRole("button", { name: "Join" }));
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
});
|
||||
});
|
|
@ -57,7 +57,7 @@ describe("<VoiceRecordComposerTile/>", () => {
|
|||
durationSeconds: 1337,
|
||||
contentType: "audio/ogg",
|
||||
getPlayback: () => ({
|
||||
thumbnailWaveform: [],
|
||||
thumbnailWaveform: [1.4, 2.5, 3.6],
|
||||
}),
|
||||
} as unknown as VoiceRecording;
|
||||
voiceRecordComposerTile = mount(<VoiceRecordComposerTile {...props} />);
|
||||
|
@ -88,7 +88,11 @@ describe("<VoiceRecordComposerTile/>", () => {
|
|||
"msgtype": MsgType.Audio,
|
||||
"org.matrix.msc1767.audio": {
|
||||
"duration": 1337000,
|
||||
"waveform": [],
|
||||
"waveform": [
|
||||
1434,
|
||||
2560,
|
||||
3686,
|
||||
],
|
||||
},
|
||||
"org.matrix.msc1767.file": {
|
||||
"file": undefined,
|
||||
|
|
|
@ -15,7 +15,14 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
// eslint-disable-next-line deprecate/import
|
||||
import { mount, ReactWrapper } from 'enzyme';
|
||||
import { IPushRule, IPushRules, RuleId, IPusher } from 'matrix-js-sdk/src/matrix';
|
||||
import {
|
||||
IPushRule,
|
||||
IPushRules,
|
||||
RuleId,
|
||||
IPusher,
|
||||
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
||||
MatrixEvent,
|
||||
} from 'matrix-js-sdk/src/matrix';
|
||||
import { IThreepid, ThreepidMedium } from 'matrix-js-sdk/src/@types/threepids';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
|
@ -67,6 +74,17 @@ describe('<Notifications />', () => {
|
|||
setPushRuleEnabled: jest.fn(),
|
||||
setPushRuleActions: jest.fn(),
|
||||
getRooms: jest.fn().mockReturnValue([]),
|
||||
getAccountData: jest.fn().mockImplementation(eventType => {
|
||||
if (eventType.startsWith(LOCAL_NOTIFICATION_SETTINGS_PREFIX.name)) {
|
||||
return new MatrixEvent({
|
||||
type: eventType,
|
||||
content: {
|
||||
is_silenced: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
}),
|
||||
setAccountData: jest.fn(),
|
||||
});
|
||||
mockClient.getPushRules.mockResolvedValue(pushRules);
|
||||
|
||||
|
@ -117,6 +135,7 @@ describe('<Notifications />', () => {
|
|||
const component = await getComponentAndWait();
|
||||
|
||||
expect(findByTestId(component, 'notif-master-switch').length).toBeTruthy();
|
||||
expect(findByTestId(component, 'notif-device-switch').length).toBeTruthy();
|
||||
expect(findByTestId(component, 'notif-setting-notificationsEnabled').length).toBeTruthy();
|
||||
expect(findByTestId(component, 'notif-setting-notificationBodyEnabled').length).toBeTruthy();
|
||||
expect(findByTestId(component, 'notif-setting-audioNotificationsEnabled').length).toBeTruthy();
|
||||
|
|
|
@ -112,16 +112,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -214,6 +214,7 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
|||
class="mx_Checkbox mx_SelectableDeviceTile_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
|
||||
>
|
||||
<input
|
||||
data-testid="device-tile-checkbox-device_2"
|
||||
id="device-tile-checkbox-device_2"
|
||||
type="checkbox"
|
||||
/>
|
||||
|
@ -234,16 +235,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -295,6 +296,7 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
|||
class="mx_Checkbox mx_SelectableDeviceTile_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
|
||||
>
|
||||
<input
|
||||
data-testid="device-tile-checkbox-device_3"
|
||||
id="device-tile-checkbox-device_3"
|
||||
type="checkbox"
|
||||
/>
|
||||
|
@ -315,16 +317,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -60,9 +60,10 @@ exports[`<Notifications /> main notification switches renders only enable notifi
|
|||
className="mx_UserNotifSettings"
|
||||
>
|
||||
<LabelledToggleSwitch
|
||||
caption="Turn off to disable notifications on all your devices and sessions"
|
||||
data-test-id="notif-master-switch"
|
||||
disabled={false}
|
||||
label="Enable for this account"
|
||||
label="Enable notifications for this account"
|
||||
onChange={[Function]}
|
||||
value={false}
|
||||
>
|
||||
|
@ -72,10 +73,18 @@ exports[`<Notifications /> main notification switches renders only enable notifi
|
|||
<span
|
||||
className="mx_SettingsFlag_label"
|
||||
>
|
||||
Enable for this account
|
||||
Enable notifications for this account
|
||||
<br />
|
||||
<Caption>
|
||||
<span
|
||||
className="mx_Caption"
|
||||
>
|
||||
Turn off to disable notifications on all your devices and sessions
|
||||
</span>
|
||||
</Caption>
|
||||
</span>
|
||||
<_default
|
||||
aria-label="Enable for this account"
|
||||
aria-label="Enable notifications for this account"
|
||||
checked={false}
|
||||
disabled={false}
|
||||
onChange={[Function]}
|
||||
|
@ -83,7 +92,7 @@ exports[`<Notifications /> main notification switches renders only enable notifi
|
|||
<AccessibleButton
|
||||
aria-checked={false}
|
||||
aria-disabled={false}
|
||||
aria-label="Enable for this account"
|
||||
aria-label="Enable notifications for this account"
|
||||
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
|
@ -93,7 +102,7 @@ exports[`<Notifications /> main notification switches renders only enable notifi
|
|||
<div
|
||||
aria-checked={false}
|
||||
aria-disabled={false}
|
||||
aria-label="Enable for this account"
|
||||
aria-label="Enable notifications for this account"
|
||||
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
|
|
|
@ -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,
|
||||
|
@ -33,7 +35,7 @@ describe('<DeviceDetails />', () => {
|
|||
isLoading: false,
|
||||
onSignOutDevice: jest.fn(),
|
||||
saveDeviceName: jest.fn(),
|
||||
setPusherEnabled: jest.fn(),
|
||||
setPushNotifications: jest.fn(),
|
||||
supportsMSC3881: true,
|
||||
};
|
||||
|
||||
|
@ -58,6 +60,7 @@ describe('<DeviceDetails />', () => {
|
|||
display_name: 'My Device',
|
||||
last_seen_ip: '123.456.789',
|
||||
last_seen_ts: now - 60000000,
|
||||
appName: 'Element Web',
|
||||
};
|
||||
const { container } = render(getComponent({ device }));
|
||||
expect(container).toMatchSnapshot();
|
||||
|
@ -157,6 +160,27 @@ describe('<DeviceDetails />', () => {
|
|||
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
expect(defaultProps.setPusherEnabled).toHaveBeenCalledWith(device.device_id, !enabled);
|
||||
expect(defaultProps.setPushNotifications).toHaveBeenCalledWith(device.device_id, !enabled);
|
||||
});
|
||||
|
||||
it('changes the local notifications settings status when clicked', () => {
|
||||
const device = {
|
||||
...baseDevice,
|
||||
};
|
||||
|
||||
const enabled = false;
|
||||
|
||||
const { getByTestId } = render(getComponent({
|
||||
device,
|
||||
localNotificationSettings: {
|
||||
is_silenced: !enabled,
|
||||
},
|
||||
isSigningOut: true,
|
||||
}));
|
||||
|
||||
const checkbox = getByTestId('device-detail-push-notification-checkbox');
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
expect(defaultProps.setPushNotifications).toHaveBeenCalledWith(device.device_id, !enabled);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 = {}) => (
|
||||
|
|
|
@ -17,15 +17,15 @@ limitations under the License.
|
|||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { DeviceType } from '../../../../../src/components/views/settings/devices/DeviceType';
|
||||
import { DeviceTypeIcon } from '../../../../../src/components/views/settings/devices/DeviceTypeIcon';
|
||||
|
||||
describe('<DeviceType />', () => {
|
||||
describe('<DeviceTypeIcon />', () => {
|
||||
const defaultProps = {
|
||||
isVerified: false,
|
||||
isSelected: false,
|
||||
};
|
||||
const getComponent = (props = {}) =>
|
||||
<DeviceType {...defaultProps} {...props} />;
|
||||
<DeviceTypeIcon {...defaultProps} {...props} />;
|
||||
|
||||
it('renders an unverified device', () => {
|
||||
const { container } = render(getComponent());
|
|
@ -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,23 +32,39 @@ 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(),
|
||||
onDeviceExpandToggle: jest.fn(),
|
||||
onSignOutDevices: jest.fn(),
|
||||
saveDeviceName: jest.fn(),
|
||||
setPushNotifications: jest.fn(),
|
||||
setPusherEnabled: jest.fn(),
|
||||
setSelectedDeviceIds: jest.fn(),
|
||||
localNotificationSettings: new Map(),
|
||||
expandedDeviceIds: [],
|
||||
signingOutDeviceIds: [],
|
||||
selectedDeviceIds: [],
|
||||
devices: {
|
||||
[unverifiedNoMetadata.device_id]: unverifiedNoMetadata,
|
||||
[verifiedNoMetadata.device_id]: verifiedNoMetadata,
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
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 { fireEvent, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import FilteredDeviceListHeader from '../../../../../src/components/views/settings/devices/FilteredDeviceListHeader';
|
||||
|
||||
describe('<FilteredDeviceListHeader />', () => {
|
||||
const defaultProps = {
|
||||
selectedDeviceCount: 0,
|
||||
isAllSelected: false,
|
||||
toggleSelectAll: jest.fn(),
|
||||
children: <div>test</div>,
|
||||
['data-testid']: 'test123',
|
||||
};
|
||||
const getComponent = (props = {}) => (<FilteredDeviceListHeader {...defaultProps} {...props} />);
|
||||
|
||||
it('renders correctly when no devices are selected', () => {
|
||||
const { container } = render(getComponent());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when all devices are selected', () => {
|
||||
const { container } = render(getComponent({ isAllSelected: true }));
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when some devices are selected', () => {
|
||||
const { getByText } = render(getComponent({ selectedDeviceCount: 2 }));
|
||||
expect(getByText('2 sessions selected')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('clicking checkbox toggles selection', () => {
|
||||
const toggleSelectAll = jest.fn();
|
||||
const { getByTestId } = render(getComponent({ toggleSelectAll }));
|
||||
fireEvent.click(getByTestId('device-select-all-checkbox'));
|
||||
|
||||
expect(toggleSelectAll).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -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(),
|
||||
|
|
|
@ -76,6 +76,7 @@ HTMLCollection [
|
|||
</p>
|
||||
<table
|
||||
class="mx_DeviceDetails_metadataTable"
|
||||
data-testid="device-detail-metadata-session"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -90,39 +91,6 @@ HTMLCollection [
|
|||
alices_device
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
>
|
||||
Last activity
|
||||
</td>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataValue"
|
||||
/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table
|
||||
class="mx_DeviceDetails_metadataTable"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Device
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
>
|
||||
IP address
|
||||
</td>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataValue"
|
||||
/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
@ -183,16 +151,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -299,16 +267,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -64,6 +64,7 @@ exports[`<DeviceDetails /> renders a verified device 1`] = `
|
|||
</p>
|
||||
<table
|
||||
class="mx_DeviceDetails_metadataTable"
|
||||
data-testid="device-detail-metadata-session"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -78,39 +79,6 @@ exports[`<DeviceDetails /> renders a verified device 1`] = `
|
|||
my-device
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
>
|
||||
Last activity
|
||||
</td>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataValue"
|
||||
/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table
|
||||
class="mx_DeviceDetails_metadataTable"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Device
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
>
|
||||
IP address
|
||||
</td>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataValue"
|
||||
/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
@ -198,6 +166,7 @@ exports[`<DeviceDetails /> renders device with metadata 1`] = `
|
|||
</p>
|
||||
<table
|
||||
class="mx_DeviceDetails_metadataTable"
|
||||
data-testid="device-detail-metadata-session"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -228,6 +197,33 @@ exports[`<DeviceDetails /> renders device with metadata 1`] = `
|
|||
</table>
|
||||
<table
|
||||
class="mx_DeviceDetails_metadataTable"
|
||||
data-testid="device-detail-metadata-application"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Application
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
>
|
||||
Name
|
||||
</td>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataValue"
|
||||
>
|
||||
Element Web
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table
|
||||
class="mx_DeviceDetails_metadataTable"
|
||||
data-testid="device-detail-metadata-device"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -336,6 +332,7 @@ exports[`<DeviceDetails /> renders device without metadata 1`] = `
|
|||
</p>
|
||||
<table
|
||||
class="mx_DeviceDetails_metadataTable"
|
||||
data-testid="device-detail-metadata-session"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -350,39 +347,6 @@ exports[`<DeviceDetails /> renders device without metadata 1`] = `
|
|||
my-device
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
>
|
||||
Last activity
|
||||
</td>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataValue"
|
||||
/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table
|
||||
class="mx_DeviceDetails_metadataTable"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Device
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataLabel"
|
||||
>
|
||||
IP address
|
||||
</td>
|
||||
<td
|
||||
class="mxDeviceDetails_metadataValue"
|
||||
/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
|
|
@ -7,16 +7,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -58,16 +58,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -109,16 +109,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -160,16 +160,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<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,58 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<DeviceTypeIcon /> renders a verified device 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<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
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<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
|
||||
aria-label="Unknown device type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,88 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<FilteredDeviceListHeader /> renders correctly when all devices are selected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_FilteredDeviceListHeader"
|
||||
data-testid="test123"
|
||||
>
|
||||
<div
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
|
||||
>
|
||||
<input
|
||||
aria-label="Deselect all"
|
||||
checked=""
|
||||
data-testid="device-select-all-checkbox"
|
||||
id="device-select-all-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
for="device-select-all-checkbox"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_background"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_checkmark"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
class="mx_FilteredDeviceListHeader_label"
|
||||
>
|
||||
Sessions
|
||||
</span>
|
||||
<div>
|
||||
test
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<FilteredDeviceListHeader /> renders correctly when no devices are selected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_FilteredDeviceListHeader"
|
||||
data-testid="test123"
|
||||
>
|
||||
<div
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
|
||||
>
|
||||
<input
|
||||
aria-label="Select all"
|
||||
data-testid="device-select-all-checkbox"
|
||||
id="device-select-all-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
for="device-select-all-checkbox"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_background"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_checkmark"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
class="mx_FilteredDeviceListHeader_label"
|
||||
>
|
||||
Sessions
|
||||
</span>
|
||||
<div>
|
||||
test
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -3,6 +3,7 @@
|
|||
exports[`<SelectableDeviceTile /> renders selected tile 1`] = `
|
||||
<input
|
||||
checked=""
|
||||
data-testid="device-tile-checkbox-my-device"
|
||||
id="device-tile-checkbox-my-device"
|
||||
type="checkbox"
|
||||
/>
|
||||
|
@ -17,6 +18,7 @@ exports[`<SelectableDeviceTile /> renders unselected device tile with checkbox 1
|
|||
class="mx_Checkbox mx_SelectableDeviceTile_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
|
||||
>
|
||||
<input
|
||||
data-testid="device-tile-checkbox-my-device"
|
||||
id="device-tile-checkbox-my-device"
|
||||
type="checkbox"
|
||||
/>
|
||||
|
@ -37,16 +39,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<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,
|
||||
|
|
|
@ -22,7 +22,14 @@ import { logger } from 'matrix-js-sdk/src/logger';
|
|||
import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
|
||||
import { VerificationRequest } from 'matrix-js-sdk/src/crypto/verification/request/VerificationRequest';
|
||||
import { sleep } from 'matrix-js-sdk/src/utils';
|
||||
import { IMyDevice, PUSHER_DEVICE_ID, PUSHER_ENABLED } from 'matrix-js-sdk/src/matrix';
|
||||
import {
|
||||
ClientEvent,
|
||||
IMyDevice,
|
||||
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
||||
MatrixEvent,
|
||||
PUSHER_DEVICE_ID,
|
||||
PUSHER_ENABLED,
|
||||
} from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import SessionManagerTab from '../../../../../../src/components/views/settings/tabs/user/SessionManagerTab';
|
||||
import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext';
|
||||
|
@ -31,10 +38,17 @@ import {
|
|||
getMockClientWithEventEmitter,
|
||||
mkPusher,
|
||||
mockClientMethodsUser,
|
||||
mockPlatformPeg,
|
||||
} from '../../../../../test-utils';
|
||||
import Modal from '../../../../../../src/Modal';
|
||||
import LogoutDialog from '../../../../../../src/components/views/dialogs/LogoutDialog';
|
||||
import { DeviceWithVerification } from '../../../../../../src/components/views/settings/devices/types';
|
||||
import {
|
||||
DeviceSecurityVariation,
|
||||
ExtendedDevice,
|
||||
} from '../../../../../../src/components/views/settings/devices/types';
|
||||
import { INACTIVE_DEVICE_AGE_MS } from '../../../../../../src/components/views/settings/devices/filter';
|
||||
|
||||
mockPlatformPeg();
|
||||
|
||||
describe('<SessionManagerTab />', () => {
|
||||
const aliceId = '@alice:server.org';
|
||||
|
@ -54,6 +68,11 @@ describe('<SessionManagerTab />', () => {
|
|||
last_seen_ts: Date.now() - 600000,
|
||||
};
|
||||
|
||||
const alicesInactiveDevice = {
|
||||
device_id: 'alices_older_mobile_device',
|
||||
last_seen_ts: Date.now() - (INACTIVE_DEVICE_AGE_MS + 1000),
|
||||
};
|
||||
|
||||
const mockCrossSigningInfo = {
|
||||
checkDeviceTrust: jest.fn(),
|
||||
};
|
||||
|
@ -68,9 +87,11 @@ describe('<SessionManagerTab />', () => {
|
|||
deleteMultipleDevices: jest.fn(),
|
||||
generateClientSecret: jest.fn(),
|
||||
setDeviceDetails: jest.fn(),
|
||||
getAccountData: jest.fn(),
|
||||
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(true),
|
||||
getPushers: jest.fn(),
|
||||
setPusher: jest.fn(),
|
||||
setLocalNotificationSettings: jest.fn(),
|
||||
});
|
||||
|
||||
const defaultProps = {};
|
||||
|
@ -83,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}`);
|
||||
|
@ -91,6 +112,36 @@ describe('<SessionManagerTab />', () => {
|
|||
fireEvent.click(toggle);
|
||||
};
|
||||
|
||||
const toggleDeviceSelection = (
|
||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||
deviceId: ExtendedDevice['device_id'],
|
||||
) => {
|
||||
const checkbox = getByTestId(`device-tile-checkbox-${deviceId}`);
|
||||
fireEvent.click(checkbox);
|
||||
};
|
||||
|
||||
const setFilter = async (
|
||||
container: HTMLElement,
|
||||
option: DeviceSecurityVariation | string,
|
||||
) => await act(async () => {
|
||||
const dropdown = container.querySelector('[aria-label="Filter devices"]');
|
||||
|
||||
fireEvent.click(dropdown as Element);
|
||||
// tick to let dropdown render
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(container.querySelector(`#device-list-filter__${option}`) as Element);
|
||||
});
|
||||
|
||||
const isDeviceSelected = (
|
||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||
deviceId: ExtendedDevice['device_id'],
|
||||
): boolean => !!(getByTestId(`device-tile-checkbox-${deviceId}`) as HTMLInputElement).checked;
|
||||
|
||||
const isSelectAllChecked = (
|
||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||
): boolean => !!(getByTestId('device-select-all-checkbox') as HTMLInputElement).checked;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(logger, 'error').mockRestore();
|
||||
|
@ -114,6 +165,19 @@ describe('<SessionManagerTab />', () => {
|
|||
[PUSHER_ENABLED.name]: true,
|
||||
})],
|
||||
});
|
||||
|
||||
mockClient.getAccountData
|
||||
.mockReset()
|
||||
.mockImplementation(eventType => {
|
||||
if (eventType.startsWith(LOCAL_NOTIFICATION_SETTINGS_PREFIX.name)) {
|
||||
return new MatrixEvent({
|
||||
type: eventType,
|
||||
content: {
|
||||
is_silenced: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('renders spinner while devices load', () => {
|
||||
|
@ -179,6 +243,48 @@ describe('<SessionManagerTab />', () => {
|
|||
expect(getByTestId(`device-tile-${alicesDevice.device_id}`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('extends device with client information when available', async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getAccountData.mockImplementation((eventType: string) => {
|
||||
const content = {
|
||||
name: 'Element Web',
|
||||
version: '1.2.3',
|
||||
url: 'test.com',
|
||||
};
|
||||
return new MatrixEvent({
|
||||
type: eventType,
|
||||
content,
|
||||
});
|
||||
});
|
||||
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
// twice for each device
|
||||
expect(mockClient.getAccountData).toHaveBeenCalledTimes(4);
|
||||
|
||||
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
|
||||
// application metadata section rendered
|
||||
expect(getByTestId('device-detail-metadata-application')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders devices without available client information without error', async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
|
||||
const { getByTestId, queryByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
|
||||
// application metadata section not rendered
|
||||
expect(queryByTestId('device-detail-metadata-application')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('renders current session section with an unverified session', async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
@ -258,7 +364,7 @@ describe('<SessionManagerTab />', () => {
|
|||
await flushPromisesWithFakeTimers();
|
||||
|
||||
// unverified filter is set
|
||||
expect(container.querySelector('.mx_FilteredDeviceList_header')).toMatchSnapshot();
|
||||
expect(container.querySelector('.mx_FilteredDeviceListHeader')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('device detail expansion', () => {
|
||||
|
@ -333,7 +439,6 @@ describe('<SessionManagerTab />', () => {
|
|||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||
mockCrossSigningInfo.checkDeviceTrust
|
||||
.mockImplementation((_userId, { deviceId }) => {
|
||||
console.log('hhh', deviceId);
|
||||
if (deviceId === alicesDevice.device_id) {
|
||||
return new DeviceTrustLevel(true, true, false, false);
|
||||
}
|
||||
|
@ -363,7 +468,6 @@ describe('<SessionManagerTab />', () => {
|
|||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||
mockCrossSigningInfo.checkDeviceTrust
|
||||
.mockImplementation((_userId, { deviceId }) => {
|
||||
console.log('hhh', deviceId);
|
||||
if (deviceId === alicesDevice.device_id) {
|
||||
return new DeviceTrustLevel(true, true, false, false);
|
||||
}
|
||||
|
@ -577,6 +681,33 @@ describe('<SessionManagerTab />', () => {
|
|||
'[data-testid="device-detail-sign-out-cta"]',
|
||||
) as Element).getAttribute('aria-disabled')).toEqual(null);
|
||||
});
|
||||
|
||||
it('deletes multiple devices', async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [
|
||||
alicesDevice, alicesMobileDevice, alicesOlderMobileDevice,
|
||||
] });
|
||||
mockClient.deleteMultipleDevices.mockResolvedValue({});
|
||||
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
toggleDeviceSelection(getByTestId, alicesMobileDevice.device_id);
|
||||
toggleDeviceSelection(getByTestId, alicesOlderMobileDevice.device_id);
|
||||
|
||||
fireEvent.click(getByTestId('sign-out-selection-cta'));
|
||||
|
||||
// delete called with both ids
|
||||
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
|
||||
[
|
||||
alicesMobileDevice.device_id,
|
||||
alicesOlderMobileDevice.device_id,
|
||||
],
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -682,6 +813,167 @@ describe('<SessionManagerTab />', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Multiple selection', () => {
|
||||
beforeEach(() => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [
|
||||
alicesDevice, alicesMobileDevice, alicesOlderMobileDevice,
|
||||
] });
|
||||
});
|
||||
|
||||
it('toggles session selection', async () => {
|
||||
const { getByTestId, getByText } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
toggleDeviceSelection(getByTestId, alicesMobileDevice.device_id);
|
||||
toggleDeviceSelection(getByTestId, alicesOlderMobileDevice.device_id);
|
||||
|
||||
// header displayed correctly
|
||||
expect(getByText('2 sessions selected')).toBeTruthy();
|
||||
|
||||
expect(isDeviceSelected(getByTestId, alicesMobileDevice.device_id)).toBeTruthy();
|
||||
expect(isDeviceSelected(getByTestId, alicesOlderMobileDevice.device_id)).toBeTruthy();
|
||||
|
||||
toggleDeviceSelection(getByTestId, alicesMobileDevice.device_id);
|
||||
|
||||
// unselected
|
||||
expect(isDeviceSelected(getByTestId, alicesMobileDevice.device_id)).toBeFalsy();
|
||||
// still selected
|
||||
expect(isDeviceSelected(getByTestId, alicesOlderMobileDevice.device_id)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('cancel button clears selection', async () => {
|
||||
const { getByTestId, getByText } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
toggleDeviceSelection(getByTestId, alicesMobileDevice.device_id);
|
||||
toggleDeviceSelection(getByTestId, alicesOlderMobileDevice.device_id);
|
||||
|
||||
// header displayed correctly
|
||||
expect(getByText('2 sessions selected')).toBeTruthy();
|
||||
|
||||
fireEvent.click(getByTestId('cancel-selection-cta'));
|
||||
|
||||
// unselected
|
||||
expect(isDeviceSelected(getByTestId, alicesMobileDevice.device_id)).toBeFalsy();
|
||||
expect(isDeviceSelected(getByTestId, alicesOlderMobileDevice.device_id)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('changing the filter clears selection', async () => {
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
toggleDeviceSelection(getByTestId, alicesMobileDevice.device_id);
|
||||
expect(isDeviceSelected(getByTestId, alicesMobileDevice.device_id)).toBeTruthy();
|
||||
|
||||
fireEvent.click(getByTestId('unverified-devices-cta'));
|
||||
|
||||
// our session manager waits a tick for rerender
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
// unselected
|
||||
expect(isDeviceSelected(getByTestId, alicesOlderMobileDevice.device_id)).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('toggling select all', () => {
|
||||
it('selects all sessions when there is not existing selection', async () => {
|
||||
const { getByTestId, getByText } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
fireEvent.click(getByTestId('device-select-all-checkbox'));
|
||||
|
||||
// header displayed correctly
|
||||
expect(getByText('2 sessions selected')).toBeTruthy();
|
||||
expect(isSelectAllChecked(getByTestId)).toBeTruthy();
|
||||
|
||||
// devices selected
|
||||
expect(isDeviceSelected(getByTestId, alicesMobileDevice.device_id)).toBeTruthy();
|
||||
expect(isDeviceSelected(getByTestId, alicesOlderMobileDevice.device_id)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('selects all sessions when some sessions are already selected', async () => {
|
||||
const { getByTestId, getByText } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
toggleDeviceSelection(getByTestId, alicesMobileDevice.device_id);
|
||||
|
||||
fireEvent.click(getByTestId('device-select-all-checkbox'));
|
||||
|
||||
// header displayed correctly
|
||||
expect(getByText('2 sessions selected')).toBeTruthy();
|
||||
expect(isSelectAllChecked(getByTestId)).toBeTruthy();
|
||||
|
||||
// devices selected
|
||||
expect(isDeviceSelected(getByTestId, alicesMobileDevice.device_id)).toBeTruthy();
|
||||
expect(isDeviceSelected(getByTestId, alicesOlderMobileDevice.device_id)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('deselects all sessions when all sessions are selected', async () => {
|
||||
const { getByTestId, getByText } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
fireEvent.click(getByTestId('device-select-all-checkbox'));
|
||||
|
||||
// header displayed correctly
|
||||
expect(getByText('2 sessions selected')).toBeTruthy();
|
||||
expect(isSelectAllChecked(getByTestId)).toBeTruthy();
|
||||
|
||||
// devices selected
|
||||
expect(isDeviceSelected(getByTestId, alicesMobileDevice.device_id)).toBeTruthy();
|
||||
expect(isDeviceSelected(getByTestId, alicesOlderMobileDevice.device_id)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('selects only sessions that are part of the active filter', async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [
|
||||
alicesDevice,
|
||||
alicesMobileDevice,
|
||||
alicesInactiveDevice,
|
||||
] });
|
||||
const { getByTestId, container } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
// filter for inactive sessions
|
||||
await setFilter(container, DeviceSecurityVariation.Inactive);
|
||||
|
||||
// select all inactive sessions
|
||||
fireEvent.click(getByTestId('device-select-all-checkbox'));
|
||||
|
||||
expect(isSelectAllChecked(getByTestId)).toBeTruthy();
|
||||
|
||||
// sign out of all selected sessions
|
||||
fireEvent.click(getByTestId('sign-out-selection-cta'));
|
||||
|
||||
// only called with session from active filter
|
||||
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
|
||||
[
|
||||
alicesInactiveDevice.device_id,
|
||||
],
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("lets you change the pusher state", async () => {
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
|
@ -702,4 +994,61 @@ describe('<SessionManagerTab />', () => {
|
|||
|
||||
expect(mockClient.setPusher).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("lets you change the local notification settings state", async () => {
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
|
||||
|
||||
// device details are expanded
|
||||
expect(getByTestId(`device-detail-${alicesDevice.device_id}`)).toBeTruthy();
|
||||
expect(getByTestId('device-detail-push-notification')).toBeTruthy();
|
||||
|
||||
const checkbox = getByTestId('device-detail-push-notification-checkbox');
|
||||
|
||||
expect(checkbox).toBeTruthy();
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
expect(mockClient.setLocalNotificationSettings).toHaveBeenCalledWith(
|
||||
alicesDevice.device_id,
|
||||
{ is_silenced: true },
|
||||
);
|
||||
});
|
||||
|
||||
it("updates the UI when another session changes the local notifications", async () => {
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
|
||||
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
|
||||
|
||||
// device details are expanded
|
||||
expect(getByTestId(`device-detail-${alicesDevice.device_id}`)).toBeTruthy();
|
||||
expect(getByTestId('device-detail-push-notification')).toBeTruthy();
|
||||
|
||||
const checkbox = getByTestId('device-detail-push-notification-checkbox');
|
||||
|
||||
expect(checkbox).toBeTruthy();
|
||||
|
||||
expect(checkbox.getAttribute('aria-checked')).toEqual("true");
|
||||
|
||||
const evt = new MatrixEvent({
|
||||
type: LOCAL_NOTIFICATION_SETTINGS_PREFIX.name + "." + alicesDevice.device_id,
|
||||
content: {
|
||||
is_silenced: true,
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
mockClient.emit(ClientEvent.AccountData, evt);
|
||||
});
|
||||
|
||||
expect(checkbox.getAttribute('aria-checked')).toEqual("false");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,10 +17,35 @@ exports[`<SessionManagerTab /> Sign out Signs out of current device 1`] = `
|
|||
|
||||
exports[`<SessionManagerTab /> goes to filtered list from security recommendations 1`] = `
|
||||
<div
|
||||
class="mx_FilteredDeviceList_header"
|
||||
class="mx_FilteredDeviceListHeader"
|
||||
>
|
||||
<div
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
|
||||
>
|
||||
<input
|
||||
aria-label="Select all"
|
||||
data-testid="device-select-all-checkbox"
|
||||
id="device-select-all-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
for="device-select-all-checkbox"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_background"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_checkmark"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
class="mx_FilteredDeviceList_headerLabel"
|
||||
class="mx_FilteredDeviceListHeader_label"
|
||||
>
|
||||
Sessions
|
||||
</span>
|
||||
|
@ -69,16 +94,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Verified"
|
||||
class="mx_DeviceType_verificationIcon verified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -171,16 +196,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Unverified"
|
||||
class="mx_DeviceType_verificationIcon unverified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
@ -273,16 +298,16 @@ 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"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
<div
|
||||
aria-label="Verified"
|
||||
class="mx_DeviceType_verificationIcon verified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -187,6 +187,35 @@ describe("CallLobby", () => {
|
|||
});
|
||||
|
||||
describe("device buttons", () => {
|
||||
const fakeVideoInput1: MediaDeviceInfo = {
|
||||
deviceId: "v1",
|
||||
groupId: "v1",
|
||||
label: "Webcam",
|
||||
kind: "videoinput",
|
||||
toJSON: () => {},
|
||||
};
|
||||
const fakeVideoInput2: MediaDeviceInfo = {
|
||||
deviceId: "v2",
|
||||
groupId: "v2",
|
||||
label: "Othercam",
|
||||
kind: "videoinput",
|
||||
toJSON: () => {},
|
||||
};
|
||||
const fakeAudioInput1: MediaDeviceInfo = {
|
||||
deviceId: "v1",
|
||||
groupId: "v1",
|
||||
label: "Headphones",
|
||||
kind: "audioinput",
|
||||
toJSON: () => {},
|
||||
};
|
||||
const fakeAudioInput2: MediaDeviceInfo = {
|
||||
deviceId: "v2",
|
||||
groupId: "v2",
|
||||
label: "Tailphones",
|
||||
kind: "audioinput",
|
||||
toJSON: () => {},
|
||||
};
|
||||
|
||||
it("hide when no devices are available", async () => {
|
||||
await renderView();
|
||||
expect(screen.queryByRole("button", { name: /microphone/ })).toBe(null);
|
||||
|
@ -194,13 +223,7 @@ describe("CallLobby", () => {
|
|||
});
|
||||
|
||||
it("show without dropdown when only one device is available", async () => {
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([{
|
||||
deviceId: "1",
|
||||
groupId: "1",
|
||||
label: "Webcam",
|
||||
kind: "videoinput",
|
||||
toJSON: () => {},
|
||||
}]);
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([fakeVideoInput1]);
|
||||
|
||||
await renderView();
|
||||
screen.getByRole("button", { name: /camera/ });
|
||||
|
@ -209,27 +232,40 @@ describe("CallLobby", () => {
|
|||
|
||||
it("show with dropdown when multiple devices are available", async () => {
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([
|
||||
{
|
||||
deviceId: "1",
|
||||
groupId: "1",
|
||||
label: "Headphones",
|
||||
kind: "audioinput",
|
||||
toJSON: () => {},
|
||||
},
|
||||
{
|
||||
deviceId: "2",
|
||||
groupId: "1",
|
||||
label: "", // Should fall back to "Audio input 2"
|
||||
kind: "audioinput",
|
||||
toJSON: () => {},
|
||||
},
|
||||
fakeAudioInput1, fakeAudioInput2,
|
||||
]);
|
||||
|
||||
await renderView();
|
||||
screen.getByRole("button", { name: /microphone/ });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Audio devices" }));
|
||||
screen.getByRole("menuitem", { name: "Headphones" });
|
||||
screen.getByRole("menuitem", { name: "Audio input 2" });
|
||||
screen.getByRole("menuitem", { name: "Tailphones" });
|
||||
});
|
||||
|
||||
it("sets video device when selected", async () => {
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([
|
||||
fakeVideoInput1, fakeVideoInput2,
|
||||
]);
|
||||
|
||||
await renderView();
|
||||
screen.getByRole("button", { name: /camera/ });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Video devices" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: fakeVideoInput2.label }));
|
||||
|
||||
expect(client.getMediaHandler().setVideoInput).toHaveBeenCalledWith(fakeVideoInput2.deviceId);
|
||||
});
|
||||
|
||||
it("sets audio device when selected", async () => {
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([
|
||||
fakeAudioInput1, fakeAudioInput2,
|
||||
]);
|
||||
|
||||
await renderView();
|
||||
screen.getByRole("button", { name: /microphone/ });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Audio devices" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: fakeAudioInput2.label }));
|
||||
|
||||
expect(client.getMediaHandler().setAudioInput).toHaveBeenCalledWith(fakeAudioInput2.deviceId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -616,8 +616,8 @@ describe("ElementCall", () => {
|
|||
await call.connect();
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
expect(messaging.transport.send).toHaveBeenCalledWith(ElementWidgetActions.JoinCall, {
|
||||
audioInput: "1",
|
||||
videoInput: "2",
|
||||
audioInput: "Headphones",
|
||||
videoInput: "Built-in webcam",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -18,17 +18,18 @@ import { MatrixWidgetType } from "matrix-widget-api";
|
|||
|
||||
import type { Room } from "matrix-js-sdk/src/models/room";
|
||||
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import type { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { mkEvent } from "./test-utils";
|
||||
import { Call, ElementCall, JitsiCall } from "../../src/models/Call";
|
||||
|
||||
export class MockedCall extends Call {
|
||||
private static EVENT_TYPE = "org.example.mocked_call";
|
||||
public static readonly EVENT_TYPE = "org.example.mocked_call";
|
||||
public readonly STUCK_DEVICE_TIMEOUT_MS = 1000 * 60 * 60; // 1 hour
|
||||
|
||||
private constructor(room: Room, id: string) {
|
||||
private constructor(room: Room, public readonly event: MatrixEvent) {
|
||||
super(
|
||||
{
|
||||
id,
|
||||
id: event.getStateKey()!,
|
||||
eventId: "$1:example.org",
|
||||
roomId: room.roomId,
|
||||
type: MatrixWidgetType.Custom,
|
||||
|
@ -42,7 +43,9 @@ export class MockedCall extends Call {
|
|||
|
||||
public static get(room: Room): MockedCall | null {
|
||||
const [event] = room.currentState.getStateEvents(this.EVENT_TYPE);
|
||||
return event?.getContent().terminated ?? true ? null : new MockedCall(room, event.getStateKey()!);
|
||||
return (event === undefined || "m.terminated" in event.getContent())
|
||||
? null
|
||||
: new MockedCall(room, event);
|
||||
}
|
||||
|
||||
public static create(room: Room, id: string) {
|
||||
|
@ -52,8 +55,9 @@ export class MockedCall extends Call {
|
|||
type: this.EVENT_TYPE,
|
||||
room: room.roomId,
|
||||
user: "@alice:example.org",
|
||||
content: { terminated: false },
|
||||
content: { "m.type": "m.video", "m.intent": "m.prompt" },
|
||||
skey: id,
|
||||
ts: Date.now(),
|
||||
})]);
|
||||
}
|
||||
|
||||
|
@ -78,8 +82,9 @@ export class MockedCall extends Call {
|
|||
type: MockedCall.EVENT_TYPE,
|
||||
room: this.room.roomId,
|
||||
user: "@alice:example.org",
|
||||
content: { terminated: true },
|
||||
content: { ...this.event.getContent(), "m.terminated": "Call ended" },
|
||||
skey: this.widget.id,
|
||||
ts: Date.now(),
|
||||
})]);
|
||||
|
||||
super.destroy();
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
} from 'matrix-js-sdk/src/matrix';
|
||||
import { normalize } from "matrix-js-sdk/src/utils";
|
||||
import { ReEmitter } from "matrix-js-sdk/src/ReEmitter";
|
||||
import { MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
|
||||
|
||||
import { MatrixClientPeg as peg } from '../../src/MatrixClientPeg';
|
||||
import { makeType } from "../../src/utils/TypeUtils";
|
||||
|
@ -175,6 +176,11 @@ export function createTestClient(): MatrixClient {
|
|||
sendToDevice: jest.fn().mockResolvedValue(undefined),
|
||||
queueToDevice: jest.fn().mockResolvedValue(undefined),
|
||||
encryptAndSendToDevices: jest.fn().mockResolvedValue(undefined),
|
||||
|
||||
getMediaHandler: jest.fn().mockReturnValue({
|
||||
setVideoInput: jest.fn(),
|
||||
setAudioInput: jest.fn(),
|
||||
} as unknown as MediaHandler),
|
||||
} as unknown as MatrixClient;
|
||||
|
||||
client.reEmitter = new ReEmitter(client);
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`createVoiceMessageContent should create a voice message content 1`] = `
|
||||
Object {
|
||||
"body": "Voice message",
|
||||
"file": Object {},
|
||||
"info": Object {
|
||||
"duration": 23000,
|
||||
"mimetype": "ogg/opus",
|
||||
"size": 42000,
|
||||
},
|
||||
"msgtype": "m.audio",
|
||||
"org.matrix.msc1767.audio": Object {
|
||||
"duration": 23000,
|
||||
"waveform": Array [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
],
|
||||
},
|
||||
"org.matrix.msc1767.file": Object {
|
||||
"file": Object {},
|
||||
"mimetype": "ogg/opus",
|
||||
"name": "Voice message.ogg",
|
||||
"size": 42000,
|
||||
"url": "mxc://example.com/file",
|
||||
},
|
||||
"org.matrix.msc1767.text": "Voice message",
|
||||
"org.matrix.msc3245.voice": Object {},
|
||||
"url": "mxc://example.com/file",
|
||||
}
|
||||
`;
|
32
test/utils/createVoiceMessageContent-test.ts
Normal file
32
test/utils/createVoiceMessageContent-test.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
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 { IEncryptedFile } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { createVoiceMessageContent } from "../../src/utils/createVoiceMessageContent";
|
||||
|
||||
describe("createVoiceMessageContent", () => {
|
||||
it("should create a voice message content", () => {
|
||||
expect(createVoiceMessageContent(
|
||||
"mxc://example.com/file",
|
||||
"ogg/opus",
|
||||
23000,
|
||||
42000,
|
||||
{} as unknown as IEncryptedFile,
|
||||
[1, 2, 3],
|
||||
)).toMatchSnapshot();
|
||||
});
|
||||
});
|
146
test/utils/device/clientInformation-test.ts
Normal file
146
test/utils/device/clientInformation-test.ts
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
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 { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import BasePlatform from "../../../src/BasePlatform";
|
||||
import { IConfigOptions } from "../../../src/IConfigOptions";
|
||||
import {
|
||||
getDeviceClientInformation,
|
||||
recordClientInformation,
|
||||
} from "../../../src/utils/device/clientInformation";
|
||||
import { getMockClientWithEventEmitter } from "../../test-utils";
|
||||
|
||||
describe('recordClientInformation()', () => {
|
||||
const deviceId = 'my-device-id';
|
||||
const version = '1.2.3';
|
||||
const isElectron = window.electron;
|
||||
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
getDeviceId: jest.fn().mockReturnValue(deviceId),
|
||||
setAccountData: jest.fn(),
|
||||
});
|
||||
|
||||
const sdkConfig: IConfigOptions = {
|
||||
brand: 'Test Brand',
|
||||
element_call: { url: '', use_exclusively: false },
|
||||
};
|
||||
|
||||
const platform = {
|
||||
getAppVersion: jest.fn().mockResolvedValue(version),
|
||||
} as unknown as BasePlatform;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
window.electron = false;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// restore global
|
||||
window.electron = isElectron;
|
||||
});
|
||||
|
||||
it('saves client information without url for electron clients', async () => {
|
||||
window.electron = true;
|
||||
|
||||
await recordClientInformation(
|
||||
mockClient,
|
||||
sdkConfig,
|
||||
platform,
|
||||
);
|
||||
|
||||
expect(mockClient.setAccountData).toHaveBeenCalledWith(
|
||||
`io.element.matrix_client_information.${deviceId}`,
|
||||
{
|
||||
name: sdkConfig.brand,
|
||||
version,
|
||||
url: undefined,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('saves client information with url for non-electron clients', async () => {
|
||||
await recordClientInformation(
|
||||
mockClient,
|
||||
sdkConfig,
|
||||
platform,
|
||||
);
|
||||
|
||||
expect(mockClient.setAccountData).toHaveBeenCalledWith(
|
||||
`io.element.matrix_client_information.${deviceId}`,
|
||||
{
|
||||
name: sdkConfig.brand,
|
||||
version,
|
||||
url: 'localhost',
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDeviceClientInformation()', () => {
|
||||
const deviceId = 'my-device-id';
|
||||
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
getAccountData: jest.fn(),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('returns an empty object when no event exists for the device', () => {
|
||||
expect(getDeviceClientInformation(mockClient, deviceId)).toEqual({});
|
||||
|
||||
expect(mockClient.getAccountData).toHaveBeenCalledWith(
|
||||
`io.element.matrix_client_information.${deviceId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns client information for the device', () => {
|
||||
const eventContent = {
|
||||
name: 'Element Web',
|
||||
version: '1.2.3',
|
||||
url: 'test.com',
|
||||
};
|
||||
const event = new MatrixEvent({
|
||||
type: `io.element.matrix_client_information.${deviceId}`,
|
||||
content: eventContent,
|
||||
});
|
||||
mockClient.getAccountData.mockReturnValue(event);
|
||||
expect(getDeviceClientInformation(mockClient, deviceId)).toEqual(eventContent);
|
||||
});
|
||||
|
||||
it('excludes values with incorrect types', () => {
|
||||
const eventContent = {
|
||||
extraField: 'hello',
|
||||
name: 'Element Web',
|
||||
// wrong format
|
||||
version: { value: '1.2.3' },
|
||||
url: 'test.com',
|
||||
};
|
||||
const event = new MatrixEvent({
|
||||
type: `io.element.matrix_client_information.${deviceId}`,
|
||||
content: eventContent,
|
||||
});
|
||||
mockClient.getAccountData.mockReturnValue(event);
|
||||
// invalid fields excluded
|
||||
expect(getDeviceClientInformation(mockClient, deviceId)).toEqual({
|
||||
name: eventContent.name,
|
||||
url: eventContent.url,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
145
test/utils/device/parseUserAgent-test.ts
Normal file
145
test/utils/device/parseUserAgent-test.ts
Normal file
|
@ -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);
|
||||
});
|
61
test/utils/notifications-test.ts
Normal file
61
test/utils/notifications-test.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
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 { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import {
|
||||
localNotificationsAreSilenced,
|
||||
getLocalNotificationAccountDataEventType,
|
||||
} from "../../src/utils/notifications";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import { getMockClientWithEventEmitter } from "../test-utils/client";
|
||||
|
||||
jest.mock("../../src/settings/SettingsStore");
|
||||
|
||||
describe('notifications', () => {
|
||||
let accountDataStore = {};
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
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);
|
||||
|
||||
beforeEach(() => {
|
||||
accountDataStore = {};
|
||||
mocked(SettingsStore).getValue.mockReturnValue(false);
|
||||
});
|
||||
|
||||
describe('localNotificationsAreSilenced', () => {
|
||||
it('defaults to true when no setting exists', () => {
|
||||
expect(localNotificationsAreSilenced(mockClient)).toBeTruthy();
|
||||
});
|
||||
it('checks the persisted value', () => {
|
||||
mockClient.setAccountData(accountDataEventKey, { is_silenced: true });
|
||||
expect(localNotificationsAreSilenced(mockClient)).toBeTruthy();
|
||||
|
||||
mockClient.setAccountData(accountDataEventKey, { is_silenced: false });
|
||||
expect(localNotificationsAreSilenced(mockClient)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue