🚸(frontend) improve mailbox creation validations and error handling

- 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.
This commit is contained in:
daproclaima
2024-07-04 12:09:18 +02:00
committed by Sebastien Nobour
parent cda4373544
commit 32e6602dda
5 changed files with 348 additions and 201 deletions

View File

@@ -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);
}

View File

@@ -85,7 +85,7 @@ export function MailDomainsContent({ mailDomain }: { mailDomain: MailDomain }) {
{isCreateMailboxFormVisible && mailDomain ? (
<CreateMailboxForm
mailDomain={mailDomain}
setIsFormVisible={setIsCreateMailboxFormVisible}
closeModal={() => setIsCreateMailboxFormVisible(false)}
/>
) : null}
<TopBanner

View File

@@ -15,6 +15,7 @@ import {
useForm,
} from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { createGlobalStyle } from 'styled-components';
import { z } from 'zod';
import { Box, Text, TextErrors } from '@/components';
@@ -26,24 +27,53 @@ import { MailDomain } from '../../types';
const FORM_ID: string = 'form-create-mailbox';
const createMailboxValidationSchema = z.object({
first_name: z.string().min(1),
last_name: z.string().min(1),
local_part: z.string().min(1),
secondary_email: z.string().min(1),
});
const GlobalStyle = createGlobalStyle`
.c__field__footer__top > .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<CreateMailboxParams>({
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 (
<FormProvider {...methods}>
<Modal
@@ -102,7 +144,7 @@ export const CreateMailboxForm = ({
form={FORM_ID}
disabled={methods.formState.isSubmitting}
>
{t('Submit')}
{t('Create the mailbox')}
</Button>
}
size={ModalSize.MEDIUM}
@@ -113,15 +155,22 @@ export const CreateMailboxForm = ({
color={colorsTokens()['primary-text']}
title={t('Mailbox creation form')}
/>
<Text $size="h3" $margin="none" role="heading" aria-level={3}>
<Text
$size="h3"
$margin="none"
role="heading"
aria-level={3}
title={t('Create a mailbox')}
>
{t('Create a mailbox')}
</Text>
</Box>
}
>
<Box $width="100%" $margin={{ top: 'large', bottom: 'xl' }}>
{queryState.isError && (
<TextErrors className="mb-s" causes={queryState.error.cause} />
<GlobalStyle />
<Box $width="100%" $margin={{ top: 'none', bottom: 'xl' }}>
{!!causes?.length && (
<TextErrors $margin={{ bottom: 'small' }} causes={causes} />
)}
{methods ? (
<Form
@@ -151,76 +200,39 @@ const Form = ({
<form onSubmit={onSubmitCallback} id={FORM_ID}>
<Box $direction="column" $width="100%" $gap="2rem" $margin="auto">
<Box $margin={{ horizontal: 'none' }}>
<Controller
control={methods.control}
<FieldMailBox
name="first_name"
render={({ fieldState }) => (
<Input
aria-invalid={!!fieldState.error}
label={t('First name')}
state={fieldState.error ? 'error' : 'default'}
text={
fieldState.error
? t('Please enter your first name')
: undefined
}
{...methods.register('first_name')}
/>
)}
label={t('First name')}
methods={methods}
/>
</Box>
<Box $margin={{ horizontal: 'none' }}>
<Controller
control={methods.control}
<FieldMailBox
name="last_name"
render={({ fieldState }) => (
<Input
aria-invalid={!!fieldState.error}
label={t('Last name')}
state={fieldState.error ? 'error' : 'default'}
text={
fieldState.error
? t('Please enter your last name')
: undefined
}
{...methods.register('last_name')}
/>
)}
label={t('Last name')}
methods={methods}
/>
</Box>
<Box $margin={{ horizontal: 'none' }} $direction="row">
<Box $width="65%">
<Controller
control={methods.control}
<FieldMailBox
name="local_part"
render={({ fieldState }) => (
<Input
aria-invalid={!!fieldState.error}
label={t('Main email address')}
state={fieldState.error ? 'error' : 'default'}
text={
fieldState.error
? t(
'Please enter the first part of the email address, without including "@" in it',
)
: undefined
}
{...methods.register('local_part')}
/>
label={t('Email address prefix')}
methods={methods}
text={t(
'It must not contain spaces, accents or special characters (except "." or "-"). E.g.: jean.dupont',
)}
/>
</Box>
<Text
as="span"
$theme="primary"
$size="1rem"
$display="inline-block"
$css={`
position: relative;
display: inline-block;
left: 1rem;
top: 1rem;
`}
@@ -230,25 +242,41 @@ const Form = ({
</Box>
<Box $margin={{ horizontal: 'none' }}>
<Controller
control={methods.control}
<FieldMailBox
name="secondary_email"
render={({ fieldState }) => (
<Input
aria-invalid={!!fieldState.error}
label={t('Secondary email address')}
state={fieldState.error ? 'error' : 'default'}
text={
fieldState.error
? t('Please enter your secondary email address')
: undefined
}
{...methods.register('secondary_email')}
/>
)}
label={t('Secondary email address')}
methods={methods}
text={t('E.g. : jean.dupont@mail.fr')}
/>
</Box>
</Box>
</form>
);
};
interface FieldMailBoxProps {
name: 'first_name' | 'last_name' | 'local_part' | 'secondary_email';
label: string;
methods: UseFormReturn<CreateMailboxParams>;
text?: string;
}
const FieldMailBox = ({ name, label, methods, text }: FieldMailBoxProps) => {
return (
<Controller
control={methods.control}
name={name}
render={({ fieldState }) => (
<Input
aria-invalid={!!fieldState.error}
aria-required
required
label={label}
state={fieldState.error ? 'error' : 'default'}
text={fieldState?.error?.message ? fieldState.error.message : text}
{...methods.register(name)}
/>
)}
/>
);
};

View File

@@ -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 na 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 daccessibilité sapplique à 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 daccessibilité qui vous empêche daccéder à un contenu ou à un des services du portail et vous navez 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</1>",
"accessibility-form-defenseurdesdroits": "Écrire un message au<1>Défenseur des droits</1>",
"icon group": "icône groupe",

View File

@@ -41,8 +41,57 @@ const mailDomainsFixtures: MailDomain[] = [
const mailDomainDomainFrFixture = mailDomainsFixtures[0];
const clickOnMailDomainsNavButton = async (page: Page): Promise<void> =>
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<void> => {
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();
});
});