@pautena/react-design-system 0.2.1 → 0.4.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.
Files changed (72) hide show
  1. package/dist/cjs/index.js +13 -4
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/enhanced-select/enhanced-select.d.ts +15 -0
  4. package/dist/cjs/types/components/enhanced-select/index.d.ts +1 -0
  5. package/dist/cjs/types/components/index.d.ts +1 -0
  6. package/dist/cjs/types/components/value-displays/index.d.ts +1 -0
  7. package/dist/cjs/types/components/value-displays/value-datetime/index.d.ts +1 -0
  8. package/dist/cjs/types/components/value-displays/value-datetime/value-datetime.d.ts +18 -0
  9. package/dist/cjs/types/generators/generators.mock.d.ts +9 -5
  10. package/dist/cjs/types/generators/generators.model.d.ts +25 -1
  11. package/dist/cjs/types/generators/model-router/model-router.types.d.ts +1 -0
  12. package/dist/cjs/types/generators/model-router/screens/details-screen.d.ts +1 -1
  13. package/dist/cjs/types/hooks/index.d.ts +1 -0
  14. package/dist/cjs/types/hooks/routing/index.d.ts +1 -0
  15. package/dist/cjs/types/hooks/routing/routing.hooks.d.ts +5 -0
  16. package/dist/cjs/types/providers/notification-center/index.d.ts +1 -0
  17. package/dist/cjs/types/providers/notification-center/notification-center.hooks.d.ts +6 -0
  18. package/dist/esm/index.js +13 -4
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/types/components/enhanced-select/enhanced-select.d.ts +15 -0
  21. package/dist/esm/types/components/enhanced-select/index.d.ts +1 -0
  22. package/dist/esm/types/components/index.d.ts +1 -0
  23. package/dist/esm/types/components/value-displays/index.d.ts +1 -0
  24. package/dist/esm/types/components/value-displays/value-datetime/index.d.ts +1 -0
  25. package/dist/esm/types/components/value-displays/value-datetime/value-datetime.d.ts +18 -0
  26. package/dist/esm/types/generators/generators.mock.d.ts +9 -5
  27. package/dist/esm/types/generators/generators.model.d.ts +25 -1
  28. package/dist/esm/types/generators/model-router/model-router.types.d.ts +1 -0
  29. package/dist/esm/types/generators/model-router/screens/details-screen.d.ts +1 -1
  30. package/dist/esm/types/hooks/index.d.ts +1 -0
  31. package/dist/esm/types/hooks/routing/index.d.ts +1 -0
  32. package/dist/esm/types/hooks/routing/routing.hooks.d.ts +5 -0
  33. package/dist/esm/types/providers/notification-center/index.d.ts +1 -0
  34. package/dist/esm/types/providers/notification-center/notification-center.hooks.d.ts +6 -0
  35. package/dist/index.d.ts +66 -2
  36. package/package.json +6 -2
  37. package/src/components/enhanced-select/enhanced-select.stories.tsx +70 -0
  38. package/src/components/enhanced-select/enhanced-select.test.tsx +90 -0
  39. package/src/components/enhanced-select/enhanced-select.tsx +84 -0
  40. package/src/components/enhanced-select/index.ts +1 -0
  41. package/src/components/index.ts +1 -0
  42. package/src/components/value-displays/index.ts +1 -0
  43. package/src/components/value-displays/value-datetime/index.ts +1 -0
  44. package/src/components/value-displays/value-datetime/value-datetime.stories.tsx +21 -0
  45. package/src/components/value-displays/value-datetime/value-datetime.test.tsx +23 -0
  46. package/src/components/value-displays/value-datetime/value-datetime.tsx +40 -0
  47. package/src/components/value-displays/value-text/{value-test.test.tsx → value-text.test.tsx} +0 -0
  48. package/src/generators/generators.mock.ts +56 -17
  49. package/src/generators/generators.model.ts +39 -1
  50. package/src/generators/model-form/model-form.stories.tsx +2 -2
  51. package/src/generators/model-form/model-form.test.tsx +39 -22
  52. package/src/generators/model-form/model-form.tsx +220 -33
  53. package/src/generators/model-router/model-router.test.tsx +285 -65
  54. package/src/generators/model-router/model-router.types.ts +4 -0
  55. package/src/generators/model-router/screens/add-screen.tsx +16 -20
  56. package/src/generators/model-router/screens/details-screen.tsx +3 -2
  57. package/src/generators/model-router/screens/list-screen.tsx +17 -0
  58. package/src/generators/model-router/screens/update-screen.tsx +22 -13
  59. package/src/generators/model-router/stories/model-router.stories.tsx +6 -2
  60. package/src/generators/object-details/object-details.tsx +5 -4
  61. package/src/hooks/index.ts +1 -0
  62. package/src/hooks/routing/index.ts +1 -0
  63. package/src/hooks/routing/routing.hooks.ts +23 -0
  64. package/src/hooks/routing/routing.test.tsx +83 -0
  65. package/src/providers/notification-center/index.ts +1 -0
  66. package/src/providers/notification-center/notification-center.hooks.ts +23 -0
  67. package/src/providers/notification-center/notification-center.test.tsx +87 -1
  68. package/src/storybook.tsx +10 -0
  69. package/src/tests/actions.ts +43 -0
  70. package/src/tests/assertions.ts +75 -1
  71. package/src/tests/index.ts +1 -0
  72. package/src/tests/testing-library.tsx +5 -1
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useState } from "react";
2
2
  import { DummyModelRouter, InternalModelRouter } from "./stories/model-router.stories";
3
3
  import {
4
4
  expectModelFieldInputExist,
@@ -9,16 +9,34 @@ import {
9
9
  TestRouter,
10
10
  expectModelFieldValue,
11
11
  expectModelFieldInputValue,
12
+ selectOption,
12
13
  } from "~/tests";
13
14
  import { data as mockData } from "./stories/templates";
14
15
  import userEvent from "@testing-library/user-event";
15
16
  import { getRandomItem } from "../../utils";
16
17
  import { Model } from "../generators.model";
17
- import { createModelInstance, MockInstance, mockModel } from "../generators.mock";
18
+ import {
19
+ BirthDateFormat,
20
+ createModelInstance,
21
+ MockInstance,
22
+ mockModel,
23
+ ReturnTimeFormat,
24
+ TradeDateFormat,
25
+ } from "../generators.mock";
18
26
  import { NotificationCenterProvider } from "../../providers";
19
27
  import { Box } from "@mui/system";
20
28
  import { Button } from "@mui/material";
21
- import { Route, Routes, useNavigate } from "react-router-dom";
29
+ import { useNavigate } from "react-router-dom";
30
+ import {
31
+ clearCheckbox,
32
+ clearMultiSelect,
33
+ expectAlert,
34
+ expectToHaveBeenCalledOnceWithMockInstance,
35
+ pickDatetime,
36
+ selectOptions,
37
+ } from "../../tests";
38
+ import { AddScreen, ListScreen, UpdateScreen } from "./screens";
39
+ import { IdleRequest, LoadingRequest, SuccessRequest } from "./model-router.types";
22
40
 
23
41
  const REQUEST_TIMEOUT = 20;
24
42
 
@@ -82,30 +100,6 @@ describe("ModelRouter", () => {
82
100
  expectNotToHaveMenuOption: ({ id }: { id: string }) => {
83
101
  expect(screen.queryByTestId(`options-${id}`)).not.toBeInTheDocument();
84
102
  },
85
- expectSubmitInstanceCall: (mockFn: jest.Mock, instance: MockInstance) => {
86
- expect(mockFn).toHaveBeenCalledTimes(1);
87
- expect(mockFn).toHaveBeenCalledWith({
88
- id: instance.id,
89
- firstName: instance.firstName,
90
- middleName: instance.middleName,
91
- lastName: instance.lastName,
92
- gender: instance.gender,
93
- age: instance.age.toString(),
94
- birthDate: instance.birthDate,
95
- car: {
96
- model: instance.car.model,
97
- manufacturer: instance.car.manufacturer,
98
- color: instance.car.color,
99
- type: instance.car.type,
100
- vin: instance.car.vin,
101
- vrm: instance.car.vrm,
102
- },
103
- quantity: instance.quantity.toString(),
104
- available: instance.available.toString(),
105
- currency: instance.currency,
106
- tradeDate: instance.tradeDate,
107
- });
108
- },
109
103
  };
110
104
 
111
105
  const actions = {
@@ -149,17 +143,18 @@ describe("ModelRouter", () => {
149
143
  const firstNameElement = screen.getByRole("textbox", { name: /first name/i });
150
144
  const middleNameElement = screen.getByRole("textbox", { name: /middle name/i });
151
145
  const lastNameElement = screen.getByRole("textbox", { name: /last name/i });
152
- const genderElement = screen.getByRole("textbox", { name: /gender/i });
146
+ const genderElement = screen.getByRole("button", { name: /gender/i });
153
147
  const ageElement = screen.getByRole("spinbutton", { name: /age/i });
154
148
  const birthDateElement = screen.getByRole("textbox", { name: /birth date/i });
155
- const manufacturerElement = screen.getByRole("textbox", { name: /manufacturer/i });
156
- const modelElement = screen.getByRole("textbox", { name: /model/i });
149
+ const manufacturerElement = screen.getByRole("button", { name: /manufacturer/i });
150
+ const modelElement = screen.getByRole("button", { name: /model/i });
157
151
  const colorElement = screen.getByRole("textbox", { name: /color/i });
158
- const typeElement = screen.getByRole("textbox", { name: /type/i });
152
+ const typeElement = screen.getByRole("button", { name: /type/i });
159
153
  const vinElement = screen.getByRole("textbox", { name: /vin/i });
160
154
  const vrmElement = screen.getByRole("textbox", { name: /vrm/i });
155
+ const timeReturnElement = screen.getByRole("textbox", { name: /return time/i });
161
156
  const quantityElement = screen.getByRole("spinbutton", { name: /q/i });
162
- const availableElement = screen.getByRole("textbox", { name: /available/i });
157
+ const availableElement = screen.getByRole("checkbox", { name: /available/i });
163
158
  const currencyElement = screen.getByRole("textbox", { name: /currency/i });
164
159
  const tradeDateElement = screen.getByRole("textbox", { name: /trade date/i });
165
160
 
@@ -168,37 +163,39 @@ describe("ModelRouter", () => {
168
163
  await userEvent.clear(firstNameElement);
169
164
  await userEvent.clear(middleNameElement);
170
165
  await userEvent.clear(lastNameElement);
171
- await userEvent.clear(genderElement);
172
166
  await userEvent.clear(ageElement);
173
167
  await userEvent.clear(birthDateElement);
174
- await userEvent.clear(manufacturerElement);
175
- await userEvent.clear(modelElement);
176
168
  await userEvent.clear(colorElement);
177
- await userEvent.clear(typeElement);
178
169
  await userEvent.clear(vinElement);
179
170
  await userEvent.clear(vrmElement);
180
171
  await userEvent.clear(quantityElement);
181
172
  await userEvent.clear(availableElement);
182
173
  await userEvent.clear(currencyElement);
183
174
  await userEvent.clear(tradeDateElement);
175
+ await userEvent.clear(timeReturnElement);
176
+ await clearCheckbox(availableElement);
177
+ await clearMultiSelect(typeElement);
184
178
  }
185
179
  await userEvent.type(idElement, instance.id);
186
180
  await userEvent.type(firstNameElement, instance.firstName);
187
181
  await userEvent.type(middleNameElement, instance.middleName);
188
182
  await userEvent.type(lastNameElement, instance.lastName);
189
- await userEvent.type(genderElement, instance.gender);
183
+ await selectOption(genderElement, instance.gender);
190
184
  await userEvent.type(ageElement, instance.age.toString());
191
- await userEvent.type(birthDateElement, instance.birthDate);
192
- await userEvent.type(modelElement, instance.car.model);
193
- await userEvent.type(manufacturerElement, instance.car.manufacturer);
185
+ pickDatetime(birthDateElement, instance.birthDate, BirthDateFormat);
186
+ await selectOption(modelElement, instance.car.model);
187
+ await selectOption(manufacturerElement, instance.car.manufacturer);
194
188
  await userEvent.type(colorElement, instance.car.color);
195
- await userEvent.type(typeElement, instance.car.type);
189
+ await selectOptions(typeElement, instance.car.type);
196
190
  await userEvent.type(vinElement, instance.car.vin);
197
191
  await userEvent.type(vrmElement, instance.car.vrm);
192
+ pickDatetime(timeReturnElement, instance.car.returnTime, ReturnTimeFormat);
198
193
  await userEvent.type(quantityElement, instance.quantity.toString());
199
- await userEvent.type(availableElement, instance.available.toString());
194
+ if (instance.available) {
195
+ await userEvent.click(availableElement);
196
+ }
200
197
  await userEvent.type(currencyElement, instance.currency);
201
- await userEvent.type(tradeDateElement, instance.tradeDate);
198
+ pickDatetime(tradeDateElement, instance.tradeDate, TradeDateFormat);
202
199
 
203
200
  submit && (await userEvent.click(screen.getByRole("button", { name: /save/i })));
204
201
 
@@ -296,6 +293,98 @@ describe("ModelRouter", () => {
296
293
  );
297
294
  };
298
295
 
296
+ const renderListScreen = () => {
297
+ const error = "Lorem ipsum error";
298
+
299
+ const TestComponent = () => {
300
+ const [deleteRequest, setDeleteRequest] = useState(IdleRequest);
301
+
302
+ return (
303
+ <NotificationCenterProvider>
304
+ <ListScreen
305
+ onRequestList={jest.fn()}
306
+ listData={[]}
307
+ onClickDeleteItem={jest.fn()}
308
+ listRequest={IdleRequest}
309
+ deleteRequest={deleteRequest}
310
+ modelName="Items"
311
+ model={mockModel}
312
+ />
313
+ <Box>
314
+ <Button onClick={() => setDeleteRequest(IdleRequest)}>idle</Button>
315
+ <Button onClick={() => setDeleteRequest(LoadingRequest)}>loading</Button>
316
+ <Button onClick={() => setDeleteRequest(SuccessRequest)}>success</Button>
317
+ <Button onClick={() => setDeleteRequest({ error })}>error</Button>
318
+ </Box>
319
+ </NotificationCenterProvider>
320
+ );
321
+ };
322
+
323
+ const instance = render(<TestComponent />);
324
+
325
+ return { ...instance, error, model: mockModel };
326
+ };
327
+
328
+ const renderAddScreen = () => {
329
+ const error = "Lorem ipsum error";
330
+
331
+ const TestComponent = () => {
332
+ const [newItemRequest, setNewItemRequest] = useState(IdleRequest);
333
+
334
+ return (
335
+ <NotificationCenterProvider>
336
+ <AddScreen
337
+ modelName="Items"
338
+ model={mockModel}
339
+ onSubmitNewItem={jest.fn()}
340
+ newItemRequest={newItemRequest}
341
+ />
342
+ <Box>
343
+ <Button onClick={() => setNewItemRequest(IdleRequest)}>idle</Button>
344
+ <Button onClick={() => setNewItemRequest(LoadingRequest)}>loading</Button>
345
+ <Button onClick={() => setNewItemRequest(SuccessRequest)}>success</Button>
346
+ <Button onClick={() => setNewItemRequest({ error })}>error</Button>
347
+ </Box>
348
+ </NotificationCenterProvider>
349
+ );
350
+ };
351
+
352
+ const instance = render(<TestComponent />, { router: "router" });
353
+
354
+ return { ...instance, error, model: mockModel };
355
+ };
356
+
357
+ const renderUpdateScreen = () => {
358
+ const error = "Lorem ipsum error";
359
+
360
+ const TestComponent = () => {
361
+ const [updateRequest, setUpdateRequest] = useState(IdleRequest);
362
+
363
+ return (
364
+ <NotificationCenterProvider>
365
+ <UpdateScreen
366
+ modelName="Items"
367
+ model={mockModel}
368
+ onSubmitUpdateItem={jest.fn()}
369
+ submitUpdateItemRequest={updateRequest}
370
+ updateItemRequest={IdleRequest}
371
+ onRequestUpdateItem={jest.fn()}
372
+ />
373
+ <Box>
374
+ <Button onClick={() => setUpdateRequest(IdleRequest)}>idle</Button>
375
+ <Button onClick={() => setUpdateRequest(LoadingRequest)}>loading</Button>
376
+ <Button onClick={() => setUpdateRequest(SuccessRequest)}>success</Button>
377
+ <Button onClick={() => setUpdateRequest({ error })}>error</Button>
378
+ </Box>
379
+ </NotificationCenterProvider>
380
+ );
381
+ };
382
+
383
+ const instance = render(<TestComponent />, { router: "router" });
384
+
385
+ return { ...instance, error, model: mockModel };
386
+ };
387
+
299
388
  describe("router screens", () => {
300
389
  it("would render the list screen by default", async () => {
301
390
  await renderComponent();
@@ -381,6 +470,26 @@ describe("ModelRouter", () => {
381
470
  await assertions.expectAddScreen();
382
471
  });
383
472
 
473
+ it("would be able to go back to the list sreen from the add screen", async () => {
474
+ renderComponentInsideRouter();
475
+
476
+ await actions.navigateToInternal();
477
+ await actions.navigateToAddScreen();
478
+ await userEvent.click(screen.getByRole("link", { name: "Items" }));
479
+
480
+ await assertions.expectListScreen();
481
+ });
482
+
483
+ it("would navigate to the add screen if I click the navigaiton path", async () => {
484
+ renderComponentInsideRouter();
485
+
486
+ await actions.navigateToInternal();
487
+ await actions.navigateToAddScreen();
488
+ await userEvent.click(screen.getByRole("link", { name: "Add new Items" }));
489
+
490
+ await assertions.expectAddScreen();
491
+ });
492
+
384
493
  it("would navigate to the detail screen", async () => {
385
494
  renderComponentInsideRouter();
386
495
  const {
@@ -393,6 +502,32 @@ describe("ModelRouter", () => {
393
502
  await assertions.expectDetailScreen({ id });
394
503
  });
395
504
 
505
+ it("would be able to go back to the list sreen from the details screen", async () => {
506
+ renderComponentInsideRouter();
507
+ const {
508
+ item: { firstName },
509
+ } = getRandomItem<MockInstance>(mockData);
510
+
511
+ await actions.navigateToInternal();
512
+ await actions.navigateToDetailScreen({ name: firstName });
513
+ await userEvent.click(screen.getByRole("link", { name: "Items" }));
514
+
515
+ await assertions.expectListScreen();
516
+ });
517
+
518
+ it("would navigate to the add screen if I click the navigation path", async () => {
519
+ renderComponentInsideRouter();
520
+ const {
521
+ item: { id, firstName },
522
+ } = getRandomItem<MockInstance>(mockData);
523
+
524
+ await actions.navigateToInternal();
525
+ await actions.navigateToDetailScreen({ name: firstName });
526
+ await userEvent.click(screen.getByRole("link", { name: id }));
527
+
528
+ await assertions.expectDetailScreen({ id });
529
+ });
530
+
396
531
  it("would navigate to the update screen", async () => {
397
532
  renderComponentInsideRouter();
398
533
  const {
@@ -404,6 +539,32 @@ describe("ModelRouter", () => {
404
539
 
405
540
  await assertions.expectUpdateScreen({ id });
406
541
  });
542
+
543
+ it("would be able to go back to the list sreen from the details screen", async () => {
544
+ renderComponentInsideRouter();
545
+ const {
546
+ item: { id },
547
+ } = getRandomItem<MockInstance>(mockData);
548
+
549
+ await actions.navigateToInternal();
550
+ await actions.navigateToUpdateScreen({ id });
551
+ await userEvent.click(screen.getByRole("link", { name: "Items" }));
552
+
553
+ await assertions.expectListScreen();
554
+ });
555
+
556
+ it("would navigate to the add screen if I click the navigation path", async () => {
557
+ renderComponentInsideRouter();
558
+ const {
559
+ item: { id },
560
+ } = getRandomItem<MockInstance>(mockData);
561
+
562
+ await actions.navigateToInternal();
563
+ await actions.navigateToUpdateScreen({ id });
564
+ await userEvent.click(screen.getByRole("link", { name: `Edit ${id}` }));
565
+
566
+ await assertions.expectUpdateScreen({ id });
567
+ });
407
568
  });
408
569
 
409
570
  describe("list screen", () => {
@@ -544,7 +705,7 @@ describe("ModelRouter", () => {
544
705
 
545
706
  const newInstance = await actions.fullfillModelForm({ model, submit: true });
546
707
 
547
- assertions.expectSubmitInstanceCall(onSubmitNewItem, newInstance);
708
+ expectToHaveBeenCalledOnceWithMockInstance(onSubmitNewItem, newInstance);
548
709
  });
549
710
 
550
711
  it("would show a loading indicator when the request is in progress", async () => {
@@ -555,24 +716,36 @@ describe("ModelRouter", () => {
555
716
  expectProgressIndicator();
556
717
  });
557
718
 
558
- // TODO it("would show a success message if the request finish with a success", async () => {
559
- // await renderComponent({ screen: "add" });
719
+ it("would show a success message if the request finish with a success", async () => {
720
+ renderAddScreen();
560
721
 
561
- // await actions.fullfillModelForm();
722
+ await userEvent.click(screen.getByRole("button", { name: /success/i }));
562
723
 
563
- // await expectAlert({
564
- // message: /item added successfully/i,
565
- // severity: "success",
566
- // });
567
- // });
724
+ await expectAlert({
725
+ message: /item added successfully/i,
726
+ severity: "success",
727
+ });
728
+ });
568
729
 
569
- // TODO it("would navigate to the list screen if the request finish with a success", async () => {
570
- // await renderComponent({ screen: "add" });
730
+ it("would navigate to the list screen if the request finish with a success", async () => {
731
+ const { history } = renderAddScreen();
571
732
 
572
- // await actions.fullfillModelForm();
733
+ await userEvent.click(screen.getByRole("button", { name: /success/i }));
573
734
 
574
- // await assertions.expectListScreen();
575
- // });
735
+ expect(history.location.pathname).toBe("/");
736
+ });
737
+
738
+ it("would show an error message if the request finish with an error", async () => {
739
+ const { error } = renderAddScreen();
740
+
741
+ await userEvent.click(screen.getByRole("button", { name: /error/i }));
742
+
743
+ await expectAlert({
744
+ title: /we had an error/i,
745
+ message: error,
746
+ severity: "error",
747
+ });
748
+ });
576
749
  });
577
750
 
578
751
  describe("details screen", () => {
@@ -707,12 +880,12 @@ describe("ModelRouter", () => {
707
880
  });
708
881
 
709
882
  it("would make a request with the new values when the form is submitted", async () => {
710
- const { model } = await renderComponent({ screen: "update" });
883
+ const { model, onSubmitUpdate } = await renderComponent({ screen: "update" });
711
884
 
712
885
  await waitForProgressIndicatorToBeRemoved();
713
- const newInstance = await actions.fullfillModelForm({ model, clear: true });
886
+ const newInstance = await actions.fullfillModelForm({ model, clear: true, submit: true });
714
887
 
715
- expectModelFieldInputValue(model.fields, newInstance);
888
+ expectToHaveBeenCalledOnceWithMockInstance(onSubmitUpdate, newInstance);
716
889
  });
717
890
 
718
891
  it("would show a loading indicator while the submit request is in progress", async () => {
@@ -724,13 +897,36 @@ describe("ModelRouter", () => {
724
897
  expectProgressIndicator();
725
898
  });
726
899
 
727
- it("would navigate to the list screen when the submit request finish", async () => {
728
- const { model, onSubmitUpdate } = await renderComponent({ screen: "update" });
900
+ it("would show a success message if the request finish with a success", async () => {
901
+ renderUpdateScreen();
729
902
 
730
- await waitForProgressIndicatorToBeRemoved();
731
- const newInstance = await actions.fullfillModelForm({ model, submit: true, clear: true });
903
+ await userEvent.click(screen.getByRole("button", { name: /success/i }));
904
+
905
+ await expectAlert({
906
+ title: /item updated/i,
907
+ message: /has been updated successfully/i,
908
+ severity: "success",
909
+ });
910
+ });
911
+
912
+ it("would navigate to the list screen if the request finish with a success", async () => {
913
+ const { history } = renderUpdateScreen();
914
+
915
+ await userEvent.click(screen.getByRole("button", { name: /success/i }));
732
916
 
733
- assertions.expectSubmitInstanceCall(onSubmitUpdate, newInstance);
917
+ expect(history.location.pathname).toBe("/");
918
+ });
919
+
920
+ it("would show an error message if the request finish with an error", async () => {
921
+ const { error } = renderUpdateScreen();
922
+
923
+ await userEvent.click(screen.getByRole("button", { name: /error/i }));
924
+
925
+ await expectAlert({
926
+ title: /we had an error/i,
927
+ message: error,
928
+ severity: "error",
929
+ });
734
930
  });
735
931
  });
736
932
 
@@ -776,6 +972,30 @@ describe("ModelRouter", () => {
776
972
 
777
973
  expect(screen.queryByRole("cell", { name: firstName })).not.toBeInTheDocument();
778
974
  });
975
+
976
+ it("would show a success message if the delete request finish with a success", async () => {
977
+ renderListScreen();
978
+
979
+ await userEvent.click(screen.getByRole("button", { name: /success/i }));
980
+
981
+ await expectAlert({
982
+ title: /item deleted/i,
983
+ message: /the item has been deleted successfully/i,
984
+ severity: "success",
985
+ });
986
+ });
987
+
988
+ it("would show a error message if the delete request finish with an error", async () => {
989
+ const { error } = renderListScreen();
990
+
991
+ await userEvent.click(screen.getByRole("button", { name: /error/i }));
992
+
993
+ await expectAlert({
994
+ title: /we had an error/i,
995
+ message: error,
996
+ severity: "error",
997
+ });
998
+ });
779
999
  });
780
1000
 
781
1001
  describe("modifying enabled features", () => {
@@ -12,3 +12,7 @@ export const IdleRequest: RequestState = {
12
12
  export const LoadingRequest: RequestState = {
13
13
  loading: true,
14
14
  };
15
+
16
+ export const SuccessRequest: RequestState = {
17
+ success: true,
18
+ };
@@ -1,11 +1,11 @@
1
- import React, { useEffect } from "react";
2
- import { useNavigate } from "react-router-dom";
1
+ import React from "react";
3
2
  import { Header, Content } from "~/components";
4
3
  import { BasicModelInstance, ModelForm } from "~/generators";
5
4
  import { HeaderLayout } from "../../../layouts";
6
- import { useNotificationCenter } from "../../../providers";
5
+ import { useNotifyWhenValueChanges } from "../../../providers";
7
6
  import { RequestState } from "../model-router.types";
8
7
  import { BaseScreenProps } from "./screens.types";
8
+ import { useNavigateWhenValueChanges } from "../../../hooks";
9
9
 
10
10
  export interface AddScreenProps<T extends BasicModelInstance> extends BaseScreenProps {
11
11
  /**
@@ -28,21 +28,17 @@ export const AddScreen = <T extends BasicModelInstance>({
28
28
  onSubmitNewItem,
29
29
  newItemRequest,
30
30
  }: AddScreenProps<T>) => {
31
- const navigate = useNavigate();
32
- const { show } = useNotificationCenter();
33
-
34
- useEffect(() => {
35
- if (newItemRequest.success) {
36
- show({ message: "Item added successfully", severity: "success" });
37
- navigate(`${basePath}/`);
38
- }
39
- }, [newItemRequest.success]);
40
-
41
- useEffect(() => {
42
- if (newItemRequest.error) {
43
- show({ title: "We had an error", message: newItemRequest.error, severity: "error" });
44
- }
45
- }, [newItemRequest.error]);
31
+ useNotifyWhenValueChanges(
32
+ { message: "Item added successfully", severity: "success" },
33
+ !!newItemRequest.success,
34
+ { from: false, to: true },
35
+ );
36
+ useNavigateWhenValueChanges(`${basePath}/`, !!newItemRequest.success, { from: false, to: true });
37
+ useNotifyWhenValueChanges(
38
+ { title: "We had an error", message: newItemRequest.error || "", severity: "error" },
39
+ !!newItemRequest.error,
40
+ { from: false, to: true },
41
+ );
46
42
 
47
43
  return (
48
44
  <HeaderLayout loading={newItemRequest.loading}>
@@ -53,12 +49,12 @@ export const AddScreen = <T extends BasicModelInstance>({
53
49
  {
54
50
  id: "list",
55
51
  text: modelName,
56
- link: "/",
52
+ link: `${basePath}/`,
57
53
  },
58
54
  {
59
55
  id: "add",
60
56
  text: `Add new ${modelName}`,
61
- link: "/add",
57
+ link: `${basePath}/add`,
62
58
  },
63
59
  ]}
64
60
  />
@@ -28,6 +28,7 @@ export interface DetailsScreenProps<T extends BasicModelInstance> extends BaseSc
28
28
  export const DetailsScreen = <T extends BasicModelInstance>({
29
29
  model,
30
30
  modelName,
31
+ basePath = "",
31
32
  onRequestItem,
32
33
  itemRequest,
33
34
  detailsItem,
@@ -47,12 +48,12 @@ export const DetailsScreen = <T extends BasicModelInstance>({
47
48
  {
48
49
  id: "list",
49
50
  text: modelName,
50
- link: "/",
51
+ link: `${basePath}/`,
51
52
  },
52
53
  {
53
54
  id: "detail",
54
55
  text: id,
55
- link: `/${id}`,
56
+ link: `${basePath}/${id}`,
56
57
  },
57
58
  ]}
58
59
  />
@@ -2,6 +2,7 @@ import React, { useEffect } from "react";
2
2
  import { useNavigate } from "react-router-dom";
3
3
  import { Content, Header, HeaderAction, TableList, TableRowOption } from "~/components";
4
4
  import { BasicModelInstance } from "~/generators";
5
+ import { useNotifyWhenValueChanges } from "~/providers";
5
6
  import { HeaderLayout } from "../../../layouts";
6
7
  import { RequestState } from "../model-router.types";
7
8
  import { BaseScreenProps } from "./screens.types";
@@ -57,6 +58,22 @@ export const ListScreen = <T extends BasicModelInstance>({
57
58
  onRequestList();
58
59
  }, []);
59
60
 
61
+ useNotifyWhenValueChanges(
62
+ {
63
+ title: "Item deleted",
64
+ message: "The item has been deleted successfully",
65
+ severity: "success",
66
+ },
67
+ !!deleteRequest.success,
68
+ { from: false, to: true },
69
+ );
70
+
71
+ useNotifyWhenValueChanges(
72
+ { title: "We had an error", message: deleteRequest.error || "", severity: "error" },
73
+ !!deleteRequest.error,
74
+ { from: false, to: true },
75
+ );
76
+
60
77
  const handleClickListItem = detailsFeature
61
78
  ? (item: T) => {
62
79
  navigate(`${basePath}/${item.id}`);
@@ -2,8 +2,9 @@ import React, { useEffect } from "react";
2
2
  import { useNavigate, useParams } from "react-router-dom";
3
3
  import { Content, Header } from "~/components";
4
4
  import { BasicModelInstance, ModelForm } from "~/generators";
5
+ import { useNavigateWhenValueChanges } from "~/hooks";
5
6
  import { HeaderLayout } from "../../../layouts";
6
- import { useNotificationCenter } from "../../../providers";
7
+ import { useNotificationCenter, useNotifyWhenValueChanges } from "../../../providers";
7
8
  import { RequestState } from "../model-router.types";
8
9
  import { BaseScreenProps } from "./screens.types";
9
10
 
@@ -55,16 +56,24 @@ export const UpdateScreen = <T extends BasicModelInstance>({
55
56
  onRequestUpdateItem(id);
56
57
  }, [id]);
57
58
 
58
- useEffect(() => {
59
- if (submitUpdateItemRequest.success) {
60
- show({
61
- title: "Item updated",
62
- message: `The item ${id} has been updated successfully`,
63
- severity: "success",
64
- });
65
- navigate(`${basePath}/`);
66
- }
67
- }, [submitUpdateItemRequest.success]);
59
+ useNotifyWhenValueChanges(
60
+ {
61
+ title: "Item updated",
62
+ message: `The item ${id} has been updated successfully`,
63
+ severity: "success",
64
+ },
65
+ !!submitUpdateItemRequest.success,
66
+ { from: false, to: true },
67
+ );
68
+ useNavigateWhenValueChanges(`${basePath}/`, !!submitUpdateItemRequest.success, {
69
+ from: false,
70
+ to: true,
71
+ });
72
+ useNotifyWhenValueChanges(
73
+ { title: "We had an error", message: submitUpdateItemRequest.error || "", severity: "error" },
74
+ !!submitUpdateItemRequest.error,
75
+ { from: false, to: true },
76
+ );
68
77
 
69
78
  return (
70
79
  <HeaderLayout loading={loading}>
@@ -75,12 +84,12 @@ export const UpdateScreen = <T extends BasicModelInstance>({
75
84
  {
76
85
  id: "list",
77
86
  text: modelName,
78
- link: "/",
87
+ link: `${basePath}/`,
79
88
  },
80
89
  {
81
90
  id: "update",
82
91
  text: `Edit ${id}`,
83
- link: `/${id}/update`,
92
+ link: `${basePath}/${id}/update`,
84
93
  },
85
94
  ]}
86
95
  />