@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
@@ -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
  />
@@ -1,6 +1,10 @@
1
1
  import React from "react";
2
2
  import { ComponentMeta } from "@storybook/react";
3
- import { withMemoryRouter, withNotificationCenter } from "../../../storybook";
3
+ import {
4
+ withLocalizationProvider,
5
+ withMemoryRouter,
6
+ withNotificationCenter,
7
+ } from "../../../storybook";
4
8
  import { ModelRouter } from "../model-router";
5
9
  import { IdleRequest } from "../model-router.types";
6
10
  import { MockInstance, mockModel } from "../../generators.mock";
@@ -16,10 +20,13 @@ import {
16
20
  onSubmitUpdateAction,
17
21
  REQUEST_TIMEOUT,
18
22
  } from "./templates";
23
+ import { Route, Routes, useNavigate } from "react-router-dom";
24
+ import { Box, Button, Typography } from "@mui/material";
19
25
 
20
26
  interface DummyModelRouterProps {
21
27
  requestTimeout: number;
22
28
  initialData: MockInstance[];
29
+ basePath?: string;
23
30
  deleteFeature?: boolean;
24
31
  updateFeature?: boolean;
25
32
  addFeature?: boolean;
@@ -36,6 +43,7 @@ export const DummyModelRouter = (args: DummyModelRouterProps) => {
36
43
  const {
37
44
  requestTimeout,
38
45
  initialData,
46
+ basePath = "",
39
47
  deleteFeature = true,
40
48
  updateFeature = true,
41
49
  addFeature = true,
@@ -130,6 +138,7 @@ export const DummyModelRouter = (args: DummyModelRouterProps) => {
130
138
  {...args}
131
139
  modelName="Items"
132
140
  model={mockModel}
141
+ basePath={basePath}
133
142
  deleteFeature={deleteFeature}
134
143
  updateFeature={updateFeature}
135
144
  addFeature={addFeature}
@@ -163,13 +172,55 @@ const args: DummyModelRouterProps = {
163
172
  onSubmitUpdateAction,
164
173
  onRequestDeleteAction,
165
174
  };
166
-
167
175
  DummyModelRouter.args = args;
168
176
 
177
+ export const InternalModelRouter = () => {
178
+ const navigate = useNavigate();
179
+
180
+ return (
181
+ <Routes>
182
+ <Route
183
+ path="/"
184
+ element={
185
+ <Box>
186
+ <Typography>Internal model route</Typography>
187
+ <Button variant="contained" onClick={() => navigate("/internal")}>
188
+ Go to Internal
189
+ </Button>
190
+ </Box>
191
+ }
192
+ />
193
+ <Route
194
+ path="/internal/*"
195
+ element={
196
+ <ModelRouter
197
+ modelName="Items"
198
+ model={mockModel}
199
+ basePath="/internal"
200
+ onRequestItem={() => null}
201
+ itemRequest={IdleRequest}
202
+ onRequestList={() => null}
203
+ listData={data}
204
+ onClickDeleteItem={() => null}
205
+ listRequest={IdleRequest}
206
+ deleteRequest={IdleRequest}
207
+ onSubmitNewItem={() => null}
208
+ newItemRequest={IdleRequest}
209
+ onSubmitUpdateItem={() => null}
210
+ submitUpdateItemRequest={IdleRequest}
211
+ updateItemRequest={IdleRequest}
212
+ onRequestUpdateItem={() => null}
213
+ />
214
+ }
215
+ />
216
+ </Routes>
217
+ );
218
+ };
219
+
169
220
  export default {
170
221
  title: "Generators/ModelRouter",
171
222
  component: DummyModelRouter,
172
- decorators: [withMemoryRouter(), withNotificationCenter],
223
+ decorators: [withMemoryRouter(), withNotificationCenter, withLocalizationProvider],
173
224
  parameters: {
174
225
  layout: "fullscreen",
175
226
  },
@@ -6,16 +6,17 @@ import {
6
6
  ValueBoolean,
7
7
  ValueCard,
8
8
  ValueText,
9
+ ValueDatetime,
9
10
  } from "../../components";
10
11
  import { ModelField, GroupField, Model, BasicModelInstance } from "../generators.model";
11
12
 
12
- const singleDetailValueFactory = <T extends BasicModelInstance>(
13
- { id, name, type }: ModelField,
14
- instance: T,
15
- ) => {
13
+ const singleDetailValueFactory = <T extends BasicModelInstance>(field: ModelField, instance: T) => {
14
+ const { id, name, type } = field;
16
15
  const value = instance[id];
17
16
  if (type === "boolean") {
18
17
  return <ValueBoolean label={name} value={value} />;
18
+ } else if (type === "date" || type === "time" || type === "datetime") {
19
+ return <ValueDatetime label={name} value={value} format={field.format} />;
19
20
  }
20
21
  return <ValueText label={name} value={value?.toString()} />;
21
22
  };
@@ -0,0 +1 @@
1
+ export * from "./routing";
@@ -0,0 +1 @@
1
+ export * from "./routing.hooks";
@@ -0,0 +1,23 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useNavigate } from "react-router-dom";
3
+
4
+ export interface NavigateWhenValueChangesOptions<T> {
5
+ from: T;
6
+ to: T;
7
+ }
8
+
9
+ export const useNavigateWhenValueChanges = <T>(
10
+ path: string,
11
+ value: T | undefined,
12
+ { from, to }: NavigateWhenValueChangesOptions<T>,
13
+ ) => {
14
+ const prevRef = useRef<T>();
15
+ const navigate = useNavigate();
16
+
17
+ useEffect(() => {
18
+ if (prevRef.current === from && value === to) {
19
+ navigate(path);
20
+ }
21
+ prevRef.current = value;
22
+ }, [value]);
23
+ };
@@ -0,0 +1,83 @@
1
+ import { Box, Button, Typography } from "@mui/material";
2
+ import React, { useState } from "react";
3
+ import { Routes, Route } from "react-router-dom";
4
+ import { render, screen } from "../../tests";
5
+ import { useNavigateWhenValueChanges } from "./routing.hooks";
6
+ import userEvent from "@testing-library/user-event";
7
+
8
+ describe("useNavigateWhenValueChanges", () => {
9
+ const renderHook = ({ to, from }: { to: boolean; from: boolean }) => {
10
+ const DummyComponent = () => {
11
+ const [value, setValue] = useState<boolean | undefined>(false);
12
+ useNavigateWhenValueChanges("/destination", value, { from, to });
13
+
14
+ return (
15
+ <Box>
16
+ <Button onClick={() => setValue(undefined)}>undefined</Button>
17
+ <Button onClick={() => setValue(false)}>false</Button>
18
+ <Button onClick={() => setValue(true)}>true</Button>
19
+ </Box>
20
+ );
21
+ };
22
+
23
+ const DummyRoute = ({ label }: { label: string }) => {
24
+ return <Typography>{label}</Typography>;
25
+ };
26
+
27
+ const { history } = render(
28
+ <>
29
+ <Routes>
30
+ <Route path="" element={<DummyRoute label="initial" />} />
31
+ <Route path="" element={<DummyRoute label="destination" />} />
32
+ </Routes>
33
+ <DummyComponent />
34
+ </>,
35
+ );
36
+
37
+ return { history };
38
+ };
39
+
40
+ const changeValueTo = async (value: "undefined" | "false" | "true") => {
41
+ await userEvent.click(screen.getByRole("button", { name: value }));
42
+ };
43
+
44
+ it("wouldn't change the route when method when is rendered", () => {
45
+ const { history } = renderHook({ to: true, from: false });
46
+
47
+ expect(history.location.pathname).toBe("/");
48
+ });
49
+
50
+ it("wouldn't change the route when it changes to a value that is not to", async () => {
51
+ const { history } = renderHook({ to: true, from: false });
52
+
53
+ await changeValueTo("false");
54
+
55
+ expect(history.location.pathname).toBe("/");
56
+ });
57
+
58
+ it("wouldn't change the route when value changes to a value that is not to", async () => {
59
+ const { history } = renderHook({ to: true, from: false });
60
+
61
+ await changeValueTo("false");
62
+
63
+ expect(history.location.pathname).toBe("/");
64
+ });
65
+
66
+ it("wouldn't change the route when value changes from undefined to to", async () => {
67
+ const { history } = renderHook({ to: true, from: false });
68
+
69
+ await changeValueTo("undefined");
70
+ await changeValueTo("true");
71
+
72
+ expect(history.location.pathname).toBe("/");
73
+ });
74
+
75
+ it("would change the route when value changes from to to", async () => {
76
+ const { history } = renderHook({ to: true, from: false });
77
+
78
+ await changeValueTo("false");
79
+ await changeValueTo("true");
80
+
81
+ expect(history.location.pathname).toBe("/destination");
82
+ });
83
+ });
@@ -1,2 +1,3 @@
1
1
  export * from "./notification-center.provider";
2
2
  export * from "./notification-center.context";
3
+ export * from "./notification-center.hooks";
@@ -0,0 +1,23 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useNotificationCenter, Notification } from "./notification-center.context";
3
+
4
+ export interface NotifyWhenValueChangesOptions<T> {
5
+ from: T;
6
+ to: T;
7
+ }
8
+
9
+ export const useNotifyWhenValueChanges = <T>(
10
+ notification: Notification,
11
+ value: T | undefined,
12
+ { from, to }: NotifyWhenValueChangesOptions<T>,
13
+ ) => {
14
+ const prevRef = useRef<T>();
15
+ const { show } = useNotificationCenter();
16
+
17
+ useEffect(() => {
18
+ if (prevRef.current === from && value === to) {
19
+ show(notification);
20
+ }
21
+ prevRef.current = value;
22
+ }, [value]);
23
+ };
@@ -1,12 +1,15 @@
1
- import React from "react";
1
+ import React, { useState } from "react";
2
2
  import userEvent from "@testing-library/user-event";
3
3
  import { AlertColor, Box, Button } from "@mui/material";
4
4
  import { render, screen, waitForElementToBeRemoved, expectAlert } from "../../tests";
5
5
  import { NotificationCenterProvider } from "./notification-center.provider";
6
6
  import {
7
+ NotificationCenterContext,
7
8
  NotificationCenterProviderUndefinedError,
8
9
  useNotificationCenter,
10
+ Notification,
9
11
  } from "./notification-center.context";
12
+ import { useNotifyWhenValueChanges } from "./notification-center.hooks";
10
13
 
11
14
  describe("NotificationCenterProvider", () => {
12
15
  const renderComponent = ({ autoHideDuration }: { autoHideDuration?: number } = {}) => {
@@ -110,3 +113,86 @@ describe("useNotificationCenter", () => {
110
113
  }
111
114
  });
112
115
  });
116
+
117
+ describe("useNotifyWhenValueChanges", () => {
118
+ const DUMMY_NOTIFICATION: Notification = {
119
+ title: "Hello World",
120
+ message: "Lorem ipsum sit amet",
121
+ severity: "info",
122
+ };
123
+
124
+ const renderHook = ({ to, from }: { to: boolean; from: boolean }) => {
125
+ const show = jest.fn();
126
+
127
+ const DummyComponent = () => {
128
+ const [value, setValue] = useState<boolean | undefined>(false);
129
+ useNotifyWhenValueChanges(DUMMY_NOTIFICATION, value, { from, to });
130
+
131
+ return (
132
+ <Box>
133
+ <Button onClick={() => setValue(undefined)}>undefined</Button>
134
+ <Button onClick={() => setValue(false)}>false</Button>
135
+ <Button onClick={() => setValue(true)}>true</Button>
136
+ </Box>
137
+ );
138
+ };
139
+
140
+ render(
141
+ <NotificationCenterContext.Provider
142
+ value={{
143
+ show,
144
+ hide: jest.fn(),
145
+ }}
146
+ >
147
+ <DummyComponent />
148
+ </NotificationCenterContext.Provider>,
149
+ );
150
+
151
+ return { show };
152
+ };
153
+
154
+ const changeValueTo = async (value: "undefined" | "false" | "true") => {
155
+ await userEvent.click(screen.getByRole("button", { name: value }));
156
+ };
157
+
158
+ it("wouldn't call the show method when is rendered", () => {
159
+ const { show } = renderHook({ to: true, from: false });
160
+
161
+ expect(show).not.toHaveBeenCalled();
162
+ });
163
+
164
+ it("wouldn't call the show method when it changes to a value that is not to", async () => {
165
+ const { show } = renderHook({ to: true, from: false });
166
+
167
+ await changeValueTo("false");
168
+
169
+ expect(show).not.toHaveBeenCalled();
170
+ });
171
+
172
+ it("wouldn't call the show method when value changes to a value that is not to", async () => {
173
+ const { show } = renderHook({ to: true, from: false });
174
+
175
+ await changeValueTo("false");
176
+
177
+ expect(show).not.toHaveBeenCalled();
178
+ });
179
+
180
+ it("wouldn't call the show method when value changes from undefined to to", async () => {
181
+ const { show } = renderHook({ to: true, from: false });
182
+
183
+ await changeValueTo("undefined");
184
+ await changeValueTo("true");
185
+
186
+ expect(show).not.toHaveBeenCalled();
187
+ });
188
+
189
+ it("would call the show method when value changes from to to", async () => {
190
+ const { show } = renderHook({ to: true, from: false });
191
+
192
+ await changeValueTo("false");
193
+ await changeValueTo("true");
194
+
195
+ expect(show).toHaveBeenCalledTimes(1);
196
+ expect(show).toHaveBeenCalledWith(DUMMY_NOTIFICATION);
197
+ });
198
+ });
package/src/storybook.tsx CHANGED
@@ -6,6 +6,8 @@ import { Box } from "@mui/material";
6
6
  import { MemoryRouter, Router, Navigator, Route, Routes } from "react-router-dom";
7
7
  import { NotificationCenterProvider } from "./providers";
8
8
  import { action } from "@storybook/addon-actions";
9
+ import { LocalizationProvider } from "@mui/x-date-pickers/";
10
+ import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
9
11
 
10
12
  export function createTemplate<P>(
11
13
  C: JSXElementConstructor<P>,
@@ -88,3 +90,11 @@ export const withPadding =
88
90
  </Box>
89
91
  );
90
92
  };
93
+
94
+ export const withLocalizationProvider = (Story: FunctionComponent) => {
95
+ return (
96
+ <LocalizationProvider dateAdapter={AdapterDateFns}>
97
+ <Story />
98
+ </LocalizationProvider>
99
+ );
100
+ };
@@ -0,0 +1,43 @@
1
+ import userEvent from "@testing-library/user-event";
2
+ import { screen, fireEvent } from "./testing-library";
3
+ import { format } from "date-fns";
4
+
5
+ export const selectOption = async (element: HTMLElement, option: string) => {
6
+ await userEvent.click(element);
7
+ await userEvent.click(screen.getByRole("option", { name: option }));
8
+ };
9
+
10
+ export const selectOptions = async (element: HTMLElement, options: string[]) => {
11
+ await userEvent.click(element);
12
+
13
+ for (const option of options) {
14
+ const optionElement = screen.getByRole("option", { name: option });
15
+ await userEvent.click(optionElement);
16
+ }
17
+
18
+ const presentation = screen.getByRole("presentation").firstChild;
19
+ presentation && fireEvent.click(presentation);
20
+ };
21
+
22
+ export const clearMultiSelect = async (element: HTMLElement) => {
23
+ await userEvent.click(element);
24
+
25
+ const options = screen.queryAllByRole("option", { selected: true });
26
+
27
+ for (const option of options) {
28
+ await userEvent.click(option);
29
+ }
30
+
31
+ const presentation = screen.getByRole("presentation").firstChild;
32
+ presentation && fireEvent.click(presentation);
33
+ };
34
+
35
+ export const clearCheckbox = async (element: HTMLInputElement) => {
36
+ if (element.checked) {
37
+ await userEvent.click(element);
38
+ }
39
+ };
40
+
41
+ export const pickDatetime = (element: HTMLInputElement, value: Date, fmt: string) => {
42
+ fireEvent.change(element, { target: { value: format(value, fmt) } });
43
+ };
@@ -1,6 +1,8 @@
1
1
  import { AlertColor } from "@mui/material";
2
2
  import { ModelField } from "../generators";
3
3
  import { screen, waitForElementToBeRemoved } from "./testing-library";
4
+ import { format } from "date-fns";
5
+ import { MockInstance } from "../generators/generators.mock";
4
6
 
5
7
  export const expectContentPlaceholder = async () => {
6
8
  expect(await screen.findByTestId(/content-placeholder-test/i)).toBeInTheDocument();
@@ -12,6 +14,12 @@ export const expectModelFieldInputExist = (fields: ModelField[]) => {
12
14
  expectModelFieldInputExist(field.value);
13
15
  } else if (field.type === "number") {
14
16
  expect(screen.getByRole("spinbutton", { name: field.name })).toBeInTheDocument();
17
+ } else if (field.type === "boolean") {
18
+ expect(screen.getByRole("checkbox", { name: field.name })).toBeInTheDocument();
19
+ } else if (field.type === "enum" || field.type === "multienum") {
20
+ expect(
21
+ screen.getByRole("button", { name: new RegExp(field.name.toLowerCase(), "i") }),
22
+ ).toBeInTheDocument();
15
23
  } else {
16
24
  expect(screen.getByRole("textbox", { name: field.name })).toBeInTheDocument();
17
25
  }
@@ -25,6 +33,16 @@ export const expectModelFieldInputValue = (fields: ModelField[], initialValues:
25
33
  expectModelFieldInputValue(field.value, value);
26
34
  } else if (field.type === "number") {
27
35
  expect(screen.getByDisplayValue(value.toString())).toBeInTheDocument();
36
+ } else if (field.type === "boolean") {
37
+ expect(
38
+ screen.getByRole("checkbox", { name: field.name, checked: value }),
39
+ ).toBeInTheDocument();
40
+ } else if (field.type === "date" || field.type === "time" || field.type === "datetime") {
41
+ const expectedDateValue = format(value, field.format);
42
+ expect(screen.getByRole("textbox", { name: field.name })).toHaveAttribute(
43
+ "value",
44
+ expectedDateValue,
45
+ );
28
46
  } else {
29
47
  expect(screen.getByDisplayValue(value.toString())).toBeInTheDocument();
30
48
  }
@@ -45,9 +63,11 @@ export const expectModelFieldValue = (field: ModelField, instance: object) => {
45
63
  expect(screen.getByRole("label", { name: name })).toBeInTheDocument();
46
64
  if (type === "boolean") {
47
65
  expect(screen.getByTestId(value ? "CheckIcon" : "CloseIcon")).toBeInTheDocument();
66
+ } else if (type === "date" || type === "time" || type === "datetime") {
67
+ const formatedValue = format(value, field.format);
68
+ expect(screen.getByLabelText(name)).toHaveTextContent(formatedValue);
48
69
  } else {
49
70
  expect(screen.getByLabelText(name)).toHaveTextContent(value);
50
- //expect(screen.getByText(value)).toBeInTheDocument();
51
71
  }
52
72
  };
53
73
 
@@ -59,6 +79,11 @@ export const waitForProgressIndicatorToBeRemoved = async () => {
59
79
  await waitForElementToBeRemoved(() => screen.getByRole("progressbar"));
60
80
  };
61
81
 
82
+ export const waitForProgressFinish = async () => {
83
+ await screen.findByRole("progressbar");
84
+ await waitForProgressIndicatorToBeRemoved();
85
+ };
86
+
62
87
  export const expectAlert = async ({
63
88
  title,
64
89
  message,
@@ -74,3 +99,52 @@ export const expectAlert = async ({
74
99
  title && expect(await screen.findByText(title)).toBeInTheDocument();
75
100
  expect(await screen.findByText(message)).toBeInTheDocument();
76
101
  };
102
+
103
+ export const expectToHaveBeenCalledOnceWithMockInstance = (
104
+ mockFn: jest.Mock,
105
+ instance: MockInstance,
106
+ ) => {
107
+ expect(mockFn).toHaveBeenCalledTimes(1);
108
+
109
+ const calledReturnTime: Date = mockFn.mock.calls[0][0].car.returnTime;
110
+ const calledTradeDate: Date = mockFn.mock.calls[0][0].tradeDate;
111
+
112
+ expect(mockFn).toHaveBeenCalledWith({
113
+ id: instance.id,
114
+ firstName: instance.firstName,
115
+ middleName: instance.middleName,
116
+ lastName: instance.lastName,
117
+ gender: instance.gender,
118
+ age: instance.age,
119
+ birthDate: instance.birthDate,
120
+ car: {
121
+ model: instance.car.model,
122
+ manufacturer: instance.car.manufacturer,
123
+ color: instance.car.color,
124
+ type: instance.car.type,
125
+ vin: instance.car.vin,
126
+ vrm: instance.car.vrm,
127
+ returnTime: new Date(
128
+ calledReturnTime.getFullYear(),
129
+ calledReturnTime.getMonth(),
130
+ calledReturnTime.getDate(),
131
+ instance.car.returnTime.getHours(),
132
+ instance.car.returnTime.getMinutes(),
133
+ calledReturnTime.getSeconds(),
134
+ calledReturnTime.getMilliseconds(),
135
+ ),
136
+ },
137
+ quantity: instance.quantity,
138
+ available: instance.available,
139
+ currency: instance.currency,
140
+ tradeDate: new Date(
141
+ instance.tradeDate.getFullYear(),
142
+ instance.tradeDate.getMonth(),
143
+ instance.tradeDate.getDate(),
144
+ instance.tradeDate.getHours(),
145
+ instance.tradeDate.getMinutes(),
146
+ calledTradeDate.getSeconds(),
147
+ calledTradeDate.getMilliseconds(),
148
+ ),
149
+ });
150
+ };
@@ -1,3 +1,4 @@
1
1
  export * from "./components";
2
2
  export * from "./testing-library";
3
3
  export * from "./assertions";
4
+ export * from "./actions";
@@ -4,6 +4,8 @@ import { createMemoryHistory, MemoryHistory } from "history";
4
4
  import React from "react";
5
5
  import { ThemeProvider } from "@emotion/react";
6
6
  import { Theme, createTheme, PaletteMode } from "@mui/material";
7
+ import { LocalizationProvider } from "@mui/x-date-pickers";
8
+ import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
7
9
 
8
10
  export type TestRouter = "router" | "memory";
9
11
 
@@ -34,7 +36,9 @@ const createWrapper =
34
36
  };
35
37
  return (
36
38
  <ThemeProvider theme={theme}>
37
- <R {...routerArgs}>{children}</R>
39
+ <LocalizationProvider dateAdapter={AdapterDateFns}>
40
+ <R {...routerArgs}>{children}</R>
41
+ </LocalizationProvider>
38
42
  </ThemeProvider>
39
43
  );
40
44
  };