diff options
Diffstat (limited to 'spec/frontend/sessions/new')
-rw-r--r-- | spec/frontend/sessions/new/components/email_verification_spec.js | 251 | ||||
-rw-r--r-- | spec/frontend/sessions/new/components/update_email_spec.js | 180 |
2 files changed, 431 insertions, 0 deletions
diff --git a/spec/frontend/sessions/new/components/email_verification_spec.js b/spec/frontend/sessions/new/components/email_verification_spec.js new file mode 100644 index 00000000000..30ba2782f2f --- /dev/null +++ b/spec/frontend/sessions/new/components/email_verification_spec.js @@ -0,0 +1,251 @@ +import { GlForm, GlFormInput } from '@gitlab/ui'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { s__ } from '~/locale'; +import { createAlert, VARIANT_SUCCESS } from '~/alert'; +import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status'; +import EmailVerification from '~/sessions/new/components/email_verification.vue'; +import UpdateEmail from '~/sessions/new/components/update_email.vue'; +import { visitUrl } from '~/lib/utils/url_utility'; +import { + I18N_EMAIL_EMPTY_CODE, + I18N_EMAIL_INVALID_CODE, + I18N_GENERIC_ERROR, + I18N_UPDATE_EMAIL, + I18N_RESEND_LINK, + I18N_EMAIL_RESEND_SUCCESS, +} from '~/sessions/new/constants'; + +jest.mock('~/alert'); +jest.mock('~/lib/utils/url_utility', () => ({ + ...jest.requireActual('~/lib/utils/url_utility'), + visitUrl: jest.fn(), +})); + +describe('EmailVerification', () => { + let wrapper; + let axiosMock; + + const defaultPropsData = { + obfuscatedEmail: 'al**@g*****.com', + verifyPath: '/users/sign_in', + resendPath: '/users/resend_verification_code', + isOfferEmailReset: true, + updateEmailPath: '/users/update_email', + }; + + const createComponent = (props = {}) => { + wrapper = mountExtended(EmailVerification, { + propsData: { ...defaultPropsData, ...props }, + }); + }; + + const findForm = () => wrapper.findComponent(GlForm); + const findCodeInput = () => wrapper.findComponent(GlFormInput); + const findUpdateEmail = () => wrapper.findComponent(UpdateEmail); + const findSubmitButton = () => wrapper.find('[type="submit"]'); + const findResendLink = () => wrapper.findByText(I18N_RESEND_LINK); + const findUpdateEmailLink = () => wrapper.findByText(I18N_UPDATE_EMAIL); + const enterCode = (code) => findCodeInput().setValue(code); + const submitForm = () => findForm().trigger('submit'); + + beforeEach(() => { + axiosMock = new MockAdapter(axios); + createComponent(); + }); + + afterEach(() => { + createAlert.mockClear(); + axiosMock.restore(); + }); + + describe('rendering the form', () => { + it('contains the obfuscated email address', () => { + expect(wrapper.text()).toContain(defaultPropsData.obfuscatedEmail); + }); + }); + + describe('verifying the code', () => { + describe('when successfully verifying the code', () => { + const redirectPath = 'root'; + + beforeEach(async () => { + enterCode('123456'); + + axiosMock + .onPost(defaultPropsData.verifyPath) + .reply(HTTP_STATUS_OK, { status: 'success', redirect_path: redirectPath }); + + await submitForm(); + await axios.waitForAll(); + }); + + it('redirects to the returned redirect path', () => { + expect(visitUrl).toHaveBeenCalledWith(redirectPath); + }); + }); + + describe('error messages', () => { + it.each` + scenario | code | submit | codeValid | errorShown | message + ${'shows no error messages before submitting the form'} | ${''} | ${false} | ${false} | ${false} | ${null} + ${'shows no error messages before submitting the form'} | ${'xxx'} | ${false} | ${false} | ${false} | ${null} + ${'shows no error messages before submitting the form'} | ${'123456'} | ${false} | ${true} | ${false} | ${null} + ${'shows empty code error message when submitting the form'} | ${''} | ${true} | ${false} | ${true} | ${I18N_EMAIL_EMPTY_CODE} + ${'shows invalid error message when submitting the form'} | ${'xxx'} | ${true} | ${false} | ${true} | ${I18N_EMAIL_INVALID_CODE} + ${'shows incorrect code error message returned from the server'} | ${'123456'} | ${true} | ${true} | ${true} | ${s__('IdentityVerification|The code is incorrect. Enter it again, or send a new code.')} + `(`$scenario with code $code`, async ({ code, submit, codeValid, errorShown, message }) => { + enterCode(code); + + if (submit && codeValid) { + axiosMock + .onPost(defaultPropsData.verifyPath) + .replyOnce(HTTP_STATUS_OK, { status: 'failure', message }); + } + + if (submit) { + await submitForm(); + await axios.waitForAll(); + } + + expect(findCodeInput().classes('is-invalid')).toBe(errorShown); + expect(findSubmitButton().props('disabled')).toBe(errorShown); + if (message) expect(wrapper.text()).toContain(message); + }); + + it('keeps showing error messages for invalid codes after submitting the form', async () => { + const serverErrorMessage = 'error message'; + + enterCode('123456'); + + axiosMock + .onPost(defaultPropsData.verifyPath) + .replyOnce(HTTP_STATUS_OK, { status: 'failure', message: serverErrorMessage }); + + await submitForm(); + await axios.waitForAll(); + + expect(wrapper.text()).toContain(serverErrorMessage); + + await enterCode(''); + expect(wrapper.text()).toContain(I18N_EMAIL_EMPTY_CODE); + + await enterCode('xxx'); + expect(wrapper.text()).toContain(I18N_EMAIL_INVALID_CODE); + }); + + it('captures the error and shows an alert message when the request failed', async () => { + enterCode('123456'); + + axiosMock.onPost(defaultPropsData.verifyPath).replyOnce(HTTP_STATUS_OK, null); + + await submitForm(); + await axios.waitForAll(); + + expect(createAlert).toHaveBeenCalledWith({ + message: I18N_GENERIC_ERROR, + captureError: true, + error: expect.any(Error), + }); + }); + + it('captures the error and shows an alert message when the request undefined', async () => { + enterCode('123456'); + + axiosMock.onPost(defaultPropsData.verifyPath).reply(HTTP_STATUS_OK, { status: undefined }); + + await submitForm(); + await axios.waitForAll(); + + expect(createAlert).toHaveBeenCalledWith({ + message: I18N_GENERIC_ERROR, + captureError: true, + error: undefined, + }); + }); + }); + }); + + describe('resending the code', () => { + const failedMessage = 'Failure sending the code'; + const successAlertObject = { + message: I18N_EMAIL_RESEND_SUCCESS, + variant: VARIANT_SUCCESS, + }; + const failedAlertObject = { + message: failedMessage, + }; + const undefinedAlertObject = { + captureError: true, + error: undefined, + message: I18N_GENERIC_ERROR, + }; + const genericAlertObject = { + message: I18N_GENERIC_ERROR, + captureError: true, + error: expect.any(Error), + }; + + it.each` + scenario | statusCode | response | alertObject + ${'the code was successfully resend'} | ${HTTP_STATUS_OK} | ${{ status: 'success' }} | ${successAlertObject} + ${'there was a problem resending the code'} | ${HTTP_STATUS_OK} | ${{ status: 'failure', message: failedMessage }} | ${failedAlertObject} + ${'when the request is undefined'} | ${HTTP_STATUS_OK} | ${{ status: undefined }} | ${undefinedAlertObject} + ${'when the request failed'} | ${HTTP_STATUS_NOT_FOUND} | ${null} | ${genericAlertObject} + `(`shows an alert message when $scenario`, async ({ statusCode, response, alertObject }) => { + enterCode('xxx'); + + await submitForm(); + + axiosMock.onPost(defaultPropsData.resendPath).replyOnce(statusCode, response); + + findResendLink().trigger('click'); + + await axios.waitForAll(); + + expect(createAlert).toHaveBeenCalledWith(alertObject); + expect(findCodeInput().element.value).toBe(''); + }); + }); + + describe('updating the email', () => { + it('contains the link to show the update email form', () => { + expect(findUpdateEmailLink().exists()).toBe(true); + }); + + describe('when the isOfferEmailReset property is set to false', () => { + beforeEach(() => { + createComponent({ isOfferEmailReset: false }); + }); + + it('does not contain the link to show the update email form', () => { + expect(findUpdateEmailLink().exists()).toBe(false); + }); + }); + + it('shows the UpdateEmail component when clicking the link', async () => { + expect(findUpdateEmail().exists()).toBe(false); + + await findUpdateEmailLink().trigger('click'); + + expect(findUpdateEmail().exists()).toBe(true); + }); + + describe('when the UpdateEmail component triggers verifyToken', () => { + const newEmail = 'new@ema.il'; + + beforeEach(async () => { + enterCode('123'); + await findUpdateEmailLink().trigger('click'); + findUpdateEmail().vm.$emit('verifyToken', newEmail); + }); + + it('hides the UpdateEmail component, shows the updated email address and resets the form', () => { + expect(findUpdateEmail().exists()).toBe(false); + expect(wrapper.text()).toContain(newEmail); + expect(findCodeInput().element.value).toBe(''); + }); + }); + }); +}); diff --git a/spec/frontend/sessions/new/components/update_email_spec.js b/spec/frontend/sessions/new/components/update_email_spec.js new file mode 100644 index 00000000000..822720da898 --- /dev/null +++ b/spec/frontend/sessions/new/components/update_email_spec.js @@ -0,0 +1,180 @@ +import { GlForm, GlFormInput } from '@gitlab/ui'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { createAlert, VARIANT_SUCCESS } from '~/alert'; +import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status'; +import UpdateEmail from '~/sessions/new/components/update_email.vue'; +import { + I18N_CANCEL, + I18N_EMAIL_INVALID, + I18N_UPDATE_EMAIL_SUCCESS, + I18N_GENERIC_ERROR, + SUCCESS_RESPONSE, + FAILURE_RESPONSE, +} from '~/sessions/new/constants'; + +const validEmailAddress = 'foo+bar@ema.il'; +const invalidEmailAddress = 'invalid@ema@il'; + +jest.mock('~/alert'); +jest.mock('~/lib/utils/url_utility', () => ({ + ...jest.requireActual('~/lib/utils/url_utility'), + visitUrl: jest.fn(), +})); + +describe('EmailVerification', () => { + let wrapper; + let axiosMock; + + const defaultPropsData = { + updateEmailPath: '/users/update_email', + }; + + const createComponent = (props = {}) => { + wrapper = mountExtended(UpdateEmail, { + propsData: { ...defaultPropsData, ...props }, + }); + }; + + const findForm = () => wrapper.findComponent(GlForm); + const findEmailInput = () => wrapper.findComponent(GlFormInput); + const findSubmitButton = () => wrapper.find('[type="submit"]'); + const findCancelLink = () => wrapper.findByText(I18N_CANCEL); + const enterEmail = (email) => findEmailInput().setValue(email); + const submitForm = () => findForm().trigger('submit'); + + beforeEach(() => { + axiosMock = new MockAdapter(axios); + createComponent(); + }); + + afterEach(() => { + createAlert.mockClear(); + axiosMock.restore(); + }); + + describe('when successfully verifying the email address', () => { + beforeEach(async () => { + enterEmail(validEmailAddress); + + axiosMock + .onPatch(defaultPropsData.updateEmailPath) + .reply(HTTP_STATUS_OK, { status: SUCCESS_RESPONSE }); + + submitForm(); + await axios.waitForAll(); + }); + + it('shows a successfully updated alert', () => { + expect(createAlert).toHaveBeenCalledWith({ + message: I18N_UPDATE_EMAIL_SUCCESS, + variant: VARIANT_SUCCESS, + }); + }); + + it('emits a verifyToken event with the updated email address', () => { + expect(wrapper.emitted('verifyToken')[0]).toEqual([validEmailAddress]); + }); + }); + + describe('error messages', () => { + beforeEach(() => { + enterEmail(invalidEmailAddress); + }); + + describe('when trying to submit an invalid email address', () => { + it('shows no error message before submitting the form', () => { + expect(wrapper.text()).not.toContain(I18N_EMAIL_INVALID); + expect(findSubmitButton().props('disabled')).toBe(false); + }); + + describe('when submitting the form', () => { + beforeEach(async () => { + submitForm(); + await axios.waitForAll(); + }); + + it('shows an error message and disables the submit button', () => { + expect(wrapper.text()).toContain(I18N_EMAIL_INVALID); + expect(findSubmitButton().props('disabled')).toBe(true); + }); + + describe('when entering a valid email address', () => { + beforeEach(() => { + enterEmail(validEmailAddress); + }); + + it('hides the error message and enables the submit button again', () => { + expect(wrapper.text()).not.toContain(I18N_EMAIL_INVALID); + expect(findSubmitButton().props('disabled')).toBe(false); + }); + }); + }); + }); + + describe('when the server responds with an error message', () => { + const serverErrorMessage = 'server error message'; + + beforeEach(async () => { + enterEmail(validEmailAddress); + + axiosMock + .onPatch(defaultPropsData.updateEmailPath) + .replyOnce(HTTP_STATUS_OK, { status: FAILURE_RESPONSE, message: serverErrorMessage }); + + submitForm(); + await axios.waitForAll(); + }); + + it('shows the error message and disables the submit button', () => { + expect(wrapper.text()).toContain(serverErrorMessage); + expect(findSubmitButton().props('disabled')).toBe(true); + }); + + describe('when entering a valid email address', () => { + beforeEach(async () => { + await enterEmail(''); + enterEmail(validEmailAddress); + }); + + it('hides the error message and enables the submit button again', () => { + expect(wrapper.text()).not.toContain(serverErrorMessage); + expect(findSubmitButton().props('disabled')).toBe(false); + }); + }); + }); + + describe('when the server responds unexpectedly', () => { + it.each` + scenario | statusCode + ${'the response is undefined'} | ${HTTP_STATUS_OK} + ${'the request failed'} | ${HTTP_STATUS_NOT_FOUND} + `(`shows an alert when $scenario`, async ({ statusCode }) => { + enterEmail(validEmailAddress); + + axiosMock.onPatch(defaultPropsData.updateEmailPath).replyOnce(statusCode); + + submitForm(); + + await axios.waitForAll(); + + expect(createAlert).toHaveBeenCalledWith({ + message: I18N_GENERIC_ERROR, + captureError: true, + error: expect.any(Error), + }); + }); + }); + }); + + describe('when clicking the cancel link', () => { + beforeEach(() => { + findCancelLink().trigger('click'); + }); + + it('emits a verifyToken event without an email address', () => { + expect(wrapper.emitted('verifyToken')[0]).toEqual([]); + }); + }); +}); |