Merge branch 'develop' into feat/add-message-edition-wysiwyg-composer

This commit is contained in:
Florian Duros 2022-10-25 10:42:29 +02:00 committed by GitHub
commit b5ab1239ed
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(); cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem .mx_Checkbox').last().click();
// sign out from list selection action buttons // sign out from list selection action buttons
cy.get('[data-testid="sign-out-selection-cta"]').click(); cy.get('[data-testid="sign-out-selection-cta"]').click();
cy.get('[data-testid="dialog-primary-button"]').click();
// list updated after sign out // list updated after sign out
cy.get('.mx_FilteredDeviceList_list').find('.mx_FilteredDeviceList_listItem').should('have.length', 1); cy.get('.mx_FilteredDeviceList_list').find('.mx_FilteredDeviceList_listItem').should('have.length', 1);
// security recommendation count updated // security recommendation count updated
@ -106,6 +107,8 @@ describe("Device manager", () => {
// sign out using the device details sign out // sign out using the device details sign out
cy.get('[data-testid="device-detail-sign-out-cta"]').click(); 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 // no other sessions or security recommendations sections when only one session
cy.contains('Other sessions').should('not.exist'); cy.contains('Other sessions').should('not.exist');

View file

@ -82,7 +82,7 @@ export default class DialogButtons extends React.Component<IProps> {
cancelButton = <button cancelButton = <button
// important: the default type is 'submit' and this button comes before the // 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. // 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" type="button"
onClick={this.onCancelClick} onClick={this.onCancelClick}
className={this.props.cancelButtonClass} className={this.props.cancelButtonClass}
@ -104,7 +104,7 @@ export default class DialogButtons extends React.Component<IProps> {
{ cancelButton } { cancelButton }
{ this.props.children } { this.props.children }
<button type={this.props.primaryIsSubmit ? 'submit' : 'button'} <button type={this.props.primaryIsSubmit ? 'submit' : 'button'}
data-test-id="dialog-primary-button" data-testid="dialog-primary-button"
className={primaryButtonClassName} className={primaryButtonClassName}
onClick={this.props.onPrimaryButtonClick} onClick={this.props.onPrimaryButtonClick}
autoFocus={this.props.focus} autoFocus={this.props.focus}

View file

@ -36,6 +36,25 @@ import LoginWithQRSection from '../../devices/LoginWithQRSection';
import LoginWithQR, { Mode } from '../../../auth/LoginWithQR'; import LoginWithQR, { Mode } from '../../../auth/LoginWithQR';
import SettingsStore from '../../../../../settings/SettingsStore'; import SettingsStore from '../../../../../settings/SettingsStore';
import { useAsyncMemo } from '../../../../../hooks/useAsyncMemo'; 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 = ( const useSignOut = (
matrixClient: MatrixClient, matrixClient: MatrixClient,
@ -61,6 +80,11 @@ const useSignOut = (
if (!deviceIds.length) { if (!deviceIds.length) {
return; return;
} }
const userConfirmedSignout = await confirmSignOut(deviceIds.length);
if (!userConfirmedSignout) {
return;
}
try { try {
setSigningOutDeviceIds([...signingOutDeviceIds, ...deviceIds]); setSigningOutDeviceIds([...signingOutDeviceIds, ...deviceIds]);
await deleteDevicesWithInteractiveAuth( await deleteDevicesWithInteractiveAuth(

View file

@ -1596,6 +1596,9 @@
"Sessions": "Sessions", "Sessions": "Sessions",
"Where you're signed in": "Where you're signed in", "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.", "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", "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.", "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", "Sidebar": "Sidebar",
@ -1732,7 +1735,6 @@
"Please enter verification code sent via text.": "Please enter verification code sent via text.", "Please enter verification code sent via text.": "Please enter verification code sent via text.",
"Verification code": "Verification code", "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.", "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", "Sign out all other sessions": "Sign out all other sessions",
"Current session": "Current session", "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.", "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 { act } from 'react-dom/test-utils';
import { IPassphraseInfo } from 'matrix-js-sdk/src/crypto/api'; 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 { findById, flushPromises } from '../../../test-utils';
import AccessSecretStorageDialog from "../../../../src/components/views/dialogs/security/AccessSecretStorageDialog"; import AccessSecretStorageDialog from "../../../../src/components/views/dialogs/security/AccessSecretStorageDialog";
@ -91,7 +91,7 @@ describe("AccessSecretStorageDialog", () => {
wrapper.setProps({}); 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 // submit button is enabled when key is valid
expect(submitButton.props().disabled).toBeFalsy(); expect(submitButton.props().disabled).toBeFalsy();
expect(wrapper.find('.mx_AccessSecretStorageDialog_recoveryKeyFeedback').text()).toEqual('Looks good!'); expect(wrapper.find('.mx_AccessSecretStorageDialog_recoveryKeyFeedback').text()).toEqual('Looks good!');
@ -112,7 +112,7 @@ describe("AccessSecretStorageDialog", () => {
// @ts-ignore private // @ts-ignore private
await wrapper.instance().validateRecoveryKey(); 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 // submit button is disabled when recovery key is invalid
expect(submitButton.props().disabled).toBeTruthy(); expect(submitButton.props().disabled).toBeTruthy();
expect( expect(

View file

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

View file

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

View file

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