@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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { DummyModelRouter } from "./stories/model-router.stories";
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { DummyModelRouter, InternalModelRouter } from "./stories/model-router.stories";
|
|
3
3
|
import {
|
|
4
4
|
expectModelFieldInputExist,
|
|
5
5
|
expectProgressIndicator,
|
|
@@ -9,15 +9,34 @@ import {
|
|
|
9
9
|
TestRouter,
|
|
10
10
|
expectModelFieldValue,
|
|
11
11
|
expectModelFieldInputValue,
|
|
12
|
+
selectOption,
|
|
12
13
|
} from "~/tests";
|
|
14
|
+
import { data as mockData } from "./stories/templates";
|
|
13
15
|
import userEvent from "@testing-library/user-event";
|
|
14
16
|
import { getRandomItem } from "../../utils";
|
|
15
17
|
import { Model } from "../generators.model";
|
|
16
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
BirthDateFormat,
|
|
20
|
+
createModelInstance,
|
|
21
|
+
MockInstance,
|
|
22
|
+
mockModel,
|
|
23
|
+
ReturnTimeFormat,
|
|
24
|
+
TradeDateFormat,
|
|
25
|
+
} from "../generators.mock";
|
|
17
26
|
import { NotificationCenterProvider } from "../../providers";
|
|
18
27
|
import { Box } from "@mui/system";
|
|
19
28
|
import { Button } from "@mui/material";
|
|
20
29
|
import { useNavigate } from "react-router-dom";
|
|
30
|
+
import {
|
|
31
|
+
clearCheckbox,
|
|
32
|
+
clearMultiSelect,
|
|
33
|
+
expectAlert,
|
|
34
|
+
expectToHaveBeenCalledOnceWithMockInstance,
|
|
35
|
+
pickDatetime,
|
|
36
|
+
selectOptions,
|
|
37
|
+
} from "../../tests";
|
|
38
|
+
import { AddScreen, ListScreen, UpdateScreen } from "./screens";
|
|
39
|
+
import { IdleRequest, LoadingRequest, SuccessRequest } from "./model-router.types";
|
|
21
40
|
|
|
22
41
|
const REQUEST_TIMEOUT = 20;
|
|
23
42
|
|
|
@@ -81,35 +100,11 @@ describe("ModelRouter", () => {
|
|
|
81
100
|
expectNotToHaveMenuOption: ({ id }: { id: string }) => {
|
|
82
101
|
expect(screen.queryByTestId(`options-${id}`)).not.toBeInTheDocument();
|
|
83
102
|
},
|
|
84
|
-
expectSubmitInstanceCall: (mockFn: jest.Mock, instance: MockInstance) => {
|
|
85
|
-
expect(mockFn).toHaveBeenCalledTimes(1);
|
|
86
|
-
expect(mockFn).toHaveBeenCalledWith({
|
|
87
|
-
id: instance.id,
|
|
88
|
-
firstName: instance.firstName,
|
|
89
|
-
middleName: instance.middleName,
|
|
90
|
-
lastName: instance.lastName,
|
|
91
|
-
gender: instance.gender,
|
|
92
|
-
age: instance.age.toString(),
|
|
93
|
-
birthDate: instance.birthDate,
|
|
94
|
-
car: {
|
|
95
|
-
model: instance.car.model,
|
|
96
|
-
manufacturer: instance.car.manufacturer,
|
|
97
|
-
color: instance.car.color,
|
|
98
|
-
type: instance.car.type,
|
|
99
|
-
vin: instance.car.vin,
|
|
100
|
-
vrm: instance.car.vrm,
|
|
101
|
-
},
|
|
102
|
-
quantity: instance.quantity.toString(),
|
|
103
|
-
available: instance.available.toString(),
|
|
104
|
-
currency: instance.currency,
|
|
105
|
-
tradeDate: instance.tradeDate,
|
|
106
|
-
});
|
|
107
|
-
},
|
|
108
103
|
};
|
|
109
104
|
|
|
110
105
|
const actions = {
|
|
111
106
|
navigateToAddScreen: async () => {
|
|
112
|
-
await userEvent.click(screen.
|
|
107
|
+
await userEvent.click(await screen.findByRole("button", { name: "Add" }));
|
|
113
108
|
},
|
|
114
109
|
forceNavigateToAddScreen: async () => {
|
|
115
110
|
await userEvent.click(screen.getByRole("button", { name: /force add/i }));
|
|
@@ -124,6 +119,9 @@ describe("ModelRouter", () => {
|
|
|
124
119
|
navigateToDetailScreen: async ({ name }: { name: string }) => {
|
|
125
120
|
await userEvent.click(await screen.findByRole("cell", { name }));
|
|
126
121
|
},
|
|
122
|
+
navigateToInternal: async () => {
|
|
123
|
+
await userEvent.click(screen.getByRole("button", { name: /go to internal/i }));
|
|
124
|
+
},
|
|
127
125
|
forceNavigateToDetailsScreen: async () => {
|
|
128
126
|
await userEvent.click(screen.getByRole("button", { name: /force details/i }));
|
|
129
127
|
},
|
|
@@ -145,17 +143,18 @@ describe("ModelRouter", () => {
|
|
|
145
143
|
const firstNameElement = screen.getByRole("textbox", { name: /first name/i });
|
|
146
144
|
const middleNameElement = screen.getByRole("textbox", { name: /middle name/i });
|
|
147
145
|
const lastNameElement = screen.getByRole("textbox", { name: /last name/i });
|
|
148
|
-
const genderElement = screen.getByRole("
|
|
146
|
+
const genderElement = screen.getByRole("button", { name: /gender/i });
|
|
149
147
|
const ageElement = screen.getByRole("spinbutton", { name: /age/i });
|
|
150
148
|
const birthDateElement = screen.getByRole("textbox", { name: /birth date/i });
|
|
151
|
-
const manufacturerElement = screen.getByRole("
|
|
152
|
-
const modelElement = screen.getByRole("
|
|
149
|
+
const manufacturerElement = screen.getByRole("button", { name: /manufacturer/i });
|
|
150
|
+
const modelElement = screen.getByRole("button", { name: /model/i });
|
|
153
151
|
const colorElement = screen.getByRole("textbox", { name: /color/i });
|
|
154
|
-
const typeElement = screen.getByRole("
|
|
152
|
+
const typeElement = screen.getByRole("button", { name: /type/i });
|
|
155
153
|
const vinElement = screen.getByRole("textbox", { name: /vin/i });
|
|
156
154
|
const vrmElement = screen.getByRole("textbox", { name: /vrm/i });
|
|
155
|
+
const timeReturnElement = screen.getByRole("textbox", { name: /return time/i });
|
|
157
156
|
const quantityElement = screen.getByRole("spinbutton", { name: /q/i });
|
|
158
|
-
const availableElement = screen.getByRole("
|
|
157
|
+
const availableElement = screen.getByRole("checkbox", { name: /available/i });
|
|
159
158
|
const currencyElement = screen.getByRole("textbox", { name: /currency/i });
|
|
160
159
|
const tradeDateElement = screen.getByRole("textbox", { name: /trade date/i });
|
|
161
160
|
|
|
@@ -164,37 +163,39 @@ describe("ModelRouter", () => {
|
|
|
164
163
|
await userEvent.clear(firstNameElement);
|
|
165
164
|
await userEvent.clear(middleNameElement);
|
|
166
165
|
await userEvent.clear(lastNameElement);
|
|
167
|
-
await userEvent.clear(genderElement);
|
|
168
166
|
await userEvent.clear(ageElement);
|
|
169
167
|
await userEvent.clear(birthDateElement);
|
|
170
|
-
await userEvent.clear(manufacturerElement);
|
|
171
|
-
await userEvent.clear(modelElement);
|
|
172
168
|
await userEvent.clear(colorElement);
|
|
173
|
-
await userEvent.clear(typeElement);
|
|
174
169
|
await userEvent.clear(vinElement);
|
|
175
170
|
await userEvent.clear(vrmElement);
|
|
176
171
|
await userEvent.clear(quantityElement);
|
|
177
172
|
await userEvent.clear(availableElement);
|
|
178
173
|
await userEvent.clear(currencyElement);
|
|
179
174
|
await userEvent.clear(tradeDateElement);
|
|
175
|
+
await userEvent.clear(timeReturnElement);
|
|
176
|
+
await clearCheckbox(availableElement);
|
|
177
|
+
await clearMultiSelect(typeElement);
|
|
180
178
|
}
|
|
181
179
|
await userEvent.type(idElement, instance.id);
|
|
182
180
|
await userEvent.type(firstNameElement, instance.firstName);
|
|
183
181
|
await userEvent.type(middleNameElement, instance.middleName);
|
|
184
182
|
await userEvent.type(lastNameElement, instance.lastName);
|
|
185
|
-
await
|
|
183
|
+
await selectOption(genderElement, instance.gender);
|
|
186
184
|
await userEvent.type(ageElement, instance.age.toString());
|
|
187
|
-
|
|
188
|
-
await
|
|
189
|
-
await
|
|
185
|
+
pickDatetime(birthDateElement, instance.birthDate, BirthDateFormat);
|
|
186
|
+
await selectOption(modelElement, instance.car.model);
|
|
187
|
+
await selectOption(manufacturerElement, instance.car.manufacturer);
|
|
190
188
|
await userEvent.type(colorElement, instance.car.color);
|
|
191
|
-
await
|
|
189
|
+
await selectOptions(typeElement, instance.car.type);
|
|
192
190
|
await userEvent.type(vinElement, instance.car.vin);
|
|
193
191
|
await userEvent.type(vrmElement, instance.car.vrm);
|
|
192
|
+
pickDatetime(timeReturnElement, instance.car.returnTime, ReturnTimeFormat);
|
|
194
193
|
await userEvent.type(quantityElement, instance.quantity.toString());
|
|
195
|
-
|
|
194
|
+
if (instance.available) {
|
|
195
|
+
await userEvent.click(availableElement);
|
|
196
|
+
}
|
|
196
197
|
await userEvent.type(currencyElement, instance.currency);
|
|
197
|
-
|
|
198
|
+
pickDatetime(tradeDateElement, instance.tradeDate, TradeDateFormat);
|
|
198
199
|
|
|
199
200
|
submit && (await userEvent.click(screen.getByRole("button", { name: /save/i })));
|
|
200
201
|
|
|
@@ -205,10 +206,10 @@ describe("ModelRouter", () => {
|
|
|
205
206
|
const renderComponent = async ({
|
|
206
207
|
router = "memory",
|
|
207
208
|
screen = "initial",
|
|
208
|
-
deleteFeature
|
|
209
|
-
updateFeature
|
|
210
|
-
addFeature
|
|
211
|
-
detailsFeature
|
|
209
|
+
deleteFeature,
|
|
210
|
+
updateFeature,
|
|
211
|
+
addFeature,
|
|
212
|
+
detailsFeature,
|
|
212
213
|
}: {
|
|
213
214
|
router?: TestRouter;
|
|
214
215
|
screen?: "initial" | "add" | "details" | "update";
|
|
@@ -283,6 +284,107 @@ describe("ModelRouter", () => {
|
|
|
283
284
|
};
|
|
284
285
|
};
|
|
285
286
|
|
|
287
|
+
const renderComponentInsideRouter = () => {
|
|
288
|
+
render(
|
|
289
|
+
<NotificationCenterProvider>
|
|
290
|
+
<InternalModelRouter />
|
|
291
|
+
</NotificationCenterProvider>,
|
|
292
|
+
{ router: "memory" },
|
|
293
|
+
);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const renderListScreen = () => {
|
|
297
|
+
const error = "Lorem ipsum error";
|
|
298
|
+
|
|
299
|
+
const TestComponent = () => {
|
|
300
|
+
const [deleteRequest, setDeleteRequest] = useState(IdleRequest);
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<NotificationCenterProvider>
|
|
304
|
+
<ListScreen
|
|
305
|
+
onRequestList={jest.fn()}
|
|
306
|
+
listData={[]}
|
|
307
|
+
onClickDeleteItem={jest.fn()}
|
|
308
|
+
listRequest={IdleRequest}
|
|
309
|
+
deleteRequest={deleteRequest}
|
|
310
|
+
modelName="Items"
|
|
311
|
+
model={mockModel}
|
|
312
|
+
/>
|
|
313
|
+
<Box>
|
|
314
|
+
<Button onClick={() => setDeleteRequest(IdleRequest)}>idle</Button>
|
|
315
|
+
<Button onClick={() => setDeleteRequest(LoadingRequest)}>loading</Button>
|
|
316
|
+
<Button onClick={() => setDeleteRequest(SuccessRequest)}>success</Button>
|
|
317
|
+
<Button onClick={() => setDeleteRequest({ error })}>error</Button>
|
|
318
|
+
</Box>
|
|
319
|
+
</NotificationCenterProvider>
|
|
320
|
+
);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const instance = render(<TestComponent />);
|
|
324
|
+
|
|
325
|
+
return { ...instance, error, model: mockModel };
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const renderAddScreen = () => {
|
|
329
|
+
const error = "Lorem ipsum error";
|
|
330
|
+
|
|
331
|
+
const TestComponent = () => {
|
|
332
|
+
const [newItemRequest, setNewItemRequest] = useState(IdleRequest);
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<NotificationCenterProvider>
|
|
336
|
+
<AddScreen
|
|
337
|
+
modelName="Items"
|
|
338
|
+
model={mockModel}
|
|
339
|
+
onSubmitNewItem={jest.fn()}
|
|
340
|
+
newItemRequest={newItemRequest}
|
|
341
|
+
/>
|
|
342
|
+
<Box>
|
|
343
|
+
<Button onClick={() => setNewItemRequest(IdleRequest)}>idle</Button>
|
|
344
|
+
<Button onClick={() => setNewItemRequest(LoadingRequest)}>loading</Button>
|
|
345
|
+
<Button onClick={() => setNewItemRequest(SuccessRequest)}>success</Button>
|
|
346
|
+
<Button onClick={() => setNewItemRequest({ error })}>error</Button>
|
|
347
|
+
</Box>
|
|
348
|
+
</NotificationCenterProvider>
|
|
349
|
+
);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const instance = render(<TestComponent />, { router: "router" });
|
|
353
|
+
|
|
354
|
+
return { ...instance, error, model: mockModel };
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const renderUpdateScreen = () => {
|
|
358
|
+
const error = "Lorem ipsum error";
|
|
359
|
+
|
|
360
|
+
const TestComponent = () => {
|
|
361
|
+
const [updateRequest, setUpdateRequest] = useState(IdleRequest);
|
|
362
|
+
|
|
363
|
+
return (
|
|
364
|
+
<NotificationCenterProvider>
|
|
365
|
+
<UpdateScreen
|
|
366
|
+
modelName="Items"
|
|
367
|
+
model={mockModel}
|
|
368
|
+
onSubmitUpdateItem={jest.fn()}
|
|
369
|
+
submitUpdateItemRequest={updateRequest}
|
|
370
|
+
updateItemRequest={IdleRequest}
|
|
371
|
+
onRequestUpdateItem={jest.fn()}
|
|
372
|
+
/>
|
|
373
|
+
<Box>
|
|
374
|
+
<Button onClick={() => setUpdateRequest(IdleRequest)}>idle</Button>
|
|
375
|
+
<Button onClick={() => setUpdateRequest(LoadingRequest)}>loading</Button>
|
|
376
|
+
<Button onClick={() => setUpdateRequest(SuccessRequest)}>success</Button>
|
|
377
|
+
<Button onClick={() => setUpdateRequest({ error })}>error</Button>
|
|
378
|
+
</Box>
|
|
379
|
+
</NotificationCenterProvider>
|
|
380
|
+
);
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const instance = render(<TestComponent />, { router: "router" });
|
|
384
|
+
|
|
385
|
+
return { ...instance, error, model: mockModel };
|
|
386
|
+
};
|
|
387
|
+
|
|
286
388
|
describe("router screens", () => {
|
|
287
389
|
it("would render the list screen by default", async () => {
|
|
288
390
|
await renderComponent();
|
|
@@ -358,6 +460,113 @@ describe("ModelRouter", () => {
|
|
|
358
460
|
});
|
|
359
461
|
});
|
|
360
462
|
|
|
463
|
+
describe("inside another router", () => {
|
|
464
|
+
it("would navigate to the add screen", async () => {
|
|
465
|
+
renderComponentInsideRouter();
|
|
466
|
+
|
|
467
|
+
await actions.navigateToInternal();
|
|
468
|
+
await actions.navigateToAddScreen();
|
|
469
|
+
|
|
470
|
+
await assertions.expectAddScreen();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("would be able to go back to the list sreen from the add screen", async () => {
|
|
474
|
+
renderComponentInsideRouter();
|
|
475
|
+
|
|
476
|
+
await actions.navigateToInternal();
|
|
477
|
+
await actions.navigateToAddScreen();
|
|
478
|
+
await userEvent.click(screen.getByRole("link", { name: "Items" }));
|
|
479
|
+
|
|
480
|
+
await assertions.expectListScreen();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("would navigate to the add screen if I click the navigaiton path", async () => {
|
|
484
|
+
renderComponentInsideRouter();
|
|
485
|
+
|
|
486
|
+
await actions.navigateToInternal();
|
|
487
|
+
await actions.navigateToAddScreen();
|
|
488
|
+
await userEvent.click(screen.getByRole("link", { name: "Add new Items" }));
|
|
489
|
+
|
|
490
|
+
await assertions.expectAddScreen();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("would navigate to the detail screen", async () => {
|
|
494
|
+
renderComponentInsideRouter();
|
|
495
|
+
const {
|
|
496
|
+
item: { id, firstName },
|
|
497
|
+
} = getRandomItem<MockInstance>(mockData);
|
|
498
|
+
|
|
499
|
+
await actions.navigateToInternal();
|
|
500
|
+
await actions.navigateToDetailScreen({ name: firstName });
|
|
501
|
+
|
|
502
|
+
await assertions.expectDetailScreen({ id });
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("would be able to go back to the list sreen from the details screen", async () => {
|
|
506
|
+
renderComponentInsideRouter();
|
|
507
|
+
const {
|
|
508
|
+
item: { firstName },
|
|
509
|
+
} = getRandomItem<MockInstance>(mockData);
|
|
510
|
+
|
|
511
|
+
await actions.navigateToInternal();
|
|
512
|
+
await actions.navigateToDetailScreen({ name: firstName });
|
|
513
|
+
await userEvent.click(screen.getByRole("link", { name: "Items" }));
|
|
514
|
+
|
|
515
|
+
await assertions.expectListScreen();
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it("would navigate to the add screen if I click the navigation path", async () => {
|
|
519
|
+
renderComponentInsideRouter();
|
|
520
|
+
const {
|
|
521
|
+
item: { id, firstName },
|
|
522
|
+
} = getRandomItem<MockInstance>(mockData);
|
|
523
|
+
|
|
524
|
+
await actions.navigateToInternal();
|
|
525
|
+
await actions.navigateToDetailScreen({ name: firstName });
|
|
526
|
+
await userEvent.click(screen.getByRole("link", { name: id }));
|
|
527
|
+
|
|
528
|
+
await assertions.expectDetailScreen({ id });
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("would navigate to the update screen", async () => {
|
|
532
|
+
renderComponentInsideRouter();
|
|
533
|
+
const {
|
|
534
|
+
item: { id },
|
|
535
|
+
} = getRandomItem<MockInstance>(mockData);
|
|
536
|
+
|
|
537
|
+
await actions.navigateToInternal();
|
|
538
|
+
await actions.navigateToUpdateScreen({ id });
|
|
539
|
+
|
|
540
|
+
await assertions.expectUpdateScreen({ id });
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it("would be able to go back to the list sreen from the details screen", async () => {
|
|
544
|
+
renderComponentInsideRouter();
|
|
545
|
+
const {
|
|
546
|
+
item: { id },
|
|
547
|
+
} = getRandomItem<MockInstance>(mockData);
|
|
548
|
+
|
|
549
|
+
await actions.navigateToInternal();
|
|
550
|
+
await actions.navigateToUpdateScreen({ id });
|
|
551
|
+
await userEvent.click(screen.getByRole("link", { name: "Items" }));
|
|
552
|
+
|
|
553
|
+
await assertions.expectListScreen();
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("would navigate to the add screen if I click the navigation path", async () => {
|
|
557
|
+
renderComponentInsideRouter();
|
|
558
|
+
const {
|
|
559
|
+
item: { id },
|
|
560
|
+
} = getRandomItem<MockInstance>(mockData);
|
|
561
|
+
|
|
562
|
+
await actions.navigateToInternal();
|
|
563
|
+
await actions.navigateToUpdateScreen({ id });
|
|
564
|
+
await userEvent.click(screen.getByRole("link", { name: `Edit ${id}` }));
|
|
565
|
+
|
|
566
|
+
await assertions.expectUpdateScreen({ id });
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
361
570
|
describe("list screen", () => {
|
|
362
571
|
it("would call onRequestList when is mounted", async () => {
|
|
363
572
|
const { onRequestList } = await renderComponent();
|
|
@@ -496,7 +705,7 @@ describe("ModelRouter", () => {
|
|
|
496
705
|
|
|
497
706
|
const newInstance = await actions.fullfillModelForm({ model, submit: true });
|
|
498
707
|
|
|
499
|
-
|
|
708
|
+
expectToHaveBeenCalledOnceWithMockInstance(onSubmitNewItem, newInstance);
|
|
500
709
|
});
|
|
501
710
|
|
|
502
711
|
it("would show a loading indicator when the request is in progress", async () => {
|
|
@@ -507,24 +716,36 @@ describe("ModelRouter", () => {
|
|
|
507
716
|
expectProgressIndicator();
|
|
508
717
|
});
|
|
509
718
|
|
|
510
|
-
|
|
511
|
-
|
|
719
|
+
it("would show a success message if the request finish with a success", async () => {
|
|
720
|
+
renderAddScreen();
|
|
512
721
|
|
|
513
|
-
|
|
722
|
+
await userEvent.click(screen.getByRole("button", { name: /success/i }));
|
|
514
723
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
724
|
+
await expectAlert({
|
|
725
|
+
message: /item added successfully/i,
|
|
726
|
+
severity: "success",
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
it("would navigate to the list screen if the request finish with a success", async () => {
|
|
731
|
+
const { history } = renderAddScreen();
|
|
520
732
|
|
|
521
|
-
|
|
522
|
-
// await renderComponent({ screen: "add" });
|
|
733
|
+
await userEvent.click(screen.getByRole("button", { name: /success/i }));
|
|
523
734
|
|
|
524
|
-
|
|
735
|
+
expect(history.location.pathname).toBe("/");
|
|
736
|
+
});
|
|
525
737
|
|
|
526
|
-
|
|
527
|
-
|
|
738
|
+
it("would show an error message if the request finish with an error", async () => {
|
|
739
|
+
const { error } = renderAddScreen();
|
|
740
|
+
|
|
741
|
+
await userEvent.click(screen.getByRole("button", { name: /error/i }));
|
|
742
|
+
|
|
743
|
+
await expectAlert({
|
|
744
|
+
title: /we had an error/i,
|
|
745
|
+
message: error,
|
|
746
|
+
severity: "error",
|
|
747
|
+
});
|
|
748
|
+
});
|
|
528
749
|
});
|
|
529
750
|
|
|
530
751
|
describe("details screen", () => {
|
|
@@ -659,12 +880,12 @@ describe("ModelRouter", () => {
|
|
|
659
880
|
});
|
|
660
881
|
|
|
661
882
|
it("would make a request with the new values when the form is submitted", async () => {
|
|
662
|
-
const { model } = await renderComponent({ screen: "update" });
|
|
883
|
+
const { model, onSubmitUpdate } = await renderComponent({ screen: "update" });
|
|
663
884
|
|
|
664
885
|
await waitForProgressIndicatorToBeRemoved();
|
|
665
|
-
const newInstance = await actions.fullfillModelForm({ model, clear: true });
|
|
886
|
+
const newInstance = await actions.fullfillModelForm({ model, clear: true, submit: true });
|
|
666
887
|
|
|
667
|
-
|
|
888
|
+
expectToHaveBeenCalledOnceWithMockInstance(onSubmitUpdate, newInstance);
|
|
668
889
|
});
|
|
669
890
|
|
|
670
891
|
it("would show a loading indicator while the submit request is in progress", async () => {
|
|
@@ -676,13 +897,36 @@ describe("ModelRouter", () => {
|
|
|
676
897
|
expectProgressIndicator();
|
|
677
898
|
});
|
|
678
899
|
|
|
679
|
-
it("would
|
|
680
|
-
|
|
900
|
+
it("would show a success message if the request finish with a success", async () => {
|
|
901
|
+
renderUpdateScreen();
|
|
681
902
|
|
|
682
|
-
await
|
|
683
|
-
const newInstance = await actions.fullfillModelForm({ model, submit: true, clear: true });
|
|
903
|
+
await userEvent.click(screen.getByRole("button", { name: /success/i }));
|
|
684
904
|
|
|
685
|
-
|
|
905
|
+
await expectAlert({
|
|
906
|
+
title: /item updated/i,
|
|
907
|
+
message: /has been updated successfully/i,
|
|
908
|
+
severity: "success",
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
it("would navigate to the list screen if the request finish with a success", async () => {
|
|
913
|
+
const { history } = renderUpdateScreen();
|
|
914
|
+
|
|
915
|
+
await userEvent.click(screen.getByRole("button", { name: /success/i }));
|
|
916
|
+
|
|
917
|
+
expect(history.location.pathname).toBe("/");
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
it("would show an error message if the request finish with an error", async () => {
|
|
921
|
+
const { error } = renderUpdateScreen();
|
|
922
|
+
|
|
923
|
+
await userEvent.click(screen.getByRole("button", { name: /error/i }));
|
|
924
|
+
|
|
925
|
+
await expectAlert({
|
|
926
|
+
title: /we had an error/i,
|
|
927
|
+
message: error,
|
|
928
|
+
severity: "error",
|
|
929
|
+
});
|
|
686
930
|
});
|
|
687
931
|
});
|
|
688
932
|
|
|
@@ -728,6 +972,30 @@ describe("ModelRouter", () => {
|
|
|
728
972
|
|
|
729
973
|
expect(screen.queryByRole("cell", { name: firstName })).not.toBeInTheDocument();
|
|
730
974
|
});
|
|
975
|
+
|
|
976
|
+
it("would show a success message if the delete request finish with a success", async () => {
|
|
977
|
+
renderListScreen();
|
|
978
|
+
|
|
979
|
+
await userEvent.click(screen.getByRole("button", { name: /success/i }));
|
|
980
|
+
|
|
981
|
+
await expectAlert({
|
|
982
|
+
title: /item deleted/i,
|
|
983
|
+
message: /the item has been deleted successfully/i,
|
|
984
|
+
severity: "success",
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
it("would show a error message if the delete request finish with an error", async () => {
|
|
989
|
+
const { error } = renderListScreen();
|
|
990
|
+
|
|
991
|
+
await userEvent.click(screen.getByRole("button", { name: /error/i }));
|
|
992
|
+
|
|
993
|
+
await expectAlert({
|
|
994
|
+
title: /we had an error/i,
|
|
995
|
+
message: error,
|
|
996
|
+
severity: "error",
|
|
997
|
+
});
|
|
998
|
+
});
|
|
731
999
|
});
|
|
732
1000
|
|
|
733
1001
|
describe("modifying enabled features", () => {
|
|
@@ -18,7 +18,7 @@ export type ModelRouterProps<T extends BasicModelInstance> = DetailsScreenProps<
|
|
|
18
18
|
UpdateScreenProps<T>;
|
|
19
19
|
|
|
20
20
|
export const ModelRouter = <T extends BasicModelInstance>(props: ModelRouterProps<T>) => {
|
|
21
|
-
const { updateFeature, addFeature, detailsFeature } = props;
|
|
21
|
+
const { updateFeature = true, addFeature = true, detailsFeature = true } = props;
|
|
22
22
|
return (
|
|
23
23
|
<Routes>
|
|
24
24
|
<Route path="" element={<ListScreen {...props} />} />
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { useNavigate } from "react-router-dom";
|
|
1
|
+
import React from "react";
|
|
3
2
|
import { Header, Content } from "~/components";
|
|
4
3
|
import { BasicModelInstance, ModelForm } from "~/generators";
|
|
5
4
|
import { HeaderLayout } from "../../../layouts";
|
|
6
|
-
import {
|
|
5
|
+
import { useNotifyWhenValueChanges } from "../../../providers";
|
|
7
6
|
import { RequestState } from "../model-router.types";
|
|
8
7
|
import { BaseScreenProps } from "./screens.types";
|
|
8
|
+
import { useNavigateWhenValueChanges } from "../../../hooks";
|
|
9
9
|
|
|
10
10
|
export interface AddScreenProps<T extends BasicModelInstance> extends BaseScreenProps {
|
|
11
11
|
/**
|
|
@@ -28,21 +28,17 @@ export const AddScreen = <T extends BasicModelInstance>({
|
|
|
28
28
|
onSubmitNewItem,
|
|
29
29
|
newItemRequest,
|
|
30
30
|
}: AddScreenProps<T>) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (newItemRequest.error) {
|
|
43
|
-
show({ title: "We had an error", message: newItemRequest.error, severity: "error" });
|
|
44
|
-
}
|
|
45
|
-
}, [newItemRequest.error]);
|
|
31
|
+
useNotifyWhenValueChanges(
|
|
32
|
+
{ message: "Item added successfully", severity: "success" },
|
|
33
|
+
!!newItemRequest.success,
|
|
34
|
+
{ from: false, to: true },
|
|
35
|
+
);
|
|
36
|
+
useNavigateWhenValueChanges(`${basePath}/`, !!newItemRequest.success, { from: false, to: true });
|
|
37
|
+
useNotifyWhenValueChanges(
|
|
38
|
+
{ title: "We had an error", message: newItemRequest.error || "", severity: "error" },
|
|
39
|
+
!!newItemRequest.error,
|
|
40
|
+
{ from: false, to: true },
|
|
41
|
+
);
|
|
46
42
|
|
|
47
43
|
return (
|
|
48
44
|
<HeaderLayout loading={newItemRequest.loading}>
|
|
@@ -53,12 +49,12 @@ export const AddScreen = <T extends BasicModelInstance>({
|
|
|
53
49
|
{
|
|
54
50
|
id: "list",
|
|
55
51
|
text: modelName,
|
|
56
|
-
link:
|
|
52
|
+
link: `${basePath}/`,
|
|
57
53
|
},
|
|
58
54
|
{
|
|
59
55
|
id: "add",
|
|
60
56
|
text: `Add new ${modelName}`,
|
|
61
|
-
link:
|
|
57
|
+
link: `${basePath}/add`,
|
|
62
58
|
},
|
|
63
59
|
]}
|
|
64
60
|
/>
|
|
@@ -28,6 +28,7 @@ export interface DetailsScreenProps<T extends BasicModelInstance> extends BaseSc
|
|
|
28
28
|
export const DetailsScreen = <T extends BasicModelInstance>({
|
|
29
29
|
model,
|
|
30
30
|
modelName,
|
|
31
|
+
basePath = "",
|
|
31
32
|
onRequestItem,
|
|
32
33
|
itemRequest,
|
|
33
34
|
detailsItem,
|
|
@@ -47,12 +48,12 @@ export const DetailsScreen = <T extends BasicModelInstance>({
|
|
|
47
48
|
{
|
|
48
49
|
id: "list",
|
|
49
50
|
text: modelName,
|
|
50
|
-
link:
|
|
51
|
+
link: `${basePath}/`,
|
|
51
52
|
},
|
|
52
53
|
{
|
|
53
54
|
id: "detail",
|
|
54
55
|
text: id,
|
|
55
|
-
link:
|
|
56
|
+
link: `${basePath}/${id}`,
|
|
56
57
|
},
|
|
57
58
|
]}
|
|
58
59
|
/>
|
|
@@ -2,6 +2,7 @@ import React, { useEffect } from "react";
|
|
|
2
2
|
import { useNavigate } from "react-router-dom";
|
|
3
3
|
import { Content, Header, HeaderAction, TableList, TableRowOption } from "~/components";
|
|
4
4
|
import { BasicModelInstance } from "~/generators";
|
|
5
|
+
import { useNotifyWhenValueChanges } from "~/providers";
|
|
5
6
|
import { HeaderLayout } from "../../../layouts";
|
|
6
7
|
import { RequestState } from "../model-router.types";
|
|
7
8
|
import { BaseScreenProps } from "./screens.types";
|
|
@@ -57,6 +58,22 @@ export const ListScreen = <T extends BasicModelInstance>({
|
|
|
57
58
|
onRequestList();
|
|
58
59
|
}, []);
|
|
59
60
|
|
|
61
|
+
useNotifyWhenValueChanges(
|
|
62
|
+
{
|
|
63
|
+
title: "Item deleted",
|
|
64
|
+
message: "The item has been deleted successfully",
|
|
65
|
+
severity: "success",
|
|
66
|
+
},
|
|
67
|
+
!!deleteRequest.success,
|
|
68
|
+
{ from: false, to: true },
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
useNotifyWhenValueChanges(
|
|
72
|
+
{ title: "We had an error", message: deleteRequest.error || "", severity: "error" },
|
|
73
|
+
!!deleteRequest.error,
|
|
74
|
+
{ from: false, to: true },
|
|
75
|
+
);
|
|
76
|
+
|
|
60
77
|
const handleClickListItem = detailsFeature
|
|
61
78
|
? (item: T) => {
|
|
62
79
|
navigate(`${basePath}/${item.id}`);
|