@yoann-86/react_test_lab 0.1.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.
- package/.github/actions/install-node/action.yml +25 -0
- package/.github/workflows/build_test_deploy_react.yml +114 -0
- package/README.md +105 -0
- package/TEST_PLAN.md +94 -0
- package/babel.config.js +6 -0
- package/cypress/e2e/app-spec.cy.js +182 -0
- package/cypress/e2e/navigation.cy.js +101 -0
- package/cypress/fixtures/example.json +5 -0
- package/cypress/support/commands.js +25 -0
- package/cypress/support/e2e.js +17 -0
- package/cypress.config.js +12 -0
- package/dist/App.css +108 -0
- package/dist/App.js +59 -0
- package/dist/App.test.js +225 -0
- package/dist/components/UserForm.js +188 -0
- package/dist/components/UserForm.test.js +291 -0
- package/dist/components/UsersList.js +38 -0
- package/dist/components/UsersList.test.js +134 -0
- package/dist/components/atomic/TextInput.js +42 -0
- package/dist/index.css +13 -0
- package/dist/index.js +23 -0
- package/dist/infra/api.js +27 -0
- package/dist/infra/api.test.js +54 -0
- package/dist/logo.svg +1 -0
- package/dist/reportWebVitals.js +26 -0
- package/dist/setupTests.js +3 -0
- package/dist/utils/module.js +25 -0
- package/dist/utils/module.test.js +78 -0
- package/dist/utils/validator.js +116 -0
- package/dist/utils/validator.test.js +324 -0
- package/jsdoc.config.json +10 -0
- package/package.json +62 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +43 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/repo.md +13 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _react = require("@testing-library/react");
|
|
4
|
+
var _userEvent = _interopRequireDefault(require("@testing-library/user-event"));
|
|
5
|
+
var _UserForm = require("./UserForm");
|
|
6
|
+
var _api = require("../infra/api");
|
|
7
|
+
var _axios = _interopRequireDefault(require("axios"));
|
|
8
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
9
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
jest.mock("axios");
|
|
11
|
+
let mockNavigate;
|
|
12
|
+
jest.mock("wouter", () => ({
|
|
13
|
+
useLocation: () => ["", mockNavigate]
|
|
14
|
+
}));
|
|
15
|
+
let usersState = [];
|
|
16
|
+
const setUsersStateMock = jest.fn();
|
|
17
|
+
const getFormElements = () => ({
|
|
18
|
+
firstnameField: _react.screen.getByTestId("firstname-input"),
|
|
19
|
+
lastnameField: _react.screen.getByTestId("lastname-input"),
|
|
20
|
+
emailField: _react.screen.getByTestId("email-input"),
|
|
21
|
+
birthField: _react.screen.getByTestId("birth-input"),
|
|
22
|
+
cityField: _react.screen.getByTestId("city-input"),
|
|
23
|
+
zipField: _react.screen.getByTestId("zip-input"),
|
|
24
|
+
submitButton: _react.screen.getByTestId("submit-button"),
|
|
25
|
+
backButton: _react.screen.getByTestId("back-button"),
|
|
26
|
+
firstnameErrorText: _react.screen.getByTestId("firstname-error-text"),
|
|
27
|
+
lastnameErrorText: _react.screen.getByTestId("lastname-error-text"),
|
|
28
|
+
emailErrorText: _react.screen.getByTestId("email-error-text"),
|
|
29
|
+
birthErrorText: _react.screen.getByTestId("birth-error-text"),
|
|
30
|
+
zipErrorText: _react.screen.getByTestId("zip-error-text")
|
|
31
|
+
});
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
mockNavigate = jest.fn();
|
|
34
|
+
});
|
|
35
|
+
test("should disable submit button when required fields are empty", () => {
|
|
36
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserForm.UserForm, {
|
|
37
|
+
setUsers: setUsersStateMock
|
|
38
|
+
}));
|
|
39
|
+
const {
|
|
40
|
+
firstnameField,
|
|
41
|
+
lastnameField,
|
|
42
|
+
emailField,
|
|
43
|
+
birthField,
|
|
44
|
+
cityField,
|
|
45
|
+
zipField,
|
|
46
|
+
submitButton,
|
|
47
|
+
backButton
|
|
48
|
+
} = getFormElements();
|
|
49
|
+
expect(firstnameField).toBeInTheDocument();
|
|
50
|
+
expect(lastnameField).toBeInTheDocument();
|
|
51
|
+
expect(emailField).toBeInTheDocument();
|
|
52
|
+
expect(birthField).toBeInTheDocument();
|
|
53
|
+
expect(cityField).toBeInTheDocument();
|
|
54
|
+
expect(zipField).toBeInTheDocument();
|
|
55
|
+
expect(submitButton).toBeInTheDocument();
|
|
56
|
+
expect(backButton).toBeInTheDocument();
|
|
57
|
+
expect(submitButton).toBeDisabled();
|
|
58
|
+
});
|
|
59
|
+
test("should enable submit button when all fields are valid", async () => {
|
|
60
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserForm.UserForm, {
|
|
61
|
+
setUsers: setUsersStateMock
|
|
62
|
+
}));
|
|
63
|
+
const {
|
|
64
|
+
firstnameField,
|
|
65
|
+
lastnameField,
|
|
66
|
+
emailField,
|
|
67
|
+
birthField,
|
|
68
|
+
cityField,
|
|
69
|
+
zipField,
|
|
70
|
+
submitButton
|
|
71
|
+
} = getFormElements();
|
|
72
|
+
expect(submitButton).toBeDisabled();
|
|
73
|
+
_userEvent.default.type(firstnameField, "John");
|
|
74
|
+
_userEvent.default.type(lastnameField, "Doe");
|
|
75
|
+
_userEvent.default.type(emailField, "john@example.com");
|
|
76
|
+
_userEvent.default.type(birthField, "1990-01-01");
|
|
77
|
+
_userEvent.default.type(cityField, "Paris");
|
|
78
|
+
_userEvent.default.type(zipField, "75010");
|
|
79
|
+
expect(submitButton).toBeEnabled();
|
|
80
|
+
});
|
|
81
|
+
test("should save a new user on valid submit", async () => {
|
|
82
|
+
const data = usersState;
|
|
83
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserForm.UserForm, {
|
|
84
|
+
setUsers: setUsersStateMock
|
|
85
|
+
}));
|
|
86
|
+
const {
|
|
87
|
+
firstnameField,
|
|
88
|
+
lastnameField,
|
|
89
|
+
emailField,
|
|
90
|
+
birthField,
|
|
91
|
+
cityField,
|
|
92
|
+
zipField,
|
|
93
|
+
submitButton
|
|
94
|
+
} = getFormElements();
|
|
95
|
+
expect(submitButton).toBeDisabled();
|
|
96
|
+
_userEvent.default.type(firstnameField, "John");
|
|
97
|
+
expect(firstnameField).toHaveValue("John");
|
|
98
|
+
_userEvent.default.type(lastnameField, "Doe");
|
|
99
|
+
expect(lastnameField).toHaveValue("Doe");
|
|
100
|
+
_userEvent.default.type(emailField, "john.doe@mail.com");
|
|
101
|
+
expect(emailField).toHaveValue("john.doe@mail.com");
|
|
102
|
+
_userEvent.default.type(birthField, "1986-12-31");
|
|
103
|
+
expect(birthField).toHaveValue("1986-12-31");
|
|
104
|
+
_userEvent.default.type(cityField, "undefined city");
|
|
105
|
+
expect(cityField).toHaveValue("undefined city");
|
|
106
|
+
_userEvent.default.type(zipField, "75010");
|
|
107
|
+
expect(zipField).toHaveValue("75010");
|
|
108
|
+
expect(submitButton).toBeEnabled();
|
|
109
|
+
_axios.default.post.mockResolvedValue({
|
|
110
|
+
data: {
|
|
111
|
+
firstname: "John",
|
|
112
|
+
lastname: "Doe",
|
|
113
|
+
name: "John Doe",
|
|
114
|
+
email: "john.doe@mail.com",
|
|
115
|
+
birth: "1986-12-31",
|
|
116
|
+
zipCode: "75010",
|
|
117
|
+
city: "undefined city"
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
_userEvent.default.click(submitButton);
|
|
121
|
+
const url = "".concat(process.env.REACT_APP_API_URL, "/users");
|
|
122
|
+
expect(_axios.default.post).toHaveBeenCalledWith(url, {
|
|
123
|
+
name: "John Doe",
|
|
124
|
+
firstname: "John",
|
|
125
|
+
lastname: "Doe",
|
|
126
|
+
email: "john.doe@mail.com",
|
|
127
|
+
birth: "1986-12-31",
|
|
128
|
+
zipCode: "75010",
|
|
129
|
+
city: "undefined city"
|
|
130
|
+
});
|
|
131
|
+
await (0, _react.waitFor)(() => expect(setUsersStateMock).toHaveBeenCalled());
|
|
132
|
+
expect(firstnameField).toHaveValue("");
|
|
133
|
+
expect(lastnameField).toHaveValue("");
|
|
134
|
+
expect(emailField).toHaveValue("");
|
|
135
|
+
expect(birthField).toHaveValue("");
|
|
136
|
+
expect(zipField).toHaveValue("");
|
|
137
|
+
});
|
|
138
|
+
test("should show an error when firstname is not valid", async () => {
|
|
139
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserForm.UserForm, {
|
|
140
|
+
setUsers: setUsersStateMock
|
|
141
|
+
}));
|
|
142
|
+
const {
|
|
143
|
+
firstnameField,
|
|
144
|
+
firstnameErrorText
|
|
145
|
+
} = getFormElements();
|
|
146
|
+
|
|
147
|
+
// Test with numbers
|
|
148
|
+
_userEvent.default.type(firstnameField, "John123");
|
|
149
|
+
expect(firstnameField).toHaveValue("John123");
|
|
150
|
+
expect(firstnameErrorText).toHaveTextContent("Les chiffres sont interdits");
|
|
151
|
+
|
|
152
|
+
// Clear and test empty
|
|
153
|
+
_userEvent.default.clear(firstnameField);
|
|
154
|
+
expect(firstnameField).toHaveValue("");
|
|
155
|
+
expect(firstnameErrorText).toHaveTextContent("Le champ ne peut pas être vide");
|
|
156
|
+
|
|
157
|
+
// Test with special characters
|
|
158
|
+
_userEvent.default.type(firstnameField, '; INSERT INTO "users" (username, role) VALUES(anonymous, admin);');
|
|
159
|
+
expect(firstnameErrorText).toHaveTextContent("Les caractères spéciaux sont interdits");
|
|
160
|
+
|
|
161
|
+
// Test valid input
|
|
162
|
+
_userEvent.default.clear(firstnameField);
|
|
163
|
+
_userEvent.default.type(firstnameField, "John");
|
|
164
|
+
expect(firstnameField).toHaveValue("John");
|
|
165
|
+
expect(firstnameErrorText).toHaveTextContent("");
|
|
166
|
+
});
|
|
167
|
+
test("should show an error when lastname is not valid", async () => {
|
|
168
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserForm.UserForm, {
|
|
169
|
+
setUsers: setUsersStateMock
|
|
170
|
+
}));
|
|
171
|
+
const {
|
|
172
|
+
lastnameField,
|
|
173
|
+
lastnameErrorText
|
|
174
|
+
} = getFormElements();
|
|
175
|
+
_userEvent.default.type(lastnameField, "<Doe>");
|
|
176
|
+
expect(lastnameField).toHaveValue("<Doe>");
|
|
177
|
+
expect(lastnameErrorText).toHaveTextContent("Les caractères spéciaux sont interdits");
|
|
178
|
+
});
|
|
179
|
+
test("should show an error when email is not valid", async () => {
|
|
180
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserForm.UserForm, {
|
|
181
|
+
setUsers: setUsersStateMock
|
|
182
|
+
}));
|
|
183
|
+
const {
|
|
184
|
+
emailField,
|
|
185
|
+
emailErrorText
|
|
186
|
+
} = getFormElements();
|
|
187
|
+
_userEvent.default.type(emailField, "johndoe.com");
|
|
188
|
+
expect(emailField).toHaveValue("johndoe.com");
|
|
189
|
+
expect(emailErrorText).toHaveTextContent("Le format de l'email est invalide");
|
|
190
|
+
|
|
191
|
+
// Test valid email
|
|
192
|
+
_userEvent.default.clear(emailField);
|
|
193
|
+
_userEvent.default.type(emailField, "john@example.com");
|
|
194
|
+
expect(emailField).toHaveValue("john@example.com");
|
|
195
|
+
expect(emailErrorText).toHaveTextContent("");
|
|
196
|
+
});
|
|
197
|
+
test("should show an error when date of birth is not valid", async () => {
|
|
198
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserForm.UserForm, {
|
|
199
|
+
setUsers: setUsersStateMock
|
|
200
|
+
}));
|
|
201
|
+
const {
|
|
202
|
+
birthField,
|
|
203
|
+
birthErrorText
|
|
204
|
+
} = getFormElements();
|
|
205
|
+
_userEvent.default.type(birthField, "2012-01-01");
|
|
206
|
+
expect(birthField).toHaveValue("2012-01-01");
|
|
207
|
+
expect(birthErrorText).toHaveTextContent("Vous devez être majeur pour vous inscrire");
|
|
208
|
+
|
|
209
|
+
// Test valid birth date
|
|
210
|
+
_userEvent.default.clear(birthField);
|
|
211
|
+
_userEvent.default.type(birthField, "1990-01-01");
|
|
212
|
+
expect(birthField).toHaveValue("1990-01-01");
|
|
213
|
+
expect(birthErrorText).toHaveTextContent("");
|
|
214
|
+
});
|
|
215
|
+
test("should show an error when zipcode is not valid", async () => {
|
|
216
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserForm.UserForm, {
|
|
217
|
+
setUsers: setUsersStateMock
|
|
218
|
+
}));
|
|
219
|
+
const {
|
|
220
|
+
zipField,
|
|
221
|
+
zipErrorText
|
|
222
|
+
} = getFormElements();
|
|
223
|
+
_userEvent.default.type(zipField, "2A325");
|
|
224
|
+
expect(zipField).toHaveValue("2A325");
|
|
225
|
+
expect(zipErrorText).toHaveTextContent("Le code postal doit comporter 5 chiffres");
|
|
226
|
+
|
|
227
|
+
// Test valid zipcode
|
|
228
|
+
_userEvent.default.clear(zipField);
|
|
229
|
+
_userEvent.default.type(zipField, "75010");
|
|
230
|
+
expect(zipField).toHaveValue("75010");
|
|
231
|
+
expect(zipErrorText).toHaveTextContent("");
|
|
232
|
+
});
|
|
233
|
+
test("should reset the form after successful submission", async () => {
|
|
234
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserForm.UserForm, {
|
|
235
|
+
setUsers: setUsersStateMock
|
|
236
|
+
}));
|
|
237
|
+
const {
|
|
238
|
+
firstnameField,
|
|
239
|
+
lastnameField,
|
|
240
|
+
emailField,
|
|
241
|
+
birthField,
|
|
242
|
+
zipField,
|
|
243
|
+
submitButton
|
|
244
|
+
} = getFormElements();
|
|
245
|
+
_userEvent.default.type(firstnameField, "John");
|
|
246
|
+
_userEvent.default.type(lastnameField, "Doe");
|
|
247
|
+
_userEvent.default.type(emailField, "john@example.com");
|
|
248
|
+
_userEvent.default.type(birthField, "1990-01-01");
|
|
249
|
+
_userEvent.default.type(zipField, "75010");
|
|
250
|
+
expect(submitButton).toBeEnabled();
|
|
251
|
+
_axios.default.post.mockResolvedValue({
|
|
252
|
+
data: {
|
|
253
|
+
firstname: "John",
|
|
254
|
+
lastname: "Doe",
|
|
255
|
+
name: "John Doe",
|
|
256
|
+
email: "john@example.com",
|
|
257
|
+
birth: "1990-01-01",
|
|
258
|
+
zipCode: "75010",
|
|
259
|
+
city: ""
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
_userEvent.default.click(submitButton);
|
|
263
|
+
const url = "".concat(process.env.REACT_APP_API_URL, "/users");
|
|
264
|
+
expect(_axios.default.post).toHaveBeenCalledWith(url, {
|
|
265
|
+
name: "John Doe",
|
|
266
|
+
firstname: "John",
|
|
267
|
+
lastname: "Doe",
|
|
268
|
+
email: "john@example.com",
|
|
269
|
+
birth: "1990-01-01",
|
|
270
|
+
zipCode: "75010",
|
|
271
|
+
city: ""
|
|
272
|
+
});
|
|
273
|
+
await (0, _react.waitFor)(() => {
|
|
274
|
+
expect(firstnameField).toHaveValue("");
|
|
275
|
+
expect(lastnameField).toHaveValue("");
|
|
276
|
+
expect(emailField).toHaveValue("");
|
|
277
|
+
expect(birthField).toHaveValue("");
|
|
278
|
+
expect(zipField).toHaveValue("");
|
|
279
|
+
expect(submitButton).toBeDisabled();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
test("should navigate to home page when back button is clicked", async () => {
|
|
283
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserForm.UserForm, {
|
|
284
|
+
setUsers: setUsersStateMock
|
|
285
|
+
}));
|
|
286
|
+
const {
|
|
287
|
+
backButton
|
|
288
|
+
} = getFormElements();
|
|
289
|
+
_userEvent.default.click(backButton);
|
|
290
|
+
expect(mockNavigate).toHaveBeenCalledWith("/");
|
|
291
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.UsersList = void 0;
|
|
7
|
+
var _wouter = require("wouter");
|
|
8
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
9
|
+
const UsersList = _ref => {
|
|
10
|
+
let {
|
|
11
|
+
users
|
|
12
|
+
} = _ref;
|
|
13
|
+
const [, navigate] = (0, _wouter.useLocation)();
|
|
14
|
+
const severalUsers = users.length > 1;
|
|
15
|
+
const noUser = users.length === 0;
|
|
16
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
17
|
+
className: "users-list-container",
|
|
18
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("h2", {
|
|
19
|
+
"data-testid": "users-counter",
|
|
20
|
+
children: noUser ? "Il n'y a aucun utilisateur enregistré" : "Il y a ".concat(users.length, " utilisateur").concat(severalUsers ? "s" : "", " enregistr\xE9").concat(severalUsers ? "s" : "")
|
|
21
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("h3", {
|
|
22
|
+
children: users.length > 0 ? "Liste des 5 derniers utilisateurs :" : "Créez en un pour voir la liste des utilisateurs enregistrés !"
|
|
23
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("ul", {
|
|
24
|
+
"data-testid": "users-list",
|
|
25
|
+
className: "list-none",
|
|
26
|
+
children: users.length > 0 && users.slice(-5).map((user, index) => /*#__PURE__*/(0, _jsxRuntime.jsxs)("li", {
|
|
27
|
+
"data-testid": "user-".concat(index),
|
|
28
|
+
children: [index + 1, " - ", user.name]
|
|
29
|
+
}, index))
|
|
30
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
|
|
31
|
+
"data-testid": "navigation-button",
|
|
32
|
+
className: "button",
|
|
33
|
+
onClick: () => navigate("/register"),
|
|
34
|
+
children: "Enregistrer un nouvel utilisateur"
|
|
35
|
+
})]
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
exports.UsersList = UsersList;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _react = require("@testing-library/react");
|
|
4
|
+
var _userEvent = _interopRequireDefault(require("@testing-library/user-event"));
|
|
5
|
+
var _UsersList = require("./UsersList");
|
|
6
|
+
var _api = require("../infra/api");
|
|
7
|
+
var _axios = _interopRequireDefault(require("axios"));
|
|
8
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
9
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
jest.mock("axios");
|
|
11
|
+
let mockNavigate;
|
|
12
|
+
jest.mock("wouter", () => ({
|
|
13
|
+
useLocation: () => ["", mockNavigate]
|
|
14
|
+
}));
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockNavigate = jest.fn();
|
|
17
|
+
});
|
|
18
|
+
test("should display an empty list when no users in data response", async () => {
|
|
19
|
+
const data = [];
|
|
20
|
+
_axios.default.get.mockImplementationOnce(() => Promise.resolve({
|
|
21
|
+
data
|
|
22
|
+
}));
|
|
23
|
+
const users = await (0, _api.getAllUsers)();
|
|
24
|
+
expect(users).toEqual(data);
|
|
25
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UsersList.UsersList, {
|
|
26
|
+
users: users
|
|
27
|
+
}));
|
|
28
|
+
const usersList = _react.screen.getByTestId("users-list");
|
|
29
|
+
expect(usersList).toBeInTheDocument();
|
|
30
|
+
expect(usersList.children).toHaveLength(0);
|
|
31
|
+
});
|
|
32
|
+
test("should display users from api call on mount", async () => {
|
|
33
|
+
const data = [{
|
|
34
|
+
name: "John Doe",
|
|
35
|
+
email: "john@example.com",
|
|
36
|
+
birth: "1990-01-01",
|
|
37
|
+
city: "Paris",
|
|
38
|
+
zipCode: "75001"
|
|
39
|
+
}, {
|
|
40
|
+
name: "Jane Smith",
|
|
41
|
+
email: "jane@example.com",
|
|
42
|
+
birth: "1992-03-15",
|
|
43
|
+
city: "Lyon",
|
|
44
|
+
zipCode: "69001"
|
|
45
|
+
}];
|
|
46
|
+
_axios.default.get.mockImplementationOnce(() => Promise.resolve({
|
|
47
|
+
data
|
|
48
|
+
}));
|
|
49
|
+
const users = await (0, _api.getAllUsers)();
|
|
50
|
+
expect(users).toEqual(data);
|
|
51
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UsersList.UsersList, {
|
|
52
|
+
users: users
|
|
53
|
+
}));
|
|
54
|
+
const user1 = _react.screen.getByTestId("user-0");
|
|
55
|
+
expect(user1).toHaveTextContent("1 - John Doe");
|
|
56
|
+
const user2 = _react.screen.getByTestId("user-1");
|
|
57
|
+
expect(user2).toHaveTextContent("2 - Jane Smith");
|
|
58
|
+
});
|
|
59
|
+
test("should display only the 5 most recent users when more than 5 exist", async () => {
|
|
60
|
+
const data = Array.from({
|
|
61
|
+
length: 7
|
|
62
|
+
}, (_, i) => ({
|
|
63
|
+
name: "User".concat(i + 1, " Last").concat(i + 1),
|
|
64
|
+
email: "user".concat(i + 1, "@example.com"),
|
|
65
|
+
birth: "1990-01-01",
|
|
66
|
+
city: "Paris",
|
|
67
|
+
zipCode: "75001"
|
|
68
|
+
}));
|
|
69
|
+
_axios.default.get.mockImplementationOnce(() => Promise.resolve({
|
|
70
|
+
data
|
|
71
|
+
}));
|
|
72
|
+
const users = await (0, _api.getAllUsers)();
|
|
73
|
+
expect(users).toEqual(data);
|
|
74
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UsersList.UsersList, {
|
|
75
|
+
users: users
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
// Should display users 3-7 (last 5)
|
|
79
|
+
expect(_react.screen.getByTestId("user-0")).toHaveTextContent("1 - User3 Last3");
|
|
80
|
+
expect(_react.screen.getByTestId("user-1")).toHaveTextContent("2 - User4 Last4");
|
|
81
|
+
expect(_react.screen.getByTestId("user-2")).toHaveTextContent("3 - User5 Last5");
|
|
82
|
+
expect(_react.screen.getByTestId("user-3")).toHaveTextContent("4 - User6 Last6");
|
|
83
|
+
expect(_react.screen.getByTestId("user-4")).toHaveTextContent("5 - User7 Last7");
|
|
84
|
+
});
|
|
85
|
+
test("should re-render when users prop changes", () => {
|
|
86
|
+
const {
|
|
87
|
+
rerender
|
|
88
|
+
} = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UsersList.UsersList, {
|
|
89
|
+
users: []
|
|
90
|
+
}));
|
|
91
|
+
const initialList = _react.screen.getByTestId("users-list");
|
|
92
|
+
expect(initialList.children).toHaveLength(0);
|
|
93
|
+
const newUsers = [{
|
|
94
|
+
name: "Alice Wonder",
|
|
95
|
+
email: "alice@example.com",
|
|
96
|
+
birth: "1995-05-20",
|
|
97
|
+
city: "Marseille",
|
|
98
|
+
zipCode: "13001"
|
|
99
|
+
}];
|
|
100
|
+
rerender(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UsersList.UsersList, {
|
|
101
|
+
users: newUsers
|
|
102
|
+
}));
|
|
103
|
+
const updatedUser = _react.screen.getByTestId("user-0");
|
|
104
|
+
expect(updatedUser).toHaveTextContent("1 - Alice Wonder");
|
|
105
|
+
});
|
|
106
|
+
test("should clear users when users prop becomes empty", () => {
|
|
107
|
+
const users = [{
|
|
108
|
+
name: "Charlie Brown",
|
|
109
|
+
email: "charlie@example.com",
|
|
110
|
+
birth: "1988-03-22",
|
|
111
|
+
city: "Bordeaux",
|
|
112
|
+
zipCode: "33000"
|
|
113
|
+
}];
|
|
114
|
+
const {
|
|
115
|
+
rerender
|
|
116
|
+
} = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UsersList.UsersList, {
|
|
117
|
+
users: users
|
|
118
|
+
}));
|
|
119
|
+
let usersList = _react.screen.getByTestId("users-list");
|
|
120
|
+
expect(usersList.children).toHaveLength(1);
|
|
121
|
+
rerender(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UsersList.UsersList, {
|
|
122
|
+
users: []
|
|
123
|
+
}));
|
|
124
|
+
usersList = _react.screen.getByTestId("users-list");
|
|
125
|
+
expect(usersList.children).toHaveLength(0);
|
|
126
|
+
});
|
|
127
|
+
test("should navigate to register page when button is clicked", async () => {
|
|
128
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_UsersList.UsersList, {
|
|
129
|
+
users: []
|
|
130
|
+
}));
|
|
131
|
+
const navigationButton = _react.screen.getByTestId("navigation-button");
|
|
132
|
+
await _userEvent.default.click(navigationButton);
|
|
133
|
+
expect(mockNavigate).toHaveBeenCalledWith("/register");
|
|
134
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.TextInput = void 0;
|
|
7
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
8
|
+
const TextInput = _ref => {
|
|
9
|
+
let {
|
|
10
|
+
label,
|
|
11
|
+
id,
|
|
12
|
+
type = "text",
|
|
13
|
+
value,
|
|
14
|
+
onChange,
|
|
15
|
+
testId,
|
|
16
|
+
errorTestId,
|
|
17
|
+
errorText,
|
|
18
|
+
required = false,
|
|
19
|
+
requiredIndicator
|
|
20
|
+
} = _ref;
|
|
21
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
22
|
+
className: "input-container",
|
|
23
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("label", {
|
|
24
|
+
className: "label",
|
|
25
|
+
htmlFor: id,
|
|
26
|
+
children: [label, " ", required && requiredIndicator]
|
|
27
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("input", {
|
|
28
|
+
"data-testid": testId,
|
|
29
|
+
required: required,
|
|
30
|
+
value: value,
|
|
31
|
+
onChange: onChange,
|
|
32
|
+
className: "input",
|
|
33
|
+
id: id,
|
|
34
|
+
type: type
|
|
35
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("p", {
|
|
36
|
+
"data-testid": errorTestId,
|
|
37
|
+
className: "error-text",
|
|
38
|
+
children: errorText
|
|
39
|
+
})]
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
exports.TextInput = TextInput;
|
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,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
var _client = _interopRequireDefault(require("react-dom/client"));
|
|
9
|
+
require("./index.css");
|
|
10
|
+
var _App = _interopRequireDefault(require("./App"));
|
|
11
|
+
var _reportWebVitals = _interopRequireDefault(require("./reportWebVitals"));
|
|
12
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
+
const root = _client.default.createRoot(document.getElementById("root"));
|
|
15
|
+
root.render(/*#__PURE__*/(0, _jsxRuntime.jsx)(_react.default.StrictMode, {
|
|
16
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_App.default, {})
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
// If you want to start measuring performance in your app, pass a function
|
|
20
|
+
// to log results (for example: reportWebVitals(console.log))
|
|
21
|
+
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
22
|
+
(0, _reportWebVitals.default)();
|
|
23
|
+
var _default = exports.default = _App.default;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getAllUsers = exports.createOneUser = void 0;
|
|
7
|
+
var _axios = _interopRequireDefault(require("axios"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
const API = process.env.REACT_APP_API_URL;
|
|
10
|
+
const getAllUsers = async () => {
|
|
11
|
+
try {
|
|
12
|
+
const response = await _axios.default.get("".concat(API, "/users"));
|
|
13
|
+
return response.data;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
exports.getAllUsers = getAllUsers;
|
|
19
|
+
const createOneUser = async user => {
|
|
20
|
+
try {
|
|
21
|
+
const response = await _axios.default.post("".concat(API, "/users"), user);
|
|
22
|
+
return response.data;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.createOneUser = createOneUser;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _api = require("./api");
|
|
4
|
+
var _axios = _interopRequireDefault(require("axios"));
|
|
5
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
7
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
8
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
9
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
10
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
11
|
+
jest.mock("axios");
|
|
12
|
+
describe("getAllUsers", () => {
|
|
13
|
+
it("fetches successfully data from an API", async () => {
|
|
14
|
+
const data = {
|
|
15
|
+
data: [{
|
|
16
|
+
id: "1",
|
|
17
|
+
name: "John Doe",
|
|
18
|
+
email: "john.doe@mail.com"
|
|
19
|
+
}]
|
|
20
|
+
};
|
|
21
|
+
_axios.default.get.mockImplementationOnce(() => Promise.resolve(data));
|
|
22
|
+
await expect((0, _api.getAllUsers)()).resolves.toEqual(data.data);
|
|
23
|
+
expect(_axios.default.get).toHaveBeenCalledWith("".concat(process.env.REACT_APP_API_URL, "/users"));
|
|
24
|
+
});
|
|
25
|
+
it("fetches erroneously data from an API", async () => {
|
|
26
|
+
const errorMessage = "Network Error";
|
|
27
|
+
_axios.default.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage)));
|
|
28
|
+
await expect((0, _api.getAllUsers)()).rejects.toThrow(errorMessage);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe("createOneUser", () => {
|
|
32
|
+
it("creates a user successfully", async () => {
|
|
33
|
+
const newUser = {
|
|
34
|
+
name: "Jane Doe",
|
|
35
|
+
email: "jane.doe@mail.com"
|
|
36
|
+
};
|
|
37
|
+
const createdUser = _objectSpread(_objectSpread({}, newUser), {}, {
|
|
38
|
+
id: "2"
|
|
39
|
+
});
|
|
40
|
+
_axios.default.post.mockImplementationOnce(() => Promise.resolve({
|
|
41
|
+
data: createdUser
|
|
42
|
+
}));
|
|
43
|
+
await expect((0, _api.createOneUser)(newUser)).resolves.toEqual(createdUser);
|
|
44
|
+
});
|
|
45
|
+
it("fails to create a user", async () => {
|
|
46
|
+
const newUser = {
|
|
47
|
+
name: "Jane Doe",
|
|
48
|
+
email: "jane.doe@mail.com"
|
|
49
|
+
};
|
|
50
|
+
const errorMessage = "Failed to create user";
|
|
51
|
+
_axios.default.post.mockImplementationOnce(() => Promise.reject(new Error(errorMessage)));
|
|
52
|
+
await expect((0, _api.createOneUser)(newUser)).rejects.toThrow(errorMessage);
|
|
53
|
+
});
|
|
54
|
+
});
|
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,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
8
|
+
const reportWebVitals = onPerfEntry => {
|
|
9
|
+
if (onPerfEntry && onPerfEntry instanceof Function) {
|
|
10
|
+
Promise.resolve().then(() => _interopRequireWildcard(require('web-vitals'))).then(_ref => {
|
|
11
|
+
let {
|
|
12
|
+
getCLS,
|
|
13
|
+
getFID,
|
|
14
|
+
getFCP,
|
|
15
|
+
getLCP,
|
|
16
|
+
getTTFB
|
|
17
|
+
} = _ref;
|
|
18
|
+
getCLS(onPerfEntry);
|
|
19
|
+
getFID(onPerfEntry);
|
|
20
|
+
getFCP(onPerfEntry);
|
|
21
|
+
getLCP(onPerfEntry);
|
|
22
|
+
getTTFB(onPerfEntry);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var _default = exports.default = reportWebVitals;
|