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 onlypull/28788/head^2
							parent
							
								
									37e613bb05
								
							
						
					
					
						commit
						d473b4ac4a
					
				|  | @ -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 New Issue
	
	 Kerry
						Kerry