@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.
- package/dist/cjs/index.js +13 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/value-displays/index.d.ts +1 -0
- package/dist/cjs/types/components/value-displays/value-datetime/index.d.ts +1 -0
- package/dist/cjs/types/components/value-displays/value-datetime/value-datetime.d.ts +18 -0
- package/dist/cjs/types/generators/generators.mock.d.ts +9 -5
- package/dist/cjs/types/generators/generators.model.d.ts +25 -1
- package/dist/cjs/types/generators/model-router/index.d.ts +1 -0
- package/dist/cjs/types/generators/model-router/model-router.types.d.ts +1 -0
- package/dist/cjs/types/generators/model-router/screens/details-screen.d.ts +1 -1
- package/dist/cjs/types/hooks/index.d.ts +1 -0
- package/dist/cjs/types/hooks/routing/index.d.ts +1 -0
- package/dist/cjs/types/hooks/routing/routing.hooks.d.ts +5 -0
- package/dist/cjs/types/providers/notification-center/index.d.ts +1 -0
- package/dist/cjs/types/providers/notification-center/notification-center.hooks.d.ts +6 -0
- package/dist/esm/index.js +13 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/value-displays/index.d.ts +1 -0
- package/dist/esm/types/components/value-displays/value-datetime/index.d.ts +1 -0
- package/dist/esm/types/components/value-displays/value-datetime/value-datetime.d.ts +18 -0
- package/dist/esm/types/generators/generators.mock.d.ts +9 -5
- package/dist/esm/types/generators/generators.model.d.ts +25 -1
- package/dist/esm/types/generators/model-router/index.d.ts +1 -0
- package/dist/esm/types/generators/model-router/model-router.types.d.ts +1 -0
- package/dist/esm/types/generators/model-router/screens/details-screen.d.ts +1 -1
- package/dist/esm/types/hooks/index.d.ts +1 -0
- package/dist/esm/types/hooks/routing/index.d.ts +1 -0
- package/dist/esm/types/hooks/routing/routing.hooks.d.ts +5 -0
- package/dist/esm/types/providers/notification-center/index.d.ts +1 -0
- package/dist/esm/types/providers/notification-center/notification-center.hooks.d.ts +6 -0
- package/dist/index.d.ts +54 -2
- package/package.json +6 -2
- package/src/components/value-displays/index.ts +1 -0
- package/src/components/value-displays/value-datetime/index.ts +1 -0
- package/src/components/value-displays/value-datetime/value-datetime.stories.tsx +21 -0
- package/src/components/value-displays/value-datetime/value-datetime.test.tsx +23 -0
- package/src/components/value-displays/value-datetime/value-datetime.tsx +40 -0
- package/src/components/value-displays/value-text/{value-test.test.tsx → value-text.test.tsx} +0 -0
- package/src/generators/generators.mock.ts +56 -17
- package/src/generators/generators.model.ts +39 -1
- package/src/generators/model-form/model-form.stories.tsx +2 -2
- package/src/generators/model-form/model-form.test.tsx +39 -22
- package/src/generators/model-form/model-form.tsx +220 -33
- package/src/generators/model-router/index.ts +1 -0
- package/src/generators/model-router/model-router.test.tsx +338 -70
- package/src/generators/model-router/model-router.tsx +1 -1
- package/src/generators/model-router/model-router.types.ts +4 -0
- package/src/generators/model-router/screens/add-screen.tsx +16 -20
- package/src/generators/model-router/screens/details-screen.tsx +3 -2
- package/src/generators/model-router/screens/list-screen.tsx +17 -0
- package/src/generators/model-router/screens/update-screen.tsx +22 -13
- package/src/generators/model-router/stories/model-router.stories.tsx +54 -3
- package/src/generators/object-details/object-details.tsx +5 -4
- package/src/hooks/index.ts +1 -0
- package/src/hooks/routing/index.ts +1 -0
- package/src/hooks/routing/routing.hooks.ts +23 -0
- package/src/hooks/routing/routing.test.tsx +83 -0
- package/src/providers/notification-center/index.ts +1 -0
- package/src/providers/notification-center/notification-center.hooks.ts +23 -0
- package/src/providers/notification-center/notification-center.test.tsx +87 -1
- package/src/storybook.tsx +10 -0
- package/src/tests/actions.ts +43 -0
- package/src/tests/assertions.ts +75 -1
- package/src/tests/index.ts +1 -0
- 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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
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:
|
|
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 {
|
|
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 }
|
|
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
|
+
});
|
|
@@ -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
|
+
};
|
package/src/tests/assertions.ts
CHANGED
|
@@ -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
|
+
};
|
package/src/tests/index.ts
CHANGED
|
@@ -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
|
-
<
|
|
39
|
+
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
|
40
|
+
<R {...routerArgs}>{children}</R>
|
|
41
|
+
</LocalizationProvider>
|
|
38
42
|
</ThemeProvider>
|
|
39
43
|
);
|
|
40
44
|
};
|