@pautena/react-design-system 0.1.4 → 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/cjs/types/generators/model-router/index.d.ts +1 -0
- package/dist/cjs/types/generators/model-router/screens/add-screen.d.ts +1 -1
- package/dist/cjs/types/generators/model-router/screens/list-screen.d.ts +1 -1
- package/dist/cjs/types/generators/model-router/screens/screens.types.d.ts +20 -0
- package/dist/cjs/types/generators/model-router/screens/update-screen.d.ts +1 -1
- package/dist/cjs/types/index.d.ts +1 -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/screens/add-screen.d.ts +1 -1
- package/dist/esm/types/generators/model-router/screens/list-screen.d.ts +1 -1
- package/dist/esm/types/generators/model-router/screens/screens.types.d.ts +20 -0
- package/dist/esm/types/generators/model-router/screens/update-screen.d.ts +1 -1
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/index.d.ts +98 -4
- package/package.json +6 -2
- package/src/components/header/header.test.tsx +2 -12
- package/src/components/tab/tab-card/tab-card.tsx +2 -2
- package/src/components/table-list/table-list.test.tsx +8 -1
- package/src/components/table-list/table-list.tsx +3 -3
- 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 +264 -57
- package/src/generators/model-router/model-router.tsx +4 -3
- package/src/generators/model-router/screens/add-screen.tsx +2 -1
- package/src/generators/model-router/screens/list-screen.tsx +41 -26
- package/src/generators/model-router/screens/screens.types.ts +25 -0
- package/src/generators/model-router/screens/update-screen.tsx +2 -1
- package/src/generators/model-router/stories/list-screen.stories.tsx +51 -0
- package/src/generators/model-router/stories/model-router.stories.tsx +66 -3
- package/src/generators/object-details/object-details.tsx +5 -4
- package/src/index.ts +1 -0
- package/src/layouts/app-bar-with-drawer-layout/app-bar-with-drawer-layout.stories.tsx +1 -1
- package/src/layouts/app-bar-with-drawer-layout/app-bar-with-drawer-layout.tsx +1 -1
- 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,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { DummyModelRouter } from "./stories/model-router.stories";
|
|
2
|
+
import { DummyModelRouter, InternalModelRouter } from "./stories/model-router.stories";
|
|
3
3
|
import {
|
|
4
4
|
expectModelFieldInputExist,
|
|
5
5
|
expectProgressIndicator,
|
|
@@ -9,12 +9,31 @@ 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 {
|
|
18
|
+
import {
|
|
19
|
+
BirthDateFormat,
|
|
20
|
+
createModelInstance,
|
|
21
|
+
MockInstance,
|
|
22
|
+
mockModel,
|
|
23
|
+
ReturnTimeFormat,
|
|
24
|
+
TradeDateFormat,
|
|
25
|
+
} from "../generators.mock";
|
|
17
26
|
import { NotificationCenterProvider } from "../../providers";
|
|
27
|
+
import { Box } from "@mui/system";
|
|
28
|
+
import { Button } from "@mui/material";
|
|
29
|
+
import { useNavigate } from "react-router-dom";
|
|
30
|
+
import {
|
|
31
|
+
clearCheckbox,
|
|
32
|
+
clearMultiSelect,
|
|
33
|
+
expectToHaveBeenCalledOnceWithMockInstance,
|
|
34
|
+
pickDatetime,
|
|
35
|
+
selectOptions,
|
|
36
|
+
} from "../../tests";
|
|
18
37
|
|
|
19
38
|
const REQUEST_TIMEOUT = 20;
|
|
20
39
|
|
|
@@ -30,16 +49,39 @@ describe("ModelRouter", () => {
|
|
|
30
49
|
name: "Add Items",
|
|
31
50
|
level: 1,
|
|
32
51
|
}),
|
|
52
|
+
expectNotToBeInAddScreen: () => {
|
|
53
|
+
expect(
|
|
54
|
+
screen.queryByRole("heading", {
|
|
55
|
+
name: "Add Items",
|
|
56
|
+
level: 1,
|
|
57
|
+
}),
|
|
58
|
+
).not.toBeInTheDocument();
|
|
59
|
+
},
|
|
33
60
|
expectDetailScreen: async ({ id }: { id: string }) =>
|
|
34
61
|
await screen.findByRole("heading", {
|
|
35
62
|
name: id,
|
|
36
63
|
level: 1,
|
|
37
64
|
}),
|
|
65
|
+
expectNotToBeInDetailsScreen: ({ id }: { id: string }) => {
|
|
66
|
+
expect(
|
|
67
|
+
screen.queryByRole("heading", {
|
|
68
|
+
name: id,
|
|
69
|
+
level: 1,
|
|
70
|
+
}),
|
|
71
|
+
).not.toBeInTheDocument();
|
|
72
|
+
},
|
|
38
73
|
expectUpdateScreen: async ({ id }: { id: string }) =>
|
|
39
74
|
await screen.findByRole("heading", {
|
|
40
75
|
name: `Edit ${id}`,
|
|
41
76
|
level: 1,
|
|
42
77
|
}),
|
|
78
|
+
expectNotToBeInUpdateScreen: ({ id }: { id: string }) =>
|
|
79
|
+
expect(
|
|
80
|
+
screen.queryByRole("heading", {
|
|
81
|
+
name: `Edit ${id}`,
|
|
82
|
+
level: 1,
|
|
83
|
+
}),
|
|
84
|
+
).not.toBeInTheDocument(),
|
|
43
85
|
expectListItems: async ({ data, model }: { data: MockInstance[]; model: Model }) => {
|
|
44
86
|
for (let i = 0; i < model.fields.length; ++i) {
|
|
45
87
|
const { id, listable } = model.fields[i];
|
|
@@ -52,43 +94,34 @@ describe("ModelRouter", () => {
|
|
|
52
94
|
expectMenuOption: async ({ id }: { id: string }) => {
|
|
53
95
|
await screen.findByTestId(`options-${id}`);
|
|
54
96
|
},
|
|
55
|
-
|
|
56
|
-
expect(
|
|
57
|
-
expect(mockFn).toHaveBeenCalledWith({
|
|
58
|
-
id: instance.id,
|
|
59
|
-
firstName: instance.firstName,
|
|
60
|
-
middleName: instance.middleName,
|
|
61
|
-
lastName: instance.lastName,
|
|
62
|
-
gender: instance.gender,
|
|
63
|
-
age: instance.age.toString(),
|
|
64
|
-
birthDate: instance.birthDate,
|
|
65
|
-
car: {
|
|
66
|
-
model: instance.car.model,
|
|
67
|
-
manufacturer: instance.car.manufacturer,
|
|
68
|
-
color: instance.car.color,
|
|
69
|
-
type: instance.car.type,
|
|
70
|
-
vin: instance.car.vin,
|
|
71
|
-
vrm: instance.car.vrm,
|
|
72
|
-
},
|
|
73
|
-
quantity: instance.quantity.toString(),
|
|
74
|
-
available: instance.available.toString(),
|
|
75
|
-
currency: instance.currency,
|
|
76
|
-
tradeDate: instance.tradeDate,
|
|
77
|
-
});
|
|
97
|
+
expectNotToHaveMenuOption: ({ id }: { id: string }) => {
|
|
98
|
+
expect(screen.queryByTestId(`options-${id}`)).not.toBeInTheDocument();
|
|
78
99
|
},
|
|
79
100
|
};
|
|
80
101
|
|
|
81
102
|
const actions = {
|
|
82
103
|
navigateToAddScreen: async () => {
|
|
83
|
-
await userEvent.click(screen.
|
|
104
|
+
await userEvent.click(await screen.findByRole("button", { name: "Add" }));
|
|
105
|
+
},
|
|
106
|
+
forceNavigateToAddScreen: async () => {
|
|
107
|
+
await userEvent.click(screen.getByRole("button", { name: /force add/i }));
|
|
84
108
|
},
|
|
85
109
|
navigateToUpdateScreen: async ({ id }: { id: string }) => {
|
|
86
110
|
await actions.openItemOptions({ id });
|
|
87
111
|
await userEvent.click(screen.getByRole("menuitem", { name: /edit/i }));
|
|
88
112
|
},
|
|
113
|
+
forceNavigateToUpdateScreen: async () => {
|
|
114
|
+
await userEvent.click(screen.getByRole("button", { name: /force update/i }));
|
|
115
|
+
},
|
|
89
116
|
navigateToDetailScreen: async ({ name }: { name: string }) => {
|
|
90
117
|
await userEvent.click(await screen.findByRole("cell", { name }));
|
|
91
118
|
},
|
|
119
|
+
navigateToInternal: async () => {
|
|
120
|
+
await userEvent.click(screen.getByRole("button", { name: /go to internal/i }));
|
|
121
|
+
},
|
|
122
|
+
forceNavigateToDetailsScreen: async () => {
|
|
123
|
+
await userEvent.click(screen.getByRole("button", { name: /force details/i }));
|
|
124
|
+
},
|
|
92
125
|
openItemOptions: async ({ id }: { id: string }) => {
|
|
93
126
|
await userEvent.click(await screen.findByTestId(`options-${id}`));
|
|
94
127
|
},
|
|
@@ -107,17 +140,18 @@ describe("ModelRouter", () => {
|
|
|
107
140
|
const firstNameElement = screen.getByRole("textbox", { name: /first name/i });
|
|
108
141
|
const middleNameElement = screen.getByRole("textbox", { name: /middle name/i });
|
|
109
142
|
const lastNameElement = screen.getByRole("textbox", { name: /last name/i });
|
|
110
|
-
const genderElement = screen.getByRole("
|
|
143
|
+
const genderElement = screen.getByRole("button", { name: /gender/i });
|
|
111
144
|
const ageElement = screen.getByRole("spinbutton", { name: /age/i });
|
|
112
145
|
const birthDateElement = screen.getByRole("textbox", { name: /birth date/i });
|
|
113
|
-
const manufacturerElement = screen.getByRole("
|
|
114
|
-
const modelElement = screen.getByRole("
|
|
146
|
+
const manufacturerElement = screen.getByRole("button", { name: /manufacturer/i });
|
|
147
|
+
const modelElement = screen.getByRole("button", { name: /model/i });
|
|
115
148
|
const colorElement = screen.getByRole("textbox", { name: /color/i });
|
|
116
|
-
const typeElement = screen.getByRole("
|
|
149
|
+
const typeElement = screen.getByRole("button", { name: /type/i });
|
|
117
150
|
const vinElement = screen.getByRole("textbox", { name: /vin/i });
|
|
118
151
|
const vrmElement = screen.getByRole("textbox", { name: /vrm/i });
|
|
152
|
+
const timeReturnElement = screen.getByRole("textbox", { name: /return time/i });
|
|
119
153
|
const quantityElement = screen.getByRole("spinbutton", { name: /q/i });
|
|
120
|
-
const availableElement = screen.getByRole("
|
|
154
|
+
const availableElement = screen.getByRole("checkbox", { name: /available/i });
|
|
121
155
|
const currencyElement = screen.getByRole("textbox", { name: /currency/i });
|
|
122
156
|
const tradeDateElement = screen.getByRole("textbox", { name: /trade date/i });
|
|
123
157
|
|
|
@@ -126,37 +160,39 @@ describe("ModelRouter", () => {
|
|
|
126
160
|
await userEvent.clear(firstNameElement);
|
|
127
161
|
await userEvent.clear(middleNameElement);
|
|
128
162
|
await userEvent.clear(lastNameElement);
|
|
129
|
-
await userEvent.clear(genderElement);
|
|
130
163
|
await userEvent.clear(ageElement);
|
|
131
164
|
await userEvent.clear(birthDateElement);
|
|
132
|
-
await userEvent.clear(manufacturerElement);
|
|
133
|
-
await userEvent.clear(modelElement);
|
|
134
165
|
await userEvent.clear(colorElement);
|
|
135
|
-
await userEvent.clear(typeElement);
|
|
136
166
|
await userEvent.clear(vinElement);
|
|
137
167
|
await userEvent.clear(vrmElement);
|
|
138
168
|
await userEvent.clear(quantityElement);
|
|
139
169
|
await userEvent.clear(availableElement);
|
|
140
170
|
await userEvent.clear(currencyElement);
|
|
141
171
|
await userEvent.clear(tradeDateElement);
|
|
172
|
+
await userEvent.clear(timeReturnElement);
|
|
173
|
+
await clearCheckbox(availableElement);
|
|
174
|
+
await clearMultiSelect(typeElement);
|
|
142
175
|
}
|
|
143
176
|
await userEvent.type(idElement, instance.id);
|
|
144
177
|
await userEvent.type(firstNameElement, instance.firstName);
|
|
145
178
|
await userEvent.type(middleNameElement, instance.middleName);
|
|
146
179
|
await userEvent.type(lastNameElement, instance.lastName);
|
|
147
|
-
await
|
|
180
|
+
await selectOption(genderElement, instance.gender);
|
|
148
181
|
await userEvent.type(ageElement, instance.age.toString());
|
|
149
|
-
|
|
150
|
-
await
|
|
151
|
-
await
|
|
182
|
+
pickDatetime(birthDateElement, instance.birthDate, BirthDateFormat);
|
|
183
|
+
await selectOption(modelElement, instance.car.model);
|
|
184
|
+
await selectOption(manufacturerElement, instance.car.manufacturer);
|
|
152
185
|
await userEvent.type(colorElement, instance.car.color);
|
|
153
|
-
await
|
|
186
|
+
await selectOptions(typeElement, instance.car.type);
|
|
154
187
|
await userEvent.type(vinElement, instance.car.vin);
|
|
155
188
|
await userEvent.type(vrmElement, instance.car.vrm);
|
|
189
|
+
pickDatetime(timeReturnElement, instance.car.returnTime, ReturnTimeFormat);
|
|
156
190
|
await userEvent.type(quantityElement, instance.quantity.toString());
|
|
157
|
-
|
|
191
|
+
if (instance.available) {
|
|
192
|
+
await userEvent.click(availableElement);
|
|
193
|
+
}
|
|
158
194
|
await userEvent.type(currencyElement, instance.currency);
|
|
159
|
-
|
|
195
|
+
pickDatetime(tradeDateElement, instance.tradeDate, TradeDateFormat);
|
|
160
196
|
|
|
161
197
|
submit && (await userEvent.click(screen.getByRole("button", { name: /save/i })));
|
|
162
198
|
|
|
@@ -167,7 +203,18 @@ describe("ModelRouter", () => {
|
|
|
167
203
|
const renderComponent = async ({
|
|
168
204
|
router = "memory",
|
|
169
205
|
screen = "initial",
|
|
170
|
-
|
|
206
|
+
deleteFeature,
|
|
207
|
+
updateFeature,
|
|
208
|
+
addFeature,
|
|
209
|
+
detailsFeature,
|
|
210
|
+
}: {
|
|
211
|
+
router?: TestRouter;
|
|
212
|
+
screen?: "initial" | "add" | "details" | "update";
|
|
213
|
+
deleteFeature?: boolean;
|
|
214
|
+
updateFeature?: boolean;
|
|
215
|
+
addFeature?: boolean;
|
|
216
|
+
detailsFeature?: boolean;
|
|
217
|
+
} = {}) => {
|
|
171
218
|
const onRequestList = jest.fn();
|
|
172
219
|
const onRequestItem = jest.fn();
|
|
173
220
|
const onSubmitNewItem = jest.fn();
|
|
@@ -175,10 +222,28 @@ describe("ModelRouter", () => {
|
|
|
175
222
|
const onSubmitUpdate = jest.fn();
|
|
176
223
|
const onRequestDelete = jest.fn();
|
|
177
224
|
const args = DummyModelRouter.args;
|
|
225
|
+
const randomItem = getRandomItem(args.initialData);
|
|
226
|
+
|
|
227
|
+
const ForceNavigationComponent = ({ id }: { id: string }) => {
|
|
228
|
+
const navigate = useNavigate();
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<Box>
|
|
232
|
+
<Button onClick={() => navigate(`/${id}/update`)}>Force update</Button>
|
|
233
|
+
<Button onClick={() => navigate("/add")}>Force add</Button>
|
|
234
|
+
<Button onClick={() => navigate(`/${id}`)}>Force details</Button>
|
|
235
|
+
</Box>
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
|
|
178
239
|
const instance = render(
|
|
179
240
|
<NotificationCenterProvider>
|
|
180
241
|
<DummyModelRouter
|
|
181
242
|
{...args}
|
|
243
|
+
deleteFeature={deleteFeature}
|
|
244
|
+
updateFeature={updateFeature}
|
|
245
|
+
addFeature={addFeature}
|
|
246
|
+
detailsFeature={detailsFeature}
|
|
182
247
|
requestTimeout={REQUEST_TIMEOUT}
|
|
183
248
|
onRequestListAction={onRequestList}
|
|
184
249
|
onRequestItem={onRequestItem}
|
|
@@ -187,14 +252,13 @@ describe("ModelRouter", () => {
|
|
|
187
252
|
onSubmitUpdateAction={onSubmitUpdate}
|
|
188
253
|
onRequestDeleteAction={onRequestDelete}
|
|
189
254
|
/>
|
|
255
|
+
<ForceNavigationComponent id={randomItem.item.id} />
|
|
190
256
|
</NotificationCenterProvider>,
|
|
191
257
|
{
|
|
192
258
|
router,
|
|
193
259
|
},
|
|
194
260
|
);
|
|
195
261
|
|
|
196
|
-
const randomItem = getRandomItem(args.initialData);
|
|
197
|
-
|
|
198
262
|
if (screen === "add") {
|
|
199
263
|
await actions.navigateToAddScreen();
|
|
200
264
|
} else if (screen === "details") {
|
|
@@ -217,6 +281,15 @@ describe("ModelRouter", () => {
|
|
|
217
281
|
};
|
|
218
282
|
};
|
|
219
283
|
|
|
284
|
+
const renderComponentInsideRouter = () => {
|
|
285
|
+
render(
|
|
286
|
+
<NotificationCenterProvider>
|
|
287
|
+
<InternalModelRouter />
|
|
288
|
+
</NotificationCenterProvider>,
|
|
289
|
+
{ router: "memory" },
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
|
|
220
293
|
describe("router screens", () => {
|
|
221
294
|
it("would render the list screen by default", async () => {
|
|
222
295
|
await renderComponent();
|
|
@@ -292,6 +365,41 @@ describe("ModelRouter", () => {
|
|
|
292
365
|
});
|
|
293
366
|
});
|
|
294
367
|
|
|
368
|
+
describe("inside another router", () => {
|
|
369
|
+
it("would navigate to the add screen", async () => {
|
|
370
|
+
renderComponentInsideRouter();
|
|
371
|
+
|
|
372
|
+
await actions.navigateToInternal();
|
|
373
|
+
await actions.navigateToAddScreen();
|
|
374
|
+
|
|
375
|
+
await assertions.expectAddScreen();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("would navigate to the detail screen", async () => {
|
|
379
|
+
renderComponentInsideRouter();
|
|
380
|
+
const {
|
|
381
|
+
item: { id, firstName },
|
|
382
|
+
} = getRandomItem<MockInstance>(mockData);
|
|
383
|
+
|
|
384
|
+
await actions.navigateToInternal();
|
|
385
|
+
await actions.navigateToDetailScreen({ name: firstName });
|
|
386
|
+
|
|
387
|
+
await assertions.expectDetailScreen({ id });
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("would navigate to the update screen", async () => {
|
|
391
|
+
renderComponentInsideRouter();
|
|
392
|
+
const {
|
|
393
|
+
item: { id },
|
|
394
|
+
} = getRandomItem<MockInstance>(mockData);
|
|
395
|
+
|
|
396
|
+
await actions.navigateToInternal();
|
|
397
|
+
await actions.navigateToUpdateScreen({ id });
|
|
398
|
+
|
|
399
|
+
await assertions.expectUpdateScreen({ id });
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
295
403
|
describe("list screen", () => {
|
|
296
404
|
it("would call onRequestList when is mounted", async () => {
|
|
297
405
|
const { onRequestList } = await renderComponent();
|
|
@@ -321,13 +429,13 @@ describe("ModelRouter", () => {
|
|
|
321
429
|
it("would render an add button", async () => {
|
|
322
430
|
await renderComponent();
|
|
323
431
|
|
|
324
|
-
expect(screen.getByRole("button", { name:
|
|
432
|
+
expect(screen.getByRole("button", { name: "Add" })).toBeInTheDocument();
|
|
325
433
|
});
|
|
326
434
|
|
|
327
435
|
it("would navigate to the add screen if I press the add button", async () => {
|
|
328
436
|
const { history } = await renderComponent({ router: "router" });
|
|
329
437
|
|
|
330
|
-
await userEvent.click(screen.getByRole("button", { name:
|
|
438
|
+
await userEvent.click(screen.getByRole("button", { name: "Add" }));
|
|
331
439
|
|
|
332
440
|
expect(history.location.pathname).toBe("/add");
|
|
333
441
|
});
|
|
@@ -430,7 +538,7 @@ describe("ModelRouter", () => {
|
|
|
430
538
|
|
|
431
539
|
const newInstance = await actions.fullfillModelForm({ model, submit: true });
|
|
432
540
|
|
|
433
|
-
|
|
541
|
+
expectToHaveBeenCalledOnceWithMockInstance(onSubmitNewItem, newInstance);
|
|
434
542
|
});
|
|
435
543
|
|
|
436
544
|
it("would show a loading indicator when the request is in progress", async () => {
|
|
@@ -593,12 +701,12 @@ describe("ModelRouter", () => {
|
|
|
593
701
|
});
|
|
594
702
|
|
|
595
703
|
it("would make a request with the new values when the form is submitted", async () => {
|
|
596
|
-
const { model } = await renderComponent({ screen: "update" });
|
|
704
|
+
const { model, onSubmitUpdate } = await renderComponent({ screen: "update" });
|
|
597
705
|
|
|
598
706
|
await waitForProgressIndicatorToBeRemoved();
|
|
599
|
-
const newInstance = await actions.fullfillModelForm({ model, clear: true });
|
|
707
|
+
const newInstance = await actions.fullfillModelForm({ model, clear: true, submit: true });
|
|
600
708
|
|
|
601
|
-
|
|
709
|
+
expectToHaveBeenCalledOnceWithMockInstance(onSubmitUpdate, newInstance);
|
|
602
710
|
});
|
|
603
711
|
|
|
604
712
|
it("would show a loading indicator while the submit request is in progress", async () => {
|
|
@@ -610,14 +718,14 @@ describe("ModelRouter", () => {
|
|
|
610
718
|
expectProgressIndicator();
|
|
611
719
|
});
|
|
612
720
|
|
|
613
|
-
it("would navigate to the list screen when the submit request finish", async () => {
|
|
614
|
-
|
|
721
|
+
// it("would navigate to the list screen when the submit request finish", async () => {
|
|
722
|
+
// const { model } = await renderComponent({ screen: "update" });
|
|
615
723
|
|
|
616
|
-
|
|
617
|
-
|
|
724
|
+
// await waitForProgressIndicatorToBeRemoved();
|
|
725
|
+
// await actions.fullfillModelForm({ model, submit: true, clear: true });
|
|
618
726
|
|
|
619
|
-
|
|
620
|
-
});
|
|
727
|
+
// await assertions.expectListScreen();
|
|
728
|
+
// });
|
|
621
729
|
});
|
|
622
730
|
|
|
623
731
|
describe("delete item", () => {
|
|
@@ -663,4 +771,103 @@ describe("ModelRouter", () => {
|
|
|
663
771
|
expect(screen.queryByRole("cell", { name: firstName })).not.toBeInTheDocument();
|
|
664
772
|
});
|
|
665
773
|
});
|
|
774
|
+
|
|
775
|
+
describe("modifying enabled features", () => {
|
|
776
|
+
describe("deleteFeature disabled", () => {
|
|
777
|
+
it("wouldn't have an option to remove an item from the list", async () => {
|
|
778
|
+
const { data } = await renderComponent({ deleteFeature: false });
|
|
779
|
+
const {
|
|
780
|
+
item: { id, firstName },
|
|
781
|
+
} = getRandomItem<MockInstance>(data);
|
|
782
|
+
|
|
783
|
+
await screen.findByRole("cell", { name: firstName });
|
|
784
|
+
await actions.openItemOptions({ id });
|
|
785
|
+
|
|
786
|
+
expect(screen.queryByRole("menuitem", { name: /remove/i })).not.toBeInTheDocument();
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
describe("updateFeature disabled", () => {
|
|
791
|
+
it("wouldn't have an option to remove an item from the list", async () => {
|
|
792
|
+
const { data } = await renderComponent({ updateFeature: false });
|
|
793
|
+
const {
|
|
794
|
+
item: { id, firstName },
|
|
795
|
+
} = getRandomItem<MockInstance>(data);
|
|
796
|
+
|
|
797
|
+
await screen.findByRole("cell", { name: firstName });
|
|
798
|
+
await actions.openItemOptions({ id });
|
|
799
|
+
|
|
800
|
+
expect(screen.queryByRole("menuitem", { name: /edit/i })).not.toBeInTheDocument();
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it("wouldn't have a path to navigate to edit an item", async () => {
|
|
804
|
+
const {
|
|
805
|
+
randomItem: {
|
|
806
|
+
item: { id },
|
|
807
|
+
},
|
|
808
|
+
} = await renderComponent({
|
|
809
|
+
updateFeature: false,
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
await actions.forceNavigateToUpdateScreen();
|
|
813
|
+
|
|
814
|
+
assertions.expectNotToBeInUpdateScreen({ id });
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
describe("deleteFeature and updateFeature disabled", () => {
|
|
819
|
+
it("wouldn't render an options button in the list", async () => {
|
|
820
|
+
const { data } = await renderComponent({ deleteFeature: false, updateFeature: false });
|
|
821
|
+
const {
|
|
822
|
+
item: { id },
|
|
823
|
+
} = getRandomItem<MockInstance>(data);
|
|
824
|
+
|
|
825
|
+
assertions.expectNotToHaveMenuOption({ id });
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
describe("addFeature disabled", () => {
|
|
830
|
+
it("wouldn't render a button to navigate to the add screen", async () => {
|
|
831
|
+
await renderComponent({ addFeature: false });
|
|
832
|
+
|
|
833
|
+
expect(screen.queryByRole("button", { name: "Add" })).not.toBeInTheDocument();
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
it("wouldn't have a path to navigate to the add screen", async () => {
|
|
837
|
+
await renderComponent({
|
|
838
|
+
addFeature: false,
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
await actions.forceNavigateToAddScreen();
|
|
842
|
+
|
|
843
|
+
assertions.expectNotToBeInAddScreen();
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
describe("detailsFeature disabled", () => {
|
|
848
|
+
it("wouldn't navigate to the details screen if I click a row item", async () => {
|
|
849
|
+
const {
|
|
850
|
+
randomItem: {
|
|
851
|
+
item: { id, firstName },
|
|
852
|
+
},
|
|
853
|
+
} = await renderComponent({ detailsFeature: false });
|
|
854
|
+
|
|
855
|
+
await userEvent.click(await screen.findByRole("cell", { name: firstName }));
|
|
856
|
+
|
|
857
|
+
assertions.expectNotToBeInDetailsScreen({ id });
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
it("wouldn't have a path to navigate to the details screen", async () => {
|
|
861
|
+
const {
|
|
862
|
+
randomItem: {
|
|
863
|
+
item: { id },
|
|
864
|
+
},
|
|
865
|
+
} = await renderComponent({ detailsFeature: false });
|
|
866
|
+
|
|
867
|
+
await actions.forceNavigateToDetailsScreen();
|
|
868
|
+
|
|
869
|
+
assertions.expectNotToBeInDetailsScreen({ id });
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
});
|
|
666
873
|
});
|
|
@@ -18,12 +18,13 @@ 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 = true, addFeature = true, detailsFeature = true } = props;
|
|
21
22
|
return (
|
|
22
23
|
<Routes>
|
|
23
24
|
<Route path="" element={<ListScreen {...props} />} />
|
|
24
|
-
<Route path=":id" element={<DetailsScreen {...props} />} />
|
|
25
|
-
<Route path="add" element={<AddScreen {...props} />} />
|
|
26
|
-
<Route path=":id/update" element={<UpdateScreen {...props} />} />
|
|
25
|
+
{detailsFeature && <Route path=":id" element={<DetailsScreen {...props} />} />}
|
|
26
|
+
{addFeature && <Route path="add" element={<AddScreen {...props} />} />}
|
|
27
|
+
{updateFeature && <Route path=":id/update" element={<UpdateScreen {...props} />} />}
|
|
27
28
|
</Routes>
|
|
28
29
|
);
|
|
29
30
|
};
|
|
@@ -24,6 +24,7 @@ export interface AddScreenProps<T extends BasicModelInstance> extends BaseScreen
|
|
|
24
24
|
export const AddScreen = <T extends BasicModelInstance>({
|
|
25
25
|
model,
|
|
26
26
|
modelName,
|
|
27
|
+
basePath = "",
|
|
27
28
|
onSubmitNewItem,
|
|
28
29
|
newItemRequest,
|
|
29
30
|
}: AddScreenProps<T>) => {
|
|
@@ -33,7 +34,7 @@ export const AddScreen = <T extends BasicModelInstance>({
|
|
|
33
34
|
useEffect(() => {
|
|
34
35
|
if (newItemRequest.success) {
|
|
35
36
|
show({ message: "Item added successfully", severity: "success" });
|
|
36
|
-
navigate(
|
|
37
|
+
navigate(`${basePath}/`);
|
|
37
38
|
}
|
|
38
39
|
}, [newItemRequest.success]);
|
|
39
40
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect } from "react";
|
|
2
2
|
import { useNavigate } from "react-router-dom";
|
|
3
|
-
import { Content, Header, TableList } from "~/components";
|
|
3
|
+
import { Content, Header, HeaderAction, TableList, TableRowOption } from "~/components";
|
|
4
4
|
import { BasicModelInstance } from "~/generators";
|
|
5
5
|
import { HeaderLayout } from "../../../layouts";
|
|
6
6
|
import { RequestState } from "../model-router.types";
|
|
@@ -41,10 +41,15 @@ export const ListScreen = <T extends BasicModelInstance>({
|
|
|
41
41
|
model,
|
|
42
42
|
modelName,
|
|
43
43
|
listData,
|
|
44
|
-
onRequestList,
|
|
45
|
-
onClickDeleteItem,
|
|
46
44
|
listRequest,
|
|
47
45
|
deleteRequest,
|
|
46
|
+
basePath = "",
|
|
47
|
+
deleteFeature = true,
|
|
48
|
+
updateFeature = true,
|
|
49
|
+
addFeature = true,
|
|
50
|
+
detailsFeature = true,
|
|
51
|
+
onRequestList,
|
|
52
|
+
onClickDeleteItem,
|
|
48
53
|
}: ListScreenProps<T>) => {
|
|
49
54
|
const navigate = useNavigate();
|
|
50
55
|
|
|
@@ -52,30 +57,51 @@ export const ListScreen = <T extends BasicModelInstance>({
|
|
|
52
57
|
onRequestList();
|
|
53
58
|
}, []);
|
|
54
59
|
|
|
55
|
-
const handleClickListItem =
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
const handleClickListItem = detailsFeature
|
|
61
|
+
? (item: T) => {
|
|
62
|
+
navigate(`${basePath}/${item.id}`);
|
|
63
|
+
}
|
|
64
|
+
: undefined;
|
|
58
65
|
|
|
59
66
|
const handleClickListOption = (optionId: "edit" | "remove", item: T) => {
|
|
60
67
|
if (optionId === "edit") {
|
|
61
|
-
navigate(
|
|
68
|
+
navigate(`${basePath}/${item.id}/update`);
|
|
62
69
|
} else {
|
|
63
70
|
onClickDeleteItem(item);
|
|
64
71
|
}
|
|
65
72
|
};
|
|
66
73
|
|
|
74
|
+
const options: TableRowOption<T>[] = [];
|
|
75
|
+
|
|
76
|
+
updateFeature &&
|
|
77
|
+
options.push({
|
|
78
|
+
id: "edit",
|
|
79
|
+
label: "Edit",
|
|
80
|
+
onClick: (item: T) => handleClickListOption("edit", item),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
deleteFeature &&
|
|
84
|
+
options.push({
|
|
85
|
+
id: "remove",
|
|
86
|
+
label: "Remove",
|
|
87
|
+
onClick: (item: T) => handleClickListOption("remove", item),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const actions: HeaderAction[] = [];
|
|
91
|
+
|
|
92
|
+
addFeature &&
|
|
93
|
+
actions.push({
|
|
94
|
+
id: "add",
|
|
95
|
+
text: "Add",
|
|
96
|
+
href: `${basePath}/add`,
|
|
97
|
+
});
|
|
98
|
+
|
|
67
99
|
return (
|
|
68
100
|
<HeaderLayout loading={listRequest.loading || deleteRequest.loading}>
|
|
69
101
|
<Header
|
|
70
102
|
title={modelName}
|
|
71
103
|
preset="default"
|
|
72
|
-
actions={
|
|
73
|
-
{
|
|
74
|
-
id: "add",
|
|
75
|
-
text: "Add",
|
|
76
|
-
href: "/add",
|
|
77
|
-
},
|
|
78
|
-
]}
|
|
104
|
+
actions={actions.length > 0 ? actions : undefined}
|
|
79
105
|
/>
|
|
80
106
|
<Content>
|
|
81
107
|
<TableList
|
|
@@ -91,18 +117,7 @@ export const ListScreen = <T extends BasicModelInstance>({
|
|
|
91
117
|
data={listData}
|
|
92
118
|
defaultSort={model.fields[0].id}
|
|
93
119
|
onClick={handleClickListItem}
|
|
94
|
-
options={
|
|
95
|
-
{
|
|
96
|
-
id: "edit",
|
|
97
|
-
label: "Edit",
|
|
98
|
-
onClick: (item) => handleClickListOption("edit", item),
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
id: "remove",
|
|
102
|
-
label: "Remove",
|
|
103
|
-
onClick: (item) => handleClickListOption("remove", item),
|
|
104
|
-
},
|
|
105
|
-
]}
|
|
120
|
+
options={options.length > 0 ? options : undefined}
|
|
106
121
|
/>
|
|
107
122
|
</Content>
|
|
108
123
|
</HeaderLayout>
|
|
@@ -10,4 +10,29 @@ export interface BaseScreenProps {
|
|
|
10
10
|
* Structure that represents the fields of the model
|
|
11
11
|
*/
|
|
12
12
|
model: Model;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Path to attach before each internal ModelRouter path
|
|
16
|
+
*/
|
|
17
|
+
basePath?: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* If true delete features are enabled
|
|
21
|
+
*/
|
|
22
|
+
deleteFeature?: boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* If true update features are enabled
|
|
26
|
+
*/
|
|
27
|
+
updateFeature?: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* If true add features are enabled
|
|
31
|
+
*/
|
|
32
|
+
addFeature?: boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* If true details features are enabled
|
|
36
|
+
*/
|
|
37
|
+
detailsFeature?: boolean;
|
|
13
38
|
}
|
|
@@ -39,6 +39,7 @@ export interface UpdateScreenProps<T extends BasicModelInstance> extends BaseScr
|
|
|
39
39
|
export const UpdateScreen = <T extends BasicModelInstance>({
|
|
40
40
|
model,
|
|
41
41
|
modelName,
|
|
42
|
+
basePath = "",
|
|
42
43
|
submitUpdateItemRequest,
|
|
43
44
|
updateItemRequest,
|
|
44
45
|
updateItem,
|
|
@@ -61,7 +62,7 @@ export const UpdateScreen = <T extends BasicModelInstance>({
|
|
|
61
62
|
message: `The item ${id} has been updated successfully`,
|
|
62
63
|
severity: "success",
|
|
63
64
|
});
|
|
64
|
-
navigate(
|
|
65
|
+
navigate(`${basePath}/`);
|
|
65
66
|
}
|
|
66
67
|
}, [submitUpdateItemRequest.success]);
|
|
67
68
|
|