test-tp1-ynov-react-kleas17 1.0.1

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.
@@ -0,0 +1,298 @@
1
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import App from './App';
4
+ import * as apiModule from './api';
5
+ import * as validatorModule from './validator';
6
+
7
+ jest.mock('./api');
8
+
9
+ const champs = {
10
+ nom: /^Nom$/i,
11
+ prenom: /^Prénom$/i,
12
+ email: /email/i,
13
+ dateNaissance: /date de naissance/i,
14
+ cp: /code postal/i,
15
+ ville: /ville/i,
16
+ };
17
+
18
+ const userValide = {
19
+ nom: 'Martin',
20
+ prenom: 'Julie',
21
+ email: 'julie.martin@example.com',
22
+ dateNaissance: '1990-01-01',
23
+ cp: '69001',
24
+ ville: 'Lyon',
25
+ };
26
+
27
+ beforeEach(() => {
28
+ jest.restoreAllMocks();
29
+ jest.clearAllMocks();
30
+ window.history.pushState({}, '', '/');
31
+ apiModule.getRegistrations.mockResolvedValue([]);
32
+ apiModule.createRegistration.mockResolvedValue({ id: 1 });
33
+ });
34
+
35
+ async function renderApp() {
36
+ render(<App />);
37
+ await waitFor(() => expect(apiModule.getRegistrations).toHaveBeenCalled());
38
+ }
39
+
40
+ async function allerAuFormulaire() {
41
+ await userEvent.click(screen.getByRole('link', { name: /ajouter un utilisateur/i }));
42
+ }
43
+
44
+ async function remplirFormulaireValide() {
45
+ await userEvent.type(screen.getByLabelText(champs.nom), userValide.nom);
46
+ await userEvent.type(screen.getByLabelText(champs.prenom), userValide.prenom);
47
+ await userEvent.type(screen.getByLabelText(champs.email), userValide.email);
48
+ await userEvent.type(screen.getByLabelText(champs.dateNaissance), userValide.dateNaissance);
49
+ await userEvent.type(screen.getByLabelText(champs.cp), userValide.cp);
50
+ await userEvent.type(screen.getByLabelText(champs.ville), userValide.ville);
51
+ }
52
+
53
+ describe('Navigation SPA et formulaire - integration', () => {
54
+ test('accueil initial: compteur a 0 et liste vide', async () => {
55
+ await renderApp();
56
+
57
+ expect(screen.getByText('0 utilisateur(s) inscrit(s)')).toBeInTheDocument();
58
+ expect(screen.getByText('Aucun utilisateur inscrit pour le moment.')).toBeInTheDocument();
59
+ });
60
+
61
+ test('hydrate la liste depuis l API', async () => {
62
+ apiModule.getRegistrations.mockResolvedValueOnce([userValide]);
63
+
64
+ await renderApp();
65
+
66
+ expect(await screen.findByText('1 utilisateur(s) inscrit(s)')).toBeInTheDocument();
67
+ expect(screen.getByText('Martin Julie')).toBeInTheDocument();
68
+ });
69
+
70
+ test('erreur API au chargement: fallback liste vide', async () => {
71
+ apiModule.getRegistrations.mockRejectedValueOnce(new Error('Network Error'));
72
+
73
+ await renderApp();
74
+
75
+ expect(screen.getByText('0 utilisateur(s) inscrit(s)')).toBeInTheDocument();
76
+ expect(screen.getByText('Aucun utilisateur inscrit pour le moment.')).toBeInTheDocument();
77
+ });
78
+
79
+ test('navigation accueil vers formulaire puis retour accueil', async () => {
80
+ await renderApp();
81
+
82
+ await allerAuFormulaire();
83
+ expect(window.location.pathname).toBe('/register');
84
+ expect(screen.getByText('Formulaire utilisateur')).toBeInTheDocument();
85
+
86
+ await userEvent.click(screen.getByRole('link', { name: /retour à l'accueil/i }));
87
+ expect(window.location.pathname).toBe('/');
88
+ expect(screen.getByText("Bienvenue sur l'application d'inscription")).toBeInTheDocument();
89
+ });
90
+
91
+ test('prenom avec chiffres: erreur et blocage submit', async () => {
92
+ await renderApp();
93
+ await allerAuFormulaire();
94
+
95
+ await userEvent.type(screen.getByLabelText(champs.prenom), 'Julie9');
96
+
97
+ expect(screen.getByText(/Caractères invalides dans le nom/i)).toBeInTheDocument();
98
+ expect(screen.getByRole('button', { name: /soumettre/i })).toBeDisabled();
99
+ });
100
+
101
+ test('ville avec symbole: erreur et blocage submit', async () => {
102
+ await renderApp();
103
+ await allerAuFormulaire();
104
+
105
+ await userEvent.type(screen.getByLabelText(champs.ville), 'Paris!');
106
+
107
+ expect(screen.getByText(/Caractères invalides dans le nom/i)).toBeInTheDocument();
108
+ expect(screen.getByRole('button', { name: /soumettre/i })).toBeDisabled();
109
+ });
110
+
111
+ test('email farfelu (accent/parenthese): erreur visible', async () => {
112
+ await renderApp();
113
+ await allerAuFormulaire();
114
+
115
+ await userEvent.type(screen.getByLabelText(champs.email), 'kleas3.marc@gma)l.com');
116
+
117
+ expect(screen.getByText("Format d'email invalide")).toBeInTheDocument();
118
+ expect(screen.getByRole('button', { name: /soumettre/i })).toBeDisabled();
119
+ });
120
+
121
+ test('date farfelue (0008): erreur visible', async () => {
122
+ await renderApp();
123
+ await allerAuFormulaire();
124
+
125
+ await userEvent.type(screen.getByLabelText(champs.nom), 'Martin');
126
+ await userEvent.type(screen.getByLabelText(champs.prenom), 'Julie');
127
+ await userEvent.type(screen.getByLabelText(champs.email), 'julie.martin2@example.com');
128
+ await userEvent.type(screen.getByLabelText(champs.cp), '69001');
129
+ await userEvent.type(screen.getByLabelText(champs.ville), 'Lyon');
130
+ await userEvent.type(screen.getByLabelText(champs.dateNaissance), '0008-08-08');
131
+
132
+ expect(screen.getByText('Date de naissance invalide')).toBeInTheDocument();
133
+ expect(screen.getByRole('button', { name: /soumettre/i })).toBeDisabled();
134
+ });
135
+
136
+ test('email deja utilise: erreur visible et soumission bloquee', async () => {
137
+ apiModule.getRegistrations.mockResolvedValueOnce([{ ...userValide }]);
138
+ await renderApp();
139
+ await screen.findByText('1 utilisateur(s) inscrit(s)');
140
+ await allerAuFormulaire();
141
+
142
+ await userEvent.type(screen.getByLabelText(champs.nom), 'Durand');
143
+ await userEvent.type(screen.getByLabelText(champs.prenom), 'Luc');
144
+ await userEvent.type(screen.getByLabelText(champs.email), userValide.email);
145
+ await userEvent.type(screen.getByLabelText(champs.dateNaissance), '1992-02-02');
146
+ await userEvent.type(screen.getByLabelText(champs.cp), '75001');
147
+ await userEvent.type(screen.getByLabelText(champs.ville), 'Paris');
148
+
149
+ expect(screen.getByText(/Cet email est .*utilis/i)).toBeInTheDocument();
150
+ expect(screen.getByRole('button', { name: /soumettre/i })).toBeDisabled();
151
+ });
152
+
153
+ test('soumission valide: redirection accueil, compteur et liste mis a jour', async () => {
154
+ await renderApp();
155
+
156
+ await allerAuFormulaire();
157
+ await remplirFormulaireValide();
158
+ await userEvent.click(screen.getByRole('button', { name: /soumettre/i }));
159
+
160
+ await waitFor(() => expect(window.location.pathname).toBe('/'));
161
+ expect(await screen.findByText('1 utilisateur(s) inscrit(s)')).toBeInTheDocument();
162
+ expect(screen.getByText('Martin Julie')).toBeInTheDocument();
163
+ expect(screen.getByRole('status')).toHaveTextContent('Inscription enregistrée');
164
+
165
+ expect(apiModule.createRegistration).toHaveBeenCalledWith(userValide);
166
+ });
167
+
168
+ test('soumission invalide ne declenche pas appel API', async () => {
169
+ await renderApp();
170
+ await allerAuFormulaire();
171
+
172
+ fireEvent.submit(screen.getByRole('button', { name: /soumettre/i }).closest('form'));
173
+ expect(apiModule.createRegistration).not.toHaveBeenCalled();
174
+ });
175
+
176
+ test('echec API a la soumission: reste sur formulaire et affiche erreur', async () => {
177
+ apiModule.createRegistration.mockRejectedValueOnce(new Error('Network Error'));
178
+ await renderApp();
179
+
180
+ await allerAuFormulaire();
181
+ await remplirFormulaireValide();
182
+ await userEvent.click(screen.getByRole('button', { name: /soumettre/i }));
183
+
184
+ await waitFor(() => expect(window.location.pathname).toBe('/register'));
185
+ expect(await screen.findByRole('status')).toHaveTextContent("Erreur lors de l'inscription");
186
+ });
187
+
188
+ test('erreur metier backend 400: affiche le message du serveur', async () => {
189
+ apiModule.createRegistration.mockRejectedValueOnce({
190
+ response: {
191
+ status: 400,
192
+ data: { message: 'Cet email est déjà utilisé (back)' },
193
+ },
194
+ });
195
+
196
+ await renderApp();
197
+ await allerAuFormulaire();
198
+ await remplirFormulaireValide();
199
+ await userEvent.click(screen.getByRole('button', { name: /soumettre/i }));
200
+
201
+ await waitFor(() => expect(window.location.pathname).toBe('/register'));
202
+ expect(await screen.findByRole('status')).toHaveTextContent('Cet email est déjà utilisé (back)');
203
+ });
204
+
205
+ test('erreur metier backend 409: affiche le message du serveur', async () => {
206
+ apiModule.createRegistration.mockRejectedValueOnce({
207
+ response: {
208
+ status: 409,
209
+ data: { message: 'Conflit: email déjà existant (409)' },
210
+ },
211
+ });
212
+
213
+ await renderApp();
214
+ await allerAuFormulaire();
215
+ await remplirFormulaireValide();
216
+ await userEvent.click(screen.getByRole('button', { name: /soumettre/i }));
217
+
218
+ await waitFor(() => expect(window.location.pathname).toBe('/register'));
219
+ expect(await screen.findByRole('status')).toHaveTextContent(
220
+ 'Conflit: email déjà existant (409)'
221
+ );
222
+ });
223
+
224
+ test('erreur serveur 500: affiche une alerte utilisateur resilient', async () => {
225
+ apiModule.createRegistration.mockRejectedValueOnce({
226
+ response: {
227
+ status: 500,
228
+ },
229
+ });
230
+
231
+ await renderApp();
232
+ await allerAuFormulaire();
233
+ await remplirFormulaireValide();
234
+ await userEvent.click(screen.getByRole('button', { name: /soumettre/i }));
235
+
236
+ await waitFor(() => expect(window.location.pathname).toBe('/register'));
237
+ expect(await screen.findByRole('status')).toHaveTextContent(
238
+ 'Serveur indisponible, veuillez réessayer plus tard.'
239
+ );
240
+ });
241
+
242
+ test('erreur technique de validation: fallback affiche', async () => {
243
+ const identitySpy = jest.spyOn(validatorModule, 'validateIdentity').mockImplementation(() => {
244
+ throw new Error('unexpected');
245
+ });
246
+
247
+ await renderApp();
248
+ await allerAuFormulaire();
249
+
250
+ const nomInput = screen.getByLabelText(champs.nom);
251
+ fireEvent.change(nomInput, { target: { value: 'Martin' } });
252
+ fireEvent.blur(nomInput);
253
+
254
+ expect(screen.getByText('Erreur de validation')).toBeInTheDocument();
255
+ identitySpy.mockRestore();
256
+ });
257
+
258
+ test('snapshot de la page accueil', async () => {
259
+ const { asFragment } = render(<App />);
260
+ await waitFor(() => expect(apiModule.getRegistrations).toHaveBeenCalled());
261
+ expect(asFragment()).toMatchSnapshot();
262
+ });
263
+
264
+ test('ne met pas a jour le state apres unmount pendant le chargement initial', async () => {
265
+ let resolveRequest;
266
+ const pendingRequest = new Promise((resolve) => {
267
+ resolveRequest = resolve;
268
+ });
269
+
270
+ apiModule.getRegistrations.mockReturnValueOnce(pendingRequest);
271
+ const { unmount } = render(<App />);
272
+
273
+ expect(apiModule.getRegistrations).toHaveBeenCalledTimes(1);
274
+ unmount();
275
+
276
+ resolveRequest([userValide]);
277
+ await waitFor(() => expect(apiModule.getRegistrations).toHaveBeenCalledTimes(1));
278
+ });
279
+
280
+ test('garde un etat sain si le chargement initial echoue apres attente', async () => {
281
+ let rejectRequest;
282
+ const pendingRequest = new Promise((_, reject) => {
283
+ rejectRequest = reject;
284
+ });
285
+
286
+ apiModule.getRegistrations.mockReturnValueOnce(pendingRequest);
287
+ render(<App />);
288
+
289
+ expect(apiModule.getRegistrations).toHaveBeenCalledTimes(1);
290
+ rejectRequest(new Error('Network Error'));
291
+
292
+ await waitFor(() => {
293
+ expect(screen.getByText('0 utilisateur(s) inscrit(s)')).toBeInTheDocument();
294
+ expect(screen.getByText('Aucun utilisateur inscrit pour le moment.')).toBeInTheDocument();
295
+ });
296
+ });
297
+ });
298
+
@@ -0,0 +1,38 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`Navigation SPA et formulaire - integration snapshot de la page accueil 1`] = `
4
+ <DocumentFragment>
5
+ <div
6
+ class="App"
7
+ >
8
+ <main
9
+ class="form-container"
10
+ >
11
+ <section
12
+ data-cy="home-page"
13
+ >
14
+ <h1>
15
+ Bienvenue sur l'application d'inscription
16
+ </h1>
17
+ <p
18
+ data-cy="registered-count"
19
+ >
20
+ 0 utilisateur(s) inscrit(s)
21
+ </p>
22
+ <p
23
+ data-cy="empty-list"
24
+ >
25
+ Aucun utilisateur inscrit pour le moment.
26
+ </p>
27
+ <a
28
+ class="action-button primary-action link-button"
29
+ data-cy="go-to-register"
30
+ href="/register"
31
+ >
32
+ Ajouter un utilisateur
33
+ </a>
34
+ </section>
35
+ </main>
36
+ </div>
37
+ </DocumentFragment>
38
+ `;
package/dist/api.js ADDED
@@ -0,0 +1,48 @@
1
+ import axios from 'axios';
2
+ const DEFAULT_API_BASE_URL = 'https://jsonplaceholder.typicode.com';
3
+ const API_BASE_URL = process.env.REACT_APP_API_URL || (process.env.REACT_APP_SERVER_PORT ? `http://localhost:${process.env.REACT_APP_SERVER_PORT}` : DEFAULT_API_BASE_URL);
4
+ const USERS_PATH = '/users';
5
+ function buildRequestConfig() {
6
+ const apiToken = process.env.REACT_APP_API_TOKEN;
7
+ if (!apiToken) {
8
+ return {};
9
+ }
10
+ return {
11
+ headers: {
12
+ Authorization: `Bearer ${apiToken}`
13
+ }
14
+ };
15
+ }
16
+ function normalizeApiUsers(data) {
17
+ if (Array.isArray(data)) {
18
+ return data;
19
+ }
20
+ if (Array.isArray(data?.utilisateurs)) {
21
+ return data.utilisateurs;
22
+ }
23
+ return [];
24
+ }
25
+ function mapApiUser(rawUser = {}) {
26
+ const completeName = (rawUser.name || '').trim();
27
+ const [nomFromName = '', ...prenomParts] = completeName.split(' ').filter(Boolean);
28
+ return {
29
+ nom: rawUser.nom || nomFromName || '',
30
+ prenom: rawUser.prenom || prenomParts.join(' ') || '',
31
+ email: rawUser.email || '',
32
+ dateNaissance: rawUser.dateNaissance || '1990-01-01',
33
+ cp: rawUser.cp || rawUser.address?.zipcode || '',
34
+ ville: rawUser.ville || rawUser.address?.city || ''
35
+ };
36
+ }
37
+ export async function getRegistrations() {
38
+ const response = await axios.get(`${API_BASE_URL}${USERS_PATH}`, buildRequestConfig());
39
+ return normalizeApiUsers(response.data).map(mapApiUser);
40
+ }
41
+ export async function createRegistration(user) {
42
+ const response = await axios.post(`${API_BASE_URL}${USERS_PATH}`, user, buildRequestConfig());
43
+ return response.data;
44
+ }
45
+ export async function countUsers() {
46
+ const response = await axios.get(`${API_BASE_URL}${USERS_PATH}`, buildRequestConfig());
47
+ return normalizeApiUsers(response.data).length;
48
+ }
@@ -0,0 +1,128 @@
1
+ import axios from 'axios';
2
+ import { countUsers, createRegistration, getRegistrations } from './api';
3
+
4
+ jest.mock('axios');
5
+
6
+ describe('api integration with axios mock', () => {
7
+ beforeEach(() => {
8
+ process.env.REACT_APP_SERVER_PORT = '3000';
9
+ delete process.env.REACT_APP_API_TOKEN;
10
+ jest.clearAllMocks();
11
+ });
12
+
13
+ test('getRegistrations: succes avec payload utilisateurs', async () => {
14
+ axios.get.mockImplementationOnce(() =>
15
+ Promise.resolve({
16
+ data: {
17
+ utilisateurs: [
18
+ {
19
+ nom: 'Martin',
20
+ prenom: 'Julie',
21
+ email: 'julie.martin@example.com',
22
+ dateNaissance: '1990-01-01',
23
+ cp: '69001',
24
+ ville: 'Lyon',
25
+ },
26
+ ],
27
+ },
28
+ })
29
+ );
30
+
31
+ const users = await getRegistrations();
32
+
33
+ expect(axios.get).toHaveBeenCalledWith('http://localhost:3000/users', {});
34
+ expect(users).toEqual([
35
+ {
36
+ nom: 'Martin',
37
+ prenom: 'Julie',
38
+ email: 'julie.martin@example.com',
39
+ dateNaissance: '1990-01-01',
40
+ cp: '69001',
41
+ ville: 'Lyon',
42
+ },
43
+ ]);
44
+ });
45
+
46
+ test('getRegistrations: erreur reseau', async () => {
47
+ axios.get.mockImplementationOnce(() => Promise.reject(new Error('Network Error')));
48
+
49
+ await expect(getRegistrations()).rejects.toThrow('Network Error');
50
+ expect(axios.get).toHaveBeenCalledWith('http://localhost:3000/users', {});
51
+ });
52
+
53
+ test('getRegistrations: payload invalide retourne tableau vide', async () => {
54
+ axios.get.mockImplementationOnce(() =>
55
+ Promise.resolve({
56
+ data: { unexpected: true },
57
+ })
58
+ );
59
+
60
+ const users = await getRegistrations();
61
+
62
+ expect(users).toEqual([]);
63
+ expect(axios.get).toHaveBeenCalledWith('http://localhost:3000/users', {});
64
+ });
65
+
66
+ test('countUsers: succes avec tableau', async () => {
67
+ axios.get.mockImplementationOnce(() =>
68
+ Promise.resolve({
69
+ data: [
70
+ { id: 1, email: 'a@example.com' },
71
+ { id: 2, email: 'b@example.com' },
72
+ ],
73
+ })
74
+ );
75
+
76
+ const result = await countUsers();
77
+
78
+ expect(result).toEqual(2);
79
+ expect(axios.get).toHaveBeenCalledWith('http://localhost:3000/users', {});
80
+ });
81
+
82
+ test('countUsers: erreur reseau', async () => {
83
+ axios.get.mockImplementationOnce(() => Promise.reject(new Error('Network Error')));
84
+
85
+ await expect(countUsers()).rejects.toThrow('Network Error');
86
+ expect(axios.get).toHaveBeenCalledTimes(1);
87
+ });
88
+
89
+ test('createRegistration: succes', async () => {
90
+ const payload = {
91
+ nom: 'Durand',
92
+ prenom: 'Luc',
93
+ email: 'luc.durand@example.com',
94
+ dateNaissance: '1989-03-03',
95
+ cp: '75001',
96
+ ville: 'Paris',
97
+ };
98
+
99
+ axios.post.mockImplementationOnce(() =>
100
+ Promise.resolve({
101
+ data: { id: 101, ...payload },
102
+ })
103
+ );
104
+
105
+ const created = await createRegistration(payload);
106
+
107
+ expect(axios.post).toHaveBeenCalledWith('http://localhost:3000/users', payload, {});
108
+ expect(created).toEqual({ id: 101, ...payload });
109
+ });
110
+
111
+ test('createRegistration: erreur reseau', async () => {
112
+ axios.post.mockImplementationOnce(() => Promise.reject(new Error('Network Error')));
113
+
114
+ await expect(createRegistration({ email: 'ko@example.com' })).rejects.toThrow('Network Error');
115
+ expect(axios.post).toHaveBeenCalledTimes(1);
116
+ });
117
+
118
+ test('ajoute le header Authorization si REACT_APP_API_TOKEN est defini', async () => {
119
+ process.env.REACT_APP_API_TOKEN = 'test-token';
120
+ axios.get.mockResolvedValueOnce({ data: [] });
121
+
122
+ await getRegistrations();
123
+
124
+ expect(axios.get).toHaveBeenCalledWith('http://localhost:3000/users', {
125
+ headers: { Authorization: 'Bearer test-token' },
126
+ });
127
+ });
128
+ });
package/dist/index.css ADDED
@@ -0,0 +1,13 @@
1
+ body {
2
+ margin: 0;
3
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5
+ sans-serif;
6
+ -webkit-font-smoothing: antialiased;
7
+ -moz-osx-font-smoothing: grayscale;
8
+ }
9
+
10
+ code {
11
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12
+ monospace;
13
+ }
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import './index.css';
4
+ import App from './App';
5
+ import reportWebVitals from './reportWebVitals';
6
+ const root = ReactDOM.createRoot(document.getElementById('root'));
7
+ root.render(/*#__PURE__*/React.createElement(React.StrictMode, null, /*#__PURE__*/React.createElement(App, null)));
8
+ reportWebVitals();
9
+ export default App;
package/dist/logo.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
@@ -0,0 +1,18 @@
1
+ const reportWebVitals = onPerfEntry => {
2
+ if (onPerfEntry && onPerfEntry instanceof Function) {
3
+ import('web-vitals').then(({
4
+ getCLS,
5
+ getFID,
6
+ getFCP,
7
+ getLCP,
8
+ getTTFB
9
+ }) => {
10
+ getCLS(onPerfEntry);
11
+ getFID(onPerfEntry);
12
+ getFCP(onPerfEntry);
13
+ getLCP(onPerfEntry);
14
+ getTTFB(onPerfEntry);
15
+ });
16
+ }
17
+ };
18
+ export default reportWebVitals;
@@ -0,0 +1,5 @@
1
+ // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
+ // allows you to do things like:
3
+ // expect(element).toHaveTextContent(/react/i)
4
+ // learn more: https://github.com/testing-library/jest-dom
5
+ import '@testing-library/jest-dom';