richie-education 2.15.1 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/.storybook/main.js +17 -0
  2. package/.storybook/preview-body.html +1 -0
  3. package/.storybook/preview-head.html +1 -0
  4. package/.storybook/preview.tsx +38 -0
  5. package/jest/setup.ts +17 -0
  6. package/js/components/AddressesManagement/AddressForm.spec.tsx +191 -0
  7. package/js/components/AddressesManagement/AddressForm.tsx +29 -1
  8. package/js/components/AddressesManagement/index.spec.tsx +42 -182
  9. package/js/components/AddressesManagement/validationSchema.spec.ts +147 -0
  10. package/js/components/AddressesManagement/validationSchema.ts +51 -0
  11. package/js/components/Banner/index.stories.tsx +32 -0
  12. package/js/components/Form/CheckboxField.stories.tsx +13 -0
  13. package/js/components/Form/Field.stories.tsx +16 -0
  14. package/js/components/Form/Inputs.tsx +3 -3
  15. package/js/components/Form/RadioField.stories.tsx +18 -0
  16. package/js/components/Form/SelectField.stories.tsx +26 -0
  17. package/js/components/Form/TextAreaField.stories.tsx +13 -0
  18. package/js/components/Form/TextField.stories.tsx +13 -0
  19. package/js/components/Icon/index.stories.tsx +11 -0
  20. package/js/components/Modal/index.stories.tsx +42 -0
  21. package/js/components/SaleTunnelStepPayment/index.spec.tsx +1 -1
  22. package/js/components/SaleTunnelStepValidation/index.spec.tsx +1 -1
  23. package/js/components/Search/FiltersPaneCloseButton.tsx +49 -0
  24. package/js/components/Search/index.spec.tsx +57 -25
  25. package/js/components/Search/index.tsx +60 -49
  26. package/js/components/SearchFiltersPane/index.tsx +4 -1
  27. package/js/components/Spinner/index.stories.tsx +26 -0
  28. package/package.json +12 -2
  29. package/scss/components/templates/search/_search.scss +17 -0
  30. package/scss/objects/_form.scss +26 -21
  31. package/stories/Introduction.stories.mdx +13 -0
@@ -0,0 +1,17 @@
1
+ module.exports = {
2
+ "stories": [
3
+ "../stories/**/*.stories.mdx",
4
+ "../stories/**/*.stories.@(js|jsx|ts|tsx)",
5
+ "../js/**/*.stories.@(js|jsx|ts|tsx)",
6
+ ],
7
+ "addons": [
8
+ "@storybook/addon-links",
9
+ "@storybook/addon-essentials",
10
+ "@storybook/addon-interactions"
11
+ ],
12
+ "framework": "@storybook/react",
13
+ "core": {
14
+ "builder": "@storybook/builder-webpack5"
15
+ },
16
+ staticDirs: ['../../richie/static', '../../richie/apps/core/templates/richie'],
17
+ }
@@ -0,0 +1 @@
1
+ <div id="modal-exclude" style="min-height: 0;"></div>
@@ -0,0 +1 @@
1
+ <link rel="stylesheet" type="text/css" href="/richie/css/main.css">
@@ -0,0 +1,38 @@
1
+ import { useState } from "react";
2
+ import { useAsyncEffect } from "../js/utils/useAsyncEffect";
3
+
4
+ export const parameters = {
5
+ actions: {argTypesRegex: "^on[A-Z].*"},
6
+ controls: {
7
+ matchers: {
8
+ color: /(background|color)$/i,
9
+ date: /Date$/,
10
+ },
11
+ },
12
+ }
13
+
14
+
15
+ const IconsWrapper = props => {
16
+ const [symbols, setSymbols] = useState('');
17
+
18
+ useAsyncEffect(async () => {
19
+ const response = await fetch('/icons.html');
20
+ const body = await response.text();
21
+ setSymbols(body);
22
+ }, []);
23
+
24
+ return (
25
+ <div>
26
+ <div dangerouslySetInnerHTML={{__html: symbols}}/>
27
+ {props.children}
28
+ </div>
29
+ );
30
+ };
31
+
32
+ export const decorators = [
33
+ (Story) => (
34
+ <IconsWrapper>
35
+ <Story/>
36
+ </IconsWrapper>
37
+ ),
38
+ ];
package/jest/setup.ts CHANGED
@@ -4,6 +4,23 @@ import '@testing-library/jest-dom/extend-expect';
4
4
  import { setLogger } from 'react-query';
5
5
  import { noop } from 'utils';
6
6
 
7
+ /*
8
+ * A little trick to prevent so package to be reset when using `jest.resetModules()`.
9
+ * https://github.com/facebook/jest/issues/8987#issuecomment-584898030
10
+ */
11
+ const RESET_MODULE_EXCEPTIONS = ['react', 'react-intl'];
12
+
13
+ const mockActualRegistry: Record<PropertyKey, any> = {};
14
+
15
+ RESET_MODULE_EXCEPTIONS.forEach((moduleName) => {
16
+ jest.doMock(moduleName, () => {
17
+ if (!mockActualRegistry[moduleName]) {
18
+ mockActualRegistry[moduleName] = jest.requireActual(moduleName);
19
+ }
20
+ return mockActualRegistry[moduleName];
21
+ });
22
+ });
23
+
7
24
  /* Prevent log error during tests */
8
25
  setLogger({
9
26
  // eslint-disable-next-line no-console
@@ -0,0 +1,191 @@
1
+ import { act } from '@testing-library/react-hooks';
2
+ import { fireEvent, getByText, render, screen } from '@testing-library/react';
3
+ import * as mockFactories from 'utils/test/factories';
4
+ import { AddressFactory } from 'utils/test/factories';
5
+ import countries from 'i18n-iso-countries';
6
+ import { IntlProvider } from 'react-intl';
7
+ import { Address } from 'types/Joanie';
8
+ import { ErrorKeys } from './validationSchema';
9
+ import AddressForm from './AddressForm';
10
+
11
+ jest.mock('hooks/useAddresses', () => ({
12
+ useAddresses: () => ({
13
+ states: {
14
+ creating: false,
15
+ updating: false,
16
+ },
17
+ }),
18
+ }));
19
+
20
+ describe('AddressForm', () => {
21
+ const handleReset = jest.fn();
22
+ const onSubmit = jest.fn();
23
+
24
+ beforeEach(() => {
25
+ jest.resetAllMocks();
26
+ jest.resetModules();
27
+ });
28
+
29
+ it('renders a button with label "Use this address" when no address is provided', () => {
30
+ render(
31
+ <IntlProvider locale="en">
32
+ <AddressForm handleReset={handleReset} onSubmit={onSubmit} />
33
+ </IntlProvider>,
34
+ );
35
+
36
+ screen.getByRole('button', { name: 'Use this address' });
37
+ expect(screen.queryByRole('button', { name: 'Cancel' })).toBeNull();
38
+ });
39
+
40
+ it('renders a button with label "Use this address" and a cancel button when no address is provided', async () => {
41
+ const address: Address = AddressFactory.generate();
42
+ render(
43
+ <IntlProvider locale="en">
44
+ <AddressForm handleReset={handleReset} onSubmit={onSubmit} address={address} />
45
+ </IntlProvider>,
46
+ );
47
+
48
+ screen.getByRole('button', { name: 'Update this address' });
49
+
50
+ const $button = screen.getByRole('button', { name: 'Cancel' });
51
+ await act(async () => {
52
+ fireEvent.click($button);
53
+ });
54
+ expect(handleReset).toHaveBeenCalledTimes(1);
55
+ });
56
+
57
+ it('renders an error message when a value in the form is invalid', async () => {
58
+ render(
59
+ <IntlProvider locale="en">
60
+ <AddressForm handleReset={handleReset} onSubmit={onSubmit} />
61
+ </IntlProvider>,
62
+ );
63
+
64
+ screen.getByRole('form');
65
+ const $titleInput = screen.getByRole('textbox', { name: 'Address title' });
66
+ const $firstnameInput = screen.getByRole('textbox', { name: "Recipient's first name" });
67
+ const $lastnameInput = screen.getByRole('textbox', { name: "Recipient's last name" });
68
+ const $addressInput = screen.getByRole('textbox', { name: 'Address' });
69
+ const $cityInput = screen.getByRole('textbox', { name: 'City' });
70
+ const $postcodeInput = screen.getByRole('textbox', { name: 'Postcode' });
71
+ const $countryInput = screen.getByRole('combobox', { name: 'Country' });
72
+ const $submitButton = screen.getByRole('button', {
73
+ name: 'Use this address',
74
+ }) as HTMLButtonElement;
75
+
76
+ // - Until form is not fulfill, submit button should be disabled
77
+ expect($submitButton.disabled).toBe(true);
78
+
79
+ // - User fulfills address fields
80
+ const address = mockFactories.AddressFactory.generate();
81
+
82
+ await act(async () => {
83
+ fireEvent.input($titleInput, { target: { value: address.title } });
84
+ fireEvent.change($firstnameInput, { target: { value: address.first_name } });
85
+ fireEvent.change($lastnameInput, { target: { value: address.last_name } });
86
+ fireEvent.change($addressInput, { target: { value: address.address } });
87
+ fireEvent.change($cityInput, { target: { value: address.city } });
88
+ fireEvent.change($postcodeInput, { target: { value: address.postcode } });
89
+ fireEvent.change($countryInput, { target: { value: address.country } });
90
+ // - As form validation is triggered on blur, we need to trigger this event in
91
+ // order to update form state.
92
+ fireEvent.blur($countryInput);
93
+ });
94
+
95
+ // Once the form has been fulfilled properly, submit button should be enabled.
96
+ expect($submitButton.disabled).toBe(false);
97
+
98
+ // Before submitting, we change field values to corrupt the form data
99
+ await act(async () => {
100
+ fireEvent.input($titleInput, { target: { value: 'a' } });
101
+ fireEvent.input($firstnameInput, { target: { value: '' } });
102
+ fireEvent.change($countryInput, { target: { value: '-' } });
103
+ fireEvent.click($submitButton);
104
+ });
105
+
106
+ expect(onSubmit).not.toHaveBeenCalled();
107
+
108
+ // Error messages should have been displayed.
109
+ // Title field should have a message saying that the value is too short.
110
+ getByText($titleInput.closest('.form-field')!, 'The minimum length is 2 chars.');
111
+ // Firstname field should have a message saying that the value is required.
112
+ getByText($firstnameInput.closest('.form-field')!, 'This field is required.');
113
+ // Country field should have a message saying that the value is not valid.
114
+ getByText(
115
+ $countryInput.closest('.form-field')!,
116
+ `You must select a value within: ${Object.keys(countries.getAlpha2Codes()).join(', ')}.`,
117
+ );
118
+ });
119
+
120
+ it('renders default error message when error message does not exist', async () => {
121
+ jest.doMock('./validationSchema', () => ({
122
+ __esModule: true,
123
+ ...jest.requireActual('./validationSchema'),
124
+ errorMessages: {
125
+ [ErrorKeys.MIXED_INVALID]: {
126
+ id: 'components.AddressesManagement.validationSchema.mixedInvalid',
127
+ defaultMessage: 'This field is invalid.',
128
+ description: 'Error message displayed when a field value is invalid.',
129
+ },
130
+ },
131
+ }));
132
+
133
+ // Import locally to get module with mocked error messages.
134
+ const Form = jest.requireActual('./AddressForm').default;
135
+
136
+ render(
137
+ <IntlProvider locale="en">
138
+ <Form handleReset={handleReset} onSubmit={onSubmit} />
139
+ </IntlProvider>,
140
+ );
141
+
142
+ screen.getByRole('form');
143
+ const $titleInput = screen.getByRole('textbox', { name: 'Address title' });
144
+ const $firstnameInput = screen.getByRole('textbox', { name: "Recipient's first name" });
145
+ const $lastnameInput = screen.getByRole('textbox', { name: "Recipient's last name" });
146
+ const $addressInput = screen.getByRole('textbox', { name: 'Address' });
147
+ const $cityInput = screen.getByRole('textbox', { name: 'City' });
148
+ const $postcodeInput = screen.getByRole('textbox', { name: 'Postcode' });
149
+ const $countryInput = screen.getByRole('combobox', { name: 'Country' });
150
+ const $submitButton = screen.getByRole('button', {
151
+ name: 'Use this address',
152
+ }) as HTMLButtonElement;
153
+
154
+ // - Until form is not fulfill, submit button should be disabled
155
+ expect($submitButton.disabled).toBe(true);
156
+
157
+ // - User fulfills address fields
158
+ const address = mockFactories.AddressFactory.generate();
159
+
160
+ await act(async () => {
161
+ fireEvent.input($titleInput, { target: { value: address.title } });
162
+ fireEvent.change($firstnameInput, { target: { value: address.first_name } });
163
+ fireEvent.change($lastnameInput, { target: { value: address.last_name } });
164
+ fireEvent.change($addressInput, { target: { value: address.address } });
165
+ fireEvent.change($cityInput, { target: { value: address.city } });
166
+ fireEvent.change($postcodeInput, { target: { value: address.postcode } });
167
+ fireEvent.change($countryInput, { target: { value: address.country } });
168
+ // - As form validation is triggered on blur, we need to trigger this event in
169
+ // order to update form state.
170
+ fireEvent.blur($countryInput);
171
+ });
172
+
173
+ // Once the form has been fulfilled properly, submit button should be enabled.
174
+ expect($submitButton.disabled).toBe(false);
175
+
176
+ // Before submitting, we change field values to corrupt the form data
177
+ await act(async () => {
178
+ fireEvent.input($titleInput, { target: { value: 'a' } });
179
+ fireEvent.input($firstnameInput, { target: { value: '' } });
180
+ fireEvent.change($countryInput, { target: { value: '-' } });
181
+ fireEvent.click($submitButton);
182
+ });
183
+
184
+ expect(onSubmit).not.toHaveBeenCalled();
185
+
186
+ // Default error messages should have been displayed.
187
+ getByText($titleInput.closest('.form-field')!, 'This field is invalid.');
188
+ getByText($firstnameInput.closest('.form-field')!, 'This field is invalid.');
189
+ getByText($countryInput.closest('.form-field')!, `This field is invalid.`);
190
+ });
191
+ });
@@ -7,7 +7,8 @@ import { messages } from 'components/AddressesManagement/index';
7
7
  import { CheckboxField, SelectField, TextField } from 'components/Form';
8
8
  import { useAddresses } from 'hooks/useAddresses';
9
9
  import type { Address } from 'types/Joanie';
10
- import validationSchema from './validationSchema';
10
+ import { Maybe } from 'types/utils';
11
+ import validationSchema, { ErrorKeys, errorMessages } from './validationSchema';
11
12
 
12
13
  export type AddressFormValues = Omit<Address, 'id' | 'is_main'> & { save: boolean };
13
14
 
@@ -41,6 +42,25 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
41
42
  const [languageCode] = intl.locale.split('-');
42
43
  const countryList = countries.getNames(languageCode);
43
44
 
45
+ const getLocalizedErrorMessage = (
46
+ error: Maybe<
47
+ | string
48
+ | {
49
+ key: ErrorKeys;
50
+ values: Record<PropertyKey, string | number | Array<string | number>>;
51
+ }
52
+ >,
53
+ ) => {
54
+ if (!error) return undefined;
55
+
56
+ if (typeof error === 'string' || errorMessages[error.key] === undefined) {
57
+ // If the error has not been translated we return a default error message.
58
+ return intl.formatMessage(errorMessages[ErrorKeys.MIXED_INVALID]);
59
+ }
60
+
61
+ return intl.formatMessage(errorMessages[error.key], error.values);
62
+ };
63
+
44
64
  /**
45
65
  * Prevent form to be submitted and clear `editedAddress` state.
46
66
  */
@@ -62,6 +82,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
62
82
  id="title"
63
83
  label={intl.formatMessage(messages.titleInputLabel)}
64
84
  error={!!formState.errors.title}
85
+ message={getLocalizedErrorMessage(formState.errors.title?.message)}
65
86
  {...register('title')}
66
87
  />
67
88
  <div className="form-group">
@@ -71,6 +92,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
71
92
  id="first_name"
72
93
  label={intl.formatMessage(messages.first_nameInputLabel)}
73
94
  error={!!formState.errors.first_name}
95
+ message={getLocalizedErrorMessage(formState.errors.first_name?.message)}
74
96
  {...register('first_name')}
75
97
  />
76
98
  <TextField
@@ -79,6 +101,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
79
101
  id="last_name"
80
102
  label={intl.formatMessage(messages.last_nameInputLabel)}
81
103
  error={!!formState.errors.last_name}
104
+ message={getLocalizedErrorMessage(formState.errors.last_name?.message)}
82
105
  {...register('last_name')}
83
106
  />
84
107
  </div>
@@ -88,6 +111,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
88
111
  id="address"
89
112
  label={intl.formatMessage(messages.addressInputLabel)}
90
113
  error={!!formState.errors.address}
114
+ message={getLocalizedErrorMessage(formState.errors.address?.message)}
91
115
  {...register('address')}
92
116
  />
93
117
  <div className="form-group">
@@ -97,6 +121,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
97
121
  id="postcode"
98
122
  label={intl.formatMessage(messages.postcodeInputLabel)}
99
123
  error={!!formState.errors.postcode}
124
+ message={getLocalizedErrorMessage(formState.errors.postcode?.message)}
100
125
  {...register('postcode')}
101
126
  />
102
127
  <TextField
@@ -105,6 +130,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
105
130
  id="city"
106
131
  label={intl.formatMessage(messages.cityInputLabel)}
107
132
  error={!!formState.errors.city}
133
+ message={getLocalizedErrorMessage(formState.errors.city?.message)}
108
134
  {...register('city')}
109
135
  />
110
136
  </div>
@@ -114,6 +140,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
114
140
  id="country"
115
141
  label={intl.formatMessage(messages.countryInputLabel)}
116
142
  error={!!formState.errors.country}
143
+ message={getLocalizedErrorMessage(formState.errors.country?.message)}
117
144
  {...register('country', { value: address?.country, required: true })}
118
145
  >
119
146
  <option disabled value="-">
@@ -132,6 +159,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
132
159
  id="save"
133
160
  label={intl.formatMessage(messages.saveInputLabel)}
134
161
  error={!!formState.errors?.save}
162
+ message={getLocalizedErrorMessage(formState.errors.save?.message)}
135
163
  {...register('save')}
136
164
  />
137
165
  ) : null}
@@ -1,20 +1,16 @@
1
1
  /**
2
2
  * Test suite for AddressesManagement component
3
3
  */
4
- import { yupResolver } from '@hookform/resolvers/yup';
5
4
  import { fireEvent, render, screen } from '@testing-library/react';
6
- import { act, renderHook } from '@testing-library/react-hooks';
7
- import faker from 'faker';
5
+ import { act } from '@testing-library/react-hooks';
8
6
  import fetchMock from 'fetch-mock';
9
7
  import * as mockFactories from 'utils/test/factories';
10
8
  import { IntlProvider } from 'react-intl';
11
9
  import { QueryClientProvider } from 'react-query';
12
- import { useForm } from 'react-hook-form';
13
10
  import { SessionProvider } from 'data/SessionProvider';
14
11
  import { REACT_QUERY_SETTINGS, RICHIE_USER_TOKEN } from 'settings';
15
12
  import type * as Joanie from 'types/Joanie';
16
13
  import createQueryClient from 'utils/react-query/createQueryClient';
17
- import validationSchema from './validationSchema';
18
14
  import AddressesManagement from '.';
19
15
 
20
16
  jest.mock('utils/context', () => ({
@@ -31,142 +27,6 @@ jest.mock('utils/indirection/window', () => ({
31
27
  confirm: jest.fn(() => true),
32
28
  }));
33
29
 
34
- describe('validationSchema', () => {
35
- // Creation and Update form validation relies on a schema resolves by Yup.
36
- it('should not have error if values are valid', async () => {
37
- const defaultValues = {
38
- address: faker.address.streetAddress(),
39
- city: faker.address.city(),
40
- country: faker.address.countryCode(),
41
- first_name: faker.name.firstName(),
42
- last_name: faker.name.lastName(),
43
- postcode: faker.address.zipCode(),
44
- title: faker.random.word(),
45
- save: false,
46
- };
47
-
48
- const { result } = renderHook(() =>
49
- useForm({
50
- defaultValues,
51
- resolver: yupResolver(validationSchema),
52
- }),
53
- );
54
-
55
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
56
- result.current.formState.errors;
57
- result.current.register('address');
58
- result.current.register('city');
59
- result.current.register('country');
60
- result.current.register('first_name');
61
- result.current.register('last_name');
62
- result.current.register('postcode');
63
- result.current.register('title');
64
- result.current.register('save');
65
-
66
- await act(async () => {
67
- result.current.trigger();
68
- });
69
-
70
- const { formState } = result.current;
71
-
72
- expect(formState.errors.address).not.toBeDefined();
73
- expect(formState.errors.city).not.toBeDefined();
74
- expect(formState.errors.country).not.toBeDefined();
75
- expect(formState.errors.first_name).not.toBeDefined();
76
- expect(formState.errors.last_name).not.toBeDefined();
77
- expect(formState.errors.postcode).not.toBeDefined();
78
- expect(formState.errors.title).not.toBeDefined();
79
- expect(formState.errors.save).not.toBeDefined();
80
- expect(formState.isValid).toBe(true);
81
- });
82
-
83
- it('should have an error if values are invalid', async () => {
84
- const { result } = renderHook(() =>
85
- useForm({
86
- resolver: yupResolver(validationSchema),
87
- }),
88
- );
89
-
90
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
91
- result.current.formState.errors;
92
- result.current.register('address');
93
- result.current.register('city');
94
- result.current.register('country');
95
- result.current.register('first_name');
96
- result.current.register('last_name');
97
- result.current.register('postcode');
98
- result.current.register('title');
99
- result.current.register('save');
100
-
101
- // - Trigger form validation with empty values
102
- await act(async () => {
103
- result.current.trigger();
104
- });
105
-
106
- let { formState } = result.current;
107
- expect(formState.errors.address?.type).toEqual('required');
108
- expect(formState.errors.address?.message).toEqual('address is a required field');
109
- expect(formState.errors.city?.type).toEqual('required');
110
- expect(formState.errors.city?.message).toEqual('city is a required field');
111
- expect(formState.errors.country?.type).toEqual('required');
112
- expect(formState.errors.country?.message).toEqual('country is a required field');
113
- expect(formState.errors.first_name?.type).toEqual('required');
114
- expect(formState.errors.first_name?.message).toEqual('first_name is a required field');
115
- expect(formState.errors.last_name?.type).toEqual('required');
116
- expect(formState.errors.last_name?.message).toEqual('last_name is a required field');
117
- expect(formState.errors.postcode?.type).toEqual('required');
118
- expect(formState.errors.postcode?.message).toEqual('postcode is a required field');
119
- expect(formState.errors.title?.type).toEqual('required');
120
- expect(formState.errors.title?.message).toEqual('title is a required field');
121
- expect(formState.errors.save).not.toBeDefined();
122
- expect(formState.isValid).toBe(false);
123
-
124
- // - Set values for all field but with a wrong one for country field
125
- await act(async () => {
126
- result.current.setValue('address', faker.address.streetAddress());
127
- result.current.setValue('city', faker.address.city());
128
- // set country value with an invalid country code
129
- result.current.setValue('country', 'AA');
130
- result.current.setValue('first_name', faker.name.firstName());
131
- result.current.setValue('last_name', faker.name.lastName());
132
- result.current.setValue('postcode', faker.address.zipCode());
133
- result.current.setValue('title', faker.random.word());
134
- result.current.trigger();
135
- });
136
-
137
- formState = result.current.formState;
138
- expect(formState.errors.address).not.toBeDefined();
139
- expect(formState.errors.city).not.toBeDefined();
140
- expect(formState.errors.country?.type).toEqual('oneOf');
141
- expect(formState.errors.country?.message).toContain(
142
- 'country must be one of the following values:',
143
- );
144
- expect(formState.errors.first_name).not.toBeDefined();
145
- expect(formState.errors.last_name).not.toBeDefined();
146
- expect(formState.errors.postcode).not.toBeDefined();
147
- expect(formState.errors.title).not.toBeDefined();
148
- expect(formState.errors.save).not.toBeDefined();
149
- expect(formState.isValid).toBe(false);
150
-
151
- // - Set country value with a valid country code
152
- await act(async () => {
153
- result.current.setValue('country', 'FR');
154
- result.current.trigger();
155
- });
156
-
157
- formState = result.current.formState;
158
- expect(formState.errors.address).not.toBeDefined();
159
- expect(formState.errors.city).not.toBeDefined();
160
- expect(formState.errors.country).not.toBeDefined();
161
- expect(formState.errors.first_name).not.toBeDefined();
162
- expect(formState.errors.last_name).not.toBeDefined();
163
- expect(formState.errors.postcode).not.toBeDefined();
164
- expect(formState.errors.title).not.toBeDefined();
165
- expect(formState.errors.save).not.toBeDefined();
166
- expect(formState.isValid).toBe(true);
167
- });
168
- });
169
-
170
30
  describe('AddressesManagement', () => {
171
31
  const initializeUser = () => {
172
32
  const user = mockFactories.FonzieUserFactory.generate();
@@ -221,6 +81,46 @@ describe('AddressesManagement', () => {
221
81
  expect(handleClose).toHaveBeenCalledTimes(1);
222
82
  });
223
83
 
84
+ it("renders the user's addresses", async () => {
85
+ initializeUser();
86
+ const addresses = mockFactories.AddressFactory.generate(Math.ceil(Math.random() * 5));
87
+ fetchMock.get('https://joanie.endpoint/api/addresses/', addresses);
88
+
89
+ let container: HTMLElement;
90
+
91
+ await act(async () => {
92
+ ({ container } = render(
93
+ <QueryClientProvider client={createQueryClient({ persistor: true })}>
94
+ <IntlProvider locale="en">
95
+ <SessionProvider>
96
+ <AddressesManagement handleClose={handleClose} selectAddress={selectAddress} />
97
+ </SessionProvider>
98
+ </IntlProvider>
99
+ </QueryClientProvider>,
100
+ ));
101
+ });
102
+
103
+ // All user's addresses should be displayed
104
+ const $addresses = container!.querySelectorAll('.registered-addresses-item');
105
+ expect($addresses).toHaveLength(addresses.length);
106
+
107
+ addresses.forEach((address: Joanie.Address) => {
108
+ const $address = screen.getByTestId(`address-${address.id}-title`);
109
+ expect($address.textContent).toEqual(address.title);
110
+ });
111
+
112
+ // - User selects one of its existing address
113
+ const address = addresses[0];
114
+ const $selectButton = screen.getByRole('button', {
115
+ name: `Select "${address.title}" address`,
116
+ exact: true,
117
+ });
118
+ await act(async () => {
119
+ fireEvent.click($selectButton);
120
+ });
121
+ expect(selectAddress).toHaveBeenNthCalledWith(1, address);
122
+ });
123
+
224
124
  it('renders a form to create an address', async () => {
225
125
  initializeUser();
226
126
  fetchMock.get('https://joanie.endpoint/api/addresses/', []);
@@ -307,47 +207,7 @@ describe('AddressesManagement', () => {
307
207
  });
308
208
  });
309
209
 
310
- it("renders the user's addresses", async () => {
311
- initializeUser();
312
- const addresses = mockFactories.AddressFactory.generate(Math.ceil(Math.random() * 5));
313
- fetchMock.get('https://joanie.endpoint/api/addresses/', addresses);
314
-
315
- let container: HTMLElement;
316
-
317
- await act(async () => {
318
- ({ container } = render(
319
- <QueryClientProvider client={createQueryClient({ persistor: true })}>
320
- <IntlProvider locale="en">
321
- <SessionProvider>
322
- <AddressesManagement handleClose={handleClose} selectAddress={selectAddress} />
323
- </SessionProvider>
324
- </IntlProvider>
325
- </QueryClientProvider>,
326
- ));
327
- });
328
-
329
- // All user's addresses should be displayed
330
- const $addresses = container!.querySelectorAll('.registered-addresses-item');
331
- expect($addresses).toHaveLength(addresses.length);
332
-
333
- addresses.forEach((address: Joanie.Address) => {
334
- const $address = screen.getByTestId(`address-${address.id}-title`);
335
- expect($address.textContent).toEqual(address.title);
336
- });
337
-
338
- // - User selects one of its existing address
339
- const address = addresses[0];
340
- const $selectButton = screen.getByRole('button', {
341
- name: `Select "${address.title}" address`,
342
- exact: true,
343
- });
344
- await act(async () => {
345
- fireEvent.click($selectButton);
346
- });
347
- expect(selectAddress).toHaveBeenNthCalledWith(1, address);
348
- });
349
-
350
- it('renders an updated form when user selects an address to edit', async () => {
210
+ it('renders a form to edit an address when user selects an address to edit', async () => {
351
211
  initializeUser();
352
212
  const address = mockFactories.AddressFactory.generate();
353
213
  fetchMock.get('https://joanie.endpoint/api/addresses/', [address]);