device manager - add spinners while devices are signing out (#9433)
parent
72d7939afc
commit
877c95df8f
|
@ -45,4 +45,8 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_FilteredDeviceList_headerButton {
|
.mx_FilteredDeviceList_headerButton {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
// override inline button styling
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: $spacing-8;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ limitations under the License.
|
||||||
&.mx_AccessibleButton_kind_primary_sm,
|
&.mx_AccessibleButton_kind_primary_sm,
|
||||||
&.mx_AccessibleButton_kind_link,
|
&.mx_AccessibleButton_kind_link,
|
||||||
&.mx_AccessibleButton_kind_link_inline,
|
&.mx_AccessibleButton_kind_link_inline,
|
||||||
|
&.mx_AccessibleButton_kind_danger_inline,
|
||||||
|
&.mx_AccessibleButton_kind_content_inline,
|
||||||
&.mx_AccessibleButton_kind_link_sm {
|
&.mx_AccessibleButton_kind_link_sm {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import {
|
||||||
} from './types';
|
} from './types';
|
||||||
import { DevicesState } from './useOwnDevices';
|
import { DevicesState } from './useOwnDevices';
|
||||||
import FilteredDeviceListHeader from './FilteredDeviceListHeader';
|
import FilteredDeviceListHeader from './FilteredDeviceListHeader';
|
||||||
|
import Spinner from '../../elements/Spinner';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
devices: DevicesDictionary;
|
devices: DevicesDictionary;
|
||||||
|
@ -183,6 +184,7 @@ const DeviceListItem: React.FC<{
|
||||||
onClick={onDeviceExpandToggle}
|
onClick={onDeviceExpandToggle}
|
||||||
device={device}
|
device={device}
|
||||||
>
|
>
|
||||||
|
{ isSigningOut && <Spinner w={16} h={16} /> }
|
||||||
<DeviceExpandDetailsButton
|
<DeviceExpandDetailsButton
|
||||||
isExpanded={isExpanded}
|
isExpanded={isExpanded}
|
||||||
onClick={onDeviceExpandToggle}
|
onClick={onDeviceExpandToggle}
|
||||||
|
@ -276,6 +278,8 @@ export const FilteredDeviceList =
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isSigningOut = !!signingOutDeviceIds.length;
|
||||||
|
|
||||||
return <div className='mx_FilteredDeviceList' ref={ref}>
|
return <div className='mx_FilteredDeviceList' ref={ref}>
|
||||||
<FilteredDeviceListHeader
|
<FilteredDeviceListHeader
|
||||||
selectedDeviceCount={selectedDeviceIds.length}
|
selectedDeviceCount={selectedDeviceIds.length}
|
||||||
|
@ -287,14 +291,17 @@ export const FilteredDeviceList =
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
data-testid='sign-out-selection-cta'
|
data-testid='sign-out-selection-cta'
|
||||||
kind='danger_inline'
|
kind='danger_inline'
|
||||||
|
disabled={isSigningOut}
|
||||||
onClick={() => onSignOutDevices(selectedDeviceIds)}
|
onClick={() => onSignOutDevices(selectedDeviceIds)}
|
||||||
className='mx_FilteredDeviceList_headerButton'
|
className='mx_FilteredDeviceList_headerButton'
|
||||||
>
|
>
|
||||||
|
{ isSigningOut && <Spinner w={16} h={16} /> }
|
||||||
{ _t('Sign out') }
|
{ _t('Sign out') }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
data-testid='cancel-selection-cta'
|
data-testid='cancel-selection-cta'
|
||||||
kind='content_inline'
|
kind='content_inline'
|
||||||
|
disabled={isSigningOut}
|
||||||
onClick={() => setSelectedDeviceIds([])}
|
onClick={() => setSelectedDeviceIds([])}
|
||||||
className='mx_FilteredDeviceList_headerButton'
|
className='mx_FilteredDeviceList_headerButton'
|
||||||
>
|
>
|
||||||
|
|
|
@ -69,7 +69,7 @@ describe('<SessionManagerTab />', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const alicesInactiveDevice = {
|
const alicesInactiveDevice = {
|
||||||
device_id: 'alices_older_mobile_device',
|
device_id: 'alices_older_inactive_mobile_device',
|
||||||
last_seen_ts: Date.now() - (INACTIVE_DEVICE_AGE_MS + 1000),
|
last_seen_ts: Date.now() - (INACTIVE_DEVICE_AGE_MS + 1000),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ describe('<SessionManagerTab />', () => {
|
||||||
const toggleDeviceDetails = (
|
const toggleDeviceDetails = (
|
||||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||||
deviceId: ExtendedDevice['device_id'],
|
deviceId: ExtendedDevice['device_id'],
|
||||||
) => {
|
): void => {
|
||||||
// open device detail
|
// open device detail
|
||||||
const tile = getByTestId(`device-tile-${deviceId}`);
|
const tile = getByTestId(`device-tile-${deviceId}`);
|
||||||
const toggle = tile.querySelector('[aria-label="Toggle device details"]') as Element;
|
const toggle = tile.querySelector('[aria-label="Toggle device details"]') as Element;
|
||||||
|
@ -115,11 +115,18 @@ describe('<SessionManagerTab />', () => {
|
||||||
const toggleDeviceSelection = (
|
const toggleDeviceSelection = (
|
||||||
getByTestId: ReturnType<typeof render>['getByTestId'],
|
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||||
deviceId: ExtendedDevice['device_id'],
|
deviceId: ExtendedDevice['device_id'],
|
||||||
) => {
|
): void => {
|
||||||
const checkbox = getByTestId(`device-tile-checkbox-${deviceId}`);
|
const checkbox = getByTestId(`device-tile-checkbox-${deviceId}`);
|
||||||
fireEvent.click(checkbox);
|
fireEvent.click(checkbox);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDeviceTile = (
|
||||||
|
getByTestId: ReturnType<typeof render>['getByTestId'],
|
||||||
|
deviceId: ExtendedDevice['device_id'],
|
||||||
|
): HTMLElement => {
|
||||||
|
return getByTestId(`device-tile-${deviceId}`);
|
||||||
|
};
|
||||||
|
|
||||||
const setFilter = async (
|
const setFilter = async (
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
option: DeviceSecurityVariation | string,
|
option: DeviceSecurityVariation | string,
|
||||||
|
@ -749,6 +756,7 @@ describe('<SessionManagerTab />', () => {
|
||||||
it('deletes multiple devices', async () => {
|
it('deletes multiple devices', async () => {
|
||||||
mockClient.getDevices.mockResolvedValue({ devices: [
|
mockClient.getDevices.mockResolvedValue({ devices: [
|
||||||
alicesDevice, alicesMobileDevice, alicesOlderMobileDevice,
|
alicesDevice, alicesMobileDevice, alicesOlderMobileDevice,
|
||||||
|
alicesInactiveDevice,
|
||||||
] });
|
] });
|
||||||
mockClient.deleteMultipleDevices.mockResolvedValue({});
|
mockClient.deleteMultipleDevices.mockResolvedValue({});
|
||||||
|
|
||||||
|
@ -763,6 +771,24 @@ describe('<SessionManagerTab />', () => {
|
||||||
|
|
||||||
fireEvent.click(getByTestId('sign-out-selection-cta'));
|
fireEvent.click(getByTestId('sign-out-selection-cta'));
|
||||||
|
|
||||||
|
// buttons disabled in list header
|
||||||
|
expect(getByTestId('sign-out-selection-cta').getAttribute('aria-disabled')).toBeTruthy();
|
||||||
|
expect(getByTestId('cancel-selection-cta').getAttribute('aria-disabled')).toBeTruthy();
|
||||||
|
// spinner rendered in list header
|
||||||
|
expect(getByTestId('sign-out-selection-cta').querySelector('.mx_Spinner')).toBeTruthy();
|
||||||
|
|
||||||
|
// spinners on signing out devices
|
||||||
|
expect(getDeviceTile(
|
||||||
|
getByTestId, alicesMobileDevice.device_id,
|
||||||
|
).querySelector('.mx_Spinner')).toBeTruthy();
|
||||||
|
expect(getDeviceTile(
|
||||||
|
getByTestId, alicesOlderMobileDevice.device_id,
|
||||||
|
).querySelector('.mx_Spinner')).toBeTruthy();
|
||||||
|
// no spinner for device that is not signing out
|
||||||
|
expect(getDeviceTile(
|
||||||
|
getByTestId, alicesInactiveDevice.device_id,
|
||||||
|
).querySelector('.mx_Spinner')).toBeFalsy();
|
||||||
|
|
||||||
// delete called with both ids
|
// delete called with both ids
|
||||||
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
|
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
|
||||||
[
|
[
|
||||||
|
|
Loading…
Reference in New Issue