🥅(frontend) improve error catching in forms
- 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)
This commit is contained in:
committed by
Sebastien Nobour
parent
25898bbb64
commit
e4aed82ff2
@@ -11,6 +11,7 @@ and this project adheres to
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- 📈(monitoring) configure sentry monitoring #378
|
- 📈(monitoring) configure sentry monitoring #378
|
||||||
|
- 🥅(frontend) improve api error handling #355
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ and this project adheres to
|
|||||||
- 💬(frontend) fix group member removal text #382
|
- 💬(frontend) fix group member removal text #382
|
||||||
- 💬(frontend) fix add mail domain text #382
|
- 💬(frontend) fix add mail domain text #382
|
||||||
- 🐛(frontend) fix keyboard navigation #379
|
- 🐛(frontend) fix keyboard navigation #379
|
||||||
|
- 🐛(frontend) fix add mail domain form submission #355
|
||||||
|
|
||||||
## [1.0.2] - 2024-08-30
|
## [1.0.2] - 2024-08-30
|
||||||
|
|
||||||
@@ -30,10 +32,6 @@ and this project adheres to
|
|||||||
|
|
||||||
- 👽️(mailboxes) fix mailbox creation after dimail api improvement (#360)
|
- 👽️(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
|
## [1.0.1] - 2024-08-19
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ export const parseAPIErrorCause = ({
|
|||||||
}): string[] =>
|
}): string[] =>
|
||||||
causes.reduce((arrayCauses, cause) => {
|
causes.reduce((arrayCauses, cause) => {
|
||||||
const foundErrorParams = Object.values(errorParams).find((params) =>
|
const foundErrorParams = Object.values(errorParams).find((params) =>
|
||||||
params.causes.find((knownCause) => knownCause.match(cause)),
|
params.causes.find((knownCause) =>
|
||||||
|
new RegExp(knownCause, 'i').test(cause),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!foundErrorParams) {
|
if (!foundErrorParams) {
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ import { MailDomain } from '@/features/mail-domains';
|
|||||||
|
|
||||||
import { KEY_LIST_MAIL_DOMAIN } from './useMailDomains';
|
import { KEY_LIST_MAIL_DOMAIN } from './useMailDomains';
|
||||||
|
|
||||||
export const createMailDomain = async (name: string): Promise<MailDomain> => {
|
export interface AddMailDomainParams {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addMailDomain = async (
|
||||||
|
name: AddMailDomainParams['name'],
|
||||||
|
): Promise<MailDomain> => {
|
||||||
const response = await fetchAPI(`mail-domains/`, {
|
const response = await fetchAPI(`mail-domains/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -23,19 +29,24 @@ export const createMailDomain = async (name: string): Promise<MailDomain> => {
|
|||||||
return response.json() as Promise<MailDomain>;
|
return response.json() as Promise<MailDomain>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useCreateMailDomain({
|
export const useAddMailDomain = ({
|
||||||
onSuccess,
|
onSuccess,
|
||||||
|
onError,
|
||||||
}: {
|
}: {
|
||||||
onSuccess: (data: MailDomain) => void;
|
onSuccess: (data: MailDomain) => void;
|
||||||
}) {
|
onError: (error: APIError) => void;
|
||||||
|
}) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation<MailDomain, APIError, string>({
|
return useMutation<MailDomain, APIError, string>({
|
||||||
mutationFn: createMailDomain,
|
mutationFn: addMailDomain,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [KEY_LIST_MAIL_DOMAIN],
|
queryKey: [KEY_LIST_MAIL_DOMAIN],
|
||||||
});
|
});
|
||||||
onSuccess(data);
|
onSuccess(data);
|
||||||
},
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
onError(error);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
@@ -26,7 +26,6 @@ export const createMailbox = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
// TODO: extend errorCauses to return the name of the invalid field names to highlight in the form?
|
|
||||||
throw new APIError(
|
throw new APIError(
|
||||||
'Failed to create the mailbox',
|
'Failed to create the mailbox',
|
||||||
await errorCauses(response),
|
await errorCauses(response),
|
||||||
@@ -40,7 +39,7 @@ type UseCreateMailboxParams = { mailDomainSlug: string } & UseMutationOptions<
|
|||||||
CreateMailboxParams
|
CreateMailboxParams
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function useCreateMailbox(options: UseCreateMailboxParams) {
|
export const useCreateMailbox = (options: UseCreateMailboxParams) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation<void, APIError, CreateMailboxParams>({
|
return useMutation<void, APIError, CreateMailboxParams>({
|
||||||
mutationFn: createMailbox,
|
mutationFn: createMailbox,
|
||||||
@@ -61,4 +60,4 @@ export function useCreateMailbox(options: UseCreateMailboxParams) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { default as MailDomainsLogo } from '../assets/mail-domains-logo.svg';
|
|||||||
import { PAGE_SIZE } from '../conf';
|
import { PAGE_SIZE } from '../conf';
|
||||||
import { MailDomain, MailDomainMailbox } from '../types';
|
import { MailDomain, MailDomainMailbox } from '../types';
|
||||||
|
|
||||||
import { CreateMailboxForm } from './forms/CreateMailboxForm';
|
import { ModalCreateMailbox } from './ModalCreateMailbox';
|
||||||
|
|
||||||
export type ViewMailbox = {
|
export type ViewMailbox = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -87,7 +87,7 @@ export function MailDomainsContent({ mailDomain }: { mailDomain: MailDomain }) {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{isCreateMailboxFormVisible && mailDomain ? (
|
{isCreateMailboxFormVisible && mailDomain ? (
|
||||||
<CreateMailboxForm
|
<ModalCreateMailbox
|
||||||
mailDomain={mailDomain}
|
mailDomain={mailDomain}
|
||||||
closeModal={() => setIsCreateMailboxFormVisible(false)}
|
closeModal={() => setIsCreateMailboxFormVisible(false)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,91 +1,27 @@
|
|||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Button, Input, Loader, ModalSize } from '@openfun/cunningham-react';
|
import { Button, Input, Loader, ModalSize } from '@openfun/cunningham-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
||||||
Controller,
|
|
||||||
FormProvider,
|
|
||||||
UseFormReturn,
|
|
||||||
useForm,
|
|
||||||
} from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { APIError } from '@/api';
|
import { parseAPIError } from '@/api/parseAPIError';
|
||||||
import { Box, Text, TextErrors } from '@/components';
|
import { Box, Text, TextErrors } from '@/components';
|
||||||
import { Modal } from '@/components/Modal';
|
import { Modal } from '@/components/Modal';
|
||||||
import { useCreateMailDomain } from '@/features/mail-domains';
|
import { useAddMailDomain } from '@/features/mail-domains';
|
||||||
|
|
||||||
import { default as MailDomainsLogo } from '../assets/mail-domains-logo.svg';
|
import { default as MailDomainsLogo } from '../assets/mail-domains-logo.svg';
|
||||||
|
|
||||||
const FORM_ID = 'form-add-mail-domain';
|
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 | string[]>(
|
|
||||||
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 = () => {
|
export const ModalAddMailDomain = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const createMailDomainValidationSchema = z.object({
|
const [errorCauses, setErrorCauses] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const addMailDomainValidationSchema = z.object({
|
||||||
name: z.string().min(1, t('Example: saint-laurent.fr')),
|
name: z.string().min(1, t('Example: saint-laurent.fr')),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,26 +32,62 @@ export const ModalAddMailDomain = () => {
|
|||||||
},
|
},
|
||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
reValidateMode: 'onChange',
|
reValidateMode: 'onChange',
|
||||||
resolver: zodResolver(createMailDomainValidationSchema),
|
resolver: zodResolver(addMailDomainValidationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const { mutate: addMailDomain, isPending } = useAddMailDomain({
|
||||||
mutate: createMailDomain,
|
|
||||||
isPending,
|
|
||||||
error,
|
|
||||||
} = useCreateMailDomain({
|
|
||||||
onSuccess: (mailDomain) => {
|
onSuccess: (mailDomain) => {
|
||||||
router.push(`/mail-domains/${mailDomain.slug}`);
|
router.push(`/mail-domains/${mailDomain.slug}`);
|
||||||
},
|
},
|
||||||
});
|
onError: (error) => {
|
||||||
|
const unhandledCauses = parseAPIError({
|
||||||
|
error,
|
||||||
|
errorParams: {
|
||||||
|
name: {
|
||||||
|
causes: [
|
||||||
|
'Mail domain with this name already exists.',
|
||||||
|
'Mail domain with this Slug already exists.',
|
||||||
|
],
|
||||||
|
handleError: () => {
|
||||||
|
if (methods.formState.errors.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const errorCauses = useAddMailDomainApiError({ error, methods });
|
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');
|
||||||
|
},
|
||||||
|
defaultMessage: 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((prevState) =>
|
||||||
|
unhandledCauses &&
|
||||||
|
JSON.stringify(unhandledCauses) !== JSON.stringify(prevState)
|
||||||
|
? unhandledCauses
|
||||||
|
: prevState,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const onSubmitCallback = (event: React.FormEvent) => {
|
const onSubmitCallback = (event: React.FormEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
void methods.handleSubmit(({ name }) => {
|
void methods.handleSubmit(({ name }) => {
|
||||||
void createMailDomain(name);
|
void addMailDomain(name);
|
||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -139,7 +111,11 @@ export const ModalAddMailDomain = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
form={FORM_ID}
|
form={FORM_ID}
|
||||||
disabled={!methods.watch('name') || isPending}
|
disabled={
|
||||||
|
methods.formState.isSubmitting ||
|
||||||
|
!methods.formState.isValid ||
|
||||||
|
isPending
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t('Add the domain')}
|
{t('Add the domain')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -163,7 +139,11 @@ export const ModalAddMailDomain = () => {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form id={FORM_ID} onSubmit={onSubmitCallback}>
|
<form
|
||||||
|
id={FORM_ID}
|
||||||
|
onSubmit={onSubmitCallback}
|
||||||
|
title={t('Mail domain addition form')}
|
||||||
|
>
|
||||||
<Controller
|
<Controller
|
||||||
control={methods.control}
|
control={methods.control}
|
||||||
name="name"
|
name="name"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
VariantType,
|
VariantType,
|
||||||
useToastProvider,
|
useToastProvider,
|
||||||
} from '@openfun/cunningham-react';
|
} from '@openfun/cunningham-react';
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
FormProvider,
|
FormProvider,
|
||||||
@@ -17,11 +17,12 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { createGlobalStyle } from 'styled-components';
|
import { createGlobalStyle } from 'styled-components';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { parseAPIError } from '@/api/parseAPIError';
|
||||||
import { Box, Text, TextErrors } from '@/components';
|
import { Box, Text, TextErrors } from '@/components';
|
||||||
import { Modal } from '@/components/Modal';
|
import { Modal } from '@/components/Modal';
|
||||||
|
|
||||||
import { CreateMailboxParams, useCreateMailbox } from '../../api';
|
import { CreateMailboxParams, useCreateMailbox } from '../api';
|
||||||
import { MailDomain } from '../../types';
|
import { MailDomain } from '../types';
|
||||||
|
|
||||||
const FORM_ID: string = 'form-create-mailbox';
|
const FORM_ID: string = 'form-create-mailbox';
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ const GlobalStyle = createGlobalStyle`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CreateMailboxForm = ({
|
export const ModalCreateMailbox = ({
|
||||||
mailDomain,
|
mailDomain,
|
||||||
closeModal,
|
closeModal,
|
||||||
}: {
|
}: {
|
||||||
@@ -42,6 +43,8 @@ export const CreateMailboxForm = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToastProvider();
|
const { toast } = useToastProvider();
|
||||||
|
|
||||||
|
const [errorCauses, setErrorCauses] = useState<string[]>([]);
|
||||||
|
|
||||||
const messageInvalidMinChar = t('You must have minimum 1 character');
|
const messageInvalidMinChar = t('You must have minimum 1 character');
|
||||||
|
|
||||||
const createMailboxValidationSchema = z.object({
|
const createMailboxValidationSchema = z.object({
|
||||||
@@ -77,7 +80,7 @@ export const CreateMailboxForm = ({
|
|||||||
resolver: zodResolver(createMailboxValidationSchema),
|
resolver: zodResolver(createMailboxValidationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: createMailbox, error } = useCreateMailbox({
|
const { mutate: createMailbox, isPending } = useCreateMailbox({
|
||||||
mailDomainSlug: mailDomain.slug,
|
mailDomainSlug: mailDomain.slug,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast(t('Mailbox created!'), VariantType.SUCCESS, {
|
toast(t('Mailbox created!'), VariantType.SUCCESS, {
|
||||||
@@ -86,6 +89,52 @@ export const CreateMailboxForm = ({
|
|||||||
|
|
||||||
closeModal();
|
closeModal();
|
||||||
},
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
const unhandledCauses = parseAPIError({
|
||||||
|
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.",
|
||||||
|
`Secret not valid for this domain`,
|
||||||
|
],
|
||||||
|
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');
|
||||||
|
},
|
||||||
|
defaultMessage: 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((prevState) =>
|
||||||
|
unhandledCauses &&
|
||||||
|
JSON.stringify(unhandledCauses) !== JSON.stringify(prevState)
|
||||||
|
? unhandledCauses
|
||||||
|
: prevState,
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmitCallback = (event: React.FormEvent) => {
|
const onSubmitCallback = (event: React.FormEvent) => {
|
||||||
@@ -95,20 +144,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 (
|
return (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<Modal
|
<Modal
|
||||||
@@ -132,7 +167,11 @@ export const CreateMailboxForm = ({
|
|||||||
fullWidth
|
fullWidth
|
||||||
type="submit"
|
type="submit"
|
||||||
form={FORM_ID}
|
form={FORM_ID}
|
||||||
disabled={methods.formState.isSubmitting}
|
disabled={
|
||||||
|
methods.formState.isSubmitting ||
|
||||||
|
!methods.formState.isValid ||
|
||||||
|
isPending
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t('Create the mailbox')}
|
{t('Create the mailbox')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -152,8 +191,12 @@ export const CreateMailboxForm = ({
|
|||||||
>
|
>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<Box $width="100%" $margin={{ top: 'none', bottom: 'xl' }}>
|
<Box $width="100%" $margin={{ top: 'none', bottom: 'xl' }}>
|
||||||
{!!causes?.length && (
|
{!!errorCauses?.length && (
|
||||||
<TextErrors $margin={{ bottom: 'small' }} causes={causes} />
|
<TextErrors
|
||||||
|
$margin={{ bottom: 'small' }}
|
||||||
|
causes={errorCauses}
|
||||||
|
$textAlign="left"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Text
|
<Text
|
||||||
$margin={{ horizontal: 'none', vertical: 'big' }}
|
$margin={{ horizontal: 'none', vertical: 'big' }}
|
||||||
@@ -188,7 +231,11 @@ const Form = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={onSubmitCallback} id={FORM_ID}>
|
<form
|
||||||
|
onSubmit={onSubmitCallback}
|
||||||
|
id={FORM_ID}
|
||||||
|
title={t('Mailbox creation form')}
|
||||||
|
>
|
||||||
<Box $direction="column" $width="100%" $gap="2rem" $margin="auto">
|
<Box $direction="column" $width="100%" $gap="2rem" $margin="auto">
|
||||||
<Box $margin={{ horizontal: 'none' }}>
|
<Box $margin={{ horizontal: 'none' }}>
|
||||||
<FieldMailBox
|
<FieldMailBox
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import fetchMock from 'fetch-mock';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { AppWrapper } from '@/tests/utils';
|
||||||
|
|
||||||
|
import { ModalAddMailDomain } from '../ModalAddMailDomain';
|
||||||
|
|
||||||
|
const mockPush = jest.fn();
|
||||||
|
jest.mock('next/navigation', () => ({
|
||||||
|
useRouter: jest.fn().mockImplementation(() => ({
|
||||||
|
push: mockPush,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('ModalAddMailDomain', () => {
|
||||||
|
const getElements = () => ({
|
||||||
|
modalElement: screen.getByText('Add a 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fetchMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders all the elements', () => {
|
||||||
|
render(<ModalAddMailDomain />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
|
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', () => {
|
||||||
|
render(<ModalAddMailDomain />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
|
const { buttonSubmit } = getElements();
|
||||||
|
|
||||||
|
expect(buttonSubmit).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays validation error on empty submit', async () => {
|
||||||
|
fetchMock.mock(`end:mail-domains/`, 201);
|
||||||
|
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<ModalAddMailDomain />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
|
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(fetchMock.lastUrl()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submits the form when validation passes', async () => {
|
||||||
|
fetchMock.mock(`end:mail-domains/`, {
|
||||||
|
status: 201,
|
||||||
|
body: {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<ModalAddMailDomain />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
|
const { inputName, buttonSubmit } = getElements();
|
||||||
|
|
||||||
|
await user.type(inputName, 'domain.fr');
|
||||||
|
|
||||||
|
await user.click(buttonSubmit);
|
||||||
|
|
||||||
|
expect(fetchMock.lastUrl()).toContain('/mail-domains/');
|
||||||
|
expect(fetchMock.lastOptions()).toEqual({
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: 'domain.fr',
|
||||||
|
}),
|
||||||
|
credentials: 'include',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockPush).toHaveBeenCalledWith(`/mail-domains/domainfr`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submits the form on key enter press', async () => {
|
||||||
|
fetchMock.mock(`end:mail-domains/`, 201);
|
||||||
|
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<ModalAddMailDomain />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
|
const { inputName } = getElements();
|
||||||
|
|
||||||
|
await user.type(inputName, 'domain.fr');
|
||||||
|
await user.type(inputName, '{enter}');
|
||||||
|
|
||||||
|
expect(fetchMock.lastUrl()).toContain('/mail-domains/');
|
||||||
|
expect(fetchMock.lastOptions()).toEqual({
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: 'domain.fr',
|
||||||
|
}),
|
||||||
|
credentials: 'include',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays right error message error when maildomain name is already used', async () => {
|
||||||
|
fetchMock.mock(`end:mail-domains/`, {
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
name: 'Mail domain with this name already exists.',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<ModalAddMailDomain />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
await user.type(inputName, 'domain2.fr');
|
||||||
|
expect(buttonSubmit).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays right error message error when maildomain slug is already used', async () => {
|
||||||
|
fetchMock.mock(`end:mail-domains/`, {
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
name: 'Mail domain with this Slug already exists.',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<ModalAddMailDomain />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
await user.type(inputName, 'domain2fr');
|
||||||
|
|
||||||
|
expect(buttonSubmit).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays right error message error when error 500 is received', async () => {
|
||||||
|
fetchMock.mock(`end:mail-domains/`, {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<ModalAddMailDomain />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
|
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();
|
||||||
|
expect(buttonSubmit).toBeEnabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import fetchMock from 'fetch-mock';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { APIError } from '@/api';
|
||||||
|
import { AppWrapper } from '@/tests/utils';
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockOnSuccess = jest.fn();
|
||||||
|
jest.mock('../../api/useCreateMailbox', () => {
|
||||||
|
const { createMailbox } = jest.requireActual('../../api/useCreateMailbox');
|
||||||
|
|
||||||
|
return {
|
||||||
|
useCreateMailbox: jest.fn().mockImplementation(({ onError }) =>
|
||||||
|
useMutation<void, APIError, CreateMailboxParams>({
|
||||||
|
mutationFn: createMailbox,
|
||||||
|
onSuccess: mockOnSuccess,
|
||||||
|
onError: (error) => onError(error),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ModalCreateMailbox', () => {
|
||||||
|
const mockCloseModal = jest.fn();
|
||||||
|
const renderModalCreateMailbox = () => {
|
||||||
|
return render(
|
||||||
|
<ModalCreateMailbox
|
||||||
|
mailDomain={mockMailDomain}
|
||||||
|
closeModal={mockCloseModal}
|
||||||
|
/>,
|
||||||
|
{ wrapper: AppWrapper },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fetchMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
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).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
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(fetchMock.lastUrl()).toBeFalsy();
|
||||||
|
expect(buttonSubmit).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submits the form when validation passes', async () => {
|
||||||
|
fetchMock.mock(`end:mail-domains/${mockMailDomain.slug}/mailboxes/`, 201);
|
||||||
|
|
||||||
|
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(fetchMock.lastOptions()).toEqual({
|
||||||
|
body: JSON.stringify({
|
||||||
|
first_name: 'John',
|
||||||
|
last_name: 'Doe',
|
||||||
|
local_part: 'john.doe',
|
||||||
|
secondary_email: 'john.doe@mail.com',
|
||||||
|
}),
|
||||||
|
credentials: 'include',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockOnSuccess).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submits the form on key enter press', async () => {
|
||||||
|
fetchMock.mock(`end:mail-domains/${mockMailDomain.slug}/mailboxes/`, 201);
|
||||||
|
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.type(buttonSubmit, '{enter}');
|
||||||
|
|
||||||
|
expect(fetchMock.lastOptions()).toEqual({
|
||||||
|
body: JSON.stringify({
|
||||||
|
first_name: 'John',
|
||||||
|
last_name: 'Doe',
|
||||||
|
local_part: 'john.doe',
|
||||||
|
secondary_email: 'john.doe@mail.com',
|
||||||
|
}),
|
||||||
|
credentials: 'include',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockOnSuccess).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
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.'],
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
fetchMock.mock(`end:mail-domains/${mockMailDomain.slug}/mailboxes/`, {
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
local_part: '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 () => {
|
||||||
|
fetchMock.mock(`end:mail-domains/${mockMailDomain.slug}/mailboxes/`, {
|
||||||
|
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();
|
||||||
|
expect(buttonSubmit).toBeEnabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -83,8 +83,10 @@
|
|||||||
"List members card": "Carte liste des membres",
|
"List members card": "Carte liste des membres",
|
||||||
"Logout": "Se déconnecter",
|
"Logout": "Se déconnecter",
|
||||||
"Mail Domains": "Domaines de messagerie",
|
"Mail Domains": "Domaines de messagerie",
|
||||||
|
"Mail domain addition form": "Formulaire d'ajout de domaine de messagerie",
|
||||||
"Mail domains panel": "Panel des domaines de messagerie",
|
"Mail domains panel": "Panel des domaines de messagerie",
|
||||||
"Mailbox created!": "Boîte mail créée !",
|
"Mailbox created!": "Boîte mail créée !",
|
||||||
|
"Mailbox creation form": "Formulaire de création de boite mail",
|
||||||
"Mailboxes list": "Liste des boîtes mail",
|
"Mailboxes list": "Liste des boîtes mail",
|
||||||
"Marianne Logo": "Logo Marianne",
|
"Marianne Logo": "Logo Marianne",
|
||||||
"Member": "Membre",
|
"Member": "Membre",
|
||||||
@@ -134,6 +136,7 @@
|
|||||||
"Teams": "Équipes",
|
"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 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 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 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 role has been updated": "Le rôle a bien été mis à jour",
|
||||||
"The team has been removed.": "Le groupe a été supprimé.",
|
"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 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",
|
"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 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é]",
|
"[disabled]": "[désactivé]",
|
||||||
"[enabled]": "[actif]",
|
"[enabled]": "[actif]",
|
||||||
"[failed]": "[erroné]",
|
"[failed]": "[erroné]",
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import React, { ReactElement } from 'react';
|
|||||||
|
|
||||||
import { Box } from '@/components';
|
import { Box } from '@/components';
|
||||||
import { MailDomainsLayout } from '@/features/mail-domains';
|
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';
|
import { NextPageWithLayout } from '@/types/next';
|
||||||
|
|
||||||
const Page: NextPageWithLayout = () => {
|
const Page: NextPageWithLayout = () => {
|
||||||
return (
|
return (
|
||||||
<Box $padding="large" $height="inherit">
|
<Box $padding="large" $height="inherit">
|
||||||
<ModalCreateMailDomain />
|
<ModalAddMailDomain />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -314,157 +314,4 @@ test.describe('Mail domain create mailbox', () => {
|
|||||||
page.getByRole('button', { name: 'Create a mailbox' }),
|
page.getByRole('button', { name: 'Create a mailbox' }),
|
||||||
).not.toBeInViewport();
|
).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();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -132,142 +132,6 @@ test.describe('Add Mail Domains', () => {
|
|||||||
).toBeVisible();
|
).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 }) => {
|
test('checks 404 on mail-domains/[slug] page', async ({ page }) => {
|
||||||
await page.goto('/mail-domains/unknown-domain');
|
await page.goto('/mail-domains/unknown-domain');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user