diff --git a/contentcuration/contentcuration/frontend/accounts/components/StudioMessageLayout.vue b/contentcuration/contentcuration/frontend/accounts/components/StudioMessageLayout.vue index 8be9c6a54d..8ebea3ace0 100644 --- a/contentcuration/contentcuration/frontend/accounts/components/StudioMessageLayout.vue +++ b/contentcuration/contentcuration/frontend/accounts/components/StudioMessageLayout.vue @@ -100,6 +100,7 @@ } .message-slot-container { + width: 100%; max-width: 400px; margin: 24px auto 0; text-align: center; diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/forgotPassword.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/forgotPassword.spec.js index ca34fa905f..c474929293 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/forgotPassword.spec.js +++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/forgotPassword.spec.js @@ -1,6 +1,21 @@ -import { render, screen, fireEvent, waitFor } from '@testing-library/vue'; +import { render, screen, waitFor } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; import VueRouter from 'vue-router'; import ForgotPassword from '../resetPassword/ForgotPassword'; +import commonStrings from 'shared/translator'; +import { createTranslator } from 'shared/i18n'; + +const { fieldRequired$ } = commonStrings; +const { + forgotPasswordTitle$, + emailLabel$, + submitButton$, + emailValidationMessage$, + forgotPasswordFailed$, +} = createTranslator(ForgotPassword.name, ForgotPassword.$trs); + +const INVALID_EMAIL = 'invalid-email'; +const VALID_EMAIL = 'test@test.com'; const sendPasswordResetLinkMock = jest.fn(() => Promise.resolve()); @@ -31,38 +46,66 @@ const renderComponent = () => { }; describe('ForgotPassword', () => { + let user; + + const emailInput = () => screen.getByLabelText(emailLabel$()); + const submitButton = () => screen.getByRole('button', { name: submitButton$() }); + beforeEach(() => { jest.clearAllMocks(); + user = userEvent.setup(); }); it('should render the forgot password form', () => { renderComponent(); - expect(screen.getByText('Reset your password')).toBeInTheDocument(); - expect(screen.getByLabelText('Email')).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument(); + expect(screen.getByText(forgotPasswordTitle$())).toBeInTheDocument(); + expect(emailInput()).toBeInTheDocument(); + expect(submitButton()).toBeInTheDocument(); + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); }); it('should not submit form with empty email', async () => { renderComponent(); - await fireEvent.click(screen.getByRole('button', { name: 'Submit' })); + await user.click(submitButton()); + await waitFor(() => { + expect(screen.getByTestId('email-field')).toHaveTextContent(fieldRequired$()); + }); expect(sendPasswordResetLinkMock).not.toHaveBeenCalled(); }); it('should not submit form with invalid email', async () => { renderComponent(); - await fireEvent.update(screen.getByLabelText(/email/i), 'invalid-email'); - await fireEvent.click(screen.getByRole('button', { name: 'Submit' })); + await user.type(emailInput(), INVALID_EMAIL); + await user.click(submitButton()); + await waitFor(() => { + expect(screen.getByTestId('email-field')).toHaveTextContent(emailValidationMessage$()); + }); expect(sendPasswordResetLinkMock).not.toHaveBeenCalled(); }); + it('should not show validation error while typing', async () => { + renderComponent(); + await user.type(emailInput(), INVALID_EMAIL); + expect(screen.getByTestId('email-field')).not.toHaveTextContent(emailValidationMessage$()); + }); + + it('should show validation error on blur', async () => { + renderComponent(); + await user.type(emailInput(), INVALID_EMAIL); + await user.tab(); + await waitFor(() => { + expect(screen.getByTestId('email-field')).toHaveTextContent(emailValidationMessage$()); + }); + }); + it('should submit form with valid email and navigate to success page', async () => { sendPasswordResetLinkMock.mockResolvedValue({}); const { router } = renderComponent(); - await fireEvent.update(screen.getByLabelText(/email/i), 'test@test.com'); - await fireEvent.click(screen.getByRole('button', { name: 'Submit' })); + await user.type(emailInput(), VALID_EMAIL); + await user.click(submitButton()); await waitFor(() => { expect(sendPasswordResetLinkMock).toHaveBeenCalledTimes(1); - expect(sendPasswordResetLinkMock).toHaveBeenCalledWith(expect.anything(), 'test@test.com'); + expect(sendPasswordResetLinkMock).toHaveBeenCalledWith(expect.anything(), VALID_EMAIL); }); await waitFor(() => { expect(router.currentRoute.name).toBe('PasswordInstructionsSent'); @@ -72,8 +115,8 @@ describe('ForgotPassword', () => { it('should show error banner when submission fails', async () => { sendPasswordResetLinkMock.mockRejectedValue(new Error('Failed')); renderComponent(); - await fireEvent.update(screen.getByLabelText(/email/i), 'test@test.com'); - await fireEvent.click(screen.getByRole('button', { name: 'Submit' })); - expect(await screen.findByTestId('error-banner')).toBeInTheDocument(); + await user.type(emailInput(), VALID_EMAIL); + await user.click(submitButton()); + expect(await screen.findByRole('alert')).toHaveTextContent(forgotPasswordFailed$()); }); }); diff --git a/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ForgotPassword.vue b/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ForgotPassword.vue index c6581cd722..9173161fe1 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ForgotPassword.vue +++ b/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ForgotPassword.vue @@ -1,24 +1,29 @@ @@ -35,29 +40,50 @@