@pautena/react-design-system 0.2.0 → 0.3.1

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 (65) 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/value-displays/index.d.ts +1 -0
  4. package/dist/cjs/types/components/value-displays/value-datetime/index.d.ts +1 -0
  5. package/dist/cjs/types/components/value-displays/value-datetime/value-datetime.d.ts +18 -0
  6. package/dist/cjs/types/generators/generators.mock.d.ts +9 -5
  7. package/dist/cjs/types/generators/generators.model.d.ts +25 -1
  8. package/dist/cjs/types/generators/model-router/index.d.ts +1 -0
  9. package/dist/cjs/types/generators/model-router/model-router.types.d.ts +1 -0
  10. package/dist/cjs/types/generators/model-router/screens/details-screen.d.ts +1 -1
  11. package/dist/cjs/types/hooks/index.d.ts +1 -0
  12. package/dist/cjs/types/hooks/routing/index.d.ts +1 -0
  13. package/dist/cjs/types/hooks/routing/routing.hooks.d.ts +5 -0
  14. package/dist/cjs/types/providers/notification-center/index.d.ts +1 -0
  15. package/dist/cjs/types/providers/notification-center/notification-center.hooks.d.ts +6 -0
  16. package/dist/esm/index.js +13 -4
  17. package/dist/esm/index.js.map +1 -1
  18. package/dist/esm/types/components/value-displays/index.d.ts +1 -0
  19. package/dist/esm/types/components/value-displays/value-datetime/index.d.ts +1 -0
  20. package/dist/esm/types/components/value-displays/value-datetime/value-datetime.d.ts +18 -0
  21. package/dist/esm/types/generators/generators.mock.d.ts +9 -5
  22. package/dist/esm/types/generators/generators.model.d.ts +25 -1
  23. package/dist/esm/types/generators/model-router/index.d.ts +1 -0
  24. package/dist/esm/types/generators/model-router/model-router.types.d.ts +1 -0
  25. package/dist/esm/types/generators/model-router/screens/details-screen.d.ts +1 -1
  26. package/dist/esm/types/hooks/index.d.ts +1 -0
  27. package/dist/esm/types/hooks/routing/index.d.ts +1 -0
  28. package/dist/esm/types/hooks/routing/routing.hooks.d.ts +5 -0
  29. package/dist/esm/types/providers/notification-center/index.d.ts +1 -0
  30. package/dist/esm/types/providers/notification-center/notification-center.hooks.d.ts +6 -0
  31. package/dist/index.d.ts +54 -2
  32. package/package.json +6 -2
  33. package/src/components/value-displays/index.ts +1 -0
  34. package/src/components/value-displays/value-datetime/index.ts +1 -0
  35. package/src/components/value-displays/value-datetime/value-datetime.stories.tsx +21 -0
  36. package/src/components/value-displays/value-datetime/value-datetime.test.tsx +23 -0
  37. package/src/components/value-displays/value-datetime/value-datetime.tsx +40 -0
  38. package/src/components/value-displays/value-text/{value-test.test.tsx → value-text.test.tsx} +0 -0
  39. package/src/generators/generators.mock.ts +56 -17
  40. package/src/generators/generators.model.ts +39 -1
  41. package/src/generators/model-form/model-form.stories.tsx +2 -2
  42. package/src/generators/model-form/model-form.test.tsx +39 -22
  43. package/src/generators/model-form/model-form.tsx +220 -33
  44. package/src/generators/model-router/index.ts +1 -0
  45. package/src/generators/model-router/model-router.test.tsx +338 -70
  46. package/src/generators/model-router/model-router.tsx +1 -1
  47. package/src/generators/model-router/model-router.types.ts +4 -0
  48. package/src/generators/model-router/screens/add-screen.tsx +16 -20
  49. package/src/generators/model-router/screens/details-screen.tsx +3 -2
  50. package/src/generators/model-router/screens/list-screen.tsx +17 -0
  51. package/src/generators/model-router/screens/update-screen.tsx +22 -13
  52. package/src/generators/model-router/stories/model-router.stories.tsx +54 -3
  53. package/src/generators/object-details/object-details.tsx +5 -4
  54. package/src/hooks/index.ts +1 -0
  55. package/src/hooks/routing/index.ts +1 -0
  56. package/src/hooks/routing/routing.hooks.ts +23 -0
  57. package/src/hooks/routing/routing.test.tsx +83 -0
  58. package/src/providers/notification-center/index.ts +1 -0
  59. package/src/providers/notification-center/notification-center.hooks.ts +23 -0
  60. package/src/providers/notification-center/notification-center.test.tsx +87 -1
  61. package/src/storybook.tsx +10 -0
  62. package/src/tests/actions.ts +43 -0
  63. package/src/tests/assertions.ts +75 -1
  64. package/src/tests/index.ts +1 -0
  65. package/src/tests/testing-library.tsx +5 -1
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { DummyModelRouter } from "./stories/model-router.stories";
1
+ import React, { useState } from "react";
2
+ import { DummyModelRouter, InternalModelRouter } from "./stories/model-router.stories";
3
3
  import {
4
4
  expectModelFieldInputExist,
5
5
  expectProgressIndicator,
@@ -9,15 +9,34 @@ import {
9
9
  TestRouter,
10
10
  expectModelFieldValue,
11
11
  expectModelFieldInputValue,
12
+ selectOption,
12
13
  } from "~/tests";
14
+ import { data as mockData } from "./stories/templates";
13
15
  import userEvent from "@testing-library/user-event";
14
16
  import { getRandomItem } from "../../utils";
15
17
  import { Model } from "../generators.model";
16
- 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";
17
26
  import { NotificationCenterProvider } from "../../providers";
18
27
  import { Box } from "@mui/system";
19
28
  import { Button } from "@mui/material";
20
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";
21
40
 
22
41
  const REQUEST_TIMEOUT = 20;
23
42
 
@@ -81,35 +100,11 @@ describe("ModelRouter", () => {
81
100
  expectNotToHaveMenuOption: ({ id }: { id: string }) => {
82
101
  expect(screen.queryByTestId(`options-${id}`)).not.toBeInTheDocument();
83
102
  },
84
- expectSubmitInstanceCall: (mockFn: jest.Mock, instance: MockInstance) => {
85
- expect(mockFn).toHaveBeenCalledTimes(1);
86
- expect(mockFn).toHaveBeenCalledWith({
87
- id: instance.id,
88
- firstName: instance.firstName,
89
- middleName: instance.middleName,
90
- lastName: instance.lastName,
91
- gender: instance.gender,
92
- age: instance.age.toString(),
93
- birthDate: instance.birthDate,
94
- car: {
95
- model: instance.car.model,
96
- manufacturer: instance.car.manufacturer,
97
- color: instance.car.color,
98
- type: instance.car.type,
99
- vin: instance.car.vin,
100
- vrm: instance.car.vrm,
101
- },
102
- quantity: instance.quantity.toString(),
103
- available: instance.available.toString(),
104
- currency: instance.currency,
105
- tradeDate: instance.tradeDate,
106
- });
107
- },
108
103
  };
109
104
 
110
105
  const actions = {
111
106
  navigateToAddScreen: async () => {
112
- await userEvent.click(screen.getByRole("button", { name: "Add" }));
107
+ await userEvent.click(await screen.findByRole("button", { name: "Add" }));
113
108
  },
114
109
  forceNavigateToAddScreen: async () => {
115
110
  await userEvent.click(screen.getByRole("button", { name: /force add/i }));
@@ -124,6 +119,9 @@ describe("ModelRouter", () => {
124
119
  navigateToDetailScreen: async ({ name }: { name: string }) => {
125
120
  await userEvent.click(await screen.findByRole("cell", { name }));
126
121
  },
122
+ navigateToInternal: async () => {
123
+ await userEvent.click(screen.getByRole("button", { name: /go to internal/i }));
124
+ },
127
125
  forceNavigateToDetailsScreen: async () => {
128
126
  await userEvent.click(screen.getByRole("button", { name: /force details/i }));
129
127
  },
@@ -145,17 +143,18 @@ describe("ModelRouter", () => {
145
143
  const firstNameElement = screen.getByRole("textbox", { name: /first name/i });
146
144
  const middleNameElement = screen.getByRole("textbox", { name: /middle name/i });
147
145
  const lastNameElement = screen.getByRole("textbox", { name: /last name/i });
148
- const genderElement = screen.getByRole("textbox", { name: /gender/i });
146
+ const genderElement = screen.getByRole("button", { name: /gender/i });
149
147
  const ageElement = screen.getByRole("spinbutton", { name: /age/i });
150
148
  const birthDateElement = screen.getByRole("textbox", { name: /birth date/i });
151
- const manufacturerElement = screen.getByRole("textbox", { name: /manufacturer/i });
152
- 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 });
153
151
  const colorElement = screen.getByRole("textbox", { name: /color/i });
154
- const typeElement = screen.getByRole("textbox", { name: /type/i });
152
+ const typeElement = screen.getByRole("button", { name: /type/i });
155
153
  const vinElement = screen.getByRole("textbox", { name: /vin/i });
156
154
  const vrmElement = screen.getByRole("textbox", { name: /vrm/i });
155
+ const timeReturnElement = screen.getByRole("textbox", { name: /return time/i });
157
156
  const quantityElement = screen.getByRole("spinbutton", { name: /q/i });
158
- const availableElement = screen.getByRole("textbox", { name: /available/i });
157
+ const availableElement = screen.getByRole("checkbox", { name: /available/i });
159
158
  const currencyElement = screen.getByRole("textbox", { name: /currency/i });
160
159
  const tradeDateElement = screen.getByRole("textbox", { name: /trade date/i });
161
160
 
@@ -164,37 +163,39 @@ describe("ModelRouter", () => {
164
163
  await userEvent.clear(firstNameElement);
165
164
  await userEvent.clear(middleNameElement);
166
165
  await userEvent.clear(lastNameElement);
167
- await userEvent.clear(genderElement);
168
166
  await userEvent.clear(ageElement);
169
167
  await userEvent.clear(birthDateElement);
170
- await userEvent.clear(manufacturerElement);
171
- await userEvent.clear(modelElement);
172
168
  await userEvent.clear(colorElement);
173
- await userEvent.clear(typeElement);
174
169
  await userEvent.clear(vinElement);
175
170
  await userEvent.clear(vrmElement);
176
171
  await userEvent.clear(quantityElement);
177
172
  await userEvent.clear(availableElement);
178
173
  await userEvent.clear(currencyElement);
179
174
  await userEvent.clear(tradeDateElement);
175
+ await userEvent.clear(timeReturnElement);
176
+ await clearCheckbox(availableElement);
177
+ await clearMultiSelect(typeElement);
180
178
  }
181
179
  await userEvent.type(idElement, instance.id);
182
180
  await userEvent.type(firstNameElement, instance.firstName);
183
181
  await userEvent.type(middleNameElement, instance.middleName);
184
182
  await userEvent.type(lastNameElement, instance.lastName);
185
- await userEvent.type(genderElement, instance.gender);
183
+ await selectOption(genderElement, instance.gender);
186
184
  await userEvent.type(ageElement, instance.age.toString());
187
- await userEvent.type(birthDateElement, instance.birthDate);
188
- await userEvent.type(modelElement, instance.car.model);
189
- 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);
190
188
  await userEvent.type(colorElement, instance.car.color);
191
- await userEvent.type(typeElement, instance.car.type);
189
+ await selectOptions(typeElement, instance.car.type);
192
190
  await userEvent.type(vinElement, instance.car.vin);
193
191
  await userEvent.type(vrmElement, instance.car.vrm);
192
+ pickDatetime(timeReturnElement, instance.car.returnTime, ReturnTimeFormat);
194
193
  await userEvent.type(quantityElement, instance.quantity.toString());
195
- await userEvent.type(availableElement, instance.available.toString());
194
+ if (instance.available) {
195
+ await userEvent.click(availableElement);
196
+ }
196
197
  await userEvent.type(currencyElement, instance.currency);
197
- await userEvent.type(tradeDateElement, instance.tradeDate);
198
+ pickDatetime(tradeDateElement, instance.tradeDate, TradeDateFormat);
198
199
 
199
200
  submit && (await userEvent.click(screen.getByRole("button", { name: /save/i })));
200
201
 
@@ -205,10 +206,10 @@ describe("ModelRouter", () => {
205
206
  const renderComponent = async ({
206
207
  router = "memory",
207
208
  screen = "initial",
208
- deleteFeature = true,
209
- updateFeature = true,
210
- addFeature = true,
211
- detailsFeature = true,
209
+ deleteFeature,
210
+ updateFeature,
211
+ addFeature,
212
+ detailsFeature,
212
213
  }: {
213
214
  router?: TestRouter;
214
215
  screen?: "initial" | "add" | "details" | "update";
@@ -283,6 +284,107 @@ describe("ModelRouter", () => {
283
284
  };
284
285
  };
285
286
 
287
+ const renderComponentInsideRouter = () => {
288
+ render(
289
+ <NotificationCenterProvider>
290
+ <InternalModelRouter />
291
+ </NotificationCenterProvider>,
292
+ { router: "memory" },
293
+ );
294
+ };
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
+
286
388
  describe("router screens", () => {
287
389
  it("would render the list screen by default", async () => {
288
390
  await renderComponent();
@@ -358,6 +460,113 @@ describe("ModelRouter", () => {
358
460
  });
359
461
  });
360
462
 
463
+ describe("inside another router", () => {
464
+ it("would navigate to the add screen", async () => {
465
+ renderComponentInsideRouter();
466
+
467
+ await actions.navigateToInternal();
468
+ await actions.navigateToAddScreen();
469
+
470
+ await assertions.expectAddScreen();
471
+ });
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
+
493
+ it("would navigate to the detail screen", async () => {
494
+ renderComponentInsideRouter();
495
+ const {
496
+ item: { id, firstName },
497
+ } = getRandomItem<MockInstance>(mockData);
498
+
499
+ await actions.navigateToInternal();
500
+ await actions.navigateToDetailScreen({ name: firstName });
501
+
502
+ await assertions.expectDetailScreen({ id });
503
+ });
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
+
531
+ it("would navigate to the update screen", async () => {
532
+ renderComponentInsideRouter();
533
+ const {
534
+ item: { id },
535
+ } = getRandomItem<MockInstance>(mockData);
536
+
537
+ await actions.navigateToInternal();
538
+ await actions.navigateToUpdateScreen({ id });
539
+
540
+ await assertions.expectUpdateScreen({ id });
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
+ });
568
+ });
569
+
361
570
  describe("list screen", () => {
362
571
  it("would call onRequestList when is mounted", async () => {
363
572
  const { onRequestList } = await renderComponent();
@@ -496,7 +705,7 @@ describe("ModelRouter", () => {
496
705
 
497
706
  const newInstance = await actions.fullfillModelForm({ model, submit: true });
498
707
 
499
- assertions.expectSubmitInstanceCall(onSubmitNewItem, newInstance);
708
+ expectToHaveBeenCalledOnceWithMockInstance(onSubmitNewItem, newInstance);
500
709
  });
501
710
 
502
711
  it("would show a loading indicator when the request is in progress", async () => {
@@ -507,24 +716,36 @@ describe("ModelRouter", () => {
507
716
  expectProgressIndicator();
508
717
  });
509
718
 
510
- // TODO it("would show a success message if the request finish with a success", async () => {
511
- // await renderComponent({ screen: "add" });
719
+ it("would show a success message if the request finish with a success", async () => {
720
+ renderAddScreen();
512
721
 
513
- // await actions.fullfillModelForm();
722
+ await userEvent.click(screen.getByRole("button", { name: /success/i }));
514
723
 
515
- // await expectAlert({
516
- // message: /item added successfully/i,
517
- // severity: "success",
518
- // });
519
- // });
724
+ await expectAlert({
725
+ message: /item added successfully/i,
726
+ severity: "success",
727
+ });
728
+ });
729
+
730
+ it("would navigate to the list screen if the request finish with a success", async () => {
731
+ const { history } = renderAddScreen();
520
732
 
521
- // TODO it("would navigate to the list screen if the request finish with a success", async () => {
522
- // await renderComponent({ screen: "add" });
733
+ await userEvent.click(screen.getByRole("button", { name: /success/i }));
523
734
 
524
- // await actions.fullfillModelForm();
735
+ expect(history.location.pathname).toBe("/");
736
+ });
525
737
 
526
- // await assertions.expectListScreen();
527
- // });
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
+ });
528
749
  });
529
750
 
530
751
  describe("details screen", () => {
@@ -659,12 +880,12 @@ describe("ModelRouter", () => {
659
880
  });
660
881
 
661
882
  it("would make a request with the new values when the form is submitted", async () => {
662
- const { model } = await renderComponent({ screen: "update" });
883
+ const { model, onSubmitUpdate } = await renderComponent({ screen: "update" });
663
884
 
664
885
  await waitForProgressIndicatorToBeRemoved();
665
- const newInstance = await actions.fullfillModelForm({ model, clear: true });
886
+ const newInstance = await actions.fullfillModelForm({ model, clear: true, submit: true });
666
887
 
667
- expectModelFieldInputValue(model.fields, newInstance);
888
+ expectToHaveBeenCalledOnceWithMockInstance(onSubmitUpdate, newInstance);
668
889
  });
669
890
 
670
891
  it("would show a loading indicator while the submit request is in progress", async () => {
@@ -676,13 +897,36 @@ describe("ModelRouter", () => {
676
897
  expectProgressIndicator();
677
898
  });
678
899
 
679
- it("would navigate to the list screen when the submit request finish", async () => {
680
- const { model, onSubmitUpdate } = await renderComponent({ screen: "update" });
900
+ it("would show a success message if the request finish with a success", async () => {
901
+ renderUpdateScreen();
681
902
 
682
- await waitForProgressIndicatorToBeRemoved();
683
- const newInstance = await actions.fullfillModelForm({ model, submit: true, clear: true });
903
+ await userEvent.click(screen.getByRole("button", { name: /success/i }));
684
904
 
685
- assertions.expectSubmitInstanceCall(onSubmitUpdate, newInstance);
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 }));
916
+
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
+ });
686
930
  });
687
931
  });
688
932
 
@@ -728,6 +972,30 @@ describe("ModelRouter", () => {
728
972
 
729
973
  expect(screen.queryByRole("cell", { name: firstName })).not.toBeInTheDocument();
730
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
+ });
731
999
  });
732
1000
 
733
1001
  describe("modifying enabled features", () => {
@@ -18,7 +18,7 @@ export type ModelRouterProps<T extends BasicModelInstance> = DetailsScreenProps<
18
18
  UpdateScreenProps<T>;
19
19
 
20
20
  export const ModelRouter = <T extends BasicModelInstance>(props: ModelRouterProps<T>) => {
21
- const { updateFeature, addFeature, detailsFeature } = props;
21
+ const { updateFeature = true, addFeature = true, detailsFeature = true } = props;
22
22
  return (
23
23
  <Routes>
24
24
  <Route path="" element={<ListScreen {...props} />} />
@@ -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}`);