🚸(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:
committed by
Sebastien Nobour
parent
cda4373544
commit
32e6602dda
@@ -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);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export function MailDomainsContent({ mailDomain }: { mailDomain: MailDomain }) {
|
||||
{isCreateMailboxFormVisible && mailDomain ? (
|
||||
<CreateMailboxForm
|
||||
mailDomain={mailDomain}
|
||||
setIsFormVisible={setIsCreateMailboxFormVisible}
|
||||
closeModal={() => setIsCreateMailboxFormVisible(false)}
|
||||
/>
|
||||
) : null}
|
||||
<TopBanner
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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</1>",
|
||||
"accessibility-form-defenseurdesdroits": "Écrire un message au<1>Défenseur des droits</1>",
|
||||
"icon group": "icône groupe",
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user