From 6fd0d92a414167a9aa215382d69279975a6e5a62 Mon Sep 17 00:00:00 2001 From: daproclaima Date: Wed, 4 Sep 2024 23:23:13 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A5=85(frontend)=20improve=20error=20catc?= =?UTF-8?q?hing=20in=20forms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rename CreateMailboxForm into ModalCreateMailbox, and useCreateMailDomain into useAddMailDomain - use useAPIError hook in ModalCreateMailbox.tsx and ModalAddMailDomain - update translations and tests (include removal of e2e test able to be asserted by component tests) --- CHANGELOG.md | 11 +- ...ateMailDomain.tsx => useAddMailDomain.tsx} | 14 +- .../mail-domains/api/useCreateMailbox.tsx | 5 +- .../components/MailDomainsContent.tsx | 4 +- .../components/ModalAddMailDomain.tsx | 112 +++--- ...MailboxForm.tsx => ModalCreateMailbox.tsx} | 81 +++-- .../__tests__/ModalAddMailDomain.test.tsx | 237 +++++++++++++ .../__tests__/ModalCreateMailbox.test.tsx | 322 ++++++++++++++++++ .../apps/desk/src/i18n/translations.json | 4 + .../apps/desk/src/pages/mail-domains/add.tsx | 4 +- .../mail-domain-create-mailbox.spec.ts | 153 --------- .../app-desk/mail-domains-add.spec.ts | 136 -------- 12 files changed, 684 insertions(+), 399 deletions(-) rename src/frontend/apps/desk/src/features/mail-domains/api/{useCreateMailDomain.tsx => useAddMailDomain.tsx} (81%) rename src/frontend/apps/desk/src/features/mail-domains/components/{forms/CreateMailboxForm.tsx => ModalCreateMailbox.tsx} (78%) create mode 100644 src/frontend/apps/desk/src/features/mail-domains/components/__tests__/ModalAddMailDomain.test.tsx create mode 100644 src/frontend/apps/desk/src/features/mail-domains/components/__tests__/ModalCreateMailbox.test.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e2b48b53..a57cf1285 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ and this project adheres to ## [Unreleased] +### Fixed + +🐛(frontend) fix mai domain addition submission #355 + +### Added +🥅(frontend) improve api error handling #355 + ## [1.0.2] - 2024-08-30 ### Added @@ -19,10 +26,6 @@ and this project adheres to - 👽️(mailboxes) fix mailbox creation after dimail api improvement (#360) -### Fixed - -- 🐛(frontend) user can submit form to add mail domain by pressing "Enter" key - ## [1.0.1] - 2024-08-19 ### Fixed diff --git a/src/frontend/apps/desk/src/features/mail-domains/api/useCreateMailDomain.tsx b/src/frontend/apps/desk/src/features/mail-domains/api/useAddMailDomain.tsx similarity index 81% rename from src/frontend/apps/desk/src/features/mail-domains/api/useCreateMailDomain.tsx rename to src/frontend/apps/desk/src/features/mail-domains/api/useAddMailDomain.tsx index fe1f721f6..c6263089e 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/api/useCreateMailDomain.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/api/useAddMailDomain.tsx @@ -5,7 +5,13 @@ import { MailDomain } from '@/features/mail-domains'; import { KEY_LIST_MAIL_DOMAIN } from './useMailDomains'; -export const createMailDomain = async (name: string): Promise => { +export interface AddMailDomainParams { + name: string; +} + +export const createMailDomain = async ( + name: AddMailDomainParams['name'], +): Promise => { const response = await fetchAPI(`mail-domains/`, { method: 'POST', body: JSON.stringify({ @@ -23,11 +29,11 @@ export const createMailDomain = async (name: string): Promise => { return response.json() as Promise; }; -export function useCreateMailDomain({ +export const useAddMailDomain = ({ onSuccess, }: { onSuccess: (data: MailDomain) => void; -}) { +}) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: createMailDomain, @@ -38,4 +44,4 @@ export function useCreateMailDomain({ onSuccess(data); }, }); -} +}; diff --git a/src/frontend/apps/desk/src/features/mail-domains/api/useCreateMailbox.tsx b/src/frontend/apps/desk/src/features/mail-domains/api/useCreateMailbox.tsx index 86a19a21a..d9686db81 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/api/useCreateMailbox.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/api/useCreateMailbox.tsx @@ -26,7 +26,6 @@ export const createMailbox = async ({ }); if (!response.ok) { - // TODO: extend errorCauses to return the name of the invalid field names to highlight in the form? throw new APIError( 'Failed to create the mailbox', await errorCauses(response), @@ -40,7 +39,7 @@ type UseCreateMailboxParams = { mailDomainSlug: string } & UseMutationOptions< CreateMailboxParams >; -export function useCreateMailbox(options: UseCreateMailboxParams) { +export const useCreateMailbox = (options: UseCreateMailboxParams) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: createMailbox, @@ -61,4 +60,4 @@ export function useCreateMailbox(options: UseCreateMailboxParams) { } }, }); -} +}; diff --git a/src/frontend/apps/desk/src/features/mail-domains/components/MailDomainsContent.tsx b/src/frontend/apps/desk/src/features/mail-domains/components/MailDomainsContent.tsx index 5d8ecb7b1..186c2a4bf 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/components/MailDomainsContent.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/components/MailDomainsContent.tsx @@ -19,7 +19,7 @@ import { default as MailDomainsLogo } from '../assets/mail-domains-logo.svg'; import { PAGE_SIZE } from '../conf'; import { MailDomain, MailDomainMailbox } from '../types'; -import { CreateMailboxForm } from './forms/CreateMailboxForm'; +import { ModalCreateMailbox } from './ModalCreateMailbox'; export type ViewMailbox = { name: string; @@ -87,7 +87,7 @@ export function MailDomainsContent({ mailDomain }: { mailDomain: MailDomain }) { ) : ( <> {isCreateMailboxFormVisible && mailDomain ? ( - setIsCreateMailboxFormVisible(false)} /> diff --git a/src/frontend/apps/desk/src/features/mail-domains/components/ModalAddMailDomain.tsx b/src/frontend/apps/desk/src/features/mail-domains/components/ModalAddMailDomain.tsx index 88d82405e..fd9ecbd2c 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/components/ModalAddMailDomain.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/components/ModalAddMailDomain.tsx @@ -8,84 +8,18 @@ import { } from '@openfun/cunningham-react'; import { useRouter } from 'next/navigation'; import React from 'react'; -import { - Controller, - FormProvider, - UseFormReturn, - useForm, -} from 'react-hook-form'; +import { Controller, FormProvider, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { z } from 'zod'; -import { APIError } from '@/api'; +import { useAPIError } from '@/api/useAPIError'; import { Box, StyledLink, Text, TextErrors } from '@/components'; -import { useCreateMailDomain } from '@/features/mail-domains'; +import { useAddMailDomain } from '@/features/mail-domains'; import { default as MailDomainsLogo } from '../assets/mail-domains-logo.svg'; const FORM_ID = 'form-add-mail-domain'; -const useAddMailDomainApiError = ({ - error, - methods, -}: { - error: APIError | null; - methods: UseFormReturn<{ name: string }> | null; -}): string[] | undefined => { - const [errorCauses, setErrorCauses] = React.useState( - undefined, - ); - const { t } = useTranslation(); - - React.useEffect(() => { - if (methods && t && error) { - let causes = undefined; - - if (error.cause?.length) { - const parseCauses = (causes: string[]) => - causes.reduce((arrayCauses, cause) => { - switch (cause) { - case 'Mail domain with this name already exists.': - case 'Mail domain with this Slug already exists.': - methods.setError('name', { - type: 'manual', - message: t( - 'This mail domain is already used. Please, choose another one.', - ), - }); - break; - default: - arrayCauses.push(cause); - } - - return arrayCauses; - }, [] as string[]); - - causes = parseCauses(error.cause); - } - - if (error.status === 500 || !error.cause) { - causes = [ - t( - 'Your request cannot be processed because the server is experiencing an error. If the problem ' + - 'persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr.', - ), - ]; - } - - setErrorCauses(causes); - } - }, [methods, t, error]); - - React.useEffect(() => { - if (errorCauses && methods) { - methods.setFocus('name'); - } - }, [methods, errorCauses]); - - return errorCauses; -}; - export const ModalAddMailDomain = () => { const { t } = useTranslation(); const router = useRouter(); @@ -108,13 +42,37 @@ export const ModalAddMailDomain = () => { mutate: createMailDomain, isPending, error, - } = useCreateMailDomain({ + } = useAddMailDomain({ onSuccess: (mailDomain) => { router.push(`/mail-domains/${mailDomain.slug}`); }, }); - const errorCauses = useAddMailDomainApiError({ error, methods }); + const errorCauses = useAPIError({ + error, + errorParams: { + name: { + causes: [ + 'Mail domain with this name already exists.', + 'Mail domain with this Slug already exists.', + ], + handleError: () => { + methods.setError('name', { + type: 'manual', + message: t( + 'This mail domain is already used. Please, choose another one.', + ), + }); + methods.setFocus('name'); + }, + }, + }, + serverErrorParams: { + handleError: () => { + methods.setFocus('name'); + }, + }, + }); const onSubmitCallback = (event: React.FormEvent) => { event.preventDefault(); @@ -146,7 +104,11 @@ export const ModalAddMailDomain = () => { @@ -170,7 +132,11 @@ export const ModalAddMailDomain = () => { ) : null} -
+ { toast(t('Mailbox created!'), VariantType.SUCCESS, { @@ -88,6 +93,40 @@ export const CreateMailboxForm = ({ }, }); + const errorCauses = useAPIError({ + error, + errorParams: { + local_part: { + causes: ['Mailbox with this Local_part and Domain already exists.'], + handleError: () => { + methods.setError('local_part', { + type: 'manual', + message: t('This email prefix is already used.'), + }); + methods.setFocus('local_part'); + }, + }, + secret: { + causes: [ + "Please configure your domain's secret before creating any mailbox.", + `Please check secret of the mail domain ${mailDomain.slug}`, + ], + causeShown: t( + 'The mail domain secret is misconfigured. Please, contact ' + + 'our support team to solve the issue: suiteterritoriale@anct.gouv.fr', + ), + handleError: () => { + methods.setFocus('first_name'); + }, + }, + }, + serverErrorParams: { + handleError: () => { + methods.setFocus('first_name'); + }, + }, + }); + const onSubmitCallback = (event: React.FormEvent) => { event.preventDefault(); void methods.handleSubmit((data) => @@ -95,20 +134,6 @@ export const CreateMailboxForm = ({ )(); }; - const causes = error?.cause?.filter((cause) => { - const isFound = - cause === 'Mailbox with this Local_part and Domain already exists.'; - - if (isFound) { - methods.setError('local_part', { - type: 'manual', - message: t('This email prefix is already used.'), - }); - } - - return !isFound; - }); - return ( {t('Create the mailbox')} @@ -152,8 +181,12 @@ export const CreateMailboxForm = ({ > - {!!causes?.length && ( - + {!!errorCauses?.length && ( + )} + ({ + useTranslation: () => { + return { + t: (key: string) => key, + i18n: { + changeLanguage: () => new Promise(() => {}), + }, + }; + }, + initReactI18next: { + type: '3rdParty', + init: () => {}, + }, +})); + +const mockPush = jest.fn(); +jest.mock('next/navigation', () => ({ + useRouter: jest.fn().mockImplementation(() => ({ + push: mockPush, + })), +})); + +const mockAddMailDomain = jest.fn(); +const mockOnSuccess = jest.fn(); +jest.mock('../../api/useAddMailDomain', () => ({ + useAddMailDomain: jest.fn().mockImplementation(() => { + return useMutation({ + mutationFn: mockAddMailDomain, + onSuccess: mockOnSuccess, + }); + }), +})); + +describe('ModalAddMailDomain', () => { + const queryClient = new QueryClient(); + + const renderModalAddMailDomain = () => { + return render( +
+ + + + + +
, + ); + }; + + const getElements = () => ({ + modalElement: screen.getByText('Add your mail domain'), + formTag: screen.getByTitle('Mail domain addition form'), + inputName: screen.getByLabelText(/Domain name/i), + buttonCancel: screen.getByRole('button', { name: /Cancel/i, hidden: true }), + buttonSubmit: screen.getByRole('button', { + name: /Add the domain/i, + hidden: true, + }), + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders all the elements', () => { + renderModalAddMailDomain(); + + const { modalElement, formTag, inputName, buttonCancel, buttonSubmit } = + getElements(); + + expect(modalElement).toBeVisible(); + expect(formTag).toBeVisible(); + expect(inputName).toBeVisible(); + expect(screen.getByText('Example: saint-laurent.fr')).toBeVisible(); + expect(buttonCancel).toBeVisible(); + expect(buttonSubmit).toBeVisible(); + }); + + it('should disable submit button when no field is filled', () => { + renderModalAddMailDomain(); + + const { buttonSubmit } = getElements(); + + expect(buttonSubmit).toBeDisabled(); + }); + + it('displays validation error on empty submit', async () => { + const user = userEvent.setup(); + renderModalAddMailDomain(); + + const { inputName, buttonSubmit } = getElements(); + + await user.type(inputName, 'domain.fr'); + await user.clear(inputName); + + await user.click(buttonSubmit); + + await waitFor(() => { + expect( + screen.getByText(/Example: saint-laurent.fr/i), + ).toBeInTheDocument(); + }); + + expect(mockAddMailDomain).not.toHaveBeenCalled(); + }); + + it('submits the form when validation passes', async () => { + const user = userEvent.setup(); + + renderModalAddMailDomain(); + + const { inputName, buttonSubmit } = getElements(); + + await user.type(inputName, 'domain.fr'); + + await user.click(buttonSubmit); + + expect(mockAddMailDomain).toHaveBeenNthCalledWith(1, 'domain.fr'); + + expect(mockOnSuccess).toHaveBeenCalled(); + }); + + it('submits the form on key enter press', async () => { + const user = userEvent.setup(); + + renderModalAddMailDomain(); + + const { inputName } = getElements(); + + await user.type(inputName, 'domain.fr'); + await user.type(inputName, '{enter}'); + + expect(mockAddMailDomain).toHaveBeenNthCalledWith(1, 'domain.fr'); + }); + + it('displays right error message error when maildomain name is already used', async () => { + mockAddMailDomain.mockRejectedValueOnce( + new APIError('Failed to create the mailbox', { + status: 400, + cause: ['Mail domain with this name already exists.'], + }), + ); + + const user = userEvent.setup(); + + renderModalAddMailDomain(); + + const { inputName, buttonSubmit } = getElements(); + + await user.type(inputName, 'domain.fr'); + + await user.click(buttonSubmit); + + await waitFor(() => { + expect( + screen.getByText( + /This mail domain is already used. Please, choose another one./i, + ), + ).toBeInTheDocument(); + }); + + expect(inputName).toHaveFocus(); + }); + + it('displays right error message error when maildomain slug is already used', async () => { + mockAddMailDomain.mockRejectedValueOnce( + new APIError('Failed to create the mailbox', { + status: 400, + cause: ['Mail domain with this Slug already exists.'], + }), + ); + + const user = userEvent.setup(); + + renderModalAddMailDomain(); + + const { inputName, buttonSubmit } = getElements(); + + await user.type(inputName, 'domainfr'); + + await user.click(buttonSubmit); + + await waitFor(() => { + expect( + screen.getByText( + /This mail domain is already used. Please, choose another one./i, + ), + ).toBeInTheDocument(); + }); + + expect(inputName).toHaveFocus(); + }); + + it('displays right error message error when error 500 is received', async () => { + mockAddMailDomain.mockRejectedValueOnce( + new APIError('Failed to add the mail domain', { + status: 500, + }), + ); + + const user = userEvent.setup(); + + renderModalAddMailDomain(); + + const { inputName, buttonSubmit } = getElements(); + + await user.type(inputName, 'domain.fr'); + + await user.click(buttonSubmit); + + await waitFor(() => { + expect( + screen.getByText( + 'Your request cannot be processed because the server is experiencing an error. If the problem ' + + 'persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr', + ), + ).toBeInTheDocument(); + }); + + expect(inputName).toHaveFocus(); + }); +}); diff --git a/src/frontend/apps/desk/src/features/mail-domains/components/__tests__/ModalCreateMailbox.test.tsx b/src/frontend/apps/desk/src/features/mail-domains/components/__tests__/ModalCreateMailbox.test.tsx new file mode 100644 index 000000000..7ab639e3c --- /dev/null +++ b/src/frontend/apps/desk/src/features/mail-domains/components/__tests__/ModalCreateMailbox.test.tsx @@ -0,0 +1,322 @@ +import { ModalProvider } from '@openfun/cunningham-react'; +import { + QueryClient, + QueryClientProvider, + useMutation, +} from '@tanstack/react-query'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { APIError } from '@/api'; + +import { CreateMailboxParams } from '../../api'; +import { MailDomain } from '../../types'; +import { ModalCreateMailbox } from '../ModalCreateMailbox'; + +const mockMailDomain: MailDomain = { + name: 'domain.fr', + id: '456ac6ca-0402-4615-8005-69bc1efde43f', + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + slug: 'domainfr', + status: 'enabled', + abilities: { + get: true, + patch: true, + put: true, + post: true, + delete: true, + manage_accesses: true, + }, +}; + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +jest.mock('@openfun/cunningham-react', () => ({ + ...jest.requireActual('@openfun/cunningham-react'), + useToastProvider: () => ({ + toast: jest.fn(), + }), +})); + +const mockCreateMailbox = jest.fn(); +const mockOnSuccess = jest.fn(); +jest.mock('../../api/useCreateMailbox', () => ({ + useCreateMailbox: jest.fn().mockImplementation(() => { + return useMutation({ + mutationFn: mockCreateMailbox, + onSuccess: mockOnSuccess, + }); + }), +})); + +describe('ModalCreateMailbox', () => { + const queryClient = new QueryClient(); + const mockCloseModal = jest.fn(); + const renderModalCreateMailbox = () => { + return render( +
+ + + + + +
, + ); + }; + + const getFormElements = () => ({ + formTag: screen.getByTitle('Mailbox creation form'), + inputFirstName: screen.getByLabelText(/First name/i), + inputLastName: screen.getByLabelText(/Last name/i), + inputLocalPart: screen.getByLabelText(/Email address prefix/i), + inputEmailAddress: screen.getByLabelText(/Secondary email address/i), + buttonCancel: screen.getByRole('button', { name: /Cancel/i, hidden: true }), + buttonSubmit: screen.getByRole('button', { + name: /Create the mailbox/i, + hidden: true, + }), + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders all the elements', () => { + renderModalCreateMailbox(); + const { + formTag, + inputFirstName, + inputLastName, + inputLocalPart, + inputEmailAddress, + buttonCancel, + buttonSubmit, + } = getFormElements(); + + expect(formTag).toBeVisible(); + expect(inputFirstName).toBeVisible(); + expect(inputLastName).toBeVisible(); + expect(inputLocalPart).toBeVisible(); + expect(screen.getByText(`@${mockMailDomain.name}`)).toBeVisible(); + expect(inputEmailAddress).toBeVisible(); + expect(buttonCancel).toBeVisible(); + expect(buttonSubmit).toBeVisible(); + }); + + it('clicking on cancel button closes modal', async () => { + const user = userEvent.setup(); + + renderModalCreateMailbox(); + + const { buttonCancel } = getFormElements(); + + expect(buttonCancel).toBeVisible(); + + await user.click(buttonCancel); + + expect(mockCloseModal).toHaveBeenCalledTimes(1); + }); + + it('displays validation errors on empty submit', async () => { + const user = userEvent.setup(); + + renderModalCreateMailbox(); + + const { + inputFirstName, + inputLastName, + inputLocalPart, + inputEmailAddress, + buttonSubmit, + } = getFormElements(); + + // To bypass html form validation we need to fill and clear the fields + await user.type(inputFirstName, 'John'); + await user.type(inputLastName, 'Doe'); + await user.type(inputLocalPart, 'john.doe'); + await user.type(inputEmailAddress, 'john.doe@mail.com'); + + await user.clear(inputFirstName); + await user.clear(inputLastName); + await user.clear(inputLocalPart); + await user.clear(inputEmailAddress); + + await user.click(buttonSubmit); + + expect(screen.getByText(`@${mockMailDomain.name}`)).toBeVisible(); + + await waitFor(() => { + expect( + screen.getByText(/Please enter your first name/i), + ).toBeInTheDocument(); + }); + await waitFor(() => { + expect( + screen.getByText(/Please enter your last name/i), + ).toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + screen.getByText(/You must have minimum 1 character/i), + ).toBeInTheDocument(); + }); + + expect(mockCreateMailbox).not.toHaveBeenCalled(); + }); + + it('submits the form when validation passes', async () => { + const user = userEvent.setup(); + + renderModalCreateMailbox(); + + const { + inputFirstName, + inputLastName, + inputLocalPart, + inputEmailAddress, + buttonSubmit, + } = getFormElements(); + + await user.type(inputFirstName, 'John'); + await user.type(inputLastName, 'Doe'); + await user.type(inputLocalPart, 'john.doe'); + await user.type(inputEmailAddress, 'john.doe@mail.com'); + + await user.click(buttonSubmit); + + await waitFor(() => { + expect( + screen.queryByText(/Please enter your first name/i), + ).not.toBeInTheDocument(); + }); + await waitFor(() => { + expect( + screen.queryByText(/Please enter your last name/i), + ).not.toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + screen.queryByText(/You must have minimum 1 character/i), + ).not.toBeInTheDocument(); + }); + + expect(mockCreateMailbox).toHaveBeenNthCalledWith(1, { + first_name: 'John', + last_name: 'Doe', + local_part: 'john.doe', + secondary_email: 'john.doe@mail.com', + mailDomainSlug: mockMailDomain.slug, + }); + + expect(mockOnSuccess).toHaveBeenCalled(); + }); + + it('submits the form on key enter press', async () => { + const user = userEvent.setup(); + + renderModalCreateMailbox(); + + const { inputFirstName, inputLastName, inputLocalPart, inputEmailAddress } = + getFormElements(); + + await user.type(inputFirstName, 'John'); + await user.type(inputLastName, 'Doe'); + await user.type(inputLocalPart, 'john.doe'); + await user.type(inputEmailAddress, 'john.doe@mail.com'); + + await user.type(inputEmailAddress, '{enter}'); + + expect(mockCreateMailbox).toHaveBeenNthCalledWith(1, { + first_name: 'John', + last_name: 'Doe', + local_part: 'john.doe', + secondary_email: 'john.doe@mail.com', + mailDomainSlug: mockMailDomain.slug, + }); + }); + + it('displays right error message error when mailbox prefix is already used', async () => { + mockCreateMailbox.mockRejectedValueOnce( + new APIError('Failed to create the mailbox', { + status: 400, + cause: ['Mailbox with this Local_part and Domain already exists.'], + }), + ); + + const user = userEvent.setup(); + + renderModalCreateMailbox(); + + const { + inputFirstName, + inputLastName, + inputLocalPart, + inputEmailAddress, + buttonSubmit, + } = getFormElements(); + + await user.type(inputFirstName, 'John'); + await user.type(inputLastName, 'Doe'); + await user.type(inputLocalPart, 'john.doe'); + await user.type(inputEmailAddress, 'john.doe@mail.com'); + + await user.click(buttonSubmit); + + await waitFor(() => { + expect( + screen.getByText(/This email prefix is already used./i), + ).toBeInTheDocument(); + }); + + expect(inputLocalPart).toHaveFocus(); + }); + + it('displays right error message error when error 500 is received', async () => { + mockCreateMailbox.mockRejectedValueOnce( + new APIError('Failed to create the mailbox', { + status: 500, + }), + ); + + const user = userEvent.setup(); + + renderModalCreateMailbox(); + + const { + inputFirstName, + inputLastName, + inputLocalPart, + inputEmailAddress, + buttonSubmit, + } = getFormElements(); + + await user.type(inputFirstName, 'John'); + await user.type(inputLastName, 'Doe'); + await user.type(inputLocalPart, 'john.doe'); + await user.type(inputEmailAddress, 'john.doe@mail.com'); + + await user.click(buttonSubmit); + + await waitFor(() => { + expect( + screen.getByText( + 'Your request cannot be processed because the server is experiencing an error. If the problem ' + + 'persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr', + ), + ).toBeInTheDocument(); + }); + + expect(inputFirstName).toHaveFocus(); + }); +}); diff --git a/src/frontend/apps/desk/src/i18n/translations.json b/src/frontend/apps/desk/src/i18n/translations.json index 962a1a6bd..be0b01cb9 100644 --- a/src/frontend/apps/desk/src/i18n/translations.json +++ b/src/frontend/apps/desk/src/i18n/translations.json @@ -83,8 +83,10 @@ "List members card": "Carte liste des membres", "Logout": "Se déconnecter", "Mail Domains": "Domaines de messagerie", + "Mail domain addition form": "Formulaire d'ajout de domaine de messagerie", "Mail domains panel": "Panel des domaines de messagerie", "Mailbox created!": "Boîte mail créée !", + "Mailbox creation form": "Formulaire de création de boite mail", "Mailboxes list": "Liste des boîtes mail", "Marianne Logo": "Logo Marianne", "Member": "Membre", @@ -134,6 +136,7 @@ "Teams": "Équipes", "The National Agency for Territorial Cohesion undertakes to make its\n service accessible, in accordance with article 47 of law no. 2005-102\n of February 11, 2005.": "L'Agence Nationale de la Cohésion des Territoires s’engage à rendre son service accessible, conformément à l’article 47 de la loi n° 2005-102 du 11 février 2005.", "The domain name encounters an error. Please contact our support team to solve the problem:": "Le nom de domaine rencontre une erreur. Veuillez contacter notre support pour résoudre le problème :", + "The mail domain secret is misconfigured. Please, contact our support team to solve the issue: suiteterritoriale@anct.gouv.fr": "Le secret du domaine de messagerie est mal configuré. Veuillez contacter notre support pour résoudre le problème : suiteterritoriale@anct.gouv.fr", "The member has been removed from the team": "Le membre a été supprimé de votre groupe", "The role has been updated": "Le rôle a bien été mis à jour", "The team has been removed.": "Le groupe a été supprimé.", @@ -164,6 +167,7 @@ "You cannot update the role of other owner.": "Vous ne pouvez pas mettre à jour les rôles d'autre propriétaire.", "You must have minimum 1 character": "Vous devez entrer au moins 1 caractère", "Your domain name is being validated. You will not be able to create mailboxes until your domain name has been validated by our team.": "Votre nom de domaine est en cours de validation. Vous ne pourrez créer de boîtes mail que lorsque votre nom de domaine sera validé par notre équipe.", + "Your request cannot be processed because the server is experiencing an error. If the problem persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr": "Votre demande ne peut pas être traitée car le serveur rencontre une erreur. Si le problème persiste, veuillez contacter notre support pour résoudre le problème : suiteterritoriale@anct.gouv.fr", "[disabled]": "[désactivé]", "[enabled]": "[actif]", "[failed]": "[erroné]", diff --git a/src/frontend/apps/desk/src/pages/mail-domains/add.tsx b/src/frontend/apps/desk/src/pages/mail-domains/add.tsx index 2efb6073d..1e4227b95 100644 --- a/src/frontend/apps/desk/src/pages/mail-domains/add.tsx +++ b/src/frontend/apps/desk/src/pages/mail-domains/add.tsx @@ -2,13 +2,13 @@ import React, { ReactElement } from 'react'; import { Box } from '@/components'; import { MailDomainsLayout } from '@/features/mail-domains'; -import { ModalCreateMailDomain } from '@/features/mail-domains/components/ModalAddMailDomain'; +import { ModalAddMailDomain } from '@/features/mail-domains/components/ModalAddMailDomain'; import { NextPageWithLayout } from '@/types/next'; const Page: NextPageWithLayout = () => { return ( - + ); }; diff --git a/src/frontend/apps/e2e/__tests__/app-desk/mail-domain-create-mailbox.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/mail-domain-create-mailbox.spec.ts index 646acbd8e..53c1cfa2c 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/mail-domain-create-mailbox.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/mail-domain-create-mailbox.spec.ts @@ -314,157 +314,4 @@ test.describe('Mail domain create mailbox', () => { page.getByRole('button', { name: 'Create a mailbox' }), ).not.toBeInViewport(); }); - - test('checks client invalidation messages are displayed and no mailbox creation request is sent when fields are not properly filled', async ({ - page, - }) => { - let isCreateMailboxRequestSent = false; - page.on( - 'request', - (request) => - (isCreateMailboxRequestSent = - request.url().includes('/mail-domains/domainfr/mailboxes/') && - request.method() === 'POST'), - ); - - void interceptCommonApiRequests(page); - - await navigateToMailboxCreationFormForMailDomainFr(page); - - const inputFirstName = page.getByLabel('First name'); - const inputLastName = page.getByLabel('Last name'); - const inputLocalPart = page.getByLabel('Email address prefix'); - const inputSecondaryEmailAddress = page.getByLabel( - 'Secondary email address', - ); - const textInvalidLocalPart = page.getByText( - 'It must not contain spaces, accents or special characters (except "." or "-"). E.g.: jean.dupont', - ); - const textInvalidSecondaryEmailAddress = page.getByText( - 'Please enter a valid email address.\nE.g. : jean.dupont@mail.fr', - ); - - await inputFirstName.fill(' '); - await inputFirstName.clear(); - await expect(page.getByText('Please enter your first name')).toBeVisible(); - - await inputLastName.fill(' '); - await inputLastName.clear(); - await expect(page.getByText('Please enter your last name')).toBeVisible(); - - await inputLocalPart.fill('wrong@'); - await expect(textInvalidLocalPart).toBeVisible(); - - await inputSecondaryEmailAddress.fill('uncomplete@mail'); - await expect(textInvalidSecondaryEmailAddress).toBeVisible(); - - await inputLocalPart.clear(); - await inputLocalPart.fill('wrong '); - await expect(textInvalidLocalPart).toBeVisible(); - - await inputLocalPart.clear(); - await expect( - page.getByText('You must have minimum 1 character'), - ).toBeVisible(); - - await page.getByRole('button', { name: 'Create the mailbox' }).click(); - - expect(isCreateMailboxRequestSent).toBeFalsy(); - }); - - test('checks field invalidation messages are displayed when sending already existing local_part data in mail domain to api', async ({ - page, - }) => { - const interceptRequests = (page: Page) => { - void interceptCommonApiRequests(page); - - void page.route( - '**/api/v1.0/mail-domains/domainfr/mailboxes/', - (route) => { - if (route.request().method() === 'POST') { - void route.fulfill({ - status: 400, - json: { - local_part: [ - 'Mailbox with this Local_part and Domain already exists.', - ], - }, - }); - } - }, - { times: 1 }, - ); - }; - - void interceptRequests(page); - - await navigateToMailboxCreationFormForMailDomainFr(page); - - const inputFirstName = page.getByLabel('First name'); - const inputLastName = page.getByLabel('Last name'); - const inputLocalPart = page.getByLabel('Email address prefix'); - const inputSecondaryEmailAddress = page.getByLabel( - 'Secondary email address', - ); - const submitButton = page.getByRole('button', { - name: 'Create the mailbox', - }); - - const textAlreadyUsedLocalPart = page.getByText( - 'This email prefix is already used.', - ); - - await inputFirstName.fill('John'); - await inputLastName.fill('Doe'); - await inputLocalPart.fill('john.already'); - await inputSecondaryEmailAddress.fill('john.already@mail.com'); - - await submitButton.click(); - - await expect(textAlreadyUsedLocalPart).toBeVisible(); - }); - - test('checks unknown api error causes are displayed above form when they are not related with invalid field', async ({ - page, - }) => { - const interceptRequests = async (page: Page) => { - void interceptCommonApiRequests(page); - - await page.route( - '**/api/v1.0/mail-domains/domainfr/mailboxes/', - async (route) => { - if (route.request().method() === 'POST') { - await route.fulfill({ - status: 500, - json: { - unknown_error: ['Unknown error from server'], - }, - }); - } - }, - { times: 1 }, - ); - }; - - void interceptRequests(page); - - await navigateToMailboxCreationFormForMailDomainFr(page); - - const inputFirstName = page.getByLabel('First name'); - const inputLastName = page.getByLabel('Last name'); - const inputLocalPart = page.getByLabel('Email address prefix'); - const inputSecondaryEmailAddress = page.getByLabel( - 'Secondary email address', - ); - - await inputFirstName.fill('John'); - await inputLastName.fill('Doe'); - await inputLocalPart.fill('john.doe'); - - await inputSecondaryEmailAddress.fill('john.do@mail.fr'); - - await page.getByRole('button', { name: 'Create the mailbox' }).click(); - - await expect(page.getByText('Unknown error from server')).toBeVisible(); - }); }); diff --git a/src/frontend/apps/e2e/__tests__/app-desk/mail-domains-add.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/mail-domains-add.spec.ts index 11cb33435..978033953 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/mail-domains-add.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/mail-domains-add.spec.ts @@ -132,142 +132,6 @@ test.describe('Add Mail Domains', () => { ).toBeVisible(); }); - test('checks form submits at "Enter" key press', async ({ page }) => { - void page.route('**/api/v1.0/mail-domains/', (route) => { - if (route.request().method() === 'POST') { - void route.fulfill({ - json: { - id: '2ebcfcfb-1dfa-4ed1-8e4a-554c63307b7c', - name: 'enter.fr', - slug: 'enterfr', - status: 'pending', - abilities: { - get: true, - patch: true, - put: true, - post: true, - delete: true, - manage_accesses: true, - }, - created_at: '2024-08-21T10:55:21.081994Z', - updated_at: '2024-08-21T10:55:21.082109Z', - }, - }); - } else { - void route.continue(); - } - }); - - await page.goto('/mail-domains/'); - - const { linkIndexPageAddDomain, inputName } = getElements(page); - - await linkIndexPageAddDomain.click(); - - await inputName.fill('enter.fr'); - await page.keyboard.press('Enter'); - - await expect(page).toHaveURL(`/mail-domains/enterfr/`); - }); - - test('checks error when duplicate mail domain name', async ({ - page, - browserName, - }) => { - await page.goto('/mail-domains/'); - - const { linkIndexPageAddDomain, inputName, buttonSubmit } = - getElements(page); - - const mailDomainName = randomName('duplicate.fr', browserName, 1)[0]; - const mailDomainSlug = mailDomainName.replace('.', ''); - - await linkIndexPageAddDomain.click(); - await inputName.fill(mailDomainName); - await buttonSubmit.click(); - - await expect(page).toHaveURL(`/mail-domains\/${mailDomainSlug}\/`); - - await linkIndexPageAddDomain.click(); - - await inputName.fill(mailDomainName); - await buttonSubmit.click(); - - await expect(page).toHaveURL(/mail-domains\//); - await expect( - page.getByText( - 'This mail domain is already used. Please, choose another one.', - ), - ).toBeVisible(); - await expect(inputName).toBeFocused(); - }); - - test('checks error when duplicate mail domain slug', async ({ - page, - browserName, - }) => { - await page.goto('/mail-domains/'); - - const { linkIndexPageAddDomain, inputName, buttonSubmit } = - getElements(page); - - const mailDomainSlug = randomName('duplicate', browserName, 1)[0]; - - await linkIndexPageAddDomain.click(); - await inputName.fill(mailDomainSlug); - await buttonSubmit.click(); - - await expect(page).toHaveURL(`/mail-domains\/${mailDomainSlug}\/`); - - await linkIndexPageAddDomain.click(); - - await inputName.fill(mailDomainSlug); - await buttonSubmit.click(); - - await expect(page).toHaveURL(/mail-domains\//); - await expect( - page.getByText( - 'This mail domain is already used. Please, choose another one.', - ), - ).toBeVisible(); - await expect(inputName).toBeFocused(); - }); - - test('checks unknown api error causes are displayed', async ({ page }) => { - await page.route( - '**/api/v1.0/mail-domains/', - async (route) => { - if (route.request().method() === 'POST') { - await route.fulfill({ - status: 500, - json: { - unknown_error: ['Unknown error from server'], - }, - }); - } - }, - { times: 1 }, - ); - - await page.goto('/mail-domains/'); - - const { linkIndexPageAddDomain, inputName, buttonSubmit } = - getElements(page); - - await linkIndexPageAddDomain.click(); - await inputName.fill('server-error.fr'); - await buttonSubmit.click(); - - await expect(page).toHaveURL(/mail-domains\//); - await expect( - page.getByText( - 'Your request cannot be processed because the server is experiencing an error. If the problem ' + - 'persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr.', - ), - ).toBeVisible(); - await expect(inputName).toBeFocused(); - }); - test('checks 404 on mail-domains/[slug] page', async ({ page }) => { await page.goto('/mail-domains/unknown-domain');