@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/dist/App.css ADDED
@@ -0,0 +1,108 @@
1
+ .App {
2
+ text-align: center;
3
+ background-color: #282c34;
4
+ min-height: 100vh;
5
+ display: flex;
6
+ flex-direction: column;
7
+ align-items: center;
8
+ font-size: calc(10px + 2vmin);
9
+ color: white;
10
+
11
+ ul,
12
+ ol {
13
+ justify-self: center;
14
+ list-style: none;
15
+ padding-left: 0;
16
+ margin: 0;
17
+
18
+ li {
19
+ justify-self: start;
20
+ }
21
+ }
22
+ }
23
+
24
+ .button {
25
+ margin: 20px;
26
+ padding: 10px 20px;
27
+ font-size: 1.5rem;
28
+ cursor: pointer;
29
+ background-color: #61dafb;
30
+ border: none;
31
+ border-radius: 5px;
32
+ }
33
+
34
+ .submit-button {
35
+ margin-top: 40px;
36
+ }
37
+
38
+ .disabled {
39
+ background-color: #ccc;
40
+ cursor: not-allowed;
41
+ }
42
+
43
+ .counter {
44
+ font-size: 5rem;
45
+ font-weight: bold;
46
+ color: #fff;
47
+ }
48
+
49
+ .counter-container {
50
+ display: flex;
51
+ flex-direction: column;
52
+ align-items: center;
53
+ padding: 20px;
54
+ }
55
+
56
+ .form-container {
57
+ display: flex;
58
+ flex-direction: column;
59
+ align-items: center;
60
+ padding: 20px;
61
+ width: max-content;
62
+ }
63
+
64
+ .input-container {
65
+ display: grid;
66
+ grid-template-areas:
67
+ "label input"
68
+ "error error";
69
+ grid-template-columns: 1fr 2fr;
70
+ row-gap: 4px;
71
+ column-gap: 16px;
72
+ margin: 10px 0;
73
+ width: 100%;
74
+ justify-content: space-between;
75
+ align-items: center;
76
+ }
77
+
78
+ .label {
79
+ grid-area: label;
80
+ justify-self: start;
81
+ display: flex;
82
+ flex-wrap: wrap;
83
+ }
84
+
85
+ .input {
86
+ grid-area: input;
87
+ width: 100%;
88
+ cursor: pointer;
89
+ box-sizing: border-box;
90
+ padding: 10px;
91
+ font-size: 1rem;
92
+ border-radius: 5px;
93
+ }
94
+
95
+ .error-text {
96
+ grid-area: error;
97
+ display: flex;
98
+ justify-content: center;
99
+ color: red;
100
+ font-size: 1rem;
101
+ margin: 0;
102
+ height: fit-content;
103
+ }
104
+
105
+ .required-indicator {
106
+ font-size: 0.7rem;
107
+ margin-left: 4px;
108
+ }
package/dist/App.js ADDED
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = require("react");
8
+ var _wouter = require("wouter");
9
+ var _UserForm = require("./components/UserForm");
10
+ var _UsersList = require("./components/UsersList");
11
+ var _api = require("./infra/api");
12
+ require("./App.css");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ function App() {
15
+ const [users, setUsers] = (0, _react.useState)([]);
16
+ (0, _react.useEffect)(() => {
17
+ let isMounted = true;
18
+ const loadUsers = async () => {
19
+ try {
20
+ const fetchedUsers = await (0, _api.getAllUsers)();
21
+ if (!isMounted) return;
22
+ setUsers(Array.isArray(fetchedUsers) ? fetchedUsers : []);
23
+ } catch (error) {
24
+ if (!isMounted) return;
25
+ console.error("Failed to fetch users:", error);
26
+ alert("Une erreur est survenue lors du chargement des utilisateurs. Veuillez réessayer.");
27
+ setUsers([]);
28
+ }
29
+ };
30
+ loadUsers();
31
+ return () => {
32
+ isMounted = false;
33
+ };
34
+ }, []);
35
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_wouter.Router, {
36
+ base: "/react-test-lab",
37
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
38
+ className: "App",
39
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("h1", {
40
+ children: "Bienvenue sur le React Test Lab"
41
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_wouter.Switch, {
42
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_wouter.Route, {
43
+ path: "/",
44
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_UsersList.UsersList, {
45
+ users: users
46
+ })
47
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_wouter.Route, {
48
+ path: "/register",
49
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_UserForm.UserForm, {
50
+ setUsers: setUsers
51
+ })
52
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_wouter.Route, {
53
+ children: "404: No such page!"
54
+ })]
55
+ })]
56
+ })
57
+ });
58
+ }
59
+ var _default = exports.default = App;
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+
3
+ var _react = require("@testing-library/react");
4
+ var _userEvent = _interopRequireDefault(require("@testing-library/user-event"));
5
+ var _App = _interopRequireDefault(require("./App"));
6
+ var _axios = _interopRequireDefault(require("axios"));
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
+ jest.mock("axios");
10
+ beforeAll(() => {
11
+ jest.spyOn(window, "alert").mockImplementation(() => {});
12
+ });
13
+ const renderAndWait = async () => {
14
+ const utils = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_App.default, {}));
15
+ await (0, _react.waitFor)(() => expect(_axios.default.get).toHaveBeenCalled());
16
+ return utils;
17
+ };
18
+ describe("App component", () => {
19
+ beforeEach(() => {
20
+ jest.clearAllMocks();
21
+ _axios.default.get.mockResolvedValue({
22
+ data: []
23
+ });
24
+ });
25
+ afterAll(() => {
26
+ window.alert.mockRestore();
27
+ });
28
+ test("renders welcome title on App", async () => {
29
+ await renderAndWait();
30
+ const title = _react.screen.getByText("Bienvenue sur le React Test Lab");
31
+ expect(title).toBeInTheDocument();
32
+ });
33
+ test("renders an empty counter when no users are registered", async () => {
34
+ Object.defineProperty(window, "location", {
35
+ value: {
36
+ pathname: "/react-test-lab"
37
+ },
38
+ writable: true
39
+ });
40
+ _axios.default.get.mockImplementation(() => Promise.resolve({
41
+ data: []
42
+ }));
43
+ await renderAndWait();
44
+ const counter = _react.screen.getByTestId("users-counter");
45
+ expect(counter).toBeInTheDocument();
46
+ expect(counter).toHaveTextContent("Il n'y a aucun utilisateur enregistré");
47
+ });
48
+ test("renders a counter when a user is registered", async () => {
49
+ Object.defineProperty(window, "location", {
50
+ value: {
51
+ pathname: "/react-test-lab"
52
+ },
53
+ writable: true
54
+ });
55
+ const data = {
56
+ data: [{
57
+ name: "John Doe",
58
+ firstname: "John",
59
+ lastname: "Doe",
60
+ email: "john@example.com",
61
+ birth: "1990-01-01",
62
+ zipCode: "75010",
63
+ city: ""
64
+ }]
65
+ };
66
+ _axios.default.get.mockImplementation(() => Promise.resolve(data));
67
+ await renderAndWait();
68
+ const counter = _react.screen.getByTestId("users-counter");
69
+ expect(counter).toBeInTheDocument();
70
+ await (0, _react.waitFor)(() => expect(counter).toHaveTextContent("Il y a 1 utilisateur enregistré"));
71
+ });
72
+ test("renders a counter when several users are registered", async () => {
73
+ Object.defineProperty(window, "location", {
74
+ value: {
75
+ pathname: "/react-test-lab"
76
+ },
77
+ writable: true
78
+ });
79
+ const data = {
80
+ data: [{
81
+ name: "John Doe",
82
+ firstname: "John",
83
+ lastname: "Doe",
84
+ email: "john@example.com",
85
+ birth: "1990-01-01",
86
+ zipCode: "75010",
87
+ city: ""
88
+ }, {
89
+ name: "Jane Smith",
90
+ firstname: "Jane",
91
+ lastname: "Smith",
92
+ email: "jane@example.com",
93
+ birth: "1992-05-05",
94
+ zipCode: "75001",
95
+ city: ""
96
+ }]
97
+ };
98
+ _axios.default.get.mockImplementationOnce(() => Promise.resolve(data));
99
+ await renderAndWait();
100
+ const counter = _react.screen.getByTestId("users-counter");
101
+ expect(counter).toBeInTheDocument();
102
+ await (0, _react.waitFor)(() => expect(counter).toHaveTextContent("Il y a 2 utilisateurs enregistrés"));
103
+ });
104
+ test("displays UsersList component by default", async () => {
105
+ Object.defineProperty(window, "location", {
106
+ value: {
107
+ pathname: "/react-test-lab"
108
+ },
109
+ writable: true
110
+ });
111
+ await renderAndWait();
112
+ const userListTitle = _react.screen.getByText("Créez en un pour voir la liste des utilisateurs enregistrés !");
113
+ expect(userListTitle).toBeInTheDocument();
114
+ });
115
+ test("displays UserForm component when location is set to /register", async () => {
116
+ Object.defineProperty(window, "location", {
117
+ value: {
118
+ pathname: "/react-test-lab/register"
119
+ },
120
+ writable: true
121
+ });
122
+ await renderAndWait();
123
+ const userFormTitle = _react.screen.getByText("Enregistrer un nouvel utilisateur");
124
+ expect(userFormTitle).toBeInTheDocument();
125
+ });
126
+ test("displays 404 message for unknown routes", async () => {
127
+ Object.defineProperty(window, "location", {
128
+ value: {
129
+ pathname: "/react-test-lab/unknown"
130
+ },
131
+ writable: true
132
+ });
133
+ await renderAndWait();
134
+ const notFoundMessage = _react.screen.getByText("404: No such page!");
135
+ expect(notFoundMessage).toBeInTheDocument();
136
+ });
137
+ test("handles fetch errors by alerting and clearing users", async () => {
138
+ Object.defineProperty(window, "location", {
139
+ value: {
140
+ pathname: "/react-test-lab"
141
+ },
142
+ writable: true
143
+ });
144
+ const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
145
+ _axios.default.get.mockRejectedValueOnce(new Error("Network Error"));
146
+ await renderAndWait();
147
+ await (0, _react.waitFor)(() => expect(window.alert).toHaveBeenCalledWith("Une erreur est survenue lors du chargement des utilisateurs. Veuillez réessayer."));
148
+ const counter = _react.screen.getByTestId("users-counter");
149
+ expect(counter).toHaveTextContent("Il n'y a aucun utilisateur enregistré");
150
+ consoleSpy.mockRestore();
151
+ });
152
+ test("shows alert when API returns 500", async () => {
153
+ Object.defineProperty(window, "location", {
154
+ value: {
155
+ pathname: "/react-test-lab"
156
+ },
157
+ writable: true
158
+ });
159
+ const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
160
+ const error = new Error("Server error");
161
+ error.response = {
162
+ status: 500
163
+ };
164
+ _axios.default.get.mockRejectedValueOnce(error);
165
+ await renderAndWait();
166
+ await (0, _react.waitFor)(() => expect(window.alert).toHaveBeenCalledWith("Une erreur est survenue lors du chargement des utilisateurs. Veuillez réessayer."));
167
+ consoleSpy.mockRestore();
168
+ });
169
+ test("shows alert when API returns 400", async () => {
170
+ Object.defineProperty(window, "location", {
171
+ value: {
172
+ pathname: "/react-test-lab"
173
+ },
174
+ writable: true
175
+ });
176
+ const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
177
+ const error = new Error("Bad request");
178
+ error.response = {
179
+ status: 400
180
+ };
181
+ _axios.default.get.mockRejectedValueOnce(error);
182
+ await renderAndWait();
183
+ await (0, _react.waitFor)(() => expect(window.alert).toHaveBeenCalledWith("Une erreur est survenue lors du chargement des utilisateurs. Veuillez réessayer."));
184
+ consoleSpy.mockRestore();
185
+ });
186
+ test("handles non-array API response by clearing users", async () => {
187
+ Object.defineProperty(window, "location", {
188
+ value: {
189
+ pathname: "/react-test-lab"
190
+ },
191
+ writable: true
192
+ });
193
+ _axios.default.get.mockResolvedValueOnce({
194
+ data: {
195
+ unexpected: true
196
+ }
197
+ });
198
+ await renderAndWait();
199
+ const counter = _react.screen.getByTestId("users-counter");
200
+ expect(counter).toHaveTextContent("Il n'y a aucun utilisateur enregistré");
201
+ });
202
+ test("ignores late rejection after unmount", async () => {
203
+ Object.defineProperty(window, "location", {
204
+ value: {
205
+ pathname: "/react-test-lab"
206
+ },
207
+ writable: true
208
+ });
209
+ const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
210
+ let rejectFn;
211
+ _axios.default.get.mockReturnValueOnce(new Promise((_, reject) => {
212
+ rejectFn = reject;
213
+ }));
214
+ const {
215
+ unmount
216
+ } = await renderAndWait();
217
+ unmount();
218
+ rejectFn(new Error("Late failure"));
219
+ await (0, _react.waitFor)(() => {
220
+ expect(window.alert).not.toHaveBeenCalled();
221
+ expect(consoleSpy).not.toHaveBeenCalled();
222
+ });
223
+ consoleSpy.mockRestore();
224
+ });
225
+ });
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.UserForm = void 0;
7
+ var _react = require("react");
8
+ var _wouter = require("wouter");
9
+ var _validator = require("../utils/validator");
10
+ var _TextInput = require("./atomic/TextInput");
11
+ var _api = require("../infra/api");
12
+ var _jsxRuntime = require("react/jsx-runtime");
13
+ 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; }
14
+ 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; }
15
+ 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; }
16
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
17
+ 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); }
18
+ const INITIAL_PERSON = {
19
+ firstname: "",
20
+ lastname: "",
21
+ email: "",
22
+ birth: "",
23
+ city: "",
24
+ zipCode: ""
25
+ };
26
+ const UserForm = _ref => {
27
+ let {
28
+ setUsers
29
+ } = _ref;
30
+ const [disabled, setDisabled] = (0, _react.useState)(true);
31
+ const [person, setPerson] = (0, _react.useState)(INITIAL_PERSON);
32
+ const [personError, setPersonError] = (0, _react.useState)(INITIAL_PERSON);
33
+ const [, navigate] = (0, _wouter.useLocation)();
34
+ const handleChange = function (field, value) {
35
+ let validator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
36
+ setPerson(_objectSpread(_objectSpread({}, person), {}, {
37
+ [field]: value
38
+ }));
39
+ if (!validator) {
40
+ setPersonError(_objectSpread(_objectSpread({}, personError), {}, {
41
+ [field]: ""
42
+ }));
43
+ return;
44
+ }
45
+ try {
46
+ const validateData = field === "birth" ? {
47
+ birth: new Date(value)
48
+ } : field === "email" ? {
49
+ email: value
50
+ } : field === "zipCode" ? {
51
+ zipCode: value
52
+ } : value;
53
+ validator(validateData);
54
+ setPersonError(_objectSpread(_objectSpread({}, personError), {}, {
55
+ [field]: ""
56
+ }));
57
+ } catch (error) {
58
+ setPersonError(_objectSpread(_objectSpread({}, personError), {}, {
59
+ [field]: error.message
60
+ }));
61
+ }
62
+ };
63
+ (0, _react.useEffect)(() => {
64
+ if (!person.firstname || !person.lastname || !person.birth || !new Date(person.birth) instanceof Date || !person.email || !person.zipCode) return;
65
+ try {
66
+ if ((0, _validator.validateIndentity)(person) && (0, _validator.validateAge)(_objectSpread(_objectSpread({}, person), {}, {
67
+ birth: new Date(person.birth)
68
+ })) && (0, _validator.validateZipCode)(person) && (0, _validator.validateEmail)(person)) return setDisabled(false);
69
+ } catch (error) {
70
+ return setDisabled(true);
71
+ }
72
+ }, [person]);
73
+ const onSubmit = async e => {
74
+ e.preventDefault();
75
+ const user = _objectSpread({
76
+ name: "".concat(person.firstname, " ").concat(person.lastname)
77
+ }, person);
78
+ try {
79
+ const createdUser = await (0, _api.createOneUser)(user);
80
+ setUsers(prev => {
81
+ const updatedUsers = [...prev, createdUser];
82
+ return updatedUsers;
83
+ });
84
+ } catch (error) {
85
+ console.error("Failed to create user:", error);
86
+ alert("Une erreur est survenue lors de la création de l'utilisateur. Veuillez réessayer.");
87
+ return;
88
+ }
89
+ setPerson(INITIAL_PERSON);
90
+ setDisabled(true);
91
+ navigate("/");
92
+ };
93
+ const requiredIndicator = /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
94
+ className: "required-indicator",
95
+ "aria-hidden": "true",
96
+ children: "*"
97
+ });
98
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
99
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("h3", {
100
+ className: "form-title",
101
+ children: "Enregistrer un nouvel utilisateur"
102
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("form", {
103
+ action: "/",
104
+ method: "get",
105
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
106
+ className: "form-container",
107
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_TextInput.TextInput, {
108
+ label: "Pr\xE9nom",
109
+ id: "firstname",
110
+ value: person.firstname,
111
+ onChange: e => handleChange("firstname", e.target.value, _validator.validateName),
112
+ testId: "firstname-input",
113
+ errorTestId: "firstname-error-text",
114
+ errorText: personError.firstname,
115
+ required: true,
116
+ requiredIndicator: requiredIndicator
117
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_TextInput.TextInput, {
118
+ label: "Nom",
119
+ id: "lastname",
120
+ type: "text",
121
+ value: person.lastname,
122
+ onChange: e => handleChange("lastname", e.target.value, _validator.validateName),
123
+ testId: "lastname-input",
124
+ errorTestId: "lastname-error-text",
125
+ errorText: personError.lastname,
126
+ required: true,
127
+ requiredIndicator: requiredIndicator
128
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_TextInput.TextInput, {
129
+ label: "Email",
130
+ id: "email",
131
+ type: "email",
132
+ value: person.email,
133
+ onChange: e => handleChange("email", e.target.value, _validator.validateEmail),
134
+ testId: "email-input",
135
+ errorTestId: "email-error-text",
136
+ errorText: personError.email,
137
+ required: true,
138
+ requiredIndicator: requiredIndicator
139
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_TextInput.TextInput, {
140
+ label: "Date de naissance",
141
+ id: "birthdate",
142
+ type: "date",
143
+ value: person.birth,
144
+ onChange: e => handleChange("birth", e.target.value, _validator.validateAge),
145
+ testId: "birth-input",
146
+ errorTestId: "birth-error-text",
147
+ errorText: personError.birth,
148
+ required: true,
149
+ requiredIndicator: requiredIndicator
150
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_TextInput.TextInput, {
151
+ label: "Ville",
152
+ id: "city",
153
+ value: person.city,
154
+ onChange: e => handleChange("city", e.target.value),
155
+ testId: "city-input",
156
+ errorTestId: "city-error-text",
157
+ errorText: personError.city
158
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_TextInput.TextInput, {
159
+ label: "Code postal",
160
+ id: "zipcode",
161
+ value: person.zipCode,
162
+ onChange: e => handleChange("zipCode", e.target.value, _validator.validateZipCode),
163
+ testId: "zip-input",
164
+ errorTestId: "zip-error-text",
165
+ errorText: personError.zipCode,
166
+ required: true,
167
+ requiredIndicator: requiredIndicator
168
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
169
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
170
+ "data-testid": "back-button",
171
+ className: "button button-secondary",
172
+ type: "button",
173
+ onClick: () => navigate("/"),
174
+ children: "Retour"
175
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
176
+ "data-testid": "submit-button",
177
+ className: "button submit-button ".concat(disabled && "disabled"),
178
+ type: "submit",
179
+ onClick: onSubmit,
180
+ disabled: disabled,
181
+ children: "Enregistrer l'utilisateur"
182
+ })]
183
+ })]
184
+ })
185
+ })]
186
+ });
187
+ };
188
+ exports.UserForm = UserForm;