mirror of
https://github.com/element-hq/element-web
synced 2024-11-23 01:35:49 +03:00
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:
parent
37e613bb05
commit
d473b4ac4a
9 changed files with 174 additions and 84 deletions
|
@ -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');
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue