Skip to content

Commit 26bf5b9

Browse files
authored
Merge pull request #1415 from AppQuality/UN-1727-LOGIN-403-non-gestita
UN-1727 login error handling
2 parents 670597e + 62e2f01 commit 26bf5b9

File tree

4 files changed

+398
-7
lines changed

4 files changed

+398
-7
lines changed

src/pages/LoginPage/index.tsx

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1+
import {
2+
LoginForm,
3+
Logo,
4+
Notification,
5+
useToast,
6+
} from '@appquality/unguess-design-system';
7+
import { FormikHelpers } from 'formik';
18
import { useEffect, useState } from 'react';
2-
import { LoginForm, Logo } from '@appquality/unguess-design-system';
39
import { useTranslation } from 'react-i18next';
4-
import WPAPI from 'src/common/wpapi';
5-
import { FormikHelpers } from 'formik';
6-
import styled from 'styled-components';
710
import { useLocation, useNavigate } from 'react-router-dom';
11+
import WPAPI from 'src/common/wpapi';
812
import { useGetUsersMeQuery } from 'src/features/api';
13+
import styled from 'styled-components';
914

1015
import { Track } from 'src/common/Track';
1116
import { LoginFormFields } from './type';
@@ -42,6 +47,7 @@ const LoginPage = () => {
4247
const { isSuccess } = useGetUsersMeQuery();
4348
const navigate = useNavigate();
4449
const { state: locationState } = useLocation();
50+
const { addToast } = useToast();
4551

4652
const from = (locationState as NavigationState)?.from || '/';
4753

@@ -51,12 +57,36 @@ const LoginPage = () => {
5157
}
5258
}, [navigate, isSuccess]);
5359

60+
const showGenericErrorToast = (title?: string) => {
61+
addToast(
62+
({ close }) => (
63+
<Notification
64+
onClose={close}
65+
type="error"
66+
message={`${title}${t('__TOAST_GENERIC_ERROR_MESSAGE')}`}
67+
closeText={t('__TOAST_CLOSE_TEXT')}
68+
isPrimary
69+
/>
70+
),
71+
{ placement: 'top' }
72+
);
73+
};
74+
5475
const loginUser = async (
5576
values: LoginFormFields,
5677
{ setSubmitting, setStatus }: FormikHelpers<LoginFormFields>
5778
) => {
79+
let nonce;
80+
try {
81+
nonce = await WPAPI.getNonce();
82+
} catch (err: any) {
83+
if (err?.status !== 200) {
84+
showGenericErrorToast('Get Nonce: ');
85+
setSubmitting(false);
86+
return;
87+
}
88+
}
5889
try {
59-
const nonce = await WPAPI.getNonce();
6090
await WPAPI.login({
6191
username: values.email,
6292
password: values.password,
@@ -65,14 +95,19 @@ const LoginPage = () => {
6595

6696
setCta(`${t('__LOGIN_FORM_CTA_REDIRECT_STATE')}`);
6797
document.location.href = from || '/';
68-
} catch (e: unknown) {
98+
} catch (e: any) {
99+
if (e?.status !== 200) {
100+
showGenericErrorToast('Login: ');
101+
setSubmitting(false);
102+
return;
103+
}
69104
const { message } = e as Error;
70105
const error = JSON.parse(message);
71106

72107
if (error.type === 'invalid') {
73108
setStatus({ message: `${t('__LOGIN_FORM_FAILED_INVALID')}` });
74109
} else {
75-
document.location.href = from || '/';
110+
showGenericErrorToast();
76111
}
77112
}
78113

tests/e2e/login.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { test, expect } from '../fixtures/app';
2+
import { Login } from '../fixtures/pages/Login';
3+
4+
test.describe('Login page', () => {
5+
let login: Login;
6+
7+
test.beforeEach(async ({ page }) => {
8+
login = new Login(page);
9+
10+
await login.notLoggedIn();
11+
await login.open();
12+
});
13+
test('shows a generic error toast whenever wpapi getnonce responds with a 403', async ({
14+
i18n,
15+
}) => {
16+
await login.mockGetNonce403();
17+
await login.fillValidInputs();
18+
await login.submit();
19+
await expect(login.elements().errorToast()).toContainText(
20+
`Get Nonce: ${i18n.t('__TOAST_GENERIC_ERROR_MESSAGE')}`
21+
);
22+
});
23+
test('shows a generic error toast whenever wpapi login responds with a 403', async ({
24+
i18n,
25+
}) => {
26+
await login.mockGetNonce200();
27+
await login.mockLogin403();
28+
await login.fillValidInputs();
29+
await login.submit();
30+
await expect(login.elements().errorToast()).toContainText(
31+
`Login: ${i18n.t('__TOAST_GENERIC_ERROR_MESSAGE')}`
32+
);
33+
});
34+
});

tests/e2e/login/index.spec.ts

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import { test, expect } from '../../fixtures/app';
2+
import { Join } from '../../fixtures/pages/Join';
3+
import { Step1 } from '../../fixtures/pages/Join/Step1';
4+
import { Step2 } from '../../fixtures/pages/Join/Step2';
5+
import { Step3 } from '../../fixtures/pages/Join/Step3';
6+
7+
test.describe('The Join page first step - case new user', () => {
8+
let join: Join;
9+
let step1: Step1;
10+
let step2: Step2;
11+
12+
test.beforeEach(async ({ page }) => {
13+
join = new Join(page);
14+
await join.notLoggedIn();
15+
step1 = new Step1(page);
16+
step2 = new Step2(page);
17+
await join.open();
18+
});
19+
20+
test('display a form with user email and password input, a CTA to go to the next step', async () => {
21+
await expect(step1.elements().container()).toBeVisible();
22+
await expect(step1.elements().emailInput()).toBeVisible();
23+
await expect(step1.elements().passwordInput()).toBeVisible();
24+
await expect(step1.elements().buttonGoToStep2()).toBeVisible();
25+
});
26+
27+
test('the password input check if the password is strong enough', async ({
28+
page,
29+
i18n,
30+
}) => {
31+
await step1.elements().buttonGoToStep2().click();
32+
await expect(step1.elements().passwordError()).toHaveText(
33+
i18n.t('SIGNUP_FORM_PASSWORD_IS_A_REQUIRED_FIELD')
34+
);
35+
36+
await expect(step1.elements().passwordRequirements()).toBeVisible();
37+
38+
await step1.fillPassword('weak');
39+
await expect(
40+
page.getByText(
41+
i18n.t('SIGNUP_FORM_PASSWORD_MUST_BE_AT_LEAST_6_CHARACTER_LONG')
42+
)
43+
).toBeVisible();
44+
45+
await step1.fillPassword('weakpassword');
46+
await expect(
47+
page.getByText(
48+
i18n.t('SIGNUP_FORM_PASSWORD_MUST_CONTAIN_AT_LEAST_A_NUMBER')
49+
)
50+
).toBeVisible();
51+
52+
await step1.fillPassword('weakpassword123');
53+
await expect(
54+
page.getByText(
55+
i18n.t('SIGNUP_FORM_PASSWORD_MUST_CONTAIN_AT_LEAST_AN_UPPERCASE_LETTER')
56+
)
57+
).toBeVisible();
58+
59+
await step1.fillPassword('WEAKPASSWORD123');
60+
await expect(
61+
page.getByText(
62+
i18n.t('SIGNUP_FORM_PASSWORD_MUST_CONTAIN_AT_LEAST_A_LOWERCASE_LETTER')
63+
)
64+
).toBeVisible();
65+
66+
await step1.fillValidPassword();
67+
await expect(page.getByTestId('message-error-password')).not.toBeVisible();
68+
});
69+
70+
test('the email input check if the email is valid', async ({
71+
page,
72+
i18n,
73+
}) => {
74+
await step1.elements().buttonGoToStep2().click();
75+
await expect(step1.elements().emailError()).toHaveText(
76+
i18n.t('SIGNUP_FORM_EMAIL_IS_REQUIRED')
77+
);
78+
await step1.fillEmail('invalid-email');
79+
await expect(
80+
page.getByText(i18n.t('SIGNUP_FORM_EMAIL_MUST_BE_A_VALID_EMAIL'))
81+
).toBeVisible();
82+
83+
await step1.fillRegisteredEmail();
84+
await expect(
85+
page.getByText(i18n.t('SIGNUP_FORM_EMAIL_ALREADY_TAKEN'))
86+
).toBeVisible();
87+
88+
await step1.fillValidEmail();
89+
await expect(page.getByTestId('message-error-email')).not.toBeVisible();
90+
});
91+
92+
test('when the user click the next step cta we validate current inputs and if ok goes to the next step', async () => {
93+
await step1.goToNextStep();
94+
await expect(step1.elements().container()).not.toBeVisible();
95+
await expect(step2.elements().container()).toBeVisible();
96+
});
97+
test('display two links to go to app.unguess and a link to terms and conditions', async () => {});
98+
});
99+
100+
test.describe('The Join page second step', () => {
101+
let join: Join;
102+
let step1: Step1;
103+
let step2: Step2;
104+
let step3: Step3;
105+
106+
test.beforeEach(async ({ page }) => {
107+
join = new Join(page);
108+
await join.notLoggedIn();
109+
step1 = new Step1(page);
110+
step2 = new Step2(page);
111+
step3 = new Step3(page);
112+
113+
await step2.mockGetRoles();
114+
await step2.mockGetCompanySizes();
115+
await join.open();
116+
await step1.goToNextStep();
117+
});
118+
test('display required inputs for name, surname, job role and company size dropdowns populated from api userRole and companySize', async ({
119+
i18n,
120+
}) => {
121+
await expect(step2.elements().nameInput()).toBeVisible();
122+
await expect(step2.elements().surnameInput()).toBeVisible();
123+
await expect(step2.elements().roleSelect()).toBeVisible();
124+
await step2.elements().roleSelect().click();
125+
await expect(step2.elements().roleSelectOptions()).toHaveCount(3);
126+
127+
await expect(step2.elements().companySizeSelect()).toBeVisible();
128+
await step2.elements().companySizeSelect().click();
129+
await expect(step2.elements().companySizeSelectOptions()).toHaveCount(3);
130+
131+
await step2.elements().buttonGoToStep3().click();
132+
133+
await expect(step2.elements().nameError()).toHaveText(
134+
i18n.t('SIGNUP_FORM_NAME_IS_REQUIRED')
135+
);
136+
await expect(step2.elements().surnameError()).toHaveText(
137+
i18n.t('SIGNUP_FORM_SURNAME_IS_REQUIRED')
138+
);
139+
await expect(step2.elements().roleSelectError()).toHaveText(
140+
i18n.t('SIGNUP_FORM_ROLE_IS_REQUIRED')
141+
);
142+
await expect(step2.elements().companySizeSelectError()).toHaveText(
143+
i18n.t('SIGNUP_FORM_COMPANY_SIZE_IS_REQUIRED')
144+
);
145+
146+
await step2.fillValidFields();
147+
await expect(step2.elements().nameError()).not.toBeVisible();
148+
await expect(step2.elements().surnameError()).not.toBeVisible();
149+
await expect(step2.elements().roleSelectError()).not.toBeVisible();
150+
});
151+
test('display back and next navigation, clicking on next validate this step and goes to step 3', async () => {
152+
await expect(step2.elements().buttonBackToStep1()).toBeVisible();
153+
await expect(step2.elements().buttonGoToStep3()).toBeVisible();
154+
await step2.goToNextStep();
155+
await expect(step2.elements().container()).not.toBeVisible();
156+
await expect(step3.elements().container()).toBeVisible();
157+
});
158+
});
159+
160+
test.describe('The Join page third step', () => {
161+
let join: Join;
162+
let step1: Step1;
163+
let step2: Step2;
164+
let step3: Step3;
165+
166+
test.beforeEach(async ({ page }) => {
167+
join = new Join(page);
168+
await join.notLoggedIn();
169+
step1 = new Step1(page);
170+
step2 = new Step2(page);
171+
step3 = new Step3(page);
172+
173+
await join.open();
174+
await join.mockPostNewUser();
175+
await step1.goToNextStep();
176+
await step2.goToNextStep();
177+
});
178+
test('display a required text input for the workspace name and a back button to return to step 2', async () => {
179+
await expect(step3.elements().workspaceInput()).toBeVisible();
180+
await expect(step3.elements().buttonBackToStep2()).toBeVisible();
181+
await step3.elements().buttonBackToStep2().click();
182+
await expect(step3.elements().container()).not.toBeVisible();
183+
await expect(step2.elements().container()).toBeVisible();
184+
});
185+
test('display a submit-button, clicking on submit-button validate the whole form and calls the api post', async ({
186+
page,
187+
i18n,
188+
}) => {
189+
const postPromise = page.waitForResponse(
190+
(response) =>
191+
/\/api\/users/.test(response.url()) &&
192+
response.status() === 200 &&
193+
response.request().method() === 'POST'
194+
);
195+
await step3.elements().buttonSubmit().click();
196+
await expect(step3.elements().workspaceError()).toHaveText(
197+
i18n.t('SIGNUP_FORM_WORKSPACE_IS_REQUIRED')
198+
);
199+
await step3.fillValidWorkspace();
200+
await expect(step3.elements().workspaceError()).not.toBeVisible();
201+
await step3.elements().buttonSubmit().click();
202+
const response = await postPromise;
203+
const data = response.request().postDataJSON();
204+
expect(data).toEqual(
205+
expect.objectContaining({
206+
type: 'new',
207+
email: 'new.user@example.com',
208+
password: 'ValidPassword123',
209+
name: step2.name,
210+
surname: step2.surname,
211+
roleId: step2.roleId,
212+
companySizeId: step2.companySizeId,
213+
workspace: step3.workspace,
214+
})
215+
);
216+
});
217+
});

0 commit comments

Comments
 (0)