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 @@
-
-
-
-
+ {{ $tr('forgotPasswordFailed') }}
+
+
-
-
+
+
@@ -35,29 +40,50 @@