@pautena/react-design-system 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/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/index.d.ts +45 -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/model-router.test.tsx +46 -52
- package/src/generators/model-router/stories/model-router.stories.tsx +6 -2
- package/src/generators/object-details/object-details.tsx +5 -4
- package/src/storybook.tsx +10 -0
- package/src/tests/actions.ts +43 -0
- package/src/tests/assertions.ts +70 -1
- package/src/tests/index.ts +1 -0
- package/src/tests/testing-library.tsx +5 -1
|
@@ -1,8 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
Button,
|
|
4
|
+
Checkbox,
|
|
5
|
+
FormControl,
|
|
6
|
+
FormControlLabel,
|
|
7
|
+
Grid,
|
|
8
|
+
InputLabel,
|
|
9
|
+
ListItemText,
|
|
10
|
+
MenuItem,
|
|
11
|
+
Paper,
|
|
12
|
+
Select,
|
|
13
|
+
SelectChangeEvent,
|
|
14
|
+
TextField,
|
|
15
|
+
Typography,
|
|
16
|
+
} from "@mui/material";
|
|
17
|
+
import { DesktopDatePicker, TimePicker, DateTimePicker } from "@mui/x-date-pickers";
|
|
18
|
+
import React, { ChangeEvent, FormEvent, ReactElement, useMemo } from "react";
|
|
3
19
|
import { useState } from "react";
|
|
4
20
|
import { useGetDefaultThemeColor } from "../../utils/theme";
|
|
5
|
-
import { Model, ModelField, BasicModelInstance } from "../generators.model";
|
|
21
|
+
import { Model, ModelField, BasicModelInstance, ModelFieldTypes } from "../generators.model";
|
|
22
|
+
|
|
23
|
+
const InitialStateZeroValue: Record<string, any> = {
|
|
24
|
+
string: undefined,
|
|
25
|
+
number: undefined,
|
|
26
|
+
boolean: false,
|
|
27
|
+
enum: "",
|
|
28
|
+
multienum: [],
|
|
29
|
+
date: "01/01/1970",
|
|
30
|
+
group: {},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getFieldInitialState = <T extends BasicModelInstance>(
|
|
34
|
+
field: ModelField,
|
|
35
|
+
initialValues: T | undefined,
|
|
36
|
+
) => {
|
|
37
|
+
return initialValues ? initialValues[field.id] : InitialStateZeroValue[field.type];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getValuesInitialState = <T extends BasicModelInstance>(
|
|
41
|
+
model: Model,
|
|
42
|
+
initialValues: T | undefined,
|
|
43
|
+
): T => {
|
|
44
|
+
const obj = {} as any;
|
|
45
|
+
|
|
46
|
+
model.fields.forEach((field) => {
|
|
47
|
+
let value: any;
|
|
48
|
+
if (field.type === "group") {
|
|
49
|
+
value = {};
|
|
50
|
+
field.value.forEach((groupField) => {
|
|
51
|
+
value[groupField.id] = getFieldInitialState(
|
|
52
|
+
groupField,
|
|
53
|
+
initialValues && initialValues[field.id],
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
} else if (field.type === "date" || field.type === "time") {
|
|
57
|
+
value = (initialValues && initialValues[field.id]) || field.default;
|
|
58
|
+
} else {
|
|
59
|
+
value = getFieldInitialState(field, initialValues);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
obj[field.id] = value;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return obj;
|
|
66
|
+
};
|
|
6
67
|
|
|
7
68
|
export interface ModelFormProps<T extends BasicModelInstance> {
|
|
8
69
|
model: Model;
|
|
@@ -17,28 +78,63 @@ export const ModelForm = <T extends BasicModelInstance>({
|
|
|
17
78
|
onSubmit,
|
|
18
79
|
initialValues,
|
|
19
80
|
}: ModelFormProps<T>) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
e.target;
|
|
81
|
+
const valuesInitialState = useMemo(
|
|
82
|
+
() => getValuesInitialState<T>(model, initialValues),
|
|
83
|
+
[model, initialValues],
|
|
84
|
+
);
|
|
85
|
+
const [values, setValues] = useState<T>(valuesInitialState);
|
|
26
86
|
|
|
87
|
+
const setKeyValue = (name: string, key: string | undefined, value: any) => {
|
|
27
88
|
setValues((v) => {
|
|
28
89
|
const n: Record<string, object> = {};
|
|
29
90
|
if (key) {
|
|
30
91
|
n[key] = {
|
|
31
92
|
...v[key],
|
|
32
|
-
[
|
|
93
|
+
[name]: value,
|
|
33
94
|
};
|
|
34
95
|
} else {
|
|
35
|
-
n[
|
|
96
|
+
n[name] = value;
|
|
36
97
|
}
|
|
37
98
|
|
|
38
99
|
return { ...v, ...n };
|
|
39
100
|
});
|
|
40
101
|
};
|
|
41
102
|
|
|
103
|
+
const handleCheckboxChange = (e: ChangeEvent<any>, key: string | undefined) => {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
setKeyValue(e.target.name, key, e.target.checked);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleSelectChange = (e: SelectChangeEvent<any>, key: string | undefined) => {
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
setKeyValue(e.target.name, key, e.target.value);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const handleMultiSelectChange = (e: SelectChangeEvent<any>, key: string | undefined) => {
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
const { value } = e.target;
|
|
116
|
+
const newValue = typeof value === "string" ? value.split(",") : value;
|
|
117
|
+
setKeyValue(e.target.name, key, newValue);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const handleInputChange = (
|
|
121
|
+
e: ChangeEvent<any>,
|
|
122
|
+
key: string | undefined,
|
|
123
|
+
type: ModelFieldTypes,
|
|
124
|
+
) => {
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
|
|
127
|
+
let value = e.target.value;
|
|
128
|
+
if (type === "number") {
|
|
129
|
+
value = parseInt(e.target.value);
|
|
130
|
+
}
|
|
131
|
+
setKeyValue(e.target.name, key, value);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const handleDateChange = (value: any, key: string | undefined, id: string) => {
|
|
135
|
+
setKeyValue(id, key, value);
|
|
136
|
+
};
|
|
137
|
+
|
|
42
138
|
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
|
43
139
|
e.preventDefault();
|
|
44
140
|
onSubmit(values);
|
|
@@ -48,28 +144,113 @@ export const ModelForm = <T extends BasicModelInstance>({
|
|
|
48
144
|
const defaultColor = useGetDefaultThemeColor({ lightWeight: 200, darkWeight: 800 });
|
|
49
145
|
|
|
50
146
|
const { id, type, name, description, xs, sm, md, lg, xl } = field;
|
|
147
|
+
|
|
148
|
+
let fieldInput: ReactElement;
|
|
149
|
+
const value = key ? values[key][id] : values[id];
|
|
51
150
|
if (type === "group") {
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
</Paper>
|
|
67
|
-
</Grid>
|
|
151
|
+
fieldInput = (
|
|
152
|
+
<Paper>
|
|
153
|
+
<Box bgcolor={defaultColor} px={2} py={1} mb={2}>
|
|
154
|
+
<Typography variant="h6" role="heading" aria-level={1}>
|
|
155
|
+
{name}
|
|
156
|
+
</Typography>
|
|
157
|
+
<Typography variant="body2" role="heading" aria-level={2}>
|
|
158
|
+
{description}
|
|
159
|
+
</Typography>
|
|
160
|
+
</Box>
|
|
161
|
+
<Grid container spacing={2} sx={{ p: 2 }}>
|
|
162
|
+
{field.value.map((f) => renderField(f, id))}
|
|
163
|
+
</Grid>
|
|
164
|
+
</Paper>
|
|
68
165
|
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
166
|
+
} else if (type === "boolean") {
|
|
167
|
+
fieldInput = (
|
|
168
|
+
<Box sx={{ height: 1, display: "flex", alignItems: "center" }}>
|
|
169
|
+
<FormControlLabel
|
|
170
|
+
control={
|
|
171
|
+
<Checkbox name={id} onChange={(e) => handleCheckboxChange(e, key)} checked={value} />
|
|
172
|
+
}
|
|
173
|
+
label={name}
|
|
174
|
+
/>
|
|
175
|
+
</Box>
|
|
176
|
+
);
|
|
177
|
+
} else if (type === "enum") {
|
|
178
|
+
fieldInput = (
|
|
179
|
+
<FormControl fullWidth>
|
|
180
|
+
<InputLabel id={`${id}-select-label`}>{name}</InputLabel>
|
|
181
|
+
<Select
|
|
182
|
+
labelId={`${id}-select-label`}
|
|
183
|
+
id={`${id}-select`}
|
|
184
|
+
value={value}
|
|
185
|
+
label={name}
|
|
186
|
+
name={id}
|
|
187
|
+
onChange={(e) => handleSelectChange(e, key)}
|
|
188
|
+
required
|
|
189
|
+
>
|
|
190
|
+
{field.value.map((fieldValue) => (
|
|
191
|
+
<MenuItem key={fieldValue} value={fieldValue}>
|
|
192
|
+
{fieldValue}
|
|
193
|
+
</MenuItem>
|
|
194
|
+
))}
|
|
195
|
+
</Select>
|
|
196
|
+
</FormControl>
|
|
197
|
+
);
|
|
198
|
+
} else if (type === "multienum") {
|
|
199
|
+
fieldInput = (
|
|
200
|
+
<FormControl fullWidth>
|
|
201
|
+
<InputLabel id={`${id}-select-label`}>{name}</InputLabel>
|
|
202
|
+
<Select
|
|
203
|
+
labelId={`${id}-select-label`}
|
|
204
|
+
id={`${id}-select`}
|
|
205
|
+
value={value || []}
|
|
206
|
+
renderValue={(selected) => selected.join(", ")}
|
|
207
|
+
label={name}
|
|
208
|
+
name={id}
|
|
209
|
+
onChange={(e) => handleMultiSelectChange(e, key)}
|
|
210
|
+
required
|
|
211
|
+
multiple
|
|
212
|
+
>
|
|
213
|
+
{field.value.map((fieldValue) => (
|
|
214
|
+
<MenuItem key={fieldValue} value={fieldValue}>
|
|
215
|
+
<Checkbox checked={(value || []).includes(fieldValue)} />
|
|
216
|
+
<ListItemText primary={fieldValue} />
|
|
217
|
+
</MenuItem>
|
|
218
|
+
))}
|
|
219
|
+
</Select>
|
|
220
|
+
</FormControl>
|
|
221
|
+
);
|
|
222
|
+
} else if (type === "date") {
|
|
223
|
+
fieldInput = (
|
|
224
|
+
<DesktopDatePicker
|
|
225
|
+
label={name}
|
|
226
|
+
inputFormat={field.format}
|
|
227
|
+
value={value}
|
|
228
|
+
onChange={(value) => handleDateChange(value, key, id)}
|
|
229
|
+
renderInput={(params: any) => <TextField {...params} />}
|
|
230
|
+
/>
|
|
231
|
+
);
|
|
232
|
+
} else if (type === "time") {
|
|
233
|
+
fieldInput = (
|
|
234
|
+
<TimePicker
|
|
235
|
+
label={name}
|
|
236
|
+
inputFormat={field.format}
|
|
237
|
+
value={value}
|
|
238
|
+
onChange={(value) => handleDateChange(value, key, id)}
|
|
239
|
+
renderInput={(params: any) => <TextField {...params} />}
|
|
240
|
+
/>
|
|
241
|
+
);
|
|
242
|
+
} else if (type === "datetime") {
|
|
243
|
+
fieldInput = (
|
|
244
|
+
<DateTimePicker
|
|
245
|
+
label={name}
|
|
246
|
+
inputFormat={field.format}
|
|
247
|
+
value={value}
|
|
248
|
+
onChange={(value) => handleDateChange(value, key, id)}
|
|
249
|
+
renderInput={(params: any) => <TextField {...params} />}
|
|
250
|
+
/>
|
|
251
|
+
);
|
|
252
|
+
} else {
|
|
253
|
+
fieldInput = (
|
|
73
254
|
<TextField
|
|
74
255
|
required
|
|
75
256
|
type={type}
|
|
@@ -77,9 +258,15 @@ export const ModelForm = <T extends BasicModelInstance>({
|
|
|
77
258
|
name={id}
|
|
78
259
|
variant="outlined"
|
|
79
260
|
fullWidth
|
|
80
|
-
value={
|
|
81
|
-
onChange={(e) => handleInputChange(e, key)}
|
|
261
|
+
value={value}
|
|
262
|
+
onChange={(e) => handleInputChange(e, key, type)}
|
|
82
263
|
/>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<Grid item key={id} xs={xs} sm={sm} md={md} lg={lg} xl={xl}>
|
|
269
|
+
{fieldInput}
|
|
83
270
|
</Grid>
|
|
84
271
|
);
|
|
85
272
|
};
|
|
@@ -9,16 +9,31 @@ 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 {
|
|
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 {
|
|
29
|
+
import { useNavigate } from "react-router-dom";
|
|
30
|
+
import {
|
|
31
|
+
clearCheckbox,
|
|
32
|
+
clearMultiSelect,
|
|
33
|
+
expectToHaveBeenCalledOnceWithMockInstance,
|
|
34
|
+
pickDatetime,
|
|
35
|
+
selectOptions,
|
|
36
|
+
} from "../../tests";
|
|
22
37
|
|
|
23
38
|
const REQUEST_TIMEOUT = 20;
|
|
24
39
|
|
|
@@ -82,30 +97,6 @@ describe("ModelRouter", () => {
|
|
|
82
97
|
expectNotToHaveMenuOption: ({ id }: { id: string }) => {
|
|
83
98
|
expect(screen.queryByTestId(`options-${id}`)).not.toBeInTheDocument();
|
|
84
99
|
},
|
|
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
100
|
};
|
|
110
101
|
|
|
111
102
|
const actions = {
|
|
@@ -149,17 +140,18 @@ describe("ModelRouter", () => {
|
|
|
149
140
|
const firstNameElement = screen.getByRole("textbox", { name: /first name/i });
|
|
150
141
|
const middleNameElement = screen.getByRole("textbox", { name: /middle name/i });
|
|
151
142
|
const lastNameElement = screen.getByRole("textbox", { name: /last name/i });
|
|
152
|
-
const genderElement = screen.getByRole("
|
|
143
|
+
const genderElement = screen.getByRole("button", { name: /gender/i });
|
|
153
144
|
const ageElement = screen.getByRole("spinbutton", { name: /age/i });
|
|
154
145
|
const birthDateElement = screen.getByRole("textbox", { name: /birth date/i });
|
|
155
|
-
const manufacturerElement = screen.getByRole("
|
|
156
|
-
const modelElement = screen.getByRole("
|
|
146
|
+
const manufacturerElement = screen.getByRole("button", { name: /manufacturer/i });
|
|
147
|
+
const modelElement = screen.getByRole("button", { name: /model/i });
|
|
157
148
|
const colorElement = screen.getByRole("textbox", { name: /color/i });
|
|
158
|
-
const typeElement = screen.getByRole("
|
|
149
|
+
const typeElement = screen.getByRole("button", { name: /type/i });
|
|
159
150
|
const vinElement = screen.getByRole("textbox", { name: /vin/i });
|
|
160
151
|
const vrmElement = screen.getByRole("textbox", { name: /vrm/i });
|
|
152
|
+
const timeReturnElement = screen.getByRole("textbox", { name: /return time/i });
|
|
161
153
|
const quantityElement = screen.getByRole("spinbutton", { name: /q/i });
|
|
162
|
-
const availableElement = screen.getByRole("
|
|
154
|
+
const availableElement = screen.getByRole("checkbox", { name: /available/i });
|
|
163
155
|
const currencyElement = screen.getByRole("textbox", { name: /currency/i });
|
|
164
156
|
const tradeDateElement = screen.getByRole("textbox", { name: /trade date/i });
|
|
165
157
|
|
|
@@ -168,37 +160,39 @@ describe("ModelRouter", () => {
|
|
|
168
160
|
await userEvent.clear(firstNameElement);
|
|
169
161
|
await userEvent.clear(middleNameElement);
|
|
170
162
|
await userEvent.clear(lastNameElement);
|
|
171
|
-
await userEvent.clear(genderElement);
|
|
172
163
|
await userEvent.clear(ageElement);
|
|
173
164
|
await userEvent.clear(birthDateElement);
|
|
174
|
-
await userEvent.clear(manufacturerElement);
|
|
175
|
-
await userEvent.clear(modelElement);
|
|
176
165
|
await userEvent.clear(colorElement);
|
|
177
|
-
await userEvent.clear(typeElement);
|
|
178
166
|
await userEvent.clear(vinElement);
|
|
179
167
|
await userEvent.clear(vrmElement);
|
|
180
168
|
await userEvent.clear(quantityElement);
|
|
181
169
|
await userEvent.clear(availableElement);
|
|
182
170
|
await userEvent.clear(currencyElement);
|
|
183
171
|
await userEvent.clear(tradeDateElement);
|
|
172
|
+
await userEvent.clear(timeReturnElement);
|
|
173
|
+
await clearCheckbox(availableElement);
|
|
174
|
+
await clearMultiSelect(typeElement);
|
|
184
175
|
}
|
|
185
176
|
await userEvent.type(idElement, instance.id);
|
|
186
177
|
await userEvent.type(firstNameElement, instance.firstName);
|
|
187
178
|
await userEvent.type(middleNameElement, instance.middleName);
|
|
188
179
|
await userEvent.type(lastNameElement, instance.lastName);
|
|
189
|
-
await
|
|
180
|
+
await selectOption(genderElement, instance.gender);
|
|
190
181
|
await userEvent.type(ageElement, instance.age.toString());
|
|
191
|
-
|
|
192
|
-
await
|
|
193
|
-
await
|
|
182
|
+
pickDatetime(birthDateElement, instance.birthDate, BirthDateFormat);
|
|
183
|
+
await selectOption(modelElement, instance.car.model);
|
|
184
|
+
await selectOption(manufacturerElement, instance.car.manufacturer);
|
|
194
185
|
await userEvent.type(colorElement, instance.car.color);
|
|
195
|
-
await
|
|
186
|
+
await selectOptions(typeElement, instance.car.type);
|
|
196
187
|
await userEvent.type(vinElement, instance.car.vin);
|
|
197
188
|
await userEvent.type(vrmElement, instance.car.vrm);
|
|
189
|
+
pickDatetime(timeReturnElement, instance.car.returnTime, ReturnTimeFormat);
|
|
198
190
|
await userEvent.type(quantityElement, instance.quantity.toString());
|
|
199
|
-
|
|
191
|
+
if (instance.available) {
|
|
192
|
+
await userEvent.click(availableElement);
|
|
193
|
+
}
|
|
200
194
|
await userEvent.type(currencyElement, instance.currency);
|
|
201
|
-
|
|
195
|
+
pickDatetime(tradeDateElement, instance.tradeDate, TradeDateFormat);
|
|
202
196
|
|
|
203
197
|
submit && (await userEvent.click(screen.getByRole("button", { name: /save/i })));
|
|
204
198
|
|
|
@@ -544,7 +538,7 @@ describe("ModelRouter", () => {
|
|
|
544
538
|
|
|
545
539
|
const newInstance = await actions.fullfillModelForm({ model, submit: true });
|
|
546
540
|
|
|
547
|
-
|
|
541
|
+
expectToHaveBeenCalledOnceWithMockInstance(onSubmitNewItem, newInstance);
|
|
548
542
|
});
|
|
549
543
|
|
|
550
544
|
it("would show a loading indicator when the request is in progress", async () => {
|
|
@@ -707,12 +701,12 @@ describe("ModelRouter", () => {
|
|
|
707
701
|
});
|
|
708
702
|
|
|
709
703
|
it("would make a request with the new values when the form is submitted", async () => {
|
|
710
|
-
const { model } = await renderComponent({ screen: "update" });
|
|
704
|
+
const { model, onSubmitUpdate } = await renderComponent({ screen: "update" });
|
|
711
705
|
|
|
712
706
|
await waitForProgressIndicatorToBeRemoved();
|
|
713
|
-
const newInstance = await actions.fullfillModelForm({ model, clear: true });
|
|
707
|
+
const newInstance = await actions.fullfillModelForm({ model, clear: true, submit: true });
|
|
714
708
|
|
|
715
|
-
|
|
709
|
+
expectToHaveBeenCalledOnceWithMockInstance(onSubmitUpdate, newInstance);
|
|
716
710
|
});
|
|
717
711
|
|
|
718
712
|
it("would show a loading indicator while the submit request is in progress", async () => {
|
|
@@ -724,14 +718,14 @@ describe("ModelRouter", () => {
|
|
|
724
718
|
expectProgressIndicator();
|
|
725
719
|
});
|
|
726
720
|
|
|
727
|
-
it("would navigate to the list screen when the submit request finish", async () => {
|
|
728
|
-
|
|
721
|
+
// it("would navigate to the list screen when the submit request finish", async () => {
|
|
722
|
+
// const { model } = await renderComponent({ screen: "update" });
|
|
729
723
|
|
|
730
|
-
|
|
731
|
-
|
|
724
|
+
// await waitForProgressIndicatorToBeRemoved();
|
|
725
|
+
// await actions.fullfillModelForm({ model, submit: true, clear: true });
|
|
732
726
|
|
|
733
|
-
|
|
734
|
-
});
|
|
727
|
+
// await assertions.expectListScreen();
|
|
728
|
+
// });
|
|
735
729
|
});
|
|
736
730
|
|
|
737
731
|
describe("delete item", () => {
|
|
@@ -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";
|
|
@@ -216,7 +220,7 @@ export const InternalModelRouter = () => {
|
|
|
216
220
|
export default {
|
|
217
221
|
title: "Generators/ModelRouter",
|
|
218
222
|
component: DummyModelRouter,
|
|
219
|
-
decorators: [withMemoryRouter(), withNotificationCenter],
|
|
223
|
+
decorators: [withMemoryRouter(), withNotificationCenter, withLocalizationProvider],
|
|
220
224
|
parameters: {
|
|
221
225
|
layout: "fullscreen",
|
|
222
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
|
};
|
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
|
+
};
|