From 5dc0c4f909deaa340a818b0a29478bff6cad31ba Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 14 Sep 2022 09:59:07 +0200 Subject: [PATCH 01/10] add sign out of current device section in device details --- res/css/components/views/settings/devices/_DeviceDetails.pcss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/components/views/settings/devices/_DeviceDetails.pcss b/res/css/components/views/settings/devices/_DeviceDetails.pcss index d53dcee02b7..b27300cd13e 100644 --- a/res/css/components/views/settings/devices/_DeviceDetails.pcss +++ b/res/css/components/views/settings/devices/_DeviceDetails.pcss @@ -72,4 +72,4 @@ limitations under the License. .mxDeviceDetails_metadataValue { color: $primary-content; } -} +} \ No newline at end of file From cb896e04aed70119946fb1d58848c4eef925bf1d Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 14 Sep 2022 10:23:57 +0200 Subject: [PATCH 02/10] lint --- res/css/components/views/settings/devices/_DeviceDetails.pcss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/components/views/settings/devices/_DeviceDetails.pcss b/res/css/components/views/settings/devices/_DeviceDetails.pcss index b27300cd13e..d53dcee02b7 100644 --- a/res/css/components/views/settings/devices/_DeviceDetails.pcss +++ b/res/css/components/views/settings/devices/_DeviceDetails.pcss @@ -72,4 +72,4 @@ limitations under the License. .mxDeviceDetails_metadataValue { color: $primary-content; } -} \ No newline at end of file +} From 0d7d5751f19286cb0892e104460498b72879d7ab Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 14 Sep 2022 12:01:10 +0200 Subject: [PATCH 03/10] add sign out cta for other sessions --- .../views/settings/devices/DeviceDetails.tsx | 8 ++--- .../settings/devices/FilteredDeviceList.tsx | 18 ++++++++-- .../views/settings/devices/useOwnDevices.ts | 3 -- .../settings/tabs/user/SessionManagerTab.tsx | 32 +++++++++++++++-- .../devices/FilteredDeviceList-test.tsx | 1 + .../__snapshots__/DeviceDetails-test.tsx.snap | 36 +++++++++++++++++++ 6 files changed, 86 insertions(+), 12 deletions(-) diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index 48c32ad1a79..0b9a4473125 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -26,9 +26,7 @@ import { DeviceWithVerification } from './types'; interface Props { device: DeviceWithVerification; onVerifyDevice?: () => void; - // @TODO(kerry) optional while signout only implemented - // for current device (PSG-744) - onSignOutDevice?: () => void; + onSignOutDevice: () => void; } interface MetadataTable { @@ -87,7 +85,7 @@ const DeviceDetails: React.FC = ({ , ) } - { !!onSignOutDevice &&
+
= ({ > { _t('Sign out of this session') } -
} +
; }; diff --git a/src/components/views/settings/devices/FilteredDeviceList.tsx b/src/components/views/settings/devices/FilteredDeviceList.tsx index 4ce3e6e7da1..bfd8ea72f0d 100644 --- a/src/components/views/settings/devices/FilteredDeviceList.tsx +++ b/src/components/views/settings/devices/FilteredDeviceList.tsx @@ -39,6 +39,7 @@ interface Props { filter?: DeviceSecurityVariation; onFilterChange: (filter: DeviceSecurityVariation | undefined) => void; onDeviceExpandToggle: (deviceId: DeviceWithVerification['device_id']) => void; + onSignOutDevices: (deviceIds: DeviceWithVerification['device_id'][]) => void; onRequestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => void; } @@ -133,9 +134,13 @@ const DeviceListItem: React.FC<{ device: DeviceWithVerification; isExpanded: boolean; onDeviceExpandToggle: () => void; + onSignOutDevice: () => void; onRequestDeviceVerification?: () => void; }> = ({ - device, isExpanded, onDeviceExpandToggle, + device, + isExpanded, + onDeviceExpandToggle, + onSignOutDevice, onRequestDeviceVerification, }) =>
  • - { isExpanded && } + { + isExpanded && + + }
  • ; /** @@ -160,6 +172,7 @@ export const FilteredDeviceList = expandedDeviceIds, onFilterChange, onDeviceExpandToggle, + onSignOutDevices, onRequestDeviceVerification, }: Props, ref: ForwardedRef) => { const sortedDevices = getFilteredSortedDevices(devices, filter); @@ -214,6 +227,7 @@ export const FilteredDeviceList = device={device} isExpanded={expandedDeviceIds.includes(device.device_id)} onDeviceExpandToggle={() => onDeviceExpandToggle(device.device_id)} + onSignOutDevice={() => onSignOutDevices([device.device_id])} onRequestDeviceVerification={ onRequestDeviceVerification ? () => onRequestDeviceVerification(device.device_id) diff --git a/src/components/views/settings/devices/useOwnDevices.ts b/src/components/views/settings/devices/useOwnDevices.ts index 1a4b1e6bb20..b2d7650bc3c 100644 --- a/src/components/views/settings/devices/useOwnDevices.ts +++ b/src/components/views/settings/devices/useOwnDevices.ts @@ -18,7 +18,6 @@ import { useCallback, useContext, useEffect, useState } from "react"; import { IMyDevice, MatrixClient } 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 { User } from "matrix-js-sdk/src/models/user"; import { MatrixError } from "matrix-js-sdk/src/http-api"; import { logger } from "matrix-js-sdk/src/logger"; @@ -77,7 +76,6 @@ export enum OwnDevicesError { type DevicesState = { devices: DevicesDictionary; currentDeviceId: string; - currentUserMember?: User; isLoading: boolean; // not provided when current session cannot request verification requestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => Promise; @@ -135,7 +133,6 @@ export const useOwnDevices = (): DevicesState => { return { devices, currentDeviceId, - currentUserMember: userId && matrixClient.getUser(userId) || undefined, requestDeviceVerification, refreshDevices, isLoading, diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index 14521f84dad..dcf7d589b8b 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { logger } from 'matrix-js-sdk/src/logger'; import { _t } from "../../../../../languageHandler"; import { useOwnDevices } from '../../devices/useOwnDevices'; @@ -28,12 +29,13 @@ import Modal from '../../../../../Modal'; import SetupEncryptionDialog from '../../../dialogs/security/SetupEncryptionDialog'; import VerificationRequestDialog from '../../../dialogs/VerificationRequestDialog'; import LogoutDialog from '../../../dialogs/LogoutDialog'; +import MatrixClientContext from '../../../../../contexts/MatrixClientContext'; +import { deleteDevicesWithInteractiveAuth } from '../../devices/deleteDevices'; const SessionManagerTab: React.FC = () => { const { devices, currentDeviceId, - currentUserMember, isLoading, requestDeviceVerification, refreshDevices, @@ -43,6 +45,10 @@ const SessionManagerTab: React.FC = () => { const filteredDeviceListRef = useRef(null); const scrollIntoViewTimeoutRef = useRef>(); + const matrixClient = useContext(MatrixClientContext); + const userId = matrixClient.getUserId(); + const currentUserMember = userId && matrixClient.getUser(userId) || undefined; + const onDeviceExpandToggle = (deviceId: DeviceWithVerification['device_id']): void => { if (expandedDeviceIds.includes(deviceId)) { setExpandedDeviceIds(expandedDeviceIds.filter(id => id !== deviceId)); @@ -100,6 +106,27 @@ const SessionManagerTab: React.FC = () => { /* isPriority= */false, /* isStatic= */true); }; + const onSignOutOtherDevices = async (deviceIds: DeviceWithVerification['device_id'][]) => { + if (!deviceIds.length) { + return; + } + try { + await deleteDevicesWithInteractiveAuth( + matrixClient, + deviceIds, + (success) => { + if (success) { + // @TODO(kerrya) clear selection if was bulk deletion + // when added in PSG-659 + refreshDevices(); + } + }, + ); + } catch (error) { + logger.error("Error deleting sessions", error); + } + }; + useEffect(() => () => { clearTimeout(scrollIntoViewTimeoutRef.current); }, [scrollIntoViewTimeoutRef]); @@ -133,6 +160,7 @@ const SessionManagerTab: React.FC = () => { onFilterChange={setFilter} onDeviceExpandToggle={onDeviceExpandToggle} onRequestDeviceVerification={requestDeviceVerification ? onTriggerDeviceVerification : undefined} + onSignOutDevices={onSignOutOtherDevices} ref={filteredDeviceListRef} /> diff --git a/test/components/views/settings/devices/FilteredDeviceList-test.tsx b/test/components/views/settings/devices/FilteredDeviceList-test.tsx index ef5ae5b18b4..22352fe3c30 100644 --- a/test/components/views/settings/devices/FilteredDeviceList-test.tsx +++ b/test/components/views/settings/devices/FilteredDeviceList-test.tsx @@ -43,6 +43,7 @@ describe('', () => { const defaultProps = { onFilterChange: jest.fn(), onDeviceExpandToggle: jest.fn(), + onSignOutDevices: jest.fn(), expandedDeviceIds: [], devices: { [unverifiedNoMetadata.device_id]: unverifiedNoMetadata, diff --git a/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap index 4a3d7afade0..82cdc537b6b 100644 --- a/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap +++ b/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap @@ -101,6 +101,18 @@ exports[` renders a verified device 1`] = ` +
    +
    + Sign out of this session +
    +
    `; @@ -210,6 +222,18 @@ exports[` renders device with metadata 1`] = ` +
    +
    + Sign out of this session +
    +
    `; @@ -315,6 +339,18 @@ exports[` renders device without metadata 1`] = ` +
    +
    + Sign out of this session +
    +
    `; From 0858a80fc28bdee3c1dbf05338ca41f50d7e3430 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 14 Sep 2022 13:54:57 +0200 Subject: [PATCH 04/10] test other device sign out --- .../settings/tabs/user/SessionManagerTab.tsx | 2 + .../settings/devices/DeviceDetails-test.tsx | 1 + .../tabs/user/SessionManagerTab-test.tsx | 143 +++++++++++++++--- 3 files changed, 123 insertions(+), 23 deletions(-) diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index dcf7d589b8b..ebe4fe51d65 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -107,6 +107,7 @@ const SessionManagerTab: React.FC = () => { }; const onSignOutOtherDevices = async (deviceIds: DeviceWithVerification['device_id'][]) => { + console.log('device ids', deviceIds); if (!deviceIds.length) { return; } @@ -123,6 +124,7 @@ const SessionManagerTab: React.FC = () => { }, ); } catch (error) { + console.log('eeee', error); logger.error("Error deleting sessions", error); } }; diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx index 95897f69a1c..814b8f587c1 100644 --- a/test/components/views/settings/devices/DeviceDetails-test.tsx +++ b/test/components/views/settings/devices/DeviceDetails-test.tsx @@ -26,6 +26,7 @@ describe('', () => { }; const defaultProps = { device: baseDevice, + onSignOutDevice: jest.fn(), }; const getComponent = (props = {}) => ; // 14.03.2022 16:15 diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index ee538b27dd8..1092d4c0023 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -21,6 +21,7 @@ import { DeviceInfo } from 'matrix-js-sdk/src/crypto/deviceinfo'; 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 SessionManagerTab from '../../../../../../src/components/views/settings/tabs/user/SessionManagerTab'; import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext'; @@ -31,8 +32,9 @@ import { } 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'; -jest.useFakeTimers(); +// jest.useFakeTimers(); describe('', () => { const aliceId = '@alice:server.org'; @@ -62,6 +64,8 @@ describe('', () => { getStoredDevice: jest.fn(), getDeviceId: jest.fn().mockReturnValue(deviceId), requestVerification: jest.fn().mockResolvedValue(mockVerificationRequest), + deleteMultipleDevices: jest.fn(), + generateClientSecret: jest.fn(), }); const defaultProps = {}; @@ -72,6 +76,16 @@ describe('', () => { ); + const toggleDeviceDetails = ( + getByTestId: ReturnType['getByTestId'], + deviceId: DeviceWithVerification['device_id'], + ) => { + // open device detail + const tile = getByTestId(`device-tile-${deviceId}`); + const toggle = tile.querySelector('[aria-label="Toggle device details"]') as Element; + fireEvent.click(toggle); + }; + beforeEach(() => { jest.clearAllMocks(); jest.spyOn(logger, 'error').mockRestore(); @@ -83,6 +97,10 @@ describe('', () => { mockCrossSigningInfo.checkDeviceTrust .mockReset() .mockReturnValue(new DeviceTrustLevel(false, false, false, false)); + + mockClient.getDevices + .mockReset() + .mockResolvedValue({ devices: [alicesMobileDevice] }); }); it('renders spinner while devices load', () => { @@ -257,24 +275,18 @@ describe('', () => { await flushPromisesWithFakeTimers(); }); - const tile1 = getByTestId(`device-tile-${alicesOlderMobileDevice.device_id}`); - const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element; - fireEvent.click(toggle1); + toggleDeviceDetails(getByTestId, alicesOlderMobileDevice.device_id); // device details are expanded expect(getByTestId(`device-detail-${alicesOlderMobileDevice.device_id}`)).toBeTruthy(); - const tile2 = getByTestId(`device-tile-${alicesMobileDevice.device_id}`); - const toggle2 = tile2.querySelector('[aria-label="Toggle device details"]') as Element; - fireEvent.click(toggle2); + toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); // both device details are expanded expect(getByTestId(`device-detail-${alicesOlderMobileDevice.device_id}`)).toBeTruthy(); expect(getByTestId(`device-detail-${alicesMobileDevice.device_id}`)).toBeTruthy(); - const tile3 = getByTestId(`device-tile-${alicesMobileDevice.device_id}`); - const toggle3 = tile3.querySelector('[aria-label="Toggle device details"]') as Element; - fireEvent.click(toggle3); + toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); // alicesMobileDevice was toggled off expect(queryByTestId(`device-detail-${alicesMobileDevice.device_id}`)).toBeFalsy(); @@ -294,9 +306,7 @@ describe('', () => { await flushPromisesWithFakeTimers(); }); - const tile1 = getByTestId(`device-tile-${alicesOlderMobileDevice.device_id}`); - const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element; - fireEvent.click(toggle1); + toggleDeviceDetails(getByTestId, alicesOlderMobileDevice.device_id); // verify device button is not rendered expect(queryByTestId(`verification-status-button-${alicesOlderMobileDevice.device_id}`)).toBeFalsy(); @@ -323,9 +333,7 @@ describe('', () => { await flushPromisesWithFakeTimers(); }); - const tile1 = getByTestId(`device-tile-${alicesMobileDevice.device_id}`); - const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element; - fireEvent.click(toggle1); + toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); // click verify button from current session section fireEvent.click(getByTestId(`verification-status-button-${alicesMobileDevice.device_id}`)); @@ -355,9 +363,7 @@ describe('', () => { await flushPromisesWithFakeTimers(); }); - const tile1 = getByTestId(`device-tile-${alicesMobileDevice.device_id}`); - const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element; - fireEvent.click(toggle1); + toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); // reset mock counter before triggering verification mockClient.getDevices.mockClear(); @@ -387,10 +393,7 @@ describe('', () => { await flushPromisesWithFakeTimers(); }); - // open device detail - const tile1 = getByTestId(`device-tile-${alicesDevice.device_id}`); - const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element; - fireEvent.click(toggle1); + toggleDeviceDetails(getByTestId, alicesDevice.device_id); const signOutButton = getByTestId('device-detail-sign-out-cta'); expect(signOutButton).toMatchSnapshot(); @@ -399,5 +402,99 @@ describe('', () => { // logout dialog opened expect(modalSpy).toHaveBeenCalledWith(LogoutDialog, {}, undefined, false, true); }); + + describe('other devices', () => { + const interactiveAuthError = { httpStatus: 401, data: { flows: [{ stages: ["m.login.password"] }] } }; + + beforeEach(() => { + mockClient.deleteMultipleDevices.mockReset(); + }); + + it('deletes a device when interactive auth is not required', async () => { + mockClient.deleteMultipleDevices.mockResolvedValue({}); + mockClient.getDevices + .mockResolvedValueOnce({ devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice] }) + // pretend it was really deleted on refresh + .mockResolvedValueOnce({ devices: [alicesDevice, alicesOlderMobileDevice] }); + + const { getByTestId } = render(getComponent()); + + await act(async () => { + await flushPromisesWithFakeTimers(); + }); + + toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); + + const deviceDetails = getByTestId(`device-detail-${alicesMobileDevice.device_id}`); + const signOutButton = deviceDetails.querySelector('[data-testid="device-detail-sign-out-cta"]'); + fireEvent.click(signOutButton); + + // delete called + expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith( + [alicesMobileDevice.device_id], undefined, + ); + + await flushPromisesWithFakeTimers(); + + // devices refreshed + expect(mockClient.getDevices).toHaveBeenCalled(); + }); + + it('deletes a device when interactive auth is required', async () => { + mockClient.deleteMultipleDevices + // require auth + .mockRejectedValueOnce(interactiveAuthError) + // then succeed + .mockResolvedValueOnce({}); + + mockClient.getDevices + .mockResolvedValueOnce({ devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice] }) + // pretend it was really deleted on refresh + .mockResolvedValueOnce({ devices: [alicesDevice, alicesOlderMobileDevice] }); + + const { getByTestId, getByLabelText } = render(getComponent()); + + await act(async () => { + await flushPromisesWithFakeTimers(); + }); + + // reset mock count after initial load + mockClient.getDevices.mockClear(); + + toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); + + const deviceDetails = getByTestId(`device-detail-${alicesMobileDevice.device_id}`); + const signOutButton = deviceDetails.querySelector('[data-testid="device-detail-sign-out-cta"]'); + fireEvent.click(signOutButton); + + await flushPromisesWithFakeTimers(); + // modal rendering has some weird sleeps + await sleep(100); + + expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith( + [alicesMobileDevice.device_id], undefined, + ); + + const modal = document.getElementsByClassName('mx_Dialog'); + expect(modal.length).toBeTruthy(); + + // fill password and submit for interactive auth + act(() => { + fireEvent.change(getByLabelText('Password'), { target: { value: 'topsecret' } }); + fireEvent.submit(getByLabelText('Password')); + }); + + await flushPromisesWithFakeTimers(); + + // called again with auth + expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith([alicesMobileDevice.device_id], + { identifier: { + type: "m.id.user", user: aliceId, + }, password: "", type: "m.login.password", user: aliceId, + }); + // devices refreshed + expect(mockClient.getDevices).toHaveBeenCalled(); + }); + }); }); }); From 3ae2a0603c02db6106c8fbf6c0171d5bc7995bf8 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 14 Sep 2022 14:38:08 +0200 Subject: [PATCH 05/10] add pending sign out loader --- .../settings/devices/_DeviceDetails.pcss | 6 ++ .../settings/devices/CurrentDeviceSection.tsx | 3 + .../views/settings/devices/DeviceDetails.tsx | 13 ++- .../settings/devices/FilteredDeviceList.tsx | 6 ++ .../views/settings/devices/useOwnDevices.ts | 2 +- .../settings/tabs/user/SessionManagerTab.tsx | 88 ++++++++++++------- .../devices/CurrentDeviceSection-test.tsx | 1 + .../settings/devices/DeviceDetails-test.tsx | 9 ++ .../devices/FilteredDeviceList-test.tsx | 1 + .../CurrentDeviceSection-test.tsx.snap | 6 +- .../__snapshots__/DeviceDetails-test.tsx.snap | 46 +++++++++- .../tabs/user/SessionManagerTab-test.tsx | 69 ++++++++++++++- .../SessionManagerTab-test.tsx.snap | 34 ++++++- 13 files changed, 244 insertions(+), 40 deletions(-) diff --git a/res/css/components/views/settings/devices/_DeviceDetails.pcss b/res/css/components/views/settings/devices/_DeviceDetails.pcss index d53dcee02b7..347fe106e3b 100644 --- a/res/css/components/views/settings/devices/_DeviceDetails.pcss +++ b/res/css/components/views/settings/devices/_DeviceDetails.pcss @@ -73,3 +73,9 @@ limitations under the License. color: $primary-content; } } + +.mx_DeviceDetails_signOutButtonContent { + display: flex; + flex-direction: row; + align-items: center; +} \ No newline at end of file diff --git a/src/components/views/settings/devices/CurrentDeviceSection.tsx b/src/components/views/settings/devices/CurrentDeviceSection.tsx index 8db70aa2b1b..e720b47ede9 100644 --- a/src/components/views/settings/devices/CurrentDeviceSection.tsx +++ b/src/components/views/settings/devices/CurrentDeviceSection.tsx @@ -28,6 +28,7 @@ import { DeviceWithVerification } from './types'; interface Props { device?: DeviceWithVerification; isLoading: boolean; + isSigningOut: boolean; onVerifyCurrentDevice: () => void; onSignOutCurrentDevice: () => void; } @@ -35,6 +36,7 @@ interface Props { const CurrentDeviceSection: React.FC = ({ device, isLoading, + isSigningOut, onVerifyCurrentDevice, onSignOutCurrentDevice, }) => { @@ -58,6 +60,7 @@ const CurrentDeviceSection: React.FC = ({ { isExpanded && } diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index 0b9a4473125..61973cf8daa 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -19,12 +19,14 @@ import React from 'react'; import { formatDate } from '../../../../DateUtils'; import { _t } from '../../../../languageHandler'; import AccessibleButton from '../../elements/AccessibleButton'; +import Spinner from '../../elements/Spinner'; import Heading from '../../typography/Heading'; import { DeviceVerificationStatusCard } from './DeviceVerificationStatusCard'; import { DeviceWithVerification } from './types'; interface Props { device: DeviceWithVerification; + isSigningOut: boolean; onVerifyDevice?: () => void; onSignOutDevice: () => void; } @@ -36,6 +38,7 @@ interface MetadataTable { const DeviceDetails: React.FC = ({ device, + isSigningOut, onVerifyDevice, onSignOutDevice, }) => { @@ -89,9 +92,17 @@ const DeviceDetails: React.FC = ({ - { _t('Sign out of this session') } + + { _t('Sign out of this session') } + { isSigningOut && <> +   + + + } + ; diff --git a/src/components/views/settings/devices/FilteredDeviceList.tsx b/src/components/views/settings/devices/FilteredDeviceList.tsx index bfd8ea72f0d..74f3f5eebfd 100644 --- a/src/components/views/settings/devices/FilteredDeviceList.tsx +++ b/src/components/views/settings/devices/FilteredDeviceList.tsx @@ -36,6 +36,7 @@ import { interface Props { devices: DevicesDictionary; expandedDeviceIds: DeviceWithVerification['device_id'][]; + signingOutDeviceIds: DeviceWithVerification['device_id'][]; filter?: DeviceSecurityVariation; onFilterChange: (filter: DeviceSecurityVariation | undefined) => void; onDeviceExpandToggle: (deviceId: DeviceWithVerification['device_id']) => void; @@ -133,12 +134,14 @@ const NoResults: React.FC = ({ filter, clearFilter }) => const DeviceListItem: React.FC<{ device: DeviceWithVerification; isExpanded: boolean; + isSigningOut: boolean; onDeviceExpandToggle: () => void; onSignOutDevice: () => void; onRequestDeviceVerification?: () => void; }> = ({ device, isExpanded, + isSigningOut, onDeviceExpandToggle, onSignOutDevice, onRequestDeviceVerification, @@ -155,6 +158,7 @@ const DeviceListItem: React.FC<{ isExpanded && @@ -170,6 +174,7 @@ export const FilteredDeviceList = devices, filter, expandedDeviceIds, + signingOutDeviceIds, onFilterChange, onDeviceExpandToggle, onSignOutDevices, @@ -226,6 +231,7 @@ export const FilteredDeviceList = key={device.device_id} device={device} isExpanded={expandedDeviceIds.includes(device.device_id)} + isSigningOut={signingOutDeviceIds.includes(device.device_id)} onDeviceExpandToggle={() => onDeviceExpandToggle(device.device_id)} onSignOutDevice={() => onSignOutDevices([device.device_id])} onRequestDeviceVerification={ diff --git a/src/components/views/settings/devices/useOwnDevices.ts b/src/components/views/settings/devices/useOwnDevices.ts index b2d7650bc3c..b4e33918603 100644 --- a/src/components/views/settings/devices/useOwnDevices.ts +++ b/src/components/views/settings/devices/useOwnDevices.ts @@ -73,7 +73,7 @@ export enum OwnDevicesError { Unsupported = 'Unsupported', Default = 'Default', } -type DevicesState = { +export type DevicesState = { devices: DevicesDictionary; currentDeviceId: string; isLoading: boolean; diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index ebe4fe51d65..68cf367ad6f 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -15,10 +15,11 @@ limitations under the License. */ import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { MatrixClient } from 'matrix-js-sdk/src/client'; import { logger } from 'matrix-js-sdk/src/logger'; import { _t } from "../../../../../languageHandler"; -import { useOwnDevices } from '../../devices/useOwnDevices'; +import { DevicesState, useOwnDevices } from '../../devices/useOwnDevices'; import SettingsSubsection from '../../shared/SettingsSubsection'; import { FilteredDeviceList } from '../../devices/FilteredDeviceList'; import CurrentDeviceSection from '../../devices/CurrentDeviceSection'; @@ -32,6 +33,53 @@ import LogoutDialog from '../../../dialogs/LogoutDialog'; import MatrixClientContext from '../../../../../contexts/MatrixClientContext'; import { deleteDevicesWithInteractiveAuth } from '../../devices/deleteDevices'; +const useSignOut = ( + matrixClient: MatrixClient, + refreshDevices: DevicesState['refreshDevices'], +): { + onSignOutCurrentDevice: () => void; + onSignOutOtherDevices: (deviceIds: DeviceWithVerification['device_id'][]) => Promise; + signingOutDeviceIds: DeviceWithVerification['device_id'][]; + } => { + const [signingOutDeviceIds, setSigningOutDeviceIds] = useState([]); + + const onSignOutCurrentDevice = () => { + Modal.createDialog(LogoutDialog, + /* props= */{}, /* className= */undefined, + /* isPriority= */false, /* isStatic= */true); + }; + + const onSignOutOtherDevices = async (deviceIds: DeviceWithVerification['device_id'][]) => { + if (!deviceIds.length) { + return; + } + try { + setSigningOutDeviceIds([...signingOutDeviceIds, ...deviceIds]); + await deleteDevicesWithInteractiveAuth( + matrixClient, + deviceIds, + async (success) => { + if (success) { + // @TODO(kerrya) clear selection if was bulk deletion + // when added in PSG-659 + await refreshDevices(); + } + setSigningOutDeviceIds(signingOutDeviceIds.filter(deviceId => !deviceIds.includes(deviceId))); + }, + ); + } catch (error) { + logger.error("Error deleting sessions", error); + setSigningOutDeviceIds(signingOutDeviceIds.filter(deviceId => !deviceIds.includes(deviceId))); + } + }; + + return { + onSignOutCurrentDevice, + onSignOutOtherDevices, + signingOutDeviceIds, + }; +}; + const SessionManagerTab: React.FC = () => { const { devices, @@ -97,37 +145,11 @@ const SessionManagerTab: React.FC = () => { }); }, [requestDeviceVerification, refreshDevices, currentUserMember]); - const onSignOutCurrentDevice = () => { - if (!currentDevice) { - return; - } - Modal.createDialog(LogoutDialog, - /* props= */{}, /* className= */undefined, - /* isPriority= */false, /* isStatic= */true); - }; - - const onSignOutOtherDevices = async (deviceIds: DeviceWithVerification['device_id'][]) => { - console.log('device ids', deviceIds); - if (!deviceIds.length) { - return; - } - try { - await deleteDevicesWithInteractiveAuth( - matrixClient, - deviceIds, - (success) => { - if (success) { - // @TODO(kerrya) clear selection if was bulk deletion - // when added in PSG-659 - refreshDevices(); - } - }, - ); - } catch (error) { - console.log('eeee', error); - logger.error("Error deleting sessions", error); - } - }; + const { + onSignOutCurrentDevice, + onSignOutOtherDevices, + signingOutDeviceIds, + } = useSignOut(matrixClient, refreshDevices); useEffect(() => () => { clearTimeout(scrollIntoViewTimeoutRef.current); @@ -142,6 +164,7 @@ const SessionManagerTab: React.FC = () => { @@ -159,6 +182,7 @@ const SessionManagerTab: React.FC = () => { devices={otherDevices} filter={filter} expandedDeviceIds={expandedDeviceIds} + signingOutDeviceIds={signingOutDeviceIds} onFilterChange={setFilter} onDeviceExpandToggle={onDeviceExpandToggle} onRequestDeviceVerification={requestDeviceVerification ? onTriggerDeviceVerification : undefined} diff --git a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx index 1fd6b2ec436..a63d96fa07c 100644 --- a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx +++ b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx @@ -37,6 +37,7 @@ describe('', () => { onVerifyCurrentDevice: jest.fn(), onSignOutCurrentDevice: jest.fn(), isLoading: false, + isSigningOut: false, }; const getComponent = (props = {}): React.ReactElement => (); diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx index 814b8f587c1..e01056733f5 100644 --- a/test/components/views/settings/devices/DeviceDetails-test.tsx +++ b/test/components/views/settings/devices/DeviceDetails-test.tsx @@ -26,6 +26,7 @@ describe('', () => { }; const defaultProps = { device: baseDevice, + isSigningOut: false, onSignOutDevice: jest.fn(), }; const getComponent = (props = {}) => ; @@ -61,4 +62,12 @@ describe('', () => { const { container } = render(getComponent({ device })); expect(container).toMatchSnapshot(); }); + + it('disables sign out button while sign out is pending', () => { + const device = { + ...baseDevice, + }; + const { getByTestId } = render(getComponent({ device, isSigningOut: true })); + expect(getByTestId('device-detail-sign-out-cta')).toMatchSnapshot(); + }); }); diff --git a/test/components/views/settings/devices/FilteredDeviceList-test.tsx b/test/components/views/settings/devices/FilteredDeviceList-test.tsx index 22352fe3c30..02cff732223 100644 --- a/test/components/views/settings/devices/FilteredDeviceList-test.tsx +++ b/test/components/views/settings/devices/FilteredDeviceList-test.tsx @@ -45,6 +45,7 @@ describe('', () => { onDeviceExpandToggle: jest.fn(), onSignOutDevices: jest.fn(), expandedDeviceIds: [], + signingOutDeviceIds: [], devices: { [unverifiedNoMetadata.device_id]: unverifiedNoMetadata, [verifiedNoMetadata.device_id]: verifiedNoMetadata, diff --git a/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap index 5215e7f2086..a3c9d7de2b3 100644 --- a/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap +++ b/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap @@ -110,7 +110,11 @@ HTMLCollection [ role="button" tabindex="0" > - Sign out of this session + + Sign out of this session + , diff --git a/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap index 82cdc537b6b..614a14ad1b8 100644 --- a/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap +++ b/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap @@ -1,5 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[` disables sign out button while sign out is pending 1`] = ` +
    + + Sign out of this session +   +
    +
    +
    + +
    +`; + exports[` renders a verified device 1`] = `
    renders a verified device 1`] = ` role="button" tabindex="0" > - Sign out of this session + + Sign out of this session +
    @@ -231,7 +263,11 @@ exports[` renders device with metadata 1`] = ` role="button" tabindex="0" > - Sign out of this session + + Sign out of this session +
    @@ -348,7 +384,11 @@ exports[` renders device without metadata 1`] = ` role="button" tabindex="0" > - Sign out of this session + + Sign out of this session + diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index 1092d4c0023..11cd57f4abe 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -426,9 +426,15 @@ describe('', () => { toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); const deviceDetails = getByTestId(`device-detail-${alicesMobileDevice.device_id}`); - const signOutButton = deviceDetails.querySelector('[data-testid="device-detail-sign-out-cta"]'); + const signOutButton = deviceDetails.querySelector( + '[data-testid="device-detail-sign-out-cta"]', + ) as Element; fireEvent.click(signOutButton); + // sign out button is disabled with spinner + expect((deviceDetails.querySelector( + '[data-testid="device-detail-sign-out-cta"]', + ) as Element)).toMatchSnapshot(); // delete called expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith( [alicesMobileDevice.device_id], undefined, @@ -495,6 +501,67 @@ describe('', () => { // devices refreshed expect(mockClient.getDevices).toHaveBeenCalled(); }); + + it('clears loading state when device deletion is cancelled during interactive auth', async () => { + mockClient.deleteMultipleDevices + // require auth + .mockRejectedValueOnce(interactiveAuthError) + // then succeed + .mockResolvedValueOnce({}); + + mockClient.getDevices + .mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice] }); + + const { getByTestId, getByLabelText } = render(getComponent()); + + await act(async () => { + await flushPromisesWithFakeTimers(); + }); + + // reset mock count after initial load + mockClient.getDevices.mockClear(); + + toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); + + const deviceDetails = getByTestId(`device-detail-${alicesMobileDevice.device_id}`); + const signOutButton = deviceDetails.querySelector( + '[data-testid="device-detail-sign-out-cta"]', + ) as Element; + fireEvent.click(signOutButton); + + // button is loading + expect((deviceDetails.querySelector( + '[data-testid="device-detail-sign-out-cta"]', + ) as Element).getAttribute('aria-disabled')).toEqual("true"); + + await flushPromisesWithFakeTimers(); + // modal rendering has some weird sleeps + await sleep(100); + + expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith( + [alicesMobileDevice.device_id], undefined, + ); + + const modal = document.getElementsByClassName('mx_Dialog'); + expect(modal.length).toBeTruthy(); + + // cancel iau by closing modal + act(() => { + fireEvent.click(getByLabelText('Close dialog')); + }); + + await flushPromisesWithFakeTimers(); + + // not called again + expect(mockClient.deleteMultipleDevices).toHaveBeenCalledTimes(1); + // devices not refreshed + expect(mockClient.getDevices).not.toHaveBeenCalled(); + + // loading state cleared + expect((deviceDetails.querySelector( + '[data-testid="device-detail-sign-out-cta"]', + ) as Element).getAttribute('aria-disabled')).toEqual(null); + }); }); }); }); diff --git a/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap b/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap index 6cec91242d5..f38d240b9eb 100644 --- a/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap +++ b/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap @@ -7,7 +7,39 @@ exports[` Sign out Signs out of current device 1`] = ` role="button" tabindex="0" > - Sign out of this session + + Sign out of this session + + +`; + +exports[` Sign out other devices deletes a device when interactive auth is not required 1`] = ` +
    + + Sign out of this session +   +
    +
    +
    +
    `; From f4b6d3fa1204b8b27bb1eea73d6beb0f62d86d18 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 14 Sep 2022 14:41:15 +0200 Subject: [PATCH 06/10] tidy --- res/css/components/views/settings/devices/_DeviceDetails.pcss | 2 +- .../views/settings/tabs/user/SessionManagerTab-test.tsx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/res/css/components/views/settings/devices/_DeviceDetails.pcss b/res/css/components/views/settings/devices/_DeviceDetails.pcss index 347fe106e3b..c0811b6fea9 100644 --- a/res/css/components/views/settings/devices/_DeviceDetails.pcss +++ b/res/css/components/views/settings/devices/_DeviceDetails.pcss @@ -78,4 +78,4 @@ limitations under the License. display: flex; flex-direction: row; align-items: center; -} \ No newline at end of file +} diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index 11cd57f4abe..b952a883cbe 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -34,8 +34,6 @@ import Modal from '../../../../../../src/Modal'; import LogoutDialog from '../../../../../../src/components/views/dialogs/LogoutDialog'; import { DeviceWithVerification } from '../../../../../../src/components/views/settings/devices/types'; -// jest.useFakeTimers(); - describe('', () => { const aliceId = '@alice:server.org'; const deviceId = 'alices_device'; From 3344a19676caec450fbaae24eb2e801245e2c304 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 14 Sep 2022 15:15:04 +0200 Subject: [PATCH 07/10] fix strict error --- .../views/settings/tabs/user/SessionManagerTab-test.tsx | 4 +++- 1 file changed, 3 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 b952a883cbe..a451d2361df 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -468,7 +468,9 @@ describe('', () => { toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); const deviceDetails = getByTestId(`device-detail-${alicesMobileDevice.device_id}`); - const signOutButton = deviceDetails.querySelector('[data-testid="device-detail-sign-out-cta"]'); + const signOutButton = deviceDetails.querySelector( + '[data-testid="device-detail-sign-out-cta"]', + ) as Element; fireEvent.click(signOutButton); await flushPromisesWithFakeTimers(); From e7b4082e4a726a428ad1db358802ac7dde60e891 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 14 Sep 2022 15:37:33 +0200 Subject: [PATCH 08/10] use gap instead of nbsp --- .../components/views/settings/devices/_DeviceDetails.pcss | 1 + src/components/views/settings/devices/DeviceDetails.tsx | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/res/css/components/views/settings/devices/_DeviceDetails.pcss b/res/css/components/views/settings/devices/_DeviceDetails.pcss index c0811b6fea9..47766cec459 100644 --- a/res/css/components/views/settings/devices/_DeviceDetails.pcss +++ b/res/css/components/views/settings/devices/_DeviceDetails.pcss @@ -78,4 +78,5 @@ limitations under the License. display: flex; flex-direction: row; align-items: center; + gap: $spacing-4; } diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index 61973cf8daa..c773e2cfdbf 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -97,11 +97,7 @@ const DeviceDetails: React.FC = ({ > { _t('Sign out of this session') } - { isSigningOut && <> -   - - - } + { isSigningOut && } From da768f793ebb70b9d4b47f3c71f9ad342ecb68d4 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 14 Sep 2022 15:43:07 +0200 Subject: [PATCH 09/10] use more specific assertions in tests, tweak formatting --- .../settings/tabs/user/SessionManagerTab.tsx | 10 +++++-- .../settings/devices/DeviceDetails-test.tsx | 4 ++- .../__snapshots__/DeviceDetails-test.tsx.snap | 28 ------------------- .../tabs/user/SessionManagerTab-test.tsx | 2 +- .../SessionManagerTab-test.tsx.snap | 28 ------------------- 5 files changed, 11 insertions(+), 61 deletions(-) diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index 68cf367ad6f..0b2056b63dc 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -44,9 +44,13 @@ const useSignOut = ( const [signingOutDeviceIds, setSigningOutDeviceIds] = useState([]); const onSignOutCurrentDevice = () => { - Modal.createDialog(LogoutDialog, - /* props= */{}, /* className= */undefined, - /* isPriority= */false, /* isStatic= */true); + Modal.createDialog( + LogoutDialog, + {}, // props, + undefined, // className + false, // isPriority + true, // isStatic + ); }; const onSignOutOtherDevices = async (deviceIds: DeviceWithVerification['device_id'][]) => { diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx index e01056733f5..272f781758c 100644 --- a/test/components/views/settings/devices/DeviceDetails-test.tsx +++ b/test/components/views/settings/devices/DeviceDetails-test.tsx @@ -68,6 +68,8 @@ describe('', () => { ...baseDevice, }; const { getByTestId } = render(getComponent({ device, isSigningOut: true })); - expect(getByTestId('device-detail-sign-out-cta')).toMatchSnapshot(); + expect( + getByTestId('device-detail-sign-out-cta').getAttribute('aria-disabled'), + ).toEqual("true"); }); }); diff --git a/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap index 614a14ad1b8..6de93f65af0 100644 --- a/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap +++ b/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap @@ -1,33 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` disables sign out button while sign out is pending 1`] = ` -
    - - Sign out of this session -   -
    -
    -
    - -
    -`; - exports[` renders a verified device 1`] = `
    ', () => { // sign out button is disabled with spinner expect((deviceDetails.querySelector( '[data-testid="device-detail-sign-out-cta"]', - ) as Element)).toMatchSnapshot(); + ) as Element).getAttribute('aria-disabled')).toEqual("true"); // delete called expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith( [alicesMobileDevice.device_id], undefined, diff --git a/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap b/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap index f38d240b9eb..a0b087ce4f4 100644 --- a/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap +++ b/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap @@ -15,34 +15,6 @@ exports[` Sign out Signs out of current device 1`] = `
    `; -exports[` Sign out other devices deletes a device when interactive auth is not required 1`] = ` -
    - - Sign out of this session -   -
    -
    -
    - -
    -`; - exports[` goes to filtered list from security recommendations 1`] = `
    Date: Wed, 14 Sep 2022 17:00:12 +0200 Subject: [PATCH 10/10] tweak test --- .../views/settings/tabs/user/SessionManagerTab-test.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index 2e1229021e2..4e0a556818f 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -518,9 +518,6 @@ describe('', () => { await flushPromisesWithFakeTimers(); }); - // reset mock count after initial load - mockClient.getDevices.mockClear(); - toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id); const deviceDetails = getByTestId(`device-detail-${alicesMobileDevice.device_id}`); @@ -554,8 +551,8 @@ describe('', () => { // not called again expect(mockClient.deleteMultipleDevices).toHaveBeenCalledTimes(1); - // devices not refreshed - expect(mockClient.getDevices).not.toHaveBeenCalled(); + // devices not refreshed (not called since initial fetch) + expect(mockClient.getDevices).toHaveBeenCalledTimes(1); // loading state cleared expect((deviceDetails.querySelector(