theo_lafond_react_ci_cd 1.0.5 → 2.0.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/cypress.config.js +4 -2
- package/dist/App.js +54 -2
- package/dist/components/PersonForm.js +3 -3
- package/dist/domain/services/personService.js +59 -11
- package/dist/pages/Home.css +19 -0
- package/dist/pages/Home.js +26 -2
- package/package.json +12 -3
- package/react.Dockerfile +14 -0
package/cypress.config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const { defineConfig } = require('cypress')
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
allowCypressEnv: false,
|
|
@@ -6,7 +6,9 @@ export default defineConfig({
|
|
|
6
6
|
e2e: {
|
|
7
7
|
baseUrl: "http://localhost:5173/Test_cycle_TDD/",
|
|
8
8
|
setupNodeEvents(on, config) {
|
|
9
|
-
|
|
9
|
+
const { plugin: cypressGrepPlugin } = require('@cypress/grep/plugin')
|
|
10
|
+
cypressGrepPlugin(config)
|
|
11
|
+
return config
|
|
10
12
|
},
|
|
11
13
|
},
|
|
12
14
|
});
|
package/dist/App.js
CHANGED
|
@@ -104,7 +104,7 @@ function App(_ref) {
|
|
|
104
104
|
*/
|
|
105
105
|
var addPerson = /*#__PURE__*/function () {
|
|
106
106
|
var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(person) {
|
|
107
|
-
var existingEmails, newUser;
|
|
107
|
+
var existingEmails, newUser, users;
|
|
108
108
|
return _regenerator().w(function (_context2) {
|
|
109
109
|
while (1) switch (_context2.n) {
|
|
110
110
|
case 0:
|
|
@@ -118,7 +118,22 @@ function App(_ref) {
|
|
|
118
118
|
setPersons(function (prev) {
|
|
119
119
|
return [].concat(_toConsumableArray(prev), [newUser]);
|
|
120
120
|
});
|
|
121
|
+
if (!(newUser.firstName && newUser.email)) {
|
|
122
|
+
_context2.n = 2;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
setPersons(function (prev) {
|
|
126
|
+
return [].concat(_toConsumableArray(prev), [newUser]);
|
|
127
|
+
});
|
|
128
|
+
_context2.n = 4;
|
|
129
|
+
break;
|
|
121
130
|
case 2:
|
|
131
|
+
_context2.n = 3;
|
|
132
|
+
return (0, _personService.fetchUsers)();
|
|
133
|
+
case 3:
|
|
134
|
+
users = _context2.v;
|
|
135
|
+
setPersons(users);
|
|
136
|
+
case 4:
|
|
122
137
|
return _context2.a(2);
|
|
123
138
|
}
|
|
124
139
|
}, _callee2);
|
|
@@ -127,6 +142,42 @@ function App(_ref) {
|
|
|
127
142
|
return _ref2.apply(this, arguments);
|
|
128
143
|
};
|
|
129
144
|
}();
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Delete a person both via API and state update.
|
|
148
|
+
* @async
|
|
149
|
+
* @param {number} userId
|
|
150
|
+
*/
|
|
151
|
+
var deletePerson = /*#__PURE__*/function () {
|
|
152
|
+
var _ref3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(userId) {
|
|
153
|
+
var _t2;
|
|
154
|
+
return _regenerator().w(function (_context3) {
|
|
155
|
+
while (1) switch (_context3.p = _context3.n) {
|
|
156
|
+
case 0:
|
|
157
|
+
_context3.p = 0;
|
|
158
|
+
_context3.n = 1;
|
|
159
|
+
return (0, _personService.deleteUser)(userId);
|
|
160
|
+
case 1:
|
|
161
|
+
setPersons(function (prev) {
|
|
162
|
+
return prev.filter(function (p) {
|
|
163
|
+
return p.id !== userId;
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
_context3.n = 3;
|
|
167
|
+
break;
|
|
168
|
+
case 2:
|
|
169
|
+
_context3.p = 2;
|
|
170
|
+
_t2 = _context3.v;
|
|
171
|
+
throw _t2;
|
|
172
|
+
case 3:
|
|
173
|
+
return _context3.a(2);
|
|
174
|
+
}
|
|
175
|
+
}, _callee3, null, [[0, 2]]);
|
|
176
|
+
}));
|
|
177
|
+
return function deletePerson(_x2) {
|
|
178
|
+
return _ref3.apply(this, arguments);
|
|
179
|
+
};
|
|
180
|
+
}();
|
|
130
181
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactRouterDom.BrowserRouter, {
|
|
131
182
|
basename: basename,
|
|
132
183
|
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactRouterDom.Routes, {
|
|
@@ -135,7 +186,8 @@ function App(_ref) {
|
|
|
135
186
|
element: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Home["default"], {
|
|
136
187
|
persons: persons,
|
|
137
188
|
loading: loading,
|
|
138
|
-
serverError: serverError
|
|
189
|
+
serverError: serverError,
|
|
190
|
+
deletePerson: deletePerson
|
|
139
191
|
})
|
|
140
192
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactRouterDom.Route, {
|
|
141
193
|
path: "/register",
|
|
@@ -166,9 +166,9 @@ function PersonForm(_ref) {
|
|
|
166
166
|
case 3:
|
|
167
167
|
_context.p = 3;
|
|
168
168
|
_t = _context.v;
|
|
169
|
-
/*
|
|
169
|
+
/* c8 ignore start */
|
|
170
170
|
key = 'form';
|
|
171
|
-
/*
|
|
171
|
+
/* c8 ignore start */
|
|
172
172
|
if (_t.message.includes('SERVER_ERROR')) {
|
|
173
173
|
_reactToastify.toast.error(_errorMessages.errorMessages.SERVER_ERROR, {
|
|
174
174
|
toastId: "server-error-toast",
|
|
@@ -176,7 +176,7 @@ function PersonForm(_ref) {
|
|
|
176
176
|
});
|
|
177
177
|
} else if (_t.message.includes('FIRST_NAME')) key = 'firstName';else if (_t.message.includes('LAST_NAME')) key = 'lastName';else if (_t.message.includes('INVALID_EMAIL') || _t.message.includes('EMAIL_ALREADY_EXISTS')) key = 'email';else if (_t.message.includes('ZIP')) key = 'zip';else if (_t.message.includes('CITY')) key = 'city';else if (_t.message.includes('UNDERAGE') || _t.message.includes('FUTURE_DATE')) key = 'birthDate';
|
|
178
178
|
|
|
179
|
-
/*
|
|
179
|
+
/* c8 ignore start */
|
|
180
180
|
setErrors(_defineProperty({}, key, _t.message));
|
|
181
181
|
case 4:
|
|
182
182
|
return _context.a(2);
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.createUser = createUser;
|
|
7
|
+
exports.deleteUser = deleteUser;
|
|
7
8
|
exports.fetchUsers = fetchUsers;
|
|
8
9
|
var _axios = _interopRequireDefault(require("axios"));
|
|
9
10
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
|
|
@@ -12,10 +13,9 @@ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try {
|
|
|
12
13
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
13
14
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
14
15
|
/**
|
|
15
|
-
* Base API URL
|
|
16
|
-
* Note: JSONPlaceholder does not persist POST requests.
|
|
16
|
+
* Base API URL
|
|
17
17
|
*/
|
|
18
|
-
var API_BASE =
|
|
18
|
+
var API_BASE = import.meta.env.API_BASE || "http://localhost:8000";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Fetch all users from the API.
|
|
@@ -50,15 +50,15 @@ function _fetchUsers() {
|
|
|
50
50
|
return _axios["default"].get("".concat(API_BASE, "/users"));
|
|
51
51
|
case 1:
|
|
52
52
|
response = _context.v;
|
|
53
|
-
return _context.a(2, response.data.map(function (u) {
|
|
54
|
-
var _u$address, _u$address2;
|
|
53
|
+
return _context.a(2, response.data.utilisateurs.map(function (u) {
|
|
55
54
|
return {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
id: u[0],
|
|
56
|
+
firstName: u[1],
|
|
57
|
+
lastName: u[2],
|
|
58
|
+
email: u[3],
|
|
59
|
+
birthDate: u[4],
|
|
60
|
+
zip: u[5],
|
|
61
|
+
city: u[6]
|
|
62
62
|
};
|
|
63
63
|
}));
|
|
64
64
|
case 2:
|
|
@@ -75,6 +75,15 @@ function _fetchUsers() {
|
|
|
75
75
|
function createUser(_x) {
|
|
76
76
|
return _createUser.apply(this, arguments);
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Delete a user via API.
|
|
80
|
+
*
|
|
81
|
+
* @async
|
|
82
|
+
* @function deleteUser
|
|
83
|
+
* @param {number} userId - ID of the user to delete
|
|
84
|
+
* @returns {Promise<void>}
|
|
85
|
+
* @throws {Error} Throws "USER_NOT_FOUND" or "SERVER_ERROR"
|
|
86
|
+
*/
|
|
78
87
|
function _createUser() {
|
|
79
88
|
_createUser = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(person) {
|
|
80
89
|
var existingEmails,
|
|
@@ -123,4 +132,43 @@ function _createUser() {
|
|
|
123
132
|
}, _callee2, null, [[1, 3]]);
|
|
124
133
|
}));
|
|
125
134
|
return _createUser.apply(this, arguments);
|
|
135
|
+
}
|
|
136
|
+
function deleteUser(_x2) {
|
|
137
|
+
return _deleteUser.apply(this, arguments);
|
|
138
|
+
}
|
|
139
|
+
function _deleteUser() {
|
|
140
|
+
_deleteUser = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(userId) {
|
|
141
|
+
var _error$response2, status, _t3;
|
|
142
|
+
return _regenerator().w(function (_context3) {
|
|
143
|
+
while (1) switch (_context3.p = _context3.n) {
|
|
144
|
+
case 0:
|
|
145
|
+
_context3.p = 0;
|
|
146
|
+
_context3.n = 1;
|
|
147
|
+
return _axios["default"]["delete"]("".concat(API_BASE, "/users/").concat(userId));
|
|
148
|
+
case 1:
|
|
149
|
+
_context3.n = 5;
|
|
150
|
+
break;
|
|
151
|
+
case 2:
|
|
152
|
+
_context3.p = 2;
|
|
153
|
+
_t3 = _context3.v;
|
|
154
|
+
status = (_error$response2 = _t3.response) === null || _error$response2 === void 0 ? void 0 : _error$response2.status;
|
|
155
|
+
if (!(status === 404)) {
|
|
156
|
+
_context3.n = 3;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
throw new Error("USER_NOT_FOUND");
|
|
160
|
+
case 3:
|
|
161
|
+
if (!(status >= 500 && status < 600)) {
|
|
162
|
+
_context3.n = 4;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
throw new Error("SERVER_ERROR");
|
|
166
|
+
case 4:
|
|
167
|
+
throw new Error("SERVER_ERROR");
|
|
168
|
+
case 5:
|
|
169
|
+
return _context3.a(2);
|
|
170
|
+
}
|
|
171
|
+
}, _callee3, null, [[0, 2]]);
|
|
172
|
+
}));
|
|
173
|
+
return _deleteUser.apply(this, arguments);
|
|
126
174
|
}
|
package/dist/pages/Home.css
CHANGED
|
@@ -54,4 +54,23 @@
|
|
|
54
54
|
padding: 0.3rem 0;
|
|
55
55
|
border-bottom: 1px solid #eee;
|
|
56
56
|
font-size: 0.95rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.card .delete-btn {
|
|
60
|
+
background-color: transparent;
|
|
61
|
+
color: #3e999f;
|
|
62
|
+
border: none;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
padding: 0 0.3rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.card .delete-btn:hover {
|
|
68
|
+
background-color: transparent;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.delete-icon {
|
|
72
|
+
color: red;
|
|
73
|
+
font-size: 0.8rem;
|
|
74
|
+
vertical-align: middle;
|
|
75
|
+
transition: transform 0.2s;
|
|
57
76
|
}
|
package/dist/pages/Home.js
CHANGED
|
@@ -8,6 +8,7 @@ var _react = _interopRequireDefault(require("react"));
|
|
|
8
8
|
var _reactRouterDom = require("react-router-dom");
|
|
9
9
|
require("./Home.css");
|
|
10
10
|
var _reactToastify = require("react-toastify");
|
|
11
|
+
var _fa = require("react-icons/fa");
|
|
11
12
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
13
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
|
|
13
14
|
/**
|
|
@@ -25,7 +26,8 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default":
|
|
|
25
26
|
*/function Home(_ref) {
|
|
26
27
|
var persons = _ref.persons,
|
|
27
28
|
loading = _ref.loading,
|
|
28
|
-
serverError = _ref.serverError
|
|
29
|
+
serverError = _ref.serverError,
|
|
30
|
+
deletePerson = _ref.deletePerson;
|
|
29
31
|
var navigate = (0, _reactRouterDom.useNavigate)();
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -37,6 +39,19 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default":
|
|
|
37
39
|
var handleGoToForm = function handleGoToForm() {
|
|
38
40
|
navigate('/register');
|
|
39
41
|
};
|
|
42
|
+
var handleDelete = function handleDelete(user) {
|
|
43
|
+
if (window.confirm("Voulez-vous vraiment supprimer ".concat(user.firstName, " ").concat(user.lastName, " ?"))) {
|
|
44
|
+
deletePerson(user.id).then(function () {
|
|
45
|
+
_reactToastify.toast.success("Utilisateur supprimé avec succès !", {
|
|
46
|
+
toastId: "success-delete-user-toast"
|
|
47
|
+
});
|
|
48
|
+
})["catch"](function () {
|
|
49
|
+
_reactToastify.toast.error("Erreur lors de la suppression.", {
|
|
50
|
+
toastId: "error-delete-user-toast"
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
};
|
|
40
55
|
if (serverError) {
|
|
41
56
|
if (!_reactToastify.toast.isActive("server-error")) {
|
|
42
57
|
_reactToastify.toast.error(serverError, {
|
|
@@ -87,7 +102,16 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default":
|
|
|
87
102
|
className: "user-list",
|
|
88
103
|
children: persons.map(function (person, index) {
|
|
89
104
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("li", {
|
|
90
|
-
children: [person.firstName, " ", person.lastName, " (", person.email, ")"
|
|
105
|
+
children: [person.firstName, " ", person.lastName, " (", person.email, ")", /*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
|
|
106
|
+
className: "delete-btn",
|
|
107
|
+
onClick: function onClick() {
|
|
108
|
+
return handleDelete(person);
|
|
109
|
+
},
|
|
110
|
+
title: "Supprimer l'utilisateur",
|
|
111
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_fa.FaTrash, {
|
|
112
|
+
className: "delete-icon"
|
|
113
|
+
})
|
|
114
|
+
})]
|
|
91
115
|
}, index);
|
|
92
116
|
})
|
|
93
117
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)("p", {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "theo_lafond_react_ci_cd",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
@@ -17,15 +17,19 @@
|
|
|
17
17
|
"build": "vite build",
|
|
18
18
|
"lint": "eslint .",
|
|
19
19
|
"preview": "vite preview",
|
|
20
|
-
"test": "
|
|
20
|
+
"test": "vitest run --coverage",
|
|
21
21
|
"doc": "jsdoc -R ../README.md -c jsdoc.json -r -d ./public/docs --private",
|
|
22
22
|
"cypress": "cypress open",
|
|
23
|
+
"cy:run": "cypress run",
|
|
24
|
+
"cy:normal": "cypress run --expose grepTags=-@server-error",
|
|
25
|
+
"cy:server-error": "cypress run --expose grepTags=@server-error",
|
|
23
26
|
"build-npm": "cross-env NODE_ENV=production rimraf dist && npx babel src --out-dir dist --copy-files"
|
|
24
27
|
},
|
|
25
28
|
"dependencies": {
|
|
26
29
|
"axios": "^1.13.5",
|
|
27
30
|
"react": "^19.2.0",
|
|
28
31
|
"react-dom": "^19.2.0",
|
|
32
|
+
"react-icons": "^5.6.0",
|
|
29
33
|
"react-router-dom": "^7.13.0",
|
|
30
34
|
"react-toastify": "^11.0.5"
|
|
31
35
|
},
|
|
@@ -35,6 +39,7 @@
|
|
|
35
39
|
"@babel/plugin-transform-react-jsx": "^7.28.6",
|
|
36
40
|
"@babel/preset-env": "^7.29.0",
|
|
37
41
|
"@babel/preset-react": "^7.28.5",
|
|
42
|
+
"@cypress/grep": "^6.0.0",
|
|
38
43
|
"@eslint/js": "^9.39.1",
|
|
39
44
|
"@testing-library/jest-dom": "^6.9.1",
|
|
40
45
|
"@testing-library/react": "^16.3.2",
|
|
@@ -42,6 +47,7 @@
|
|
|
42
47
|
"@types/react": "^19.2.7",
|
|
43
48
|
"@types/react-dom": "^19.2.3",
|
|
44
49
|
"@vitejs/plugin-react": "^5.1.1",
|
|
50
|
+
"@vitest/coverage-v8": "4.1.0",
|
|
45
51
|
"babel-jest": "^30.2.0",
|
|
46
52
|
"cross-env": "^10.1.0",
|
|
47
53
|
"cypress": "^15.10.0",
|
|
@@ -52,8 +58,11 @@
|
|
|
52
58
|
"jest": "^30.2.0",
|
|
53
59
|
"jest-environment-jsdom": "^30.2.0",
|
|
54
60
|
"jsdoc": "^4.0.5",
|
|
61
|
+
"jsdom": "^29.0.1",
|
|
55
62
|
"react-test-renderer": "^19.2.4",
|
|
56
63
|
"rimraf": "^6.1.3",
|
|
57
|
-
"
|
|
64
|
+
"rollup": "^4.59.0",
|
|
65
|
+
"vite": "^7.3.1",
|
|
66
|
+
"vitest": "^4.1.0"
|
|
58
67
|
}
|
|
59
68
|
}
|
package/react.Dockerfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
FROM node:20-alpine
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
ENV PATH /app/node_modules/.bin:$PATH
|
|
6
|
+
|
|
7
|
+
# Copier seulement les fichiers de dépendances
|
|
8
|
+
COPY package.json pnpm-lock.yaml ./
|
|
9
|
+
|
|
10
|
+
# Installer pnpm et les dépendances
|
|
11
|
+
RUN npm install -g pnpm \
|
|
12
|
+
&& pnpm install --frozen-lockfile
|
|
13
|
+
|
|
14
|
+
EXPOSE 5173
|