From 5a9b919c801b6ed5ff1bd33b191f0c9dae4a2deb Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 22 Sep 2022 09:23:45 +0100 Subject: [PATCH 01/17] Create push notifications UI in session tile --- .../views/settings/devices/_DeviceDetails.pcss | 14 ++++++++++++++ res/css/views/elements/_ToggleSwitch.pcss | 4 ++++ .../views/settings/devices/DeviceDetails.tsx | 15 +++++++++++++++ src/i18n/strings/en_EN.json | 3 +++ 4 files changed, 36 insertions(+) diff --git a/res/css/components/views/settings/devices/_DeviceDetails.pcss b/res/css/components/views/settings/devices/_DeviceDetails.pcss index ebb725d28ea..754ee439982 100644 --- a/res/css/components/views/settings/devices/_DeviceDetails.pcss +++ b/res/css/components/views/settings/devices/_DeviceDetails.pcss @@ -46,6 +46,13 @@ limitations under the License. .mx_DeviceDetails_sectionHeading { margin: 0; + + .mx_DeviceDetails_sectionSubheading { + display: block; + font-size: $font-12px; + color: $secondary-content; + line-height: $font-14px; + } } .mx_DeviceDetails_metadataTable { @@ -81,3 +88,10 @@ limitations under the License. align-items: center; gap: $spacing-4; } + +.mx_DeviceDetails_pushNotifications { + display: block; + .mx_ToggleSwitch { + float: right; + } +} diff --git a/res/css/views/elements/_ToggleSwitch.pcss b/res/css/views/elements/_ToggleSwitch.pcss index 09c04f2c08e..c4ad3e0a28c 100644 --- a/res/css/views/elements/_ToggleSwitch.pcss +++ b/res/css/views/elements/_ToggleSwitch.pcss @@ -26,6 +26,10 @@ limitations under the License. background-color: $togglesw-off-color; opacity: 0.5; + + &[aria-disabled="true"] { + cursor: not-allowed; + } } .mx_ToggleSwitch_enabled { diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index 53c095a33bd..c404c21a6cf 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -20,6 +20,7 @@ import { formatDate } from '../../../../DateUtils'; import { _t } from '../../../../languageHandler'; import AccessibleButton from '../../elements/AccessibleButton'; import Spinner from '../../elements/Spinner'; +import ToggleSwitch from '../../elements/ToggleSwitch'; import { DeviceDetailHeading } from './DeviceDetailHeading'; import { DeviceVerificationStatusCard } from './DeviceVerificationStatusCard'; import { DeviceWithVerification } from './types'; @@ -93,6 +94,20 @@ const DeviceDetails: React.FC = ({ , ) } +
+ {}} + aria-label={_t("Toggle push notifications on this session.")} + /> +

+ { _t('Push notifications') } + + { _t('Receive push notifications on this session.') } + +

+
Date: Thu, 22 Sep 2022 16:05:28 +0100 Subject: [PATCH 02/17] Implement push notification toggle in device detail --- .../views/settings/devices/DeviceDetails.tsx | 49 +++++++++++++------ .../settings/devices/FilteredDeviceList.tsx | 18 +++++++ .../views/settings/devices/useOwnDevices.ts | 27 +++++++++- .../settings/tabs/user/SessionManagerTab.tsx | 4 ++ .../devices/CurrentDeviceSection-test.tsx | 13 ++++- .../settings/devices/DeviceDetails-test.tsx | 15 +++++- .../devices/FilteredDeviceList-test.tsx | 14 +++++- .../tabs/user/SessionManagerTab-test.tsx | 1 + 8 files changed, 120 insertions(+), 21 deletions(-) diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index c404c21a6cf..52284999a05 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -14,7 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { useState, useEffect, useContext } from 'react'; +import { IPusher } from 'matrix-js-sdk/src/@types/PushRules'; +import { PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event'; import { formatDate } from '../../../../DateUtils'; import { _t } from '../../../../languageHandler'; @@ -24,13 +26,16 @@ import ToggleSwitch from '../../elements/ToggleSwitch'; import { DeviceDetailHeading } from './DeviceDetailHeading'; import { DeviceVerificationStatusCard } from './DeviceVerificationStatusCard'; import { DeviceWithVerification } from './types'; +import MatrixClientContext from '../../../../contexts/MatrixClientContext'; interface Props { device: DeviceWithVerification; + pusher: IPusher | null; isSigningOut: boolean; onVerifyDevice?: () => void; onSignOutDevice: () => void; saveDeviceName: (deviceName: string) => Promise; + setPusherEnabled: (deviceId: string, enabled: boolean) => Promise; } interface MetadataTable { @@ -40,11 +45,21 @@ interface MetadataTable { const DeviceDetails: React.FC = ({ device, + pusher, isSigningOut, onVerifyDevice, onSignOutDevice, saveDeviceName, + setPusherEnabled, }) => { + const [supportMSC3881, setSupportsMSC3881] = useState(false); + const matrixClient = useContext(MatrixClientContext); + useEffect(() => { + matrixClient.doesServerSupportUnstableFeature("org.matrix.msc3881").then(isEnabled => { + setSupportsMSC3881(isEnabled); + }); + }, [matrixClient]); + const metadata: MetadataTable[] = [ { values: [ @@ -94,20 +109,24 @@ const DeviceDetails: React.FC = ({ , ) }
-
- {}} - aria-label={_t("Toggle push notifications on this session.")} - /> -

- { _t('Push notifications') } - - { _t('Receive push notifications on this session.') } - -

-
+ { pusher && ( +
+ setPusherEnabled(device.device_id, checked)} + aria-label={_t("Toggle push notifications on this session.")} + /> +

+ { _t('Push notifications') } + + { _t('Receive push notifications on this session.') } + +

+
+ ) }
void; saveDeviceName: DevicesState['saveDeviceName']; onRequestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => void; + setPusherEnabled: (deviceId: string, enabled: boolean) => Promise; } // devices without timestamp metadata should be sorted last @@ -135,20 +139,24 @@ const NoResults: React.FC = ({ filter, clearFilter }) => const DeviceListItem: React.FC<{ device: DeviceWithVerification; + pusher: IPusher | null; isExpanded: boolean; isSigningOut: boolean; onDeviceExpandToggle: () => void; onSignOutDevice: () => void; saveDeviceName: (deviceName: string) => Promise; onRequestDeviceVerification?: () => void; + setPusherEnabled: (deviceId: string, enabled: boolean) => Promise; }> = ({ device, + pusher, isExpanded, isSigningOut, onDeviceExpandToggle, onSignOutDevice, saveDeviceName, onRequestDeviceVerification, + setPusherEnabled, }) =>
  • }
  • ; @@ -177,6 +187,7 @@ const DeviceListItem: React.FC<{ export const FilteredDeviceList = forwardRef(({ devices, + pushers, filter, expandedDeviceIds, signingOutDeviceIds, @@ -185,9 +196,14 @@ export const FilteredDeviceList = saveDeviceName, onSignOutDevices, onRequestDeviceVerification, + setPusherEnabled, }: Props, ref: ForwardedRef) => { const sortedDevices = getFilteredSortedDevices(devices, filter); + function getPusherForDevice(device: DeviceWithVerification): IPusher | null { + return pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === device.device_id); + } + const options: FilterDropdownOption[] = [ { id: ALL_FILTER_ID, label: _t('All') }, { @@ -236,6 +252,7 @@ export const FilteredDeviceList = { sortedDevices.map((device) => onDeviceExpandToggle(device.device_id)} @@ -246,6 +263,7 @@ export const FilteredDeviceList = ? () => onRequestDeviceVerification(device.device_id) : undefined } + setPusherEnabled={setPusherEnabled} />, ) } diff --git a/src/components/views/settings/devices/useOwnDevices.ts b/src/components/views/settings/devices/useOwnDevices.ts index 0f7d1044da6..21c1a0e1d49 100644 --- a/src/components/views/settings/devices/useOwnDevices.ts +++ b/src/components/views/settings/devices/useOwnDevices.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { useCallback, useContext, useEffect, useState } from "react"; -import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { IMyDevice, IPusher, MatrixClient, PUSHER_DEVICE_ID, PUSHER_ENABLED } from "matrix-js-sdk/src/matrix"; import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning"; import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; import { MatrixError } from "matrix-js-sdk/src/http-api"; @@ -76,12 +76,14 @@ export enum OwnDevicesError { } export type DevicesState = { devices: DevicesDictionary; + pushers: IPusher[]; currentDeviceId: string; isLoadingDeviceList: boolean; // not provided when current session cannot request verification requestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => Promise; refreshDevices: () => Promise; saveDeviceName: (deviceId: DeviceWithVerification['device_id'], deviceName: string) => Promise; + setPusherEnabled: (deviceId: DeviceWithVerification['device_id'], enabled: boolean) => Promise; error?: OwnDevicesError; }; export const useOwnDevices = (): DevicesState => { @@ -91,6 +93,7 @@ export const useOwnDevices = (): DevicesState => { const userId = matrixClient.getUserId(); const [devices, setDevices] = useState({}); + const [pushers, setPushers] = useState([]); const [isLoadingDeviceList, setIsLoadingDeviceList] = useState(true); const [error, setError] = useState(); @@ -105,6 +108,10 @@ export const useOwnDevices = (): DevicesState => { } const devices = await fetchDevicesWithVerification(matrixClient, userId); setDevices(devices); + + const { pushers } = await matrixClient.getPushers(); + setPushers(pushers); + setIsLoadingDeviceList(false); } catch (error) { if ((error as MatrixError).httpStatus == 404) { @@ -154,13 +161,31 @@ export const useOwnDevices = (): DevicesState => { } }, [matrixClient, devices, refreshDevices]); + const setPusherEnabled = useCallback( + async (deviceId: DeviceWithVerification['device_id'], enabled: boolean): Promise => { + const pusher = pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === deviceId); + try { + await matrixClient.setPusher({ + ...pusher, + [PUSHER_ENABLED.name]: enabled, + }); + await refreshDevices(); + } catch (error) { + logger.error("Error setting session display name", error); + throw new Error(_t("Failed to set display name")); + } + }, [matrixClient, pushers, refreshDevices], + ); + return { devices, + pushers, currentDeviceId, isLoadingDeviceList, error, requestDeviceVerification, refreshDevices, saveDeviceName, + setPusherEnabled, }; }; diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index bd26965451b..1a8d1733c6b 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -87,11 +87,13 @@ const useSignOut = ( const SessionManagerTab: React.FC = () => { const { devices, + pushers, currentDeviceId, isLoadingDeviceList, requestDeviceVerification, refreshDevices, saveDeviceName, + setPusherEnabled, } = useOwnDevices(); const [filter, setFilter] = useState(); const [expandedDeviceIds, setExpandedDeviceIds] = useState([]); @@ -186,6 +188,7 @@ const SessionManagerTab: React.FC = () => { > { onRequestDeviceVerification={requestDeviceVerification ? onTriggerDeviceVerification : undefined} onSignOutDevices={onSignOutOtherDevices} saveDeviceName={saveDeviceName} + setPusherEnabled={setPusherEnabled} ref={filteredDeviceListRef} /> diff --git a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx index 22824fb1e71..9b15e49afbf 100644 --- a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx +++ b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx @@ -19,6 +19,8 @@ import { fireEvent, render } from '@testing-library/react'; import { act } from 'react-dom/test-utils'; import CurrentDeviceSection from '../../../../../src/components/views/settings/devices/CurrentDeviceSection'; +import MatrixClientContext from '../../../../../src/contexts/MatrixClientContext'; +import { getMockClientWithEventEmitter } from '../../../../test-utils'; describe('', () => { const deviceId = 'alices_device'; @@ -40,8 +42,15 @@ describe('', () => { isLoading: false, isSigningOut: false, }; - const getComponent = (props = {}): React.ReactElement => - (); + + const mockClient = getMockClientWithEventEmitter({ + doesServerSupportUnstableFeature: jest.fn().mockReturnValue(Promise.resolve(true)), + }); + + const getComponent = (props = {}) => + ( + + ); it('renders spinner while device is loading', () => { const { container } = render(getComponent({ device: undefined, isLoading: true })); diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx index dad0ce625be..50c188bcd61 100644 --- a/test/components/views/settings/devices/DeviceDetails-test.tsx +++ b/test/components/views/settings/devices/DeviceDetails-test.tsx @@ -18,6 +18,8 @@ import React from 'react'; import { render } from '@testing-library/react'; import DeviceDetails from '../../../../../src/components/views/settings/devices/DeviceDetails'; +import MatrixClientContext from '../../../../../src/contexts/MatrixClientContext'; +import { getMockClientWithEventEmitter } from '../../../../test-utils'; describe('', () => { const baseDevice = { @@ -26,12 +28,23 @@ describe('', () => { }; const defaultProps = { device: baseDevice, + pusher: null, isSigningOut: false, isLoading: false, onSignOutDevice: jest.fn(), saveDeviceName: jest.fn(), + setPusherEnabled: jest.fn(), }; - const getComponent = (props = {}) => ; + + const mockClient = getMockClientWithEventEmitter({ + doesServerSupportUnstableFeature: jest.fn().mockReturnValue(Promise.resolve(true)), + }); + + const getComponent = (props = {}) => + ( + + ); + // 14.03.2022 16:15 const now = 1647270879403; jest.useFakeTimers(); diff --git a/test/components/views/settings/devices/FilteredDeviceList-test.tsx b/test/components/views/settings/devices/FilteredDeviceList-test.tsx index 64869d31b6a..8de77f87e89 100644 --- a/test/components/views/settings/devices/FilteredDeviceList-test.tsx +++ b/test/components/views/settings/devices/FilteredDeviceList-test.tsx @@ -19,7 +19,8 @@ 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 { flushPromises, getMockClientWithEventEmitter, mockPlatformPeg } from '../../../../test-utils'; +import MatrixClientContext from '../../../../../src/contexts/MatrixClientContext'; mockPlatformPeg(); @@ -45,6 +46,7 @@ describe('', () => { onDeviceExpandToggle: jest.fn(), onSignOutDevices: jest.fn(), saveDeviceName: jest.fn(), + setPusherEnabled: jest.fn(), expandedDeviceIds: [], signingOutDeviceIds: [], devices: { @@ -54,9 +56,17 @@ describe('', () => { [hundredDaysOld.device_id]: hundredDaysOld, [hundredDaysOldUnverified.device_id]: hundredDaysOldUnverified, }, + pushers: [], }; + + const mockClient = getMockClientWithEventEmitter({ + doesServerSupportUnstableFeature: jest.fn().mockReturnValue(Promise.resolve(true)), + }); + const getComponent = (props = {}) => - (); + ( + + ); it('renders devices in correct order', () => { const { container } = render(getComponent()); diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index c69e71c32cf..ccca07a74b4 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -67,6 +67,7 @@ describe('', () => { deleteMultipleDevices: jest.fn(), generateClientSecret: jest.fn(), setDeviceDetails: jest.fn(), + doesServerSupportUnstableFeature: jest.fn().mockReturnValue(Promise.resolve(true)), }); const defaultProps = {}; From 2176d2f49bb957fe9703c9168c49520f9a3f9979 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 22 Sep 2022 16:15:17 +0100 Subject: [PATCH 03/17] Fix missing props --- .../settings/devices/CurrentDeviceSection.tsx | 3 ++ .../views/settings/devices/DeviceDetails.tsx | 34 +++++++++---------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/components/views/settings/devices/CurrentDeviceSection.tsx b/src/components/views/settings/devices/CurrentDeviceSection.tsx index 023d33b083c..f363da81a31 100644 --- a/src/components/views/settings/devices/CurrentDeviceSection.tsx +++ b/src/components/views/settings/devices/CurrentDeviceSection.tsx @@ -67,6 +67,9 @@ const CurrentDeviceSection: React.FC = ({ onVerifyDevice={onVerifyCurrentDevice} onSignOutDevice={onSignOutCurrentDevice} saveDeviceName={saveDeviceName} + // Current device can not have a pusher as Web does not use them + pusher={null} + setPusherEnabled={null} /> }
    diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index 52284999a05..591505c1d5b 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -35,7 +35,7 @@ interface Props { onVerifyDevice?: () => void; onSignOutDevice: () => void; saveDeviceName: (deviceName: string) => Promise; - setPusherEnabled: (deviceId: string, enabled: boolean) => Promise; + setPusherEnabled: (deviceId: string, enabled: boolean) => Promise | null; } interface MetadataTable { @@ -109,24 +109,24 @@ const DeviceDetails: React.FC = ({ , ) }
    - { pusher && ( -
    - + setPusherEnabled(device.device_id, checked)} - aria-label={_t("Toggle push notifications on this session.")} - /> -

    - { _t('Push notifications') } - - { _t('Receive push notifications on this session.') } - -

    -
    - ) } + checked={pusher?.[PUSHER_ENABLED.name] ?? true} + disabled={!supportMSC3881} + onChange={(checked) => setPusherEnabled?.(device.device_id, checked)} + aria-label={_t("Toggle push notifications on this session.")} + /> +

    + { _t('Push notifications') } + + { _t('Receive push notifications on this session.') } + +

    + + { /* ) } */ }
    Date: Thu, 22 Sep 2022 16:32:53 +0100 Subject: [PATCH 04/17] Move supportsMSC3881 logic in hook to keep component stateless --- .../settings/devices/CurrentDeviceSection.tsx | 1 + .../views/settings/devices/DeviceDetails.tsx | 48 +++++++++---------- .../settings/devices/FilteredDeviceList.tsx | 6 +++ .../views/settings/devices/useOwnDevices.ts | 7 +++ .../devices/FilteredDeviceList-test.tsx | 1 + 5 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/components/views/settings/devices/CurrentDeviceSection.tsx b/src/components/views/settings/devices/CurrentDeviceSection.tsx index f363da81a31..305dd0c5bed 100644 --- a/src/components/views/settings/devices/CurrentDeviceSection.tsx +++ b/src/components/views/settings/devices/CurrentDeviceSection.tsx @@ -70,6 +70,7 @@ const CurrentDeviceSection: React.FC = ({ // Current device can not have a pusher as Web does not use them pusher={null} setPusherEnabled={null} + supportsMSC3881={null} /> }
    diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index 591505c1d5b..c10b18d6267 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useState, useEffect, useContext } from 'react'; +import React from 'react'; import { IPusher } from 'matrix-js-sdk/src/@types/PushRules'; import { PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event'; @@ -26,7 +26,6 @@ import ToggleSwitch from '../../elements/ToggleSwitch'; import { DeviceDetailHeading } from './DeviceDetailHeading'; import { DeviceVerificationStatusCard } from './DeviceVerificationStatusCard'; import { DeviceWithVerification } from './types'; -import MatrixClientContext from '../../../../contexts/MatrixClientContext'; interface Props { device: DeviceWithVerification; @@ -36,6 +35,7 @@ interface Props { onSignOutDevice: () => void; saveDeviceName: (deviceName: string) => Promise; setPusherEnabled: (deviceId: string, enabled: boolean) => Promise | null; + supportsMSC3881: boolean | null; } interface MetadataTable { @@ -51,15 +51,8 @@ const DeviceDetails: React.FC = ({ onSignOutDevice, saveDeviceName, setPusherEnabled, + supportsMSC3881, }) => { - const [supportMSC3881, setSupportsMSC3881] = useState(false); - const matrixClient = useContext(MatrixClientContext); - useEffect(() => { - matrixClient.doesServerSupportUnstableFeature("org.matrix.msc3881").then(isEnabled => { - setSupportsMSC3881(isEnabled); - }); - }, [matrixClient]); - const metadata: MetadataTable[] = [ { values: [ @@ -109,24 +102,27 @@ const DeviceDetails: React.FC = ({ , ) }
    - { /* { pusher && ( */ } -
    - + setPusherEnabled?.(device.device_id, checked)} - aria-label={_t("Toggle push notifications on this session.")} - /> -

    - { _t('Push notifications') } - - { _t('Receive push notifications on this session.') } - -

    -
    - { /* ) } */ } + checked={pusher?.[PUSHER_ENABLED.name] ?? true} + disabled={!supportsMSC3881} + onChange={(checked) => setPusherEnabled?.(device.device_id, checked)} + aria-label={_t("Toggle push notifications on this session.")} + /> +

    + { _t('Push notifications') } + + { _t('Receive push notifications on this session.') } + +

    + + ) }
    void; setPusherEnabled: (deviceId: string, enabled: boolean) => Promise; + supportsMSC3881: boolean | null; } // devices without timestamp metadata should be sorted last @@ -147,6 +148,7 @@ const DeviceListItem: React.FC<{ saveDeviceName: (deviceName: string) => Promise; onRequestDeviceVerification?: () => void; setPusherEnabled: (deviceId: string, enabled: boolean) => Promise; + supportsMSC3881: boolean | null; }> = ({ device, pusher, @@ -157,6 +159,7 @@ const DeviceListItem: React.FC<{ saveDeviceName, onRequestDeviceVerification, setPusherEnabled, + supportsMSC3881, }) =>
  • }
  • ; @@ -197,6 +201,7 @@ export const FilteredDeviceList = onSignOutDevices, onRequestDeviceVerification, setPusherEnabled, + supportsMSC3881, }: Props, ref: ForwardedRef) => { const sortedDevices = getFilteredSortedDevices(devices, filter); @@ -264,6 +269,7 @@ export const FilteredDeviceList = : undefined } setPusherEnabled={setPusherEnabled} + supportsMSC3881={supportsMSC3881} />, ) } diff --git a/src/components/views/settings/devices/useOwnDevices.ts b/src/components/views/settings/devices/useOwnDevices.ts index 21c1a0e1d49..15c50dd765f 100644 --- a/src/components/views/settings/devices/useOwnDevices.ts +++ b/src/components/views/settings/devices/useOwnDevices.ts @@ -85,6 +85,7 @@ export type DevicesState = { saveDeviceName: (deviceId: DeviceWithVerification['device_id'], deviceName: string) => Promise; setPusherEnabled: (deviceId: DeviceWithVerification['device_id'], enabled: boolean) => Promise; error?: OwnDevicesError; + supportsMSC3881: boolean; }; export const useOwnDevices = (): DevicesState => { const matrixClient = useContext(MatrixClientContext); @@ -95,9 +96,14 @@ export const useOwnDevices = (): DevicesState => { const [devices, setDevices] = useState({}); const [pushers, setPushers] = useState([]); const [isLoadingDeviceList, setIsLoadingDeviceList] = useState(true); + const [supportsMSC3881, setSupportsMSC3881] = useState(true); // optimisticly saying yes! const [error, setError] = useState(); + matrixClient.doesServerSupportUnstableFeature("org.matrix.msc3881").then(hasSupport => { + setSupportsMSC3881(hasSupport); + }); + const refreshDevices = useCallback(async () => { setIsLoadingDeviceList(true); try { @@ -187,5 +193,6 @@ export const useOwnDevices = (): DevicesState => { refreshDevices, saveDeviceName, setPusherEnabled, + supportsMSC3881, }; }; diff --git a/test/components/views/settings/devices/FilteredDeviceList-test.tsx b/test/components/views/settings/devices/FilteredDeviceList-test.tsx index 8de77f87e89..43a7566f5b1 100644 --- a/test/components/views/settings/devices/FilteredDeviceList-test.tsx +++ b/test/components/views/settings/devices/FilteredDeviceList-test.tsx @@ -57,6 +57,7 @@ describe('', () => { [hundredDaysOldUnverified.device_id]: hundredDaysOldUnverified, }, pushers: [], + supportsMSC3881: true, }; const mockClient = getMockClientWithEventEmitter({ From d271173cdcc1e91ba9dccbb28c89bc67b6396844 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 22 Sep 2022 22:05:05 +0100 Subject: [PATCH 05/17] Add device details tests --- .../views/settings/devices/DeviceDetails.tsx | 1 + .../settings/tabs/user/SessionManagerTab.tsx | 2 + .../settings/devices/DeviceDetails-test.tsx | 67 +++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index c10b18d6267..9a88cc908d5 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -114,6 +114,7 @@ const DeviceDetails: React.FC = ({ disabled={!supportsMSC3881} onChange={(checked) => setPusherEnabled?.(device.device_id, checked)} aria-label={_t("Toggle push notifications on this session.")} + data-testid='device-detail-push-notification-checkbox' />

    { _t('Push notifications') } diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index 1a8d1733c6b..ec2b7e8a618 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -94,6 +94,7 @@ const SessionManagerTab: React.FC = () => { refreshDevices, saveDeviceName, setPusherEnabled, + supportsMSC3881, } = useOwnDevices(); const [filter, setFilter] = useState(); const [expandedDeviceIds, setExpandedDeviceIds] = useState([]); @@ -199,6 +200,7 @@ const SessionManagerTab: React.FC = () => { saveDeviceName={saveDeviceName} setPusherEnabled={setPusherEnabled} ref={filteredDeviceListRef} + supportsMSC3881={supportsMSC3881} /> } diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx index 50c188bcd61..7f5a43fc5af 100644 --- a/test/components/views/settings/devices/DeviceDetails-test.tsx +++ b/test/components/views/settings/devices/DeviceDetails-test.tsx @@ -16,11 +16,24 @@ limitations under the License. import React from 'react'; import { render } from '@testing-library/react'; +import { IPusher } from 'matrix-js-sdk/src/@types/PushRules'; +import { PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event'; import DeviceDetails from '../../../../../src/components/views/settings/devices/DeviceDetails'; import MatrixClientContext from '../../../../../src/contexts/MatrixClientContext'; import { getMockClientWithEventEmitter } from '../../../../test-utils'; +const mkPusher = (extra: Partial = {}): IPusher => ({ + app_display_name: "app", + app_id: "123", + data: {}, + device_display_name: "name", + kind: "http", + lang: "en", + pushkey: "pushpush", + ...extra, +}); + describe('', () => { const baseDevice = { device_id: 'my-device', @@ -34,6 +47,7 @@ describe('', () => { onSignOutDevice: jest.fn(), saveDeviceName: jest.fn(), setPusherEnabled: jest.fn(), + supportsMSC3881: true, }; const mockClient = getMockClientWithEventEmitter({ @@ -87,4 +101,57 @@ describe('', () => { getByTestId('device-detail-sign-out-cta').getAttribute('aria-disabled'), ).toEqual("true"); }); + + it('renders the push notification section when a pusher exists', () => { + const device = { + ...baseDevice, + }; + const pusher = mkPusher({ + device_id: device.device_id, + }); + + const { getByTestId } = render(getComponent({ + device, + pusher, + isSigningOut: true, + })); + + expect(getByTestId('device-detail-push-notification')).toBeTruthy(); + }); + + it('hides te push notification section when no pusher', () => { + const device = { + ...baseDevice, + }; + + const { getByTestId } = render(getComponent({ + device, + pusher: null, + isSigningOut: true, + })); + + expect(() => getByTestId('device-detail-push-notification')).toThrow(); + }); + + it('disables the checkbox when there\'s no server support', () => { + const device = { + ...baseDevice, + }; + const pusher = mkPusher({ + device_id: device.device_id, + [PUSHER_ENABLED.name]: false, + }); + + const { getByTestId } = render(getComponent({ + device, + pusher, + isSigningOut: true, + supportsMSC3881: false, + })); + + const checkbox = getByTestId('device-detail-push-notification-checkbox'); + + expect(checkbox.getAttribute('aria-disabled')).toEqual("true"); + expect(checkbox.getAttribute('aria-checked')).toEqual("false"); + }); }); From 297b9f0b3f33ab4613c1b8245d2db706b0b56cc5 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 22 Sep 2022 22:18:01 +0100 Subject: [PATCH 06/17] remove useless context providers --- .../settings/devices/CurrentDeviceSection-test.tsx | 12 ++---------- .../views/settings/devices/DeviceDetails-test.tsx | 11 +---------- .../settings/devices/FilteredDeviceList-test.tsx | 11 ++--------- 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx index 9b15e49afbf..dfb6bf626ed 100644 --- a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx +++ b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx @@ -19,8 +19,6 @@ import { fireEvent, render } from '@testing-library/react'; import { act } from 'react-dom/test-utils'; import CurrentDeviceSection from '../../../../../src/components/views/settings/devices/CurrentDeviceSection'; -import MatrixClientContext from '../../../../../src/contexts/MatrixClientContext'; -import { getMockClientWithEventEmitter } from '../../../../test-utils'; describe('', () => { const deviceId = 'alices_device'; @@ -43,14 +41,8 @@ describe('', () => { isSigningOut: false, }; - const mockClient = getMockClientWithEventEmitter({ - doesServerSupportUnstableFeature: jest.fn().mockReturnValue(Promise.resolve(true)), - }); - - const getComponent = (props = {}) => - ( - - ); + const getComponent = (props = {}): React.ReactElement => + (); it('renders spinner while device is loading', () => { const { container } = render(getComponent({ device: undefined, isLoading: true })); diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx index 7f5a43fc5af..cf086b71598 100644 --- a/test/components/views/settings/devices/DeviceDetails-test.tsx +++ b/test/components/views/settings/devices/DeviceDetails-test.tsx @@ -20,8 +20,6 @@ import { IPusher } from 'matrix-js-sdk/src/@types/PushRules'; import { PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event'; import DeviceDetails from '../../../../../src/components/views/settings/devices/DeviceDetails'; -import MatrixClientContext from '../../../../../src/contexts/MatrixClientContext'; -import { getMockClientWithEventEmitter } from '../../../../test-utils'; const mkPusher = (extra: Partial = {}): IPusher => ({ app_display_name: "app", @@ -50,14 +48,7 @@ describe('', () => { supportsMSC3881: true, }; - const mockClient = getMockClientWithEventEmitter({ - doesServerSupportUnstableFeature: jest.fn().mockReturnValue(Promise.resolve(true)), - }); - - const getComponent = (props = {}) => - ( - - ); + const getComponent = (props = {}) => ; // 14.03.2022 16:15 const now = 1647270879403; diff --git a/test/components/views/settings/devices/FilteredDeviceList-test.tsx b/test/components/views/settings/devices/FilteredDeviceList-test.tsx index 43a7566f5b1..181c435b60d 100644 --- a/test/components/views/settings/devices/FilteredDeviceList-test.tsx +++ b/test/components/views/settings/devices/FilteredDeviceList-test.tsx @@ -19,8 +19,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, getMockClientWithEventEmitter, mockPlatformPeg } from '../../../../test-utils'; -import MatrixClientContext from '../../../../../src/contexts/MatrixClientContext'; +import { flushPromises, mockPlatformPeg } from '../../../../test-utils'; mockPlatformPeg(); @@ -60,14 +59,8 @@ describe('', () => { supportsMSC3881: true, }; - const mockClient = getMockClientWithEventEmitter({ - doesServerSupportUnstableFeature: jest.fn().mockReturnValue(Promise.resolve(true)), - }); - const getComponent = (props = {}) => - ( - - ); + (); it('renders devices in correct order', () => { const { container } = render(getComponent()); From 909e3eb9e76b2b0bddd8a35e5f2e4d67f48af943 Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 27 Sep 2022 08:37:52 +0100 Subject: [PATCH 07/17] Update comment indentation Co-authored-by: Travis Ralston --- src/components/views/settings/devices/DeviceDetails.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index 9a88cc908d5..b29c712521f 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -108,8 +108,8 @@ const DeviceDetails: React.FC = ({ data-testid='device-detail-push-notification' > setPusherEnabled?.(device.device_id, checked)} From 1785092def67d499a525e381ec95436f15fd5d0e Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 27 Sep 2022 08:41:38 +0100 Subject: [PATCH 08/17] Fix typo Co-authored-by: Travis Ralston --- test/components/views/settings/devices/DeviceDetails-test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx index cf086b71598..633c70fe7b0 100644 --- a/test/components/views/settings/devices/DeviceDetails-test.tsx +++ b/test/components/views/settings/devices/DeviceDetails-test.tsx @@ -110,7 +110,7 @@ describe('', () => { expect(getByTestId('device-detail-push-notification')).toBeTruthy(); }); - it('hides te push notification section when no pusher', () => { + it('hides the push notification section when no pusher', () => { const device = { ...baseDevice, }; From 38983edee9a03f1d452a6f6861d6ad1341eebacf Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 27 Sep 2022 08:41:57 +0100 Subject: [PATCH 09/17] Fix test name Co-authored-by: Travis Ralston --- test/components/views/settings/devices/DeviceDetails-test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx index 633c70fe7b0..a43f0ec9cd2 100644 --- a/test/components/views/settings/devices/DeviceDetails-test.tsx +++ b/test/components/views/settings/devices/DeviceDetails-test.tsx @@ -124,7 +124,7 @@ describe('', () => { expect(() => getByTestId('device-detail-push-notification')).toThrow(); }); - it('disables the checkbox when there\'s no server support', () => { + it('disables the checkbox when there is no server support', () => { const device = { ...baseDevice, }; From 1e417b2e9b325e66df542d8defabc7d99a67adf2 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 27 Sep 2022 09:05:02 +0100 Subject: [PATCH 10/17] Use optional props rather than null --- .../views/settings/devices/CurrentDeviceSection.tsx | 2 -- src/components/views/settings/devices/DeviceDetails.tsx | 4 ++-- src/components/views/settings/devices/FilteredDeviceList.tsx | 4 ++-- src/components/views/settings/devices/useOwnDevices.ts | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/views/settings/devices/CurrentDeviceSection.tsx b/src/components/views/settings/devices/CurrentDeviceSection.tsx index 305dd0c5bed..a4826a1d1dd 100644 --- a/src/components/views/settings/devices/CurrentDeviceSection.tsx +++ b/src/components/views/settings/devices/CurrentDeviceSection.tsx @@ -69,8 +69,6 @@ const CurrentDeviceSection: React.FC = ({ saveDeviceName={saveDeviceName} // Current device can not have a pusher as Web does not use them pusher={null} - setPusherEnabled={null} - supportsMSC3881={null} /> }
    diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index b29c712521f..14fe998ff7f 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -34,8 +34,8 @@ interface Props { onVerifyDevice?: () => void; onSignOutDevice: () => void; saveDeviceName: (deviceName: string) => Promise; - setPusherEnabled: (deviceId: string, enabled: boolean) => Promise | null; - supportsMSC3881: boolean | null; + setPusherEnabled?: (deviceId: string, enabled: boolean) => Promise | undefined; + supportsMSC3881?: boolean | undefined; } interface MetadataTable { diff --git a/src/components/views/settings/devices/FilteredDeviceList.tsx b/src/components/views/settings/devices/FilteredDeviceList.tsx index b398ccd05f0..1e3511c9efa 100644 --- a/src/components/views/settings/devices/FilteredDeviceList.tsx +++ b/src/components/views/settings/devices/FilteredDeviceList.tsx @@ -48,7 +48,7 @@ interface Props { saveDeviceName: DevicesState['saveDeviceName']; onRequestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => void; setPusherEnabled: (deviceId: string, enabled: boolean) => Promise; - supportsMSC3881: boolean | null; + supportsMSC3881?: boolean | undefined; } // devices without timestamp metadata should be sorted last @@ -148,7 +148,7 @@ const DeviceListItem: React.FC<{ saveDeviceName: (deviceName: string) => Promise; onRequestDeviceVerification?: () => void; setPusherEnabled: (deviceId: string, enabled: boolean) => Promise; - supportsMSC3881: boolean | null; + supportsMSC3881?: boolean | undefined; }> = ({ device, pusher, diff --git a/src/components/views/settings/devices/useOwnDevices.ts b/src/components/views/settings/devices/useOwnDevices.ts index 15c50dd765f..64647f3d058 100644 --- a/src/components/views/settings/devices/useOwnDevices.ts +++ b/src/components/views/settings/devices/useOwnDevices.ts @@ -85,7 +85,7 @@ export type DevicesState = { saveDeviceName: (deviceId: DeviceWithVerification['device_id'], deviceName: string) => Promise; setPusherEnabled: (deviceId: DeviceWithVerification['device_id'], enabled: boolean) => Promise; error?: OwnDevicesError; - supportsMSC3881: boolean; + supportsMSC3881?: boolean | undefined; }; export const useOwnDevices = (): DevicesState => { const matrixClient = useContext(MatrixClientContext); From 5c4cc23f13fa72d2f273adc368e037cc6c1e505e Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 27 Sep 2022 09:06:38 +0100 Subject: [PATCH 11/17] Update incorrect getPusherForDevice return type --- .../views/settings/devices/CurrentDeviceSection.tsx | 2 -- src/components/views/settings/devices/DeviceDetails.tsx | 2 +- src/components/views/settings/devices/FilteredDeviceList.tsx | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/views/settings/devices/CurrentDeviceSection.tsx b/src/components/views/settings/devices/CurrentDeviceSection.tsx index a4826a1d1dd..023d33b083c 100644 --- a/src/components/views/settings/devices/CurrentDeviceSection.tsx +++ b/src/components/views/settings/devices/CurrentDeviceSection.tsx @@ -67,8 +67,6 @@ const CurrentDeviceSection: React.FC = ({ onVerifyDevice={onVerifyCurrentDevice} onSignOutDevice={onSignOutCurrentDevice} saveDeviceName={saveDeviceName} - // Current device can not have a pusher as Web does not use them - pusher={null} /> }
    diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index 14fe998ff7f..ed6da8fcc31 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -29,7 +29,7 @@ import { DeviceWithVerification } from './types'; interface Props { device: DeviceWithVerification; - pusher: IPusher | null; + pusher?: IPusher | undefined; isSigningOut: boolean; onVerifyDevice?: () => void; onSignOutDevice: () => void; diff --git a/src/components/views/settings/devices/FilteredDeviceList.tsx b/src/components/views/settings/devices/FilteredDeviceList.tsx index 1e3511c9efa..baee4e1d0ee 100644 --- a/src/components/views/settings/devices/FilteredDeviceList.tsx +++ b/src/components/views/settings/devices/FilteredDeviceList.tsx @@ -140,7 +140,7 @@ const NoResults: React.FC = ({ filter, clearFilter }) => const DeviceListItem: React.FC<{ device: DeviceWithVerification; - pusher: IPusher | null; + pusher?: IPusher | undefined; isExpanded: boolean; isSigningOut: boolean; onDeviceExpandToggle: () => void; @@ -205,7 +205,7 @@ export const FilteredDeviceList = }: Props, ref: ForwardedRef) => { const sortedDevices = getFilteredSortedDevices(devices, filter); - function getPusherForDevice(device: DeviceWithVerification): IPusher | null { + function getPusherForDevice(device: DeviceWithVerification): IPusher | undefined { return pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === device.device_id); } From d97e579434c2ceff9094ed79e48145b9bbc8aef4 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 27 Sep 2022 09:11:15 +0100 Subject: [PATCH 12/17] PR fixes --- .../views/settings/devices/useOwnDevices.ts | 12 +++++++----- .../settings/tabs/user/SessionManagerTab-test.tsx | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/views/settings/devices/useOwnDevices.ts b/src/components/views/settings/devices/useOwnDevices.ts index 64647f3d058..b583d4c0800 100644 --- a/src/components/views/settings/devices/useOwnDevices.ts +++ b/src/components/views/settings/devices/useOwnDevices.ts @@ -100,9 +100,11 @@ export const useOwnDevices = (): DevicesState => { const [error, setError] = useState(); - matrixClient.doesServerSupportUnstableFeature("org.matrix.msc3881").then(hasSupport => { - setSupportsMSC3881(hasSupport); - }); + useEffect(() => { + matrixClient.doesServerSupportUnstableFeature("org.matrix.msc3881").then(hasSupport => { + setSupportsMSC3881(hasSupport); + }); + }, [matrixClient]); const refreshDevices = useCallback(async () => { setIsLoadingDeviceList(true); @@ -177,8 +179,8 @@ export const useOwnDevices = (): DevicesState => { }); await refreshDevices(); } catch (error) { - logger.error("Error setting session display name", error); - throw new Error(_t("Failed to set display name")); + logger.error("Error setting pusher state", error); + throw new Error(_t("Failed to set pusher state")); } }, [matrixClient, pushers, refreshDevices], ); diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index ccca07a74b4..7e271345976 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -67,7 +67,7 @@ describe('', () => { deleteMultipleDevices: jest.fn(), generateClientSecret: jest.fn(), setDeviceDetails: jest.fn(), - doesServerSupportUnstableFeature: jest.fn().mockReturnValue(Promise.resolve(true)), + doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(true), }); const defaultProps = {}; From 45ee44fe61c2fba11f3d1948991c9954635e0bb0 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 27 Sep 2022 09:24:05 +0100 Subject: [PATCH 13/17] add test for pusher enabled --- .../settings/devices/DeviceDetails-test.tsx | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx index a43f0ec9cd2..f666a4b644b 100644 --- a/test/components/views/settings/devices/DeviceDetails-test.tsx +++ b/test/components/views/settings/devices/DeviceDetails-test.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from 'react'; -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { IPusher } from 'matrix-js-sdk/src/@types/PushRules'; import { PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event'; @@ -145,4 +145,29 @@ describe('', () => { expect(checkbox.getAttribute('aria-disabled')).toEqual("true"); expect(checkbox.getAttribute('aria-checked')).toEqual("false"); }); + + it('changes the pusher status when clicked', () => { + const device = { + ...baseDevice, + }; + + const enabled = false; + + const pusher = mkPusher({ + device_id: device.device_id, + [PUSHER_ENABLED.name]: enabled, + }); + + const { getByTestId } = render(getComponent({ + device, + pusher, + isSigningOut: true, + })); + + const checkbox = getByTestId('device-detail-push-notification-checkbox'); + + fireEvent.click(checkbox); + + expect(defaultProps.setPusherEnabled).toHaveBeenCalledWith(!enabled); + }); }); From a22f478cf7ad48487df8376d0edb827bdf8f14ef Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 27 Sep 2022 09:41:45 +0100 Subject: [PATCH 14/17] Add tests --- .../views/settings/DevicesPanel-test.tsx | 13 +++++++++++++ .../views/settings/devices/DeviceDetails-test.tsx | 15 ++------------- .../settings/tabs/user/SessionManagerTab-test.tsx | 14 +++++++++++++- test/test-utils/test-utils.ts | 12 ++++++++++++ 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/test/components/views/settings/DevicesPanel-test.tsx b/test/components/views/settings/DevicesPanel-test.tsx index d9a66ab7bd3..ef9801adacc 100644 --- a/test/components/views/settings/DevicesPanel-test.tsx +++ b/test/components/views/settings/DevicesPanel-test.tsx @@ -19,11 +19,13 @@ import { act } from 'react-dom/test-utils'; import { CrossSigningInfo } from 'matrix-js-sdk/src/crypto/CrossSigning'; import { DeviceInfo } from 'matrix-js-sdk/src/crypto/deviceinfo'; import { sleep } from 'matrix-js-sdk/src/utils'; +import { PUSHER_DEVICE_ID, PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event'; import DevicesPanel from "../../../../src/components/views/settings/DevicesPanel"; import { flushPromises, getMockClientWithEventEmitter, + mkPusher, mockClientMethodsUser, } from "../../../test-utils"; @@ -40,6 +42,8 @@ describe('', () => { getStoredCrossSigningForUser: jest.fn().mockReturnValue(new CrossSigningInfo(userId, {}, {})), getStoredDevice: jest.fn().mockReturnValue(new DeviceInfo('id')), generateClientSecret: jest.fn(), + getPushers: jest.fn(), + setPusher: jest.fn(), }); const getComponent = () => ; @@ -50,6 +54,15 @@ describe('', () => { mockClient.getDevices .mockReset() .mockResolvedValue({ devices: [device1, device2, device3] }); + + mockClient.getPushers + .mockReset() + .mockResolvedValue({ + pushers: [mkPusher({ + [PUSHER_DEVICE_ID.name]: device1.device_id, + [PUSHER_ENABLED.name]: true, + })], + }); }); it('renders device panel with devices', async () => { diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx index f666a4b644b..0cec7f387b1 100644 --- a/test/components/views/settings/devices/DeviceDetails-test.tsx +++ b/test/components/views/settings/devices/DeviceDetails-test.tsx @@ -16,21 +16,10 @@ limitations under the License. import React from 'react'; import { fireEvent, render } from '@testing-library/react'; -import { IPusher } from 'matrix-js-sdk/src/@types/PushRules'; import { PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event'; import DeviceDetails from '../../../../../src/components/views/settings/devices/DeviceDetails'; - -const mkPusher = (extra: Partial = {}): IPusher => ({ - app_display_name: "app", - app_id: "123", - data: {}, - device_display_name: "name", - kind: "http", - lang: "en", - pushkey: "pushpush", - ...extra, -}); +import { mkPusher } from '../../../../test-utils/test-utils'; describe('', () => { const baseDevice = { @@ -168,6 +157,6 @@ describe('', () => { fireEvent.click(checkbox); - expect(defaultProps.setPusherEnabled).toHaveBeenCalledWith(!enabled); + expect(defaultProps.setPusherEnabled).toHaveBeenCalledWith(device.device_id, !enabled); }); }); diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index 7e271345976..1e4f18327ee 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -22,13 +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 } from 'matrix-js-sdk/src/matrix'; +import { IMyDevice, 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'; import { flushPromisesWithFakeTimers, getMockClientWithEventEmitter, + mkPusher, mockClientMethodsUser, } from '../../../../../test-utils'; import Modal from '../../../../../../src/Modal'; @@ -68,6 +69,8 @@ describe('', () => { generateClientSecret: jest.fn(), setDeviceDetails: jest.fn(), doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(true), + getPushers: jest.fn(), + setPusher: jest.fn(), }); const defaultProps = {}; @@ -102,6 +105,15 @@ describe('', () => { mockClient.getDevices .mockReset() .mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); + + mockClient.getPushers + .mockReset() + .mockResolvedValue({ + pushers: [mkPusher({ + [PUSHER_DEVICE_ID.name]: alicesDevice.device_id, + [PUSHER_ENABLED.name]: true, + })], + }); }); it('renders spinner while devices load', () => { diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index aaf8bd95de3..5369a3b9f59 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -30,6 +30,7 @@ import { EventType, IEventRelation, IUnsigned, + IPusher, } from 'matrix-js-sdk/src/matrix'; import { normalize } from "matrix-js-sdk/src/utils"; import { ReEmitter } from "matrix-js-sdk/src/ReEmitter"; @@ -541,3 +542,14 @@ export const mkSpace = ( ))); return space; }; + +export const mkPusher = (extra: Partial = {}): IPusher => ({ + app_display_name: "app", + app_id: "123", + data: {}, + device_display_name: "name", + kind: "http", + lang: "en", + pushkey: "pushpush", + ...extra, +}); From f2da65c98ba00978d48302818d4d779c9acfd3be Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 27 Sep 2022 09:43:35 +0100 Subject: [PATCH 15/17] fix i18n --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2db0c05df1a..f107e2018a6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1752,6 +1752,7 @@ "Security recommendations": "Security recommendations", "Improve your account security by following these recommendations": "Improve your account security by following these recommendations", "View all": "View all", + "Failed to set pusher state": "Failed to set pusher state", "Unable to remove contact information": "Unable to remove contact information", "Remove %(email)s?": "Remove %(email)s?", "Invalid Email Address": "Invalid Email Address", From d3b454d90712a56a4e45c858d31b7b8e806dbfb4 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 27 Sep 2022 11:22:50 +0100 Subject: [PATCH 16/17] Add test for the pusher state --- .../tabs/user/SessionManagerTab-test.tsx | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index 1e4f18327ee..815ee256319 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -110,7 +110,7 @@ describe('', () => { .mockReset() .mockResolvedValue({ pushers: [mkPusher({ - [PUSHER_DEVICE_ID.name]: alicesDevice.device_id, + [PUSHER_DEVICE_ID.name]: alicesMobileDevice.device_id, [PUSHER_ENABLED.name]: true, })], }); @@ -681,4 +681,25 @@ describe('', () => { expect(getByTestId('device-rename-error')).toBeTruthy(); }); }); + + it.only("lets you change the pusher state", async () => { + const { getByTestId } = render(getComponent()); + + await act(async () => { + await flushPromisesWithFakeTimers(); + }); + + toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); + + // device details are expanded + expect(getByTestId(`device-detail-${alicesMobileDevice.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.setPusher).toHaveBeenCalled(); + }); }); From d9a9378686ba4d71a01dfc125d5ee90a0c38ec45 Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 27 Sep 2022 11:40:07 +0100 Subject: [PATCH 17/17] Remove "only" --- .../views/settings/tabs/user/SessionManagerTab-test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index 815ee256319..12af8a18e02 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -682,7 +682,7 @@ describe('', () => { }); }); - it.only("lets you change the pusher state", async () => { + it("lets you change the pusher state", async () => { const { getByTestId } = render(getComponent()); await act(async () => {