🚸(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

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