Device manager - confirm sign out of other sessions (PSG-921) (#9487)

* change testid attribute for dialog buttons to rtl friendly

* add confirm dialog for signing out sessions

* cleanup commented

* update cypress tets

* clear modals before test

* missing modal in jest tests on ci only
This commit is contained in:
Kerry 2022-10-25 10:39:48 +02:00 committed by GitHub
parent 37e613bb05
commit d473b4ac4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 174 additions and 84 deletions

View file

@ -78,6 +78,7 @@ describe("Device manager", () => {
cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem .mx_Checkbox').last().click();
// sign out from list selection action buttons
cy.get('[data-testid="sign-out-selection-cta"]').click();
cy.get('[data-testid="dialog-primary-button"]').click();
// list updated after sign out
cy.get('.mx_FilteredDeviceList_list').find('.mx_FilteredDeviceList_listItem').should('have.length', 1);
// security recommendation count updated
@ -106,6 +107,8 @@ describe("Device manager", () => {
// sign out using the device details sign out
cy.get('[data-testid="device-detail-sign-out-cta"]').click();
});
// confirm the signout
cy.get('[data-testid="dialog-primary-button"]').click();
// no other sessions or security recommendations sections when only one session
cy.contains('Other sessions').should('not.exist');

View file

@ -82,7 +82,7 @@ export default class DialogButtons extends React.Component<IProps> {
cancelButton = <button
// important: the default type is 'submit' and this button comes before the
// primary in the DOM so will get form submissions unless we make it not a submit.
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
type="button"
onClick={this.onCancelClick}
className={this.props.cancelButtonClass}
@ -104,7 +104,7 @@ export default class DialogButtons extends React.Component<IProps> {
{ cancelButton }
{ this.props.children }
<button type={this.props.primaryIsSubmit ? 'submit' : 'button'}
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
className={primaryButtonClassName}
onClick={this.props.onPrimaryButtonClick}
autoFocus={this.props.focus}

View file

@ -36,6 +36,25 @@ import LoginWithQRSection from '../../devices/LoginWithQRSection';
import LoginWithQR, { Mode } from '../../../auth/LoginWithQR';
import SettingsStore from '../../../../../settings/SettingsStore';
import { useAsyncMemo } from '../../../../../hooks/useAsyncMemo';
import QuestionDialog from '../../../dialogs/QuestionDialog';
const confirmSignOut = async (sessionsToSignOutCount: number): Promise<boolean> => {
const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("Sign out"),
description: (
<div>
<p>{ _t("Are you sure you want to sign out of %(count)s sessions?", {
count: sessionsToSignOutCount,
}) }</p>
</div>
),
cancelButton: _t('Cancel'),
button: _t("Sign out"),
});
const [confirmed] = await finished;
return confirmed;
};
const useSignOut = (
matrixClient: MatrixClient,
@ -61,6 +80,11 @@ const useSignOut = (
if (!deviceIds.length) {
return;
}
const userConfirmedSignout = await confirmSignOut(deviceIds.length);
if (!userConfirmedSignout) {
return;
}
try {
setSigningOutDeviceIds([...signingOutDeviceIds, ...deviceIds]);
await deleteDevicesWithInteractiveAuth(

View file

@ -1596,6 +1596,9 @@
"Sessions": "Sessions",
"Where you're signed in": "Where you're signed in",
"Manage your signed-in devices below. A device's name is visible to people you communicate with.": "Manage your signed-in devices below. A device's name is visible to people you communicate with.",
"Sign out": "Sign out",
"Are you sure you want to sign out of %(count)s sessions?|other": "Are you sure you want to sign out of %(count)s sessions?",
"Are you sure you want to sign out of %(count)s sessions?|one": "Are you sure you want to sign out of %(count)s session?",
"Other sessions": "Other sessions",
"For best security, verify your sessions and sign out from any session that you don't recognize or use anymore.": "For best security, verify your sessions and sign out from any session that you don't recognize or use anymore.",
"Sidebar": "Sidebar",
@ -1732,7 +1735,6 @@
"Please enter verification code sent via text.": "Please enter verification code sent via text.",
"Verification code": "Verification code",
"Discovery options will appear once you have added a phone number above.": "Discovery options will appear once you have added a phone number above.",
"Sign out": "Sign out",
"Sign out all other sessions": "Sign out all other sessions",
"Current session": "Current session",
"Confirm logging out these devices by using Single Sign On to prove your identity.|other": "Confirm logging out these devices by using Single Sign On to prove your identity.",

View file

@ -20,7 +20,7 @@ import { mount, ReactWrapper } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { IPassphraseInfo } from 'matrix-js-sdk/src/crypto/api';
import { findByTestId, getMockClientWithEventEmitter, unmockClientPeg } from '../../../test-utils';
import { findByAttr, getMockClientWithEventEmitter, unmockClientPeg } from '../../../test-utils';
import { findById, flushPromises } from '../../../test-utils';
import AccessSecretStorageDialog from "../../../../src/components/views/dialogs/security/AccessSecretStorageDialog";
@ -91,7 +91,7 @@ describe("AccessSecretStorageDialog", () => {
wrapper.setProps({});
});
const submitButton = findByTestId(wrapper, 'dialog-primary-button').at(0);
const submitButton = findByAttr('data-testid')(wrapper, 'dialog-primary-button').at(0);
// submit button is enabled when key is valid
expect(submitButton.props().disabled).toBeFalsy();
expect(wrapper.find('.mx_AccessSecretStorageDialog_recoveryKeyFeedback').text()).toEqual('Looks good!');
@ -112,7 +112,7 @@ describe("AccessSecretStorageDialog", () => {
// @ts-ignore private
await wrapper.instance().validateRecoveryKey();
const submitButton = findByTestId(wrapper, 'dialog-primary-button').at(0);
const submitButton = findByAttr('data-testid')(wrapper, 'dialog-primary-button').at(0);
// submit button is disabled when recovery key is invalid
expect(submitButton.props().disabled).toBeTruthy();
expect(

View file

@ -65,8 +65,8 @@ describe('<ExportDialog />', () => {
const getAttachmentsCheckbox = (component) => component.find('input[id="include-attachments"]');
const getMessageCountInput = (component) => component.find('input[id="message-count"]');
const getExportFormatInput = (component, format) => component.find(`input[id="exportFormat-${format}"]`);
const getPrimaryButton = (component) => component.find('[data-test-id="dialog-primary-button"]');
const getSecondaryButton = (component) => component.find('[data-test-id="dialog-cancel-button"]');
const getPrimaryButton = (component) => component.find('[data-testid="dialog-primary-button"]');
const getSecondaryButton = (component) => component.find('[data-testid="dialog-cancel-button"]');
const submitForm = async (component) => act(async () => {
getPrimaryButton(component).simulate('click');

View file

@ -111,14 +111,14 @@ exports[`<ChangelogDialog /> should fetch github proxy url for each repo with ol
class="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
type="button"
>
Update

View file

@ -249,14 +249,14 @@ Array [
class="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
type="button"
>
Export
@ -474,14 +474,14 @@ Array [
class="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
type="button"
>
Export
@ -827,7 +827,7 @@ Array [
className="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
disabled={false}
onClick={[Function]}
type="button"
@ -836,7 +836,7 @@ Array [
</button>
<button
className="mx_Dialog_primary"
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
onClick={[Function]}
type="button"
>
@ -1102,14 +1102,14 @@ Array [
class="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
type="button"
>
Export
@ -1327,14 +1327,14 @@ Array [
class="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
type="button"
>
Export
@ -1680,7 +1680,7 @@ Array [
className="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
disabled={false}
onClick={[Function]}
type="button"
@ -1689,7 +1689,7 @@ Array [
</button>
<button
className="mx_Dialog_primary"
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
onClick={[Function]}
type="button"
>
@ -1942,14 +1942,14 @@ Array [
class="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
type="button"
>
Export
@ -2167,14 +2167,14 @@ Array [
class="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
type="button"
>
Export
@ -2520,7 +2520,7 @@ Array [
className="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
disabled={false}
onClick={[Function]}
type="button"
@ -2529,7 +2529,7 @@ Array [
</button>
<button
className="mx_Dialog_primary"
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
onClick={[Function]}
type="button"
>
@ -2873,7 +2873,7 @@ Array [
className="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
data-testid="dialog-cancel-button"
disabled={false}
onClick={[Function]}
type="button"
@ -2882,7 +2882,7 @@ Array [
</button>
<button
className="mx_Dialog_primary"
data-test-id="dialog-primary-button"
data-testid="dialog-primary-button"
onClick={[Function]}
type="button"
>

View file

@ -29,13 +29,13 @@ import {
MatrixEvent,
PUSHER_DEVICE_ID,
PUSHER_ENABLED,
IAuthData,
} from 'matrix-js-sdk/src/matrix';
import SessionManagerTab from '../../../../../../src/components/views/settings/tabs/user/SessionManagerTab';
import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext';
import {
flushPromises,
flushPromisesWithFakeTimers,
getMockClientWithEventEmitter,
mkPusher,
mockClientMethodsUser,
@ -138,7 +138,7 @@ describe('<SessionManagerTab />', () => {
fireEvent.click(dropdown as Element);
// tick to let dropdown render
await flushPromisesWithFakeTimers();
await flushPromises();
fireEvent.click(container.querySelector(`#device-list-filter__${option}`) as Element);
});
@ -152,6 +152,19 @@ describe('<SessionManagerTab />', () => {
getByTestId: ReturnType<typeof render>['getByTestId'],
): boolean => !!(getByTestId('device-select-all-checkbox') as HTMLInputElement).checked;
const confirmSignout = async (
getByTestId: ReturnType<typeof render>['getByTestId'],
confirm = true,
): Promise<void> => {
// modal has sleeps in rendering process :(
await sleep(100);
const buttonId = confirm ? 'dialog-primary-button' : 'dialog-cancel-button';
fireEvent.click(getByTestId(buttonId));
// flush the confirmation promise
await flushPromises();
};
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(logger, 'error').mockRestore();
@ -188,6 +201,10 @@ describe('<SessionManagerTab />', () => {
});
}
});
// sometimes a verification modal is in modal state when these tests run
// make sure the coast is clear
Modal.closeCurrentModal('');
});
it('renders spinner while devices load', () => {
@ -201,7 +218,7 @@ describe('<SessionManagerTab />', () => {
expect(mockClient.getDevices).toHaveBeenCalled();
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
expect(container.getElementsByClassName('mx_Spinner').length).toBeFalsy();
});
@ -213,7 +230,7 @@ describe('<SessionManagerTab />', () => {
const { container } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
expect(container.getElementsByClassName('mx_Spinner').length).toBeFalsy();
});
@ -226,7 +243,7 @@ describe('<SessionManagerTab />', () => {
render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
// called for each device despite error
@ -246,7 +263,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
expect(mockCrossSigningInfo.checkDeviceTrust).toHaveBeenCalledTimes(2);
@ -270,7 +287,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
// twice for each device
@ -287,7 +304,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, queryByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
@ -300,7 +317,7 @@ describe('<SessionManagerTab />', () => {
const { queryByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
expect(queryByTestId('other-sessions-section')).toBeFalsy();
@ -313,7 +330,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
expect(getByTestId('other-sessions-section')).toBeTruthy();
@ -324,13 +341,13 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, container } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
fireEvent.click(getByTestId('unverified-devices-cta'));
// our session manager waits a tick for rerender
await flushPromisesWithFakeTimers();
await flushPromises();
// unverified filter is set
expect(container.querySelector('.mx_FilteredDeviceListHeader')).toMatchSnapshot();
@ -346,7 +363,7 @@ describe('<SessionManagerTab />', () => {
mockClient.getDevices.mockResolvedValue({ devices: [] });
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
expect(getByTestId('current-session-menu').getAttribute('aria-disabled')).toBeTruthy();
@ -357,7 +374,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
expect(getByTestId('current-session-section')).toMatchSnapshot();
@ -369,7 +386,7 @@ describe('<SessionManagerTab />', () => {
const modalSpy = jest.spyOn(Modal, 'createDialog');
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
// click verify button from current session section
@ -387,7 +404,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
expect(getByTestId('current-session-section')).toMatchSnapshot();
@ -402,7 +419,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
const otherSessionsSection = getByTestId('other-sessions-section');
@ -418,7 +435,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, queryByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesOlderMobileDevice.device_id);
@ -449,7 +466,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, queryByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesOlderMobileDevice.device_id);
@ -475,7 +492,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
@ -504,7 +521,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
@ -534,7 +551,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
@ -553,7 +570,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, getByLabelText } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
fireEvent.click(getByTestId('current-session-menu'));
@ -568,7 +585,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, queryByLabelText } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
fireEvent.click(getByTestId('current-session-menu'));
@ -582,11 +599,12 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, getByLabelText } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
fireEvent.click(getByTestId('current-session-menu'));
fireEvent.click(getByLabelText('Sign out all other sessions'));
await confirmSignout(getByTestId);
// other devices deleted, excluding current device
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith([
@ -611,7 +629,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
@ -622,6 +640,8 @@ describe('<SessionManagerTab />', () => {
) as Element;
fireEvent.click(signOutButton);
await confirmSignout(getByTestId);
// sign out button is disabled with spinner
expect((deviceDetails.querySelector(
'[data-testid="device-detail-sign-out-cta"]',
@ -631,12 +651,37 @@ describe('<SessionManagerTab />', () => {
[alicesMobileDevice.device_id], undefined,
);
await flushPromisesWithFakeTimers();
await flushPromises();
// devices refreshed
expect(mockClient.getDevices).toHaveBeenCalled();
});
it('deletes a device when interactive auth is not required', async () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromises();
});
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);
await confirmSignout(getByTestId, false);
// doesnt enter loading state
expect((deviceDetails.querySelector(
'[data-testid="device-detail-sign-out-cta"]',
) as Element).getAttribute('aria-disabled')).toEqual(null);
// delete not called
expect(mockClient.deleteMultipleDevices).not.toHaveBeenCalled();
});
it('deletes a device when interactive auth is required', async () => {
mockClient.deleteMultipleDevices
// require auth
@ -652,7 +697,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, getByLabelText } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
// reset mock count after initial load
@ -665,8 +710,9 @@ describe('<SessionManagerTab />', () => {
'[data-testid="device-detail-sign-out-cta"]',
) as Element;
fireEvent.click(signOutButton);
await confirmSignout(getByTestId);
await flushPromisesWithFakeTimers();
await flushPromises();
// modal rendering has some weird sleeps
await sleep(100);
@ -683,7 +729,7 @@ describe('<SessionManagerTab />', () => {
fireEvent.submit(getByLabelText('Password'));
});
await flushPromisesWithFakeTimers();
await flushPromises();
// called again with auth
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith([alicesMobileDevice.device_id],
@ -708,7 +754,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, getByLabelText } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
@ -718,13 +764,14 @@ describe('<SessionManagerTab />', () => {
'[data-testid="device-detail-sign-out-cta"]',
) as Element;
fireEvent.click(signOutButton);
await confirmSignout(getByTestId);
// button is loading
expect((deviceDetails.querySelector(
'[data-testid="device-detail-sign-out-cta"]',
) as Element).getAttribute('aria-disabled')).toEqual("true");
await flushPromisesWithFakeTimers();
await flushPromises();
// Modal rendering has some weird sleeps.
// Resetting ourselves twice in the main loop gives modal the chance to settle.
@ -743,7 +790,7 @@ describe('<SessionManagerTab />', () => {
fireEvent.click(getByLabelText('Close dialog'));
});
await flushPromisesWithFakeTimers();
await flushPromises();
// not called again
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledTimes(1);
@ -761,12 +808,21 @@ describe('<SessionManagerTab />', () => {
alicesDevice, alicesMobileDevice, alicesOlderMobileDevice,
alicesInactiveDevice,
] });
mockClient.deleteMultipleDevices.mockResolvedValue({});
// get a handle for resolving the delete call
// because promise flushing after the confirm modal is resolving this too
// and we want to test the loading state here
let resolveDeleteRequest;
mockClient.deleteMultipleDevices.mockImplementation(() => {
const promise = new Promise<IAuthData>(resolve => {
resolveDeleteRequest = resolve;
});
return promise;
});
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceSelection(getByTestId, alicesMobileDevice.device_id);
@ -774,6 +830,8 @@ describe('<SessionManagerTab />', () => {
fireEvent.click(getByTestId('sign-out-selection-cta'));
await confirmSignout(getByTestId);
// buttons disabled in list header
expect(getByTestId('sign-out-selection-cta').getAttribute('aria-disabled')).toBeTruthy();
expect(getByTestId('cancel-selection-cta').getAttribute('aria-disabled')).toBeTruthy();
@ -800,6 +858,8 @@ describe('<SessionManagerTab />', () => {
],
undefined,
);
resolveDeleteRequest?.();
});
});
});
@ -819,15 +879,15 @@ describe('<SessionManagerTab />', () => {
fireEvent.change(input, { target: { value: newDeviceName } });
fireEvent.click(getByTestId('device-rename-submit-cta'));
await flushPromisesWithFakeTimers();
await flushPromisesWithFakeTimers();
await flushPromises();
await flushPromises();
};
it('renames current session', async () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
const newDeviceName = 'new device name';
@ -844,7 +904,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
const newDeviceName = 'new device name';
@ -861,7 +921,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
await updateDeviceName(getByTestId, alicesDevice, alicesDevice.display_name);
@ -875,7 +935,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
await updateDeviceName(getByTestId, alicesDevice, '');
@ -891,13 +951,13 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
const newDeviceName = 'new device name';
await updateDeviceName(getByTestId, alicesDevice, newDeviceName);
await flushPromisesWithFakeTimers();
await flushPromises();
expect(logSpy).toHaveBeenCalledWith("Error setting session display name", error);
@ -917,7 +977,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, getByText } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceSelection(getByTestId, alicesMobileDevice.device_id);
@ -941,7 +1001,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, getByText } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceSelection(getByTestId, alicesMobileDevice.device_id);
@ -961,7 +1021,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceSelection(getByTestId, alicesMobileDevice.device_id);
@ -970,7 +1030,7 @@ describe('<SessionManagerTab />', () => {
fireEvent.click(getByTestId('unverified-devices-cta'));
// our session manager waits a tick for rerender
await flushPromisesWithFakeTimers();
await flushPromises();
// unselected
expect(isDeviceSelected(getByTestId, alicesOlderMobileDevice.device_id)).toBeFalsy();
@ -981,7 +1041,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, getByText } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
fireEvent.click(getByTestId('device-select-all-checkbox'));
@ -999,7 +1059,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, getByText } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceSelection(getByTestId, alicesMobileDevice.device_id);
@ -1019,7 +1079,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, getByText } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
fireEvent.click(getByTestId('device-select-all-checkbox'));
@ -1042,7 +1102,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId, container } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
// filter for inactive sessions
@ -1055,6 +1115,7 @@ describe('<SessionManagerTab />', () => {
// sign out of all selected sessions
fireEvent.click(getByTestId('sign-out-selection-cta'));
await confirmSignout(getByTestId);
// only called with session from active filter
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
@ -1071,7 +1132,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
@ -1092,7 +1153,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
@ -1116,7 +1177,7 @@ describe('<SessionManagerTab />', () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromisesWithFakeTimers();
await flushPromises();
});
toggleDeviceDetails(getByTestId, alicesDevice.device_id);