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 CHANGED
@@ -1,4 +1,4 @@
1
- import { defineConfig } from "cypress";
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
- // implement node event listeners here
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
- /* istanbul ignore next */
169
+ /* c8 ignore start */
170
170
  key = 'form';
171
- /* istanbul ignore next */
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
- /* istanbul ignore next */
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 for JSONPlaceholder.
16
- * Note: JSONPlaceholder does not persist POST requests.
16
+ * Base API URL
17
17
  */
18
- var API_BASE = "https://jsonplaceholder.typicode.com";
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
- firstName: u.name.split(' ')[0] || '',
57
- lastName: u.name.split(' ')[1] || '',
58
- email: u.email,
59
- birthDate: '',
60
- zip: ((_u$address = u.address) === null || _u$address === void 0 ? void 0 : _u$address.zipcode) || '',
61
- city: ((_u$address2 = u.address) === null || _u$address2 === void 0 ? void 0 : _u$address2.city) || ''
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
  }
@@ -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
  }
@@ -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": "1.0.5",
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": "jest --coverage",
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
- "vite": "^7.3.1"
64
+ "rollup": "^4.59.0",
65
+ "vite": "^7.3.1",
66
+ "vitest": "^4.1.0"
58
67
  }
59
68
  }
@@ -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