From 32e6602ddaf5ddb99c226e7bc88c58293f1989eb Mon Sep 17 00:00:00 2001 From: daproclaima Date: Thu, 4 Jul 2024 12:09:18 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8(frontend)=20improve=20mailbox=20cr?= =?UTF-8?q?eation=20validations=20and=20error=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - replace known error causes returned by the API on unsuccessful mailbox creation requests by meaningful interpolated message shown above the form - strengthen form validation rules to be identical as those of the api endpoint to prevent emitting invalid requests - designate which form fields are mandatory for accessiblity - update texts for better ux writting, and their translations - fix css style input errors - update related e2e tests. --- .../desk/src/cunningham/cunningham-style.css | 10 + .../components/MailDomainsContent.tsx | 2 +- .../components/forms/CreateMailboxForm.tsx | 188 +++++----- .../apps/desk/src/i18n/translations.json | 16 +- .../mail-domain-create-mailbox.spec.ts | 333 ++++++++++++------ 5 files changed, 348 insertions(+), 201 deletions(-) diff --git a/src/frontend/apps/desk/src/cunningham/cunningham-style.css b/src/frontend/apps/desk/src/cunningham/cunningham-style.css index bcfa7dc..55036a6 100644 --- a/src/frontend/apps/desk/src/cunningham/cunningham-style.css +++ b/src/frontend/apps/desk/src/cunningham/cunningham-style.css @@ -93,6 +93,16 @@ 0 0 2px; } +.c__input__wrapper--error.c__input__wrapper:focus-within { + border-color: var( + --c--components--forms-input--border--color-error-hover + ) !important; +} + +.c__input__wrapper--error.c__input__wrapper:focus-within label { + color: var(--c--theme--colors--danger-600); +} + .c__input__wrapper--error:not(.c__input__wrapper--disabled):hover label { color: var(--c--components--forms-input--border--color-error-hover); } 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 2d6f1ae..df7ce8d 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 @@ -85,7 +85,7 @@ export function MailDomainsContent({ mailDomain }: { mailDomain: MailDomain }) { {isCreateMailboxFormVisible && mailDomain ? ( setIsCreateMailboxFormVisible(false)} /> ) : null} .c__field__text { + white-space: pre-line; + } +`; export const CreateMailboxForm = ({ mailDomain, - setIsFormVisible, + closeModal, }: { mailDomain: MailDomain; - setIsFormVisible: (value: boolean) => void; + closeModal: () => void; }) => { const { t } = useTranslation(); const { toast } = useToastProvider(); const { colorsTokens } = useCunninghamTheme(); + const messageInvalidMinChar = t('You must have minimum 1 character'); + + const createMailboxValidationSchema = z.object({ + first_name: z + .string() + .min( + 1, + t('Please enter {{fieldName}}', { fieldName: 'your first name' }), + ), + last_name: z + .string() + .min(1, t('Please enter {{fieldName}}', { fieldName: 'your last name' })), + local_part: z + .string() + .regex( + /^((?!@|\s)([a-zA-Z0-9.\-]))*$/, + t( + 'It must not contain spaces, accents or special characters (except "." or "-"). E.g.: jean.dupont', + ), + ) + .min(1, messageInvalidMinChar), + secondary_email: z + .string() + .regex( + /[^@\s]+@[^@\s]+\.[^@\s]+/, + t('Please enter a valid email address.\nE.g. : jean.dupont@mail.fr'), + ) + .min(1, messageInvalidMinChar), + }); + const methods = useForm({ delayError: 0, defaultValues: { @@ -57,19 +87,17 @@ export const CreateMailboxForm = ({ resolver: zodResolver(createMailboxValidationSchema), }); - const { mutate: createMailbox, ...queryState } = useCreateMailbox({ + const { mutate: createMailbox, error } = useCreateMailbox({ mailDomainSlug: mailDomain.slug, onSuccess: () => { toast(t('Mailbox created!'), VariantType.SUCCESS, { duration: 4000, }); - setIsFormVisible(false); + closeModal(); }, }); - const closeModal = () => setIsFormVisible(false); - const onSubmitCallback = (event: React.FormEvent) => { event.preventDefault(); void methods.handleSubmit((data) => @@ -77,6 +105,20 @@ 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('Submit')} + {t('Create the mailbox')} } size={ModalSize.MEDIUM} @@ -113,15 +155,22 @@ export const CreateMailboxForm = ({ color={colorsTokens()['primary-text']} title={t('Mailbox creation form')} /> - + {t('Create a mailbox')} } > - - {queryState.isError && ( - + + + {!!causes?.length && ( + )} {methods ? (
- ( - - )} + label={t('First name')} + methods={methods} /> - ( - - )} + label={t('Last name')} + methods={methods} /> - ( - + label={t('Email address prefix')} + methods={methods} + text={t( + 'It must not contain spaces, accents or special characters (except "." or "-"). E.g.: jean.dupont', )} /> - ( - - )} + label={t('Secondary email address')} + methods={methods} + text={t('E.g. : jean.dupont@mail.fr')} /> ); }; + +interface FieldMailBoxProps { + name: 'first_name' | 'last_name' | 'local_part' | 'secondary_email'; + label: string; + methods: UseFormReturn; + text?: string; +} + +const FieldMailBox = ({ name, label, methods, text }: FieldMailBoxProps) => { + return ( + ( + + )} + /> + ); +}; diff --git a/src/frontend/apps/desk/src/i18n/translations.json b/src/frontend/apps/desk/src/i18n/translations.json index 6b57c5d..966480a 100644 --- a/src/frontend/apps/desk/src/i18n/translations.json +++ b/src/frontend/apps/desk/src/i18n/translations.json @@ -18,6 +18,7 @@ "Add team icon": "Icône ajout de groupe", "Address: National Agency for Territorial Cohesion - 20, avenue de Ségur TSA 10717 75 334 Paris Cedex 07 Paris": "Adresse : Agence Nationale de la Cohésion des Territoires - 20, avenue de Ségur TSA 10717 75 334 Paris Cedex 07", "Administration": "Administration", + "All fields are mandatory.": "Tous les champs sont obligatoires.", "Are you sure you want to delete {{teamName}} team?": "Êtes-vous sûr de vouloir supprimer le groupe {{teamName}}?", "Are you sure you want to remove this member from the {{team}} group?": "Êtes-vous sûr de vouloir supprimer ce membre du groupe {{team}}?", "Back to home page": "Retour à l'accueil", @@ -37,6 +38,7 @@ "Create a new group": "Créer un nouveau groupe", "Create a new team": "Créer un nouveau groupe", "Create new team card": "Carte créer un nouveau groupe", + "Create the mailbox": "Créer la boîte mail", "Create the team": "Créer le groupe", "Create your first team by clicking on the \"Create a new team\" button.": "Créez votre premier groupe en cliquant sur le bouton \"Créer un nouveau groupe\".", "Created at": "Créé le", @@ -45,6 +47,8 @@ "Delete the team": "Supprimer le groupe", "Deleting the {{teamName}} team": "Suppression du groupe {{teamName}}", "E-mail:": "E-mail:", + "E.g. : jean.dupont@mail.fr": "Ex. : jean.dupont@mail.fr", + "Email address prefix": "Préfixe de l'adresse mail", "Emails": "Emails", "Empty teams icon": "Icône de groupe vide", "Enter the new name of the selected team": "Entrez le nouveau nom du groupe sélectionné", @@ -61,6 +65,7 @@ "Improvement and contact": "Amélioration et contact", "Invitation sent to {{email}}": "Invitation envoyée à {{email}}", "Invite new members to {{teamName}}": "Invitez de nouveaux membres à rejoindre {{teamName}}", + "It must not contain spaces, accents or special characters (except \".\" or \"-\"). E.g.: jean.dupont": "Il ne doit pas contenir d'espaces, d'accents ou de caractères spéciaux (excepté \".\" ou \"-\"). Ex. : jean.dupont", "It seems that the page you are looking for does not exist or cannot be displayed correctly.": "Il semble que la page que vous cherchez n'existe pas ou ne puisse pas être affichée correctement.", "It's true, you didn't have to click on a block that covers half the page to say you agree to the placement of cookies — even if you don't know what it means!": "C'est vrai, vous n'avez pas à cliquer sur un bloc qui couvre la moitié de la page pour dire que vous acceptez le placement de cookies — même si vous ne savez pas ce que cela signifie !", "La Régie (Suite Territoriale) is non-compliant with the RGAA. The site has not yet been audited.": "La Régie (Suite Territoriale) est non conforme avec le RGAA. Le site n’a encore pas été audité.", @@ -76,9 +81,7 @@ "Mail Domains": "Domaines de messagerie", "Mail Domains icon": "Icône des domaines mail", "Mailbox created!": "Boîte mail créée !", - "Mailbox creation form": "Formulaire de création de boîte mail", "Mailboxes list": "Liste des boîtes mail", - "Main email address": "Adresse e-mail principale", "Marianne Logo": "Logo Marianne", "Member": "Membre", "Member icon": "Icône de membre", @@ -97,10 +100,8 @@ "Ouch !": "Ouch !", "Owner": "Propriétaire", "Personal data and cookies": "Données personnelles et cookies", - "Please enter the first part of the email address, without including \"@\" in it": "Veuillez entrer la première partie de l'adresse e-mail, sans y inclure \"@\"", - "Please enter your first name": "Veuillez saisir votre prénom", - "Please enter your last name": "Veuillez saisir votre nom", - "Please enter your secondary email address": "Veuillez saisir votre adresse e-mail secondaire", + "Please enter a valid email address.\nE.g. : jean.dupont@mail.fr": "Veuillez entrer une adresse e-mail valide.\nEx. : jean.dupont@mail.fr", + "Please enter {{fieldName}}": "Veuillez entrer {{fieldName}}", "Publication Director": "Directeur de la publication", "Publisher": "Éditeur", "Radio buttons to update the roles": "Boutons radio pour mettre à jour les rôles", @@ -123,7 +124,6 @@ "Sort the teams by creation date ascendent": "Trier les groupes par date de création ascendante", "Sort the teams by creation date descendent": "Trier les groupes par date de création descendante", "Stéphanie Schaer: Interministerial Digital Director (DINUM).": "Stéphanie Schaer: Directrice numérique interministériel (DINUM).", - "Submit": "Valider", "Team name": "Nom du groupe", "Teams": "Équipes", "Teams icon": "Icône de groupe", @@ -135,6 +135,7 @@ "The team in charge of the digital workspace \"La Suite numérique\" can be contacted directly at": "L'équipe responsable de l'espace de travail numérique \"La Suite numérique\" peut être contactée directement à l'adresse", "This accessibility statement applies to La Régie (Suite Territoriale)": "Cette déclaration d’accessibilité s’applique à La Régie (Suite Territoriale)", "This allows us to measure the number of visits and understand which pages are the most viewed.": "Cela nous permet de mesurer le nombre de visites et de comprendre quelles pages sont les plus consultées.", + "This email prefix is already used.": "Ce préfixe d'email est déjà utilisé.", "This procedure is to be used in the following case: you have reported to the website \n manager an accessibility defect which prevents you from accessing content or one of the \n portal's services and you have not obtained a satisfactory response.": "Cette procédure est à utiliser dans le cas suivant : vous avez signalé au responsable du site internet un défaut d’accessibilité qui vous empêche d’accéder à un contenu ou à un des services du portail et vous n’avez pas obtenu de réponse satisfaisante.", "This site does not display a cookie consent banner, why?": "Ce site n'affiche pas de bannière de consentement des cookies, pourquoi?", "This site places a small text file (a \"cookie\") on your computer when you visit it.": "Ce site place un petit fichier texte (un « cookie ») sur votre ordinateur lorsque vous le visitez.", @@ -153,6 +154,7 @@ "You can:": "Vous pouvez :", "You cannot remove other owner.": "Vous ne pouvez pas supprimer un autre propriétaire.", "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", "accessibility-contact-defenseurdesdroits": "Contacter le délégué du<1>Défenseur des droits dans votre région", "accessibility-form-defenseurdesdroits": "Écrire un message au<1>Défenseur des droits", "icon group": "icône groupe", 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 c6e507a..1dc0627 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 @@ -41,8 +41,57 @@ const mailDomainsFixtures: MailDomain[] = [ const mailDomainDomainFrFixture = mailDomainsFixtures[0]; -const clickOnMailDomainsNavButton = async (page: Page): Promise => +const mailboxesFixtures = { + domainFr: { + page1: Array.from({ length: 1 }, (_, i) => ({ + id: `456ac6ca-0402-4615-8005-69bc1efde${i}f`, + local_part: `local_part-${i}`, + secondary_email: `secondary_email-${i}`, + })), + }, +}; + +const interceptCommonApiRequests = (page: Page) => { + void page.route('**/api/v1.0/mail-domains/?page=*', (route) => { + void route.fulfill({ + json: { + count: mailDomainsFixtures.length, + next: null, + previous: null, + results: mailDomainsFixtures, + }, + }); + }); + + void page.route('**/api/v1.0/mail-domains/domainfr', (route) => { + void route.fulfill({ + json: mailDomainDomainFrFixture, + }); + }); + + void page.route( + '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=1**', + (route) => { + void route.fulfill({ + json: { + count: mailboxesFixtures.domainFr.page1.length, + next: null, + previous: null, + results: mailboxesFixtures.domainFr.page1, + }, + }); + }, + { times: 1 }, + ); +}; + +const navigateToMailboxCreationFormForMailDomainFr = async ( + page: Page, +): Promise => { await page.locator('menu').first().getByLabel(`Mail Domains button`).click(); + await page.getByRole('listbox').first().getByText('domain.fr').click(); + await page.getByRole('button', { name: 'Create a mailbox' }).click(); +}; test.describe('Mail domain create mailbox', () => { test.beforeEach(async ({ page, browserName }) => { @@ -54,57 +103,19 @@ test.describe('Mail domain create mailbox', () => { test('checks user can create a mailbox for a mail domain', async ({ page, }) => { - const mailboxesFixtures = { - domainFr: { - page1: Array.from({ length: 1 }, (_, i) => ({ - id: `456ac6ca-0402-4615-8005-69bc1efde${i}f`, - local_part: `local_part-${i}`, - secondary_email: `secondary_email-${i}`, - })), - }, - }; - const newMailbox = { id: '04433733-c9b7-453a-8122-755ac115bb00', local_part: 'john.doe', - secondary_email: 'john.doe@mail.com', + secondary_email: 'john.doe-complex2024@mail.com', }; - const interceptApiCalls = async () => { - await page.route('**/api/v1.0/mail-domains/?page=*', async (route) => { - await route.fulfill({ - json: { - count: mailDomainsFixtures.length, - next: null, - previous: null, - results: mailDomainsFixtures, - }, - }); - }); - await page.route('**/api/v1.0/mail-domains/domainfr', async (route) => { - await route.fulfill({ - json: mailDomainDomainFrFixture, - }); - }); - await page.route( - '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=1**', - async (route) => { - await route.fulfill({ - json: { - count: mailboxesFixtures.domainFr.page1.length, - next: null, - previous: null, - results: mailboxesFixtures.domainFr.page1, - }, - }); - }, - { times: 1 }, - ); + const interceptRequests = (page: Page) => { + void interceptCommonApiRequests(page); - await page.route( + void page.route( '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=1**', - async (route) => { - await route.fulfill({ + (route) => { + void route.fulfill({ json: { count: [...mailboxesFixtures.domainFr.page1, newMailbox].length, next: null, @@ -115,7 +126,7 @@ test.describe('Mail domain create mailbox', () => { }, ); - await page.route( + void page.route( '**/api/v1.0/mail-domains/domainfr/mailboxes/', (route) => { if (route.request().method() === 'POST') { @@ -149,13 +160,10 @@ test.describe('Mail domain create mailbox', () => { } }); - await interceptApiCalls(); + void interceptRequests(page); - await clickOnMailDomainsNavButton(page); + await navigateToMailboxCreationFormForMailDomainFr(page); - await page.getByRole('listbox').first().getByText('domain.fr').click(); - - await page.getByRole('button', { name: 'Create a mailbox' }).click(); await page.getByRole('button', { name: 'Cancel' }).click(); await expect(page.getByTitle('Mailbox creation form')).toBeHidden(); @@ -167,13 +175,37 @@ test.describe('Mail domain create mailbox', () => { page.getByRole('heading', { name: 'Create a mailbox' }), ).toBeVisible(); - await page.getByLabel('First name').fill('John'); - await page.getByLabel('Last name').fill('Doe'); - await page.getByLabel('Main email address').fill('john.doe'); - await expect(page.locator('span').getByText('@domain.fr')).toBeVisible(); - await page.getByLabel('Secondary email address').fill('john.doe@mail.com'); + const inputFirstName = page.getByLabel('First name'); + const inputLastName = page.getByLabel('Last name'); + const inputLocalPart = page.getByLabel('Email address prefix'); + const instructionInputLocalPart = page.getByText( + 'It must not contain spaces, accents or special characters (except "." or "-"). E.g.: jean.dupont', + ); + const inputSecondaryEmailAddress = page.getByLabel( + 'Secondary email address', + ); - await page.getByRole('button', { name: 'Submit' }).click(); + await expect(inputFirstName).toHaveAttribute('aria-required', 'true'); + await expect(inputFirstName).toHaveAttribute('required', ''); + await expect(inputLastName).toHaveAttribute('aria-required', 'true'); + await expect(inputLastName).toHaveAttribute('required', ''); + await expect(inputLocalPart).toHaveAttribute('aria-required', 'true'); + await expect(inputLocalPart).toHaveAttribute('required', ''); + await expect(inputSecondaryEmailAddress).toHaveAttribute( + 'aria-required', + 'true', + ); + await expect(inputSecondaryEmailAddress).toHaveAttribute('required', ''); + + await inputFirstName.fill('John'); + await inputLastName.fill('Doe'); + await inputLocalPart.fill('john.doe'); + + await expect(instructionInputLocalPart).toBeVisible(); + await expect(page.locator('span').getByText('@domain.fr')).toBeVisible(); + await inputSecondaryEmailAddress.fill('john.doe@mail.com'); + + await page.getByRole('button', { name: 'Create the mailbox' }).click(); expect(isCreateMailboxRequestSentWithExpectedPayload).toBeTruthy(); await expect(page.getByAltText('Mailbox creation form')).toBeHidden(); @@ -192,51 +224,9 @@ test.describe('Mail domain create mailbox', () => { ); }); - test('checks client invalidation messages are displayed when fields are not properly filled', async ({ + test('checks client invalidation messages are displayed and no mailbox creation request is sent when fields are not properly filled', async ({ page, }) => { - const mailboxesFixtures = { - domainFr: { - page1: Array.from({ length: 1 }, (_, i) => ({ - id: `456ac6ca-0402-4615-8005-69bc1efde${i}f`, - local_part: `local_part-${i}`, - secondary_email: `secondary_email-${i}`, - })), - }, - }; - - const interceptApiCalls = async () => { - await page.route('**/api/v1.0/mail-domains/?page=*', async (route) => { - await route.fulfill({ - json: { - count: mailDomainsFixtures.length, - next: null, - previous: null, - results: mailDomainsFixtures, - }, - }); - }); - await page.route('**/api/v1.0/mail-domains/domainfr', async (route) => { - await route.fulfill({ - json: mailDomainDomainFrFixture, - }); - }); - await page.route( - '**/api/v1.0/mail-domains/domainfr/mailboxes/?page=1**', - async (route) => { - await route.fulfill({ - json: { - count: mailboxesFixtures.domainFr.page1.length, - next: null, - previous: null, - results: mailboxesFixtures.domainFr.page1, - }, - }); - }, - { times: 1 }, - ); - }; - let isCreateMailboxRequestSent = false; page.on( 'request', @@ -246,27 +236,144 @@ test.describe('Mail domain create mailbox', () => { request.method() === 'POST'), ); - await interceptApiCalls(); + void interceptCommonApiRequests(page); - await clickOnMailDomainsNavButton(page); + await navigateToMailboxCreationFormForMailDomainFr(page); - await page.getByRole('listbox').first().getByText('domain.fr').click(); - - await page.getByRole('button', { name: 'Create a mailbox' }).click(); - - await page.getByRole('button', { name: 'Submit' }).click(); + 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( - 'Please enter the first part of the email address, without including "@" in it', - ), - ).toBeVisible(); - await expect( - page.getByText('Please enter your secondary email address'), + 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(); + }); });