diff --git a/services/web/app/src/router.mjs b/services/web/app/src/router.mjs index f87297c35c..a7e8d5e05f 100644 --- a/services/web/app/src/router.mjs +++ b/services/web/app/src/router.mjs @@ -182,7 +182,7 @@ const rateLimiters = { duration: 60, }), sendConfirmation: new RateLimiter('send-confirmation', { - points: 1, + points: 2, duration: 60, }), sendChatMessage: new RateLimiter('send-chat-message', { diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index c64817b94c..9862e47817 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -519,7 +519,6 @@ "enabling": "", "end_of_document": "", "ensure_recover_account": "", - "enter_6_digit_code": "", "enter_any_size_including_units_or_valid_latex_command": "", "enter_image_url": "", "enter_the_code": "", @@ -1224,8 +1223,8 @@ "please_check_your_inbox_to_confirm": "", "please_compile_pdf_before_download": "", "please_compile_pdf_before_word_count": "", - "please_confirm_primary_email": "", - "please_confirm_secondary_email": "", + "please_confirm_primary_email_or_edit": "", + "please_confirm_secondary_email_or_edit": "", "please_confirm_your_email_before_making_it_default": "", "please_contact_support_to_makes_change_to_your_plan": "", "please_enter_confirmation_code": "", @@ -1375,7 +1374,6 @@ "remote_service_error": "", "remove": "", "remove_access": "", - "remove_email_address": "", "remove_from_group": "", "remove_link": "", "remove_manager": "", @@ -1408,7 +1406,6 @@ "resend_link_sso": "", "resend_managed_user_invite": "", "resending_confirmation_code": "", - "resending_confirmation_email": "", "resize": "", "resolve_comment": "", "resolve_comment_error_message": "", @@ -1520,6 +1517,7 @@ "select_user": "", "selected": "", "selection_deleted": "", + "send_confirmation_code": "", "send_first_message": "", "send_message": "", "send_request": "", diff --git a/services/web/frontend/js/features/project-list/components/notifications/groups/confirm-email.tsx b/services/web/frontend/js/features/project-list/components/notifications/groups/confirm-email.tsx index ca73d87a0c..364e60fd3a 100644 --- a/services/web/frontend/js/features/project-list/components/notifications/groups/confirm-email.tsx +++ b/services/web/frontend/js/features/project-list/components/notifications/groups/confirm-email.tsx @@ -1,16 +1,10 @@ import { Trans, useTranslation } from 'react-i18next' import Notification from '../notification' import getMeta from '../../../../../utils/meta' -import useAsync from '../../../../../shared/hooks/use-async' import { useProjectListContext } from '../../../context/project-list-context' -import { - postJSON, - getUserFacingMessage, -} from '../../../../../infrastructure/fetch-json' import { UserEmailData } from '../../../../../../../types/user-email' -import { debugConsole } from '@/utils/debugging' -import OLButton from '@/features/ui/components/ol/ol-button' -import LoadingSpinner from '@/shared/components/loading-spinner' +import ResendConfirmationCodeModal from '@/features/settings/components/emails/resend-confirmation-code-modal' +import { ReactNode, useState } from 'react' const ssoAvailable = ({ samlProviderId, affiliation }: UserEmailData) => { const { hasSamlFeature, hasSamlBeta } = getMeta('ol-ExposedSettings') @@ -114,12 +108,17 @@ function getEmailDeletionDate(emailData: UserEmailData, signUpDate: string) { function ConfirmEmailNotification({ userEmail, signUpDate, + setIsLoading, + isLoading, }: { userEmail: UserEmailData signUpDate: string + setIsLoading: (loading: boolean) => void + isLoading: boolean }) { const { t } = useTranslation() - const { isLoading, isSuccess, isError, error, runAsync } = useAsync() + const [isSuccess, setIsSuccess] = useState(false) + const emailAddress = userEmail.email // We consider secondary emails added on or after 22.03.2024 to be trusted for account recovery // https://github.com/overleaf/internal/pull/17572 @@ -127,6 +126,7 @@ function ConfirmEmailNotification({ const emailDeletionDate = getEmailDeletionDate(userEmail, signUpDate) const isPrimary = userEmail.default + const isEmailConfirmed = !!userEmail.lastConfirmedAt const isEmailTrusted = userEmail.lastConfirmedAt && new Date(userEmail.lastConfirmedAt) >= emailTrustCutoffDate @@ -134,163 +134,97 @@ function ConfirmEmailNotification({ const shouldShowCommonsNotification = emailHasLicenceAfterConfirming(userEmail) && isOnFreeOrIndividualPlan() - const handleResendConfirmationEmail = ({ email }: UserEmailData) => { - runAsync( - postJSON('/user/emails/resend_confirmation', { - body: { email }, - }) - ).catch(debugConsole.error) - } - if (isSuccess) { return null } - if (!userEmail.lastConfirmedAt && !shouldShowCommonsNotification) { - return ( - - {isLoading ? ( -
- -
- ) : isError ? ( -
{getUserFacingMessage(error)}
- ) : ( - <> -

- {isPrimary - ? t('please_confirm_primary_email', { - emailAddress: userEmail.email, - }) - : t('please_confirm_secondary_email', { - emailAddress: userEmail.email, - })} -

- {emailDeletionDate && ( -

- {t('email_remove_by_date', { date: emailDeletionDate })} -

- )} - - )} - - } - action={ - <> - handleResendConfirmationEmail(userEmail)} - > - {t('resend_confirmation_email')} - - - {isPrimary - ? t('change_primary_email') - : t('remove_email_address')} - - - } - /> - ) - } + const confirmationCodeModal = ( + setIsSuccess(true)} + setGroupLoading={setIsLoading} + groupLoading={isLoading} + triggerVariant="secondary" + /> + ) - if (!isEmailTrusted && !isPrimary && !shouldShowCommonsNotification) { - return ( - - {isLoading ? ( -
- -
- ) : isError ? ( -
{getUserFacingMessage(error)}
- ) : ( - <> -

- {t('confirm_secondary_email')} -

-

- {t('reconfirm_secondary_email', { - emailAddress: userEmail.email, - })} -

-

{t('ensure_recover_account')}

- - )} - - } - action={ - <> - handleResendConfirmationEmail(userEmail)} - > - {t('resend_confirmation_email')} - - - {t('remove_email_address')} - - - } - /> - ) - } + let notificationType: 'info' | 'warning' | undefined + let notificationBody: ReactNode | undefined - // Only show the notification if a) a commons license is available and b) the - // user is on a free or individual plan. Users on a group or Commons plan - // already have premium features. if (shouldShowCommonsNotification) { + notificationType = 'info' + notificationBody = ( + <> + ]} // eslint-disable-line react/jsx-key + /> +
+ ]} // eslint-disable-line react/jsx-key + /> + + ) + } else if (!isEmailConfirmed) { + notificationType = 'warning' + notificationBody = ( + <> +

+ {isPrimary ? ( + , + ]} + /> + ) : ( + , + ]} + /> + )} +

+ {emailDeletionDate && ( +

{t('email_remove_by_date', { date: emailDeletionDate })}

+ )} + + ) + } else if (!isEmailTrusted && !isPrimary) { + notificationType = 'warning' + notificationBody = ( + <> +

+ {t('confirm_secondary_email')} +

+

{t('reconfirm_secondary_email', { emailAddress })}

+

{t('ensure_recover_account')}

+ + ) + } + + if (notificationType) { return ( - {isLoading ? ( - - ) : isError ? ( -
{getUserFacingMessage(error)}
- ) : ( - <> - ]} // eslint-disable-line react/jsx-key - /> -
- ]} // eslint-disable-line react/jsx-key - /> - - )} - - } - action={ - handleResendConfirmationEmail(userEmail)} - > - {t('resend_email')} - - } + type={notificationType} + content={notificationBody} + action={confirmationCodeModal} /> ) } @@ -302,6 +236,7 @@ function ConfirmEmail() { const { totalProjectsCount } = useProjectListContext() const userEmails = getMeta('ol-userEmails') || [] const signUpDate = getMeta('ol-user')?.signUpDate + const [isLoading, setIsLoading] = useState(false) if (!totalProjectsCount || !userEmails.length || !signUpDate) { return null @@ -315,6 +250,8 @@ function ConfirmEmail() { key={`confirm-email-${userEmail.email}`} userEmail={userEmail} signUpDate={signUpDate} + isLoading={isLoading} + setIsLoading={setIsLoading} /> ) : null })} diff --git a/services/web/frontend/js/features/settings/components/emails/confirm-email-form.tsx b/services/web/frontend/js/features/settings/components/emails/confirm-email-form.tsx index 66aaee9cce..d82a43315c 100644 --- a/services/web/frontend/js/features/settings/components/emails/confirm-email-form.tsx +++ b/services/web/frontend/js/features/settings/components/emails/confirm-email-form.tsx @@ -2,7 +2,7 @@ import { postJSON } from '@/infrastructure/fetch-json' import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n' import Notification from '@/shared/components/notification' import getMeta from '@/utils/meta' -import { FormEvent, MouseEventHandler, useState } from 'react' +import { FormEvent, MouseEventHandler, ReactNode, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import LoadingSpinner from '@/shared/components/loading-spinner' import MaterialIcon from '@/shared/components/material-icon' @@ -27,6 +27,7 @@ type ConfirmEmailFormProps = { interstitial: boolean isModal?: boolean onCancel?: () => void + outerError?: string } export function ConfirmEmailForm({ @@ -40,15 +41,17 @@ export function ConfirmEmailForm({ interstitial, isModal, onCancel, + outerError, }: ConfirmEmailFormProps) { const { t } = useTranslation() const [confirmationCode, setConfirmationCode] = useState('') const [feedback, setFeedback] = useState(null) const [isConfirming, setIsConfirming] = useState(false) const [isResending, setIsResending] = useState(false) + const [hasResent, setHasResent] = useState(false) const [successRedirectPath, setSuccessRedirectPath] = useState('') const { isReady } = useWaitForI18n() - + const outerErrorDisplay = (!hasResent && outerError) || null const errorHandler = (err: any, actionType?: string) => { let errorName = err?.data?.message?.key || 'generic_something_went_wrong' @@ -131,6 +134,7 @@ export function ConfirmEmailForm({ }) .finally(() => { setIsResending(false) + setHasResent(true) }) sendMB('email-verification-click', { @@ -158,8 +162,15 @@ export function ConfirmEmailForm({ ) } - let intro =
{t('confirm_your_email')}
- if (isModal) intro =
{t('we_sent_code')}
+ let intro: ReactNode | null = ( +
{t('confirm_your_email')}
+ ) + if (isModal) + intro = outerErrorDisplay ? ( +
+ ) : ( +

{outerErrorDisplay ? null : t('we_sent_code')}

+ ) if (interstitial) intro = (

{t('confirm_your_email')}

@@ -172,12 +183,14 @@ export function ConfirmEmailForm({ className="confirm-email-form" >
- {feedback?.type === 'alert' && ( + {(feedback?.type === 'alert' || outerErrorDisplay) && ( } + type={outerErrorDisplay ? 'error' : feedback!.style} + content={ + outerErrorDisplay || + } /> )} @@ -191,7 +204,6 @@ export function ConfirmEmailForm({
{feedback?.type === 'input' && ( @@ -214,7 +227,7 @@ export function ConfirmEmailForm({
{t('unconfirmed')}.
{!ssoAvailable && ( - + )}
)} diff --git a/services/web/frontend/js/features/settings/components/emails/resend-confirmation-code-modal.tsx b/services/web/frontend/js/features/settings/components/emails/resend-confirmation-code-modal.tsx index 0c7b1394fe..b17337924e 100644 --- a/services/web/frontend/js/features/settings/components/emails/resend-confirmation-code-modal.tsx +++ b/services/web/frontend/js/features/settings/components/emails/resend-confirmation-code-modal.tsx @@ -1,10 +1,8 @@ import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import Icon from '../../../../shared/components/icon' import { FetchError, postJSON } from '@/infrastructure/fetch-json' import useAsync from '../../../../shared/hooks/use-async' import { UserEmailData } from '../../../../../../types/user-email' -import { useUserEmailsContext } from '../../context/user-email-context' import OLButton from '@/features/ui/components/ol/ol-button' import OLModal, { OLModalBody, @@ -16,39 +14,33 @@ import { ConfirmEmailForm } from '@/features/settings/components/emails/confirm- type ResendConfirmationEmailButtonProps = { email: UserEmailData['email'] + groupLoading: boolean + setGroupLoading: (loading: boolean) => void + onSuccess: () => void + triggerVariant: 'link' | 'secondary' } function ResendConfirmationCodeModal({ email, + groupLoading, + setGroupLoading, + onSuccess, + triggerVariant, }: ResendConfirmationEmailButtonProps) { const { t } = useTranslation() const { error, isLoading, isError, runAsync } = useAsync() - const { - state, - setLoading: setUserEmailsContextLoading, - getEmails, - } = useUserEmailsContext() const [modalVisible, setModalVisible] = useState(false) - // Update global isLoading prop useEffect(() => { - setUserEmailsContextLoading(isLoading) - }, [setUserEmailsContextLoading, isLoading]) + setGroupLoading(isLoading) + }, [isLoading, setGroupLoading]) const handleResendConfirmationEmail = async () => { await runAsync( postJSON('/user/emails/send-confirmation-code', { body: { email } }) ) - .then(() => setModalVisible(true)) .catch(() => {}) - } - - if (isLoading) { - return ( - <> - {t('sending')}… - - ) + .finally(() => setModalVisible(true)) } const rateLimited = @@ -77,9 +69,16 @@ function ResendConfirmationCodeModal({ confirmationEndpoint="/user/emails/confirm-code" email={email} onSuccessfulConfirmation={() => { - getEmails() + onSuccess() setModalVisible(false) }} + outerError={ + isError + ? rateLimited + ? t('too_many_requests') + : t('generic_something_went_wrong') + : undefined + } /> @@ -94,21 +93,14 @@ function ResendConfirmationCodeModal({ )} - {t('resend_confirmation_code')} + {t('send_confirmation_code')} -
- {isError && ( -
- {rateLimited - ? t('too_many_requests') - : t('generic_something_went_wrong')} -
- )} ) } diff --git a/services/web/frontend/stories/hooks/use-fetch-mock.tsx b/services/web/frontend/stories/hooks/use-fetch-mock.tsx index 7f00118aac..304d9e0273 100644 --- a/services/web/frontend/stories/hooks/use-fetch-mock.tsx +++ b/services/web/frontend/stories/hooks/use-fetch-mock.tsx @@ -10,6 +10,7 @@ export default function useFetchMock( fetchMock.mockGlobal() useLayoutEffect(() => { + fetchMock.mockGlobal() callback(fetchMock) return () => { fetchMock.removeRoutes() diff --git a/services/web/frontend/stories/project-list/notifications.stories.tsx b/services/web/frontend/stories/project-list/notifications.stories.tsx index aaabe2ba5b..90fa82bfa5 100644 --- a/services/web/frontend/stories/project-list/notifications.stories.tsx +++ b/services/web/frontend/stories/project-list/notifications.stories.tsx @@ -14,6 +14,8 @@ import { setReconfirmationMeta, } from './helpers/emails' import { useMeta } from '../hooks/use-meta' +import { SplitTestProvider } from '@/shared/context/split-test-context' +import React, { ComponentType } from 'react' export const ProjectInvite = (args: any) => { useFetchMock(commonSetupMocks) @@ -343,4 +345,11 @@ export const ReconfirmedAffiliationSuccess = (args: any) => { export default { title: 'Project List / Notifications', component: UserNotifications, + decorators: [ + (Story: ComponentType) => ( + + + + ), + ], } diff --git a/services/web/frontend/stories/project-list/notifications/confirm-email.stories.tsx b/services/web/frontend/stories/project-list/notifications/confirm-email.stories.tsx new file mode 100644 index 0000000000..5bbcbcf8fb --- /dev/null +++ b/services/web/frontend/stories/project-list/notifications/confirm-email.stories.tsx @@ -0,0 +1,66 @@ +import { SplitTestProvider } from '@/shared/context/split-test-context' +import UserNotifications from '../../../js/features/project-list/components/notifications/user-notifications' +import { ProjectListProvider } from '../../../js/features/project-list/context/project-list-context' +import { useMeta } from '../../hooks/use-meta' +import useFetchMock from '../../hooks/use-fetch-mock' +import ConfirmEmailNotification from '@/features/project-list/components/notifications/groups/confirm-email' + +export const ConfirmEmail = (args: any) => { + useMeta({ + 'ol-userEmails': [ + { + email: 'erika.mustermann+unconfirmed-primary@example.com', + default: true, + }, + { email: 'erika.mustermann+unconfirmed@example.com' }, + { + email: 'erika.mustermann+untrusted@example.com', + lastConfirmedAt: '2019-01-01', + confirmedAt: '2019-01-01', + }, + { + email: 'erika.mustermann+mit@example.com', + affiliation: { + institution: { + id: 123, + name: 'Massachusetts Institute of Technology', + confirmed: true, + commonsAccount: true, + }, + }, + }, + ], + 'ol-user': { signUpDate: '2021-01-01' }, + 'ol-usersBestSubscription': { type: 'free' }, + 'ol-prefetchedProjectsBlob': { totalSize: 20 }, + }) + useFetchMock(fetchMock => { + fetchMock.post('/user/emails/send-confirmation-code', args.statusCode, { + delay: 500, + }) + fetchMock.post('/user/emails/confirm-code', args.statusCode, { + delay: 500, + }) + fetchMock.post('/user/emails/resend-confirmation-code', args.statusCode, { + delay: 500, + }) + }) + return ( + + + + + + ) +} + +export default { + title: 'Project List / Notifications', + component: ConfirmEmailNotification, + args: { + statusCode: 200, + }, + argTypes: { + statusCode: { type: 'select', options: [200, 400, 429] }, + }, +} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 4729f54756..910621f51a 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -669,7 +669,6 @@ "enabling": "Enabling", "end_of_document": "End of document", "ensure_recover_account": "This will ensure that it can be used to recover your __appName__ account in case you lose access to your primary email address.", - "enter_6_digit_code": "Enter 6-digit code", "enter_any_size_including_units_or_valid_latex_command": "Enter any size (including units) or valid LaTeX command", "enter_image_url": "Enter image URL", "enter_the_code": "Enter the 6-digit code sent to __email__.", @@ -1627,8 +1626,8 @@ "please_compile_pdf_before_download": "Please compile your project before downloading the PDF", "please_compile_pdf_before_word_count": "Please compile your project before performing a word count", "please_confirm_email": "Please confirm your email __emailAddress__ by clicking on the link in the confirmation email ", - "please_confirm_primary_email": "Please confirm your primary email address __emailAddress__ by clicking on the link in the confirmation email.", - "please_confirm_secondary_email": "Please confirm your secondary email address __emailAddress__ by clicking on the link in the confirmation email.", + "please_confirm_primary_email_or_edit": "Please confirm your primary email address __emailAddress__. To edit it, go to <0>Account settings.", + "please_confirm_secondary_email_or_edit": "Please confirm your secondary email address __emailAddress__. To edit it, go to <0>Account settings.", "please_confirm_your_email_before_making_it_default": "Please confirm your email before making it the primary.", "please_contact_support_to_makes_change_to_your_plan": "Please <0>contact Support to make changes to your plan", "please_contact_us_if_you_think_this_is_in_error": "Please <0>contact us if you think this is in error.", @@ -1810,7 +1809,6 @@ "remote_service_error": "The remote service produced an error", "remove": "Remove", "remove_access": "Remove access", - "remove_email_address": "Remove email address", "remove_from_group": "Remove from group", "remove_link": "Remove link", "remove_manager": "Remove manager", @@ -1853,7 +1851,6 @@ "resend_link_sso": "Resend SSO invite", "resend_managed_user_invite": "Resend managed user invite", "resending_confirmation_code": "Resending confirmation code", - "resending_confirmation_email": "Resending confirmation email", "reset_password": "Reset Password", "reset_password_link": "Click this link to reset your password", "reset_password_sentence_case": "Reset password", @@ -1987,6 +1984,7 @@ "selected_by_overleaf_staff": "Selected by Overleaf staff", "selection_deleted": "Selection deleted", "send": "Send", + "send_confirmation_code": "Send confirmation code", "send_first_message": "Send your first message to your collaborators", "send_message": "Send message", "send_request": "Send request", diff --git a/services/web/test/frontend/features/project-list/components/notifications.test.tsx b/services/web/test/frontend/features/project-list/components/notifications.test.tsx index 7197ddb365..78c732ebe3 100644 --- a/services/web/test/frontend/features/project-list/components/notifications.test.tsx +++ b/services/web/test/frontend/features/project-list/components/notifications.test.tsx @@ -5,7 +5,6 @@ import { render, screen, waitForElementToBeRemoved, - within, } from '@testing-library/react' import fetchMock from 'fetch-mock' import { merge, cloneDeep } from 'lodash' @@ -672,32 +671,37 @@ describe('', function () { renderWithinProjectListProvider(ConfirmEmail) await fetchMock.callHistory.flush(true) - fetchMock.post('/user/emails/resend_confirmation', 200) + fetchMock.post('/user/emails/send-confirmation-code', 200) const email = userEmails[0].email - const notificationBody = await screen.findByTestId( - 'pro-notification-body' - ) + const alert = await screen.findByRole('alert') if (isPrimary) { - expect(notificationBody.textContent).to.contain( - `Please confirm your primary email address ${email} by clicking on the link in the confirmation email.` + expect(alert.textContent).to.contain( + `Please confirm your primary email address ${email}. To edit it, go to ` ) } else { - expect(notificationBody.textContent).to.contain( - `Please confirm your secondary email address ${email} by clicking on the link in the confirmation email.` + expect(alert.textContent).to.contain( + `Please confirm your secondary email address ${email}. To edit it, go to ` ) } - const resendButton = screen.getByRole('button', { name: /resend/i }) - fireEvent.click(resendButton) + expect( + screen + .getByRole('button', { name: 'Send confirmation code' }) + .classList.contains('button-loading') + ).to.be.false - await waitForElementToBeRemoved(() => - screen.queryByRole('button', { name: /resend/i }) - ) + expect(screen.queryByRole('dialog')).to.be.null + + const sendCodeButton = await screen.findByRole('button', { + name: 'Send confirmation code', + }) + fireEvent.click(sendCodeButton) + + await screen.findByRole('dialog') expect(fetchMock.callHistory.called()).to.be.true - expect(screen.queryByRole('alert')).to.be.null }) } @@ -716,25 +720,22 @@ describe('', function () { renderWithinProjectListProvider(ConfirmEmail) await fetchMock.callHistory.flush(true) - fetchMock.post('/user/emails/resend_confirmation', 200) + fetchMock.post('/user/emails/send-confirmation-code', 200) const email = untrustedUserData.email - const notificationBody = await screen.findByTestId( - 'not-trusted-notification-body' - ) - expect(notificationBody.textContent).to.contain( + const alert = await screen.findByRole('alert') + expect(alert.textContent).to.contain( `To enhance the security of your Overleaf account, please reconfirm your secondary email address ${email}.` ) - const resendButton = screen.getByRole('button', { name: /resend/i }) + const resendButton = screen.getByRole('button', { + name: 'Send confirmation code', + }) fireEvent.click(resendButton) - await waitForElementToBeRemoved(() => - screen.getByRole('button', { name: /resend/i }) - ) + await screen.findByRole('dialog') expect(fetchMock.callHistory.called()).to.be.true - expect(screen.queryByRole('alert')).to.be.null }) it('fails to send', async function () { @@ -742,20 +743,15 @@ describe('', function () { renderWithinProjectListProvider(ConfirmEmail) await fetchMock.callHistory.flush(true) - fetchMock.post('/user/emails/resend_confirmation', 500) + fetchMock.post('/user/emails/send-confirmation-code', 500) const resendButtons = await screen.findAllByRole('button', { - name: /resend/i, + name: 'Send confirmation code', }) const resendButton = resendButtons[0] fireEvent.click(resendButton) - const notificationBody = screen.getByTestId('pro-notification-body') - await waitForElementToBeRemoved(() => - within(notificationBody).getByTestId( - 'loading-resending-confirmation-email' - ) - ) + await screen.findByRole('dialog') expect(fetchMock.callHistory.called()).to.be.true screen.getByText(/something went wrong/i) @@ -773,11 +769,10 @@ describe('', function () { const alert = await screen.findByRole('alert') const email = unconfirmedCommonsUserData.email - const notificationBody = within(alert).getByTestId('notification-body') - expect(notificationBody.textContent).to.contain( + expect(alert.textContent).to.contain( 'You are one step away from accessing Overleaf Professional features' ) - expect(notificationBody.textContent).to.contain( + expect(alert.textContent).to.contain( `Overleaf has an Overleaf subscription. Click the confirmation link sent to ${email} to upgrade to Overleaf Professional` ) }) @@ -794,17 +789,14 @@ describe('', function () { const alert = await screen.findByRole('alert') const email = unconfirmedCommonsUserData.email - const notificationBody = within(alert).getByTestId( - 'pro-notification-body' - ) const isPrimary = unconfirmedCommonsUserData.default if (isPrimary) { - expect(notificationBody.textContent).to.contain( - `Please confirm your primary email address ${email} by clicking on the link in the confirmation email` + expect(alert.textContent).to.contain( + `Please confirm your primary email address ${email}.` ) } else { - expect(notificationBody.textContent).to.contain( - `Please confirm your secondary email address ${email} by clicking on the link in the confirmation email` + expect(alert.textContent).to.contain( + `Please confirm your secondary email address ${email}.` ) } }) diff --git a/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx index e784f6aaac..55c833df1c 100644 --- a/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx @@ -99,7 +99,7 @@ describe('', function () { fetchMock.get('/user/emails?ensureAffiliation=true', [unconfirmedUserData]) render() - await screen.findByRole('button', { name: /resend confirmation code/i }) + await screen.findByRole('button', { name: 'Send confirmation code' }) }) it('renders professional label', async function () { @@ -121,24 +121,24 @@ describe('', function () { fetchMock.post('/user/emails/send-confirmation-code', 200) const button = screen.getByRole('button', { - name: /resend confirmation code/i, + name: 'Send confirmation code', }) fireEvent.click(button) expect( screen.queryByRole('button', { - name: /resend confirmation code/i, + name: 'Send confirmation code', }) ).to.be.null - await waitForElementToBeRemoved(() => screen.getByText(/sending/i)) + await screen.findByRole('dialog') expect( screen.queryByText(/an error has occurred while performing your request/i) ).to.be.null await screen.findAllByRole('button', { - name: /resend confirmation code/i, + name: 'Resend confirmation code', }) }) @@ -151,17 +151,17 @@ describe('', function () { fetchMock.post('/user/emails/send-confirmation-code', 503) const button = screen.getByRole('button', { - name: /resend confirmation code/i, + name: 'Send confirmation code', }) fireEvent.click(button) - expect(screen.queryByRole('button', { name: /resend confirmation code/i })) - .to.be.null + expect(screen.queryByRole('button', { name: 'Send confirmation code' })).to + .be.null - await waitForElementToBeRemoved(() => screen.getByText(/sending/i)) + await screen.findByRole('dialog') - screen.getByText(/sorry, something went wrong/i) - screen.getByRole('button', { name: /resend confirmation code/i }) + await screen.findByText(/sorry, something went wrong/i) + screen.getByRole('button', { name: 'Resend confirmation code' }) }) it('sorts emails with primary first, then confirmed, then unconfirmed', async function () {