@truedat/core 8.12.12 → 8.13.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/package.json +3 -3
- package/src/components/TrueModal.js +137 -0
- package/src/components/UploadJob.js +102 -178
- package/src/components/UploadJobBreadcrumbs.js +13 -17
- package/src/components/UploadJobDetails.js +256 -0
- package/src/components/UploadJobHistory.js +90 -0
- package/src/components/UploadJobParser.js +155 -89
- package/src/components/UploadJobResults.js +278 -0
- package/src/components/UploadJobScope.js +37 -0
- package/src/components/UploadJobSummary.js +211 -0
- package/src/components/UploadJobs.js +48 -17
- package/src/components/__tests__/TrueModal.spec.js +193 -0
- package/src/components/__tests__/UploadJob.spec.js +118 -42
- package/src/components/__tests__/UploadJobBreadcrumbs.spec.js +3 -0
- package/src/components/__tests__/UploadJobHistory.spec.js +71 -0
- package/src/components/__tests__/UploadJobParser.spec.js +560 -26
- package/src/components/__tests__/UploadJobResults.spec.js +289 -0
- package/src/components/__tests__/UploadJobScope.spec.js +38 -0
- package/src/components/__tests__/UploadJobSummary.spec.js +208 -0
- package/src/components/__tests__/UploadJobs.spec.js +133 -1
- package/src/components/index.js +2 -0
- package/src/styles/ModalEffects.less +43 -0
- package/src/styles/TrueModal.less +138 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { render } from "@truedat/test/render";
|
|
4
|
+
import { TrueModal } from "../index";
|
|
5
|
+
|
|
6
|
+
const setupUser = () =>
|
|
7
|
+
userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
|
|
8
|
+
const renderModal = (props = {}) =>
|
|
9
|
+
render(<TrueModal open title="Title" {...props} />);
|
|
10
|
+
|
|
11
|
+
describe("<TrueModal />", () => {
|
|
12
|
+
it("renders accessible content and preserves consumer classes and size", () => {
|
|
13
|
+
renderModal({
|
|
14
|
+
subtitle: "Subtitle",
|
|
15
|
+
icon: "rocket",
|
|
16
|
+
children: <p>Content</p>,
|
|
17
|
+
className: "custom-modal",
|
|
18
|
+
size: "large",
|
|
19
|
+
header: "Wrong header",
|
|
20
|
+
content: "Wrong content",
|
|
21
|
+
closeIcon: false,
|
|
22
|
+
});
|
|
23
|
+
const dialog = screen.getByRole("dialog", { name: "Title" });
|
|
24
|
+
expect(dialog).toHaveClass("true-modal", "custom-modal", "large");
|
|
25
|
+
expect(screen.getByText("Subtitle")).toBeInTheDocument();
|
|
26
|
+
expect(screen.getByText("Content")).toBeInTheDocument();
|
|
27
|
+
expect(dialog.querySelector(".rocket.icon")).toBeInTheDocument();
|
|
28
|
+
expect(screen.queryByText(/Wrong/)).not.toBeInTheDocument();
|
|
29
|
+
expect(screen.getByRole("button", { name: "Close" })).toBeInTheDocument();
|
|
30
|
+
expect(dialog.querySelector(".actions")).not.toBeInTheDocument();
|
|
31
|
+
expect(dialog.querySelector(".true-modal-banner")).not.toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it.each([
|
|
35
|
+
[{ headerColor: "#123456" }, { background: "#123456" }],
|
|
36
|
+
[{ headerTextColor: "#ffffff" }, { color: "#ffffff" }],
|
|
37
|
+
])("applies independent header colors %o", (props, style) => {
|
|
38
|
+
renderModal(props);
|
|
39
|
+
expect(screen.getByText("Title").closest(".header")).toHaveStyle(style);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it.each([
|
|
43
|
+
["warning", "warning.sign"],
|
|
44
|
+
["danger", "times.circle"],
|
|
45
|
+
["success", "check.circle"],
|
|
46
|
+
["info", "info.circle"],
|
|
47
|
+
])("renders %s banner", (bannerType, iconClass) => {
|
|
48
|
+
renderModal({ bannerType, banner: <strong>Notice</strong> });
|
|
49
|
+
const banner = screen.getByText("Notice").closest(".true-modal-banner");
|
|
50
|
+
expect(banner).toHaveClass(bannerType);
|
|
51
|
+
expect(banner.querySelector(`.${iconClass}.icon`)).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("supports a custom banner icon and an explicit accessible name", () => {
|
|
55
|
+
renderModal({
|
|
56
|
+
banner: "Notice",
|
|
57
|
+
bannerIcon: "rocket",
|
|
58
|
+
"aria-label": "Custom",
|
|
59
|
+
});
|
|
60
|
+
const dialog = screen.getByRole("dialog", { name: "Custom" });
|
|
61
|
+
expect(dialog).not.toHaveAttribute("aria-labelledby");
|
|
62
|
+
expect(
|
|
63
|
+
dialog.querySelector(".true-modal-banner .rocket.icon"),
|
|
64
|
+
).toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it.each([undefined, []])(
|
|
68
|
+
"resolves default callbacks for actions %o",
|
|
69
|
+
async (actions) => {
|
|
70
|
+
const onCancel = jest.fn();
|
|
71
|
+
const onCreate = jest.fn();
|
|
72
|
+
const onClose = jest.fn();
|
|
73
|
+
renderModal({ actions, onCancel, onCreate, onClose });
|
|
74
|
+
const user = setupUser();
|
|
75
|
+
await user.click(screen.getByRole("button", { name: "Cancel" }));
|
|
76
|
+
await user.click(screen.getByRole("button", { name: "Create" }));
|
|
77
|
+
expect(onCancel).toHaveBeenCalledTimes(1);
|
|
78
|
+
expect(onCreate).toHaveBeenCalledTimes(1);
|
|
79
|
+
expect(onClose).not.toHaveBeenCalled();
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
it.each(["onCancel", "onCreate"])(
|
|
84
|
+
"renders only the supplied %s callback",
|
|
85
|
+
(callback) => {
|
|
86
|
+
renderModal({ [callback]: jest.fn() });
|
|
87
|
+
expect(screen.getAllByRole("button")).toHaveLength(2);
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
it("renders three actions and blocks disabled and loading callbacks", async () => {
|
|
92
|
+
const onClick = jest.fn();
|
|
93
|
+
renderModal({
|
|
94
|
+
actions: [
|
|
95
|
+
{ key: "one", content: "One", onClick },
|
|
96
|
+
{ key: "two", content: "Two", disabled: true, onClick },
|
|
97
|
+
{
|
|
98
|
+
key: "three",
|
|
99
|
+
content: "Three",
|
|
100
|
+
loading: true,
|
|
101
|
+
disabled: false,
|
|
102
|
+
onClick,
|
|
103
|
+
type: "submit",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
const user = setupUser();
|
|
108
|
+
expect(screen.getByRole("button", { name: "One" })).toHaveAttribute(
|
|
109
|
+
"type",
|
|
110
|
+
"button",
|
|
111
|
+
);
|
|
112
|
+
expect(screen.getByRole("button", { name: "Two" })).toBeDisabled();
|
|
113
|
+
expect(screen.getByRole("button", { name: "Three" })).toBeDisabled();
|
|
114
|
+
expect(screen.getByRole("button", { name: "Three" })).toHaveClass(
|
|
115
|
+
"loading",
|
|
116
|
+
);
|
|
117
|
+
expect(screen.getByRole("button", { name: "Three" })).toHaveAttribute(
|
|
118
|
+
"type",
|
|
119
|
+
"submit",
|
|
120
|
+
);
|
|
121
|
+
await user.click(screen.getByRole("button", { name: "Two" }));
|
|
122
|
+
await user.click(screen.getByRole("button", { name: "Three" }));
|
|
123
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
124
|
+
await user.click(screen.getByRole("button", { name: "One" }));
|
|
125
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("gives custom footer precedence over both configured and default actions", () => {
|
|
129
|
+
renderModal({
|
|
130
|
+
footer: <button type="button">Custom</button>,
|
|
131
|
+
actions: [{ key: "one", content: "One" }],
|
|
132
|
+
onCancel: jest.fn(),
|
|
133
|
+
});
|
|
134
|
+
expect(screen.getByRole("button", { name: "Custom" })).toBeInTheDocument();
|
|
135
|
+
expect(
|
|
136
|
+
screen.queryByRole("button", { name: "One" }),
|
|
137
|
+
).not.toBeInTheDocument();
|
|
138
|
+
expect(
|
|
139
|
+
screen.queryByRole("button", { name: "Cancel" }),
|
|
140
|
+
).not.toBeInTheDocument();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it.each(["button", "escape", "overlay"])(
|
|
144
|
+
"closes through %s with the native callback contract",
|
|
145
|
+
async (method) => {
|
|
146
|
+
const onOpen = jest.fn();
|
|
147
|
+
const onClose = jest.fn();
|
|
148
|
+
render(
|
|
149
|
+
<TrueModal
|
|
150
|
+
title="Title"
|
|
151
|
+
trigger={<button type="button">Open</button>}
|
|
152
|
+
onOpen={onOpen}
|
|
153
|
+
onClose={onClose}
|
|
154
|
+
/>,
|
|
155
|
+
);
|
|
156
|
+
const user = setupUser();
|
|
157
|
+
await user.click(screen.getByRole("button", { name: "Open" }));
|
|
158
|
+
expect(onOpen).toHaveBeenCalledWith(
|
|
159
|
+
expect.anything(),
|
|
160
|
+
expect.objectContaining({ open: true }),
|
|
161
|
+
);
|
|
162
|
+
if (method === "button")
|
|
163
|
+
await user.click(screen.getByRole("button", { name: "Close" }));
|
|
164
|
+
if (method === "escape") await user.keyboard("{Escape}");
|
|
165
|
+
if (method === "overlay")
|
|
166
|
+
await user.click(screen.getByRole("dialog").parentElement);
|
|
167
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
168
|
+
expect(onClose).toHaveBeenCalledWith(
|
|
169
|
+
expect.anything(),
|
|
170
|
+
expect.objectContaining({ open: false }),
|
|
171
|
+
);
|
|
172
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
it("respects controlled open and disabled Escape and overlay closing", async () => {
|
|
177
|
+
const onClose = jest.fn();
|
|
178
|
+
const { rerender } = renderModal({
|
|
179
|
+
onClose,
|
|
180
|
+
closeOnEscape: false,
|
|
181
|
+
closeOnDimmerClick: false,
|
|
182
|
+
});
|
|
183
|
+
const user = setupUser();
|
|
184
|
+
await user.keyboard("{Escape}");
|
|
185
|
+
await user.click(screen.getByRole("dialog").parentElement);
|
|
186
|
+
expect(onClose).not.toHaveBeenCalled();
|
|
187
|
+
await user.click(screen.getByRole("button", { name: "Close" }));
|
|
188
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
189
|
+
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
190
|
+
rerender(<TrueModal open={false} title="Title" />);
|
|
191
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { render, waitForLoad } from "@truedat/test/render";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
|
-
import { waitFor } from "@testing-library/react";
|
|
4
3
|
|
|
5
4
|
import { useUploadJob } from "../../hooks/useUploadJobs";
|
|
6
5
|
import UploadJob from "../UploadJob";
|
|
@@ -13,25 +12,49 @@ jest.mock("react-router", () => ({
|
|
|
13
12
|
useParams: () => ({ id: "123" }),
|
|
14
13
|
}));
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const messages = {
|
|
16
|
+
en: {
|
|
17
|
+
"uploadJob.implementations.header": "Implementations upload detail",
|
|
18
|
+
"uploadJob.notes.header": "Structure notes upload detail",
|
|
19
|
+
"uploadJob.table.inserted_at": "Inserted At",
|
|
20
|
+
"uploadJob.summary.title": "Summary",
|
|
21
|
+
"uploadJob.summary.total": "Total processed",
|
|
22
|
+
"uploadJob.summary.failed": "Error processing the file: {detail}",
|
|
23
|
+
"uploadJob.parser.error.invalid_format": "Invalid file format",
|
|
24
|
+
"uploadJob.parser.result.summary.created": "Created",
|
|
25
|
+
"uploadJob.parser.result.summary.updated": "Updated",
|
|
26
|
+
"uploadJob.parser.result.summary.unchanged": "Unchanged",
|
|
27
|
+
"uploadJob.parser.result.summary.errors": "Errors",
|
|
28
|
+
"uploadJob.history.PENDING": "File received and pending processing.",
|
|
29
|
+
"uploadJob.history.STARTED": "Record reading and validation started.",
|
|
30
|
+
"uploadJob.history.COMPLETED": "File processed successfully.",
|
|
31
|
+
"uploadJob.history.FAILED": "The file could not be processed.",
|
|
32
|
+
"uploadJob.history.title": "File processing history",
|
|
33
|
+
"uploadJob.history.empty": "No processing history found.",
|
|
34
|
+
"uploadJob.results.title": "Processing results",
|
|
35
|
+
"uploadJob.results.subtitle": "Browse and filter records by upload result.",
|
|
36
|
+
"uploadJob.results.expand": "Expand processing results",
|
|
37
|
+
"uploadJob.results.collapse": "Collapse processing results",
|
|
38
|
+
"uploadJob.results.column.structure": "Structure",
|
|
39
|
+
"uploadJob.parser.event.status.PENDING": "Pending",
|
|
40
|
+
"uploadJob.parser.event.status.STARTED": "Started",
|
|
41
|
+
"uploadJob.parser.event.status.COMPLETED": "Completed",
|
|
42
|
+
"uploadJob.parser.event.status.FAILED": "Failed",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
18
45
|
|
|
46
|
+
describe("<UploadJob />", () => {
|
|
19
47
|
beforeEach(() => {
|
|
20
48
|
useUploadJob.mockReset();
|
|
21
49
|
});
|
|
22
50
|
|
|
23
|
-
it("renders loading state",
|
|
24
|
-
useUploadJob.mockReturnValue({
|
|
25
|
-
|
|
26
|
-
data: null,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const rendered = render(<UploadJob />);
|
|
30
|
-
|
|
51
|
+
it("renders loading state", () => {
|
|
52
|
+
useUploadJob.mockReturnValue({ loading: true, data: null });
|
|
53
|
+
const rendered = render(<UploadJob />, { messages });
|
|
31
54
|
expect(rendered.container.querySelector(".ui.loader")).toBeInTheDocument();
|
|
32
55
|
});
|
|
33
56
|
|
|
34
|
-
it("renders
|
|
57
|
+
it("renders the lifecycle pill with an accessible visually hidden Status header", async () => {
|
|
35
58
|
useUploadJob.mockReturnValue({
|
|
36
59
|
loading: false,
|
|
37
60
|
data: {
|
|
@@ -40,71 +63,124 @@ describe("<UploadJob />", () => {
|
|
|
40
63
|
hash: "abc123",
|
|
41
64
|
latest_status: "COMPLETED",
|
|
42
65
|
latest_event_at: "2023-01-01T00:00:00Z",
|
|
43
|
-
|
|
66
|
+
latest_event_response: {
|
|
67
|
+
insert_count: 0,
|
|
68
|
+
update_count: 0,
|
|
69
|
+
unchanged_count: 0,
|
|
70
|
+
error_count: 0,
|
|
71
|
+
},
|
|
72
|
+
events: [
|
|
73
|
+
{
|
|
74
|
+
status: "COMPLETED",
|
|
75
|
+
inserted_at: "2023-01-01T00:00:00Z",
|
|
76
|
+
response: {},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
44
79
|
},
|
|
45
80
|
},
|
|
46
81
|
});
|
|
47
82
|
|
|
48
|
-
const rendered = render(<UploadJob
|
|
83
|
+
const rendered = render(<UploadJob />, { messages });
|
|
49
84
|
await waitForLoad(rendered);
|
|
50
85
|
|
|
51
86
|
expect(rendered.getAllByText(/test.xlsx/i)).toHaveLength(2);
|
|
52
87
|
expect(rendered.getByText(/abc123/i)).toBeInTheDocument();
|
|
53
|
-
expect(
|
|
88
|
+
expect(
|
|
89
|
+
rendered.getByRole("columnheader", { name: /uploadJob.table.status/i }),
|
|
90
|
+
).toHaveClass("upload-job-visually-hidden");
|
|
91
|
+
expect(rendered.getAllByText("Completed")).toHaveLength(2);
|
|
92
|
+
expect(rendered.getByLabelText("Summary")).toHaveClass("positive");
|
|
54
93
|
});
|
|
55
94
|
|
|
56
|
-
it("
|
|
95
|
+
it("keeps row outcomes out of the processing history", async () => {
|
|
57
96
|
useUploadJob.mockReturnValue({
|
|
58
97
|
loading: false,
|
|
59
98
|
data: {
|
|
60
99
|
data: {
|
|
61
100
|
filename: "test.xlsx",
|
|
62
101
|
hash: "abc123",
|
|
63
|
-
latest_status: "
|
|
102
|
+
latest_status: "FAILED",
|
|
64
103
|
latest_event_at: "2023-01-01T00:00:00Z",
|
|
104
|
+
latest_event_response: { message: "invalid_format" },
|
|
65
105
|
events: [
|
|
106
|
+
{ status: "PENDING", inserted_at: "2023-01-01T00:00:00Z" },
|
|
107
|
+
{ status: "STARTED", inserted_at: "2023-01-01T00:01:00Z" },
|
|
66
108
|
{
|
|
67
|
-
status: "
|
|
68
|
-
inserted_at: "2023-01-01T00:
|
|
69
|
-
response: { type: "
|
|
109
|
+
status: "ERROR",
|
|
110
|
+
inserted_at: "2023-01-01T00:01:30Z",
|
|
111
|
+
response: { type: "invalid_template_name" },
|
|
70
112
|
},
|
|
71
113
|
{
|
|
72
|
-
status: "
|
|
73
|
-
inserted_at: "2023-01-01T00:
|
|
74
|
-
response: {
|
|
114
|
+
status: "FAILED",
|
|
115
|
+
inserted_at: "2023-01-01T00:02:00Z",
|
|
116
|
+
response: { message: "invalid_format" },
|
|
75
117
|
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const rendered = render(<UploadJob />, { messages });
|
|
124
|
+
await waitForLoad(rendered);
|
|
125
|
+
|
|
126
|
+
const history = rendered.getByRole("heading", {
|
|
127
|
+
name: "File processing history",
|
|
128
|
+
}).parentElement;
|
|
129
|
+
expect(history).not.toHaveTextContent("invalid_template_name");
|
|
130
|
+
expect(history).toHaveTextContent("Failed");
|
|
131
|
+
expect(
|
|
132
|
+
rendered.getByRole("button", { name: /^Processing results/i }),
|
|
133
|
+
).toBeDisabled();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("renders structure-note results for the notes scope", async () => {
|
|
137
|
+
const user = userEvent.setup({ delay: null });
|
|
138
|
+
useUploadJob.mockReturnValue({
|
|
139
|
+
loading: false,
|
|
140
|
+
data: {
|
|
141
|
+
data: {
|
|
142
|
+
filename: "notes.xlsx",
|
|
143
|
+
hash: "notes-hash",
|
|
144
|
+
latest_status: "COMPLETED",
|
|
145
|
+
latest_event_at: "2023-01-01T00:00:00Z",
|
|
146
|
+
latest_event_response: {
|
|
147
|
+
insert_count: 1,
|
|
148
|
+
update_count: 0,
|
|
149
|
+
unchanged_count: 0,
|
|
150
|
+
error_count: 0,
|
|
151
|
+
},
|
|
152
|
+
events: [
|
|
76
153
|
{
|
|
77
154
|
status: "COMPLETED",
|
|
78
155
|
inserted_at: "2023-01-01T00:00:00Z",
|
|
79
|
-
response: {
|
|
156
|
+
response: { insert_count: 1 },
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
status: "INFO",
|
|
160
|
+
inserted_at: "2023-01-01T00:00:01Z",
|
|
161
|
+
response: {
|
|
162
|
+
type: "created",
|
|
163
|
+
details: { data_structure_id: 1, external_id: "NOTE-1" },
|
|
164
|
+
},
|
|
80
165
|
},
|
|
81
166
|
],
|
|
82
167
|
},
|
|
83
168
|
},
|
|
84
169
|
});
|
|
85
170
|
|
|
86
|
-
const rendered = render(<UploadJob
|
|
171
|
+
const rendered = render(<UploadJob scope="notes" />, { messages });
|
|
87
172
|
await waitForLoad(rendered);
|
|
88
173
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// Expand INFO group
|
|
94
|
-
await user.click(rendered.getByText(/info \(1\)/i));
|
|
95
|
-
await waitFor(() => {
|
|
96
|
-
expect(rendered.getByText(/info_message/i)).toBeInTheDocument();
|
|
97
|
-
});
|
|
174
|
+
expect(
|
|
175
|
+
rendered.getByRole("heading", { name: "Structure notes upload detail" }),
|
|
176
|
+
).toBeInTheDocument();
|
|
98
177
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
expect(rendered.getByText(/error_message/i)).toBeInTheDocument();
|
|
103
|
-
});
|
|
178
|
+
await user.click(
|
|
179
|
+
rendered.getByRole("button", { name: /^Processing results/i }),
|
|
180
|
+
);
|
|
104
181
|
|
|
105
|
-
// COMPLETED events are always visible
|
|
106
182
|
expect(
|
|
107
|
-
rendered.
|
|
108
|
-
).
|
|
183
|
+
rendered.getByRole("columnheader", { name: "Structure" }),
|
|
184
|
+
).toBeInTheDocument();
|
|
109
185
|
});
|
|
110
186
|
});
|
|
@@ -19,6 +19,9 @@ describe("ImplementationUploadJobBreadcrumbs", () => {
|
|
|
19
19
|
expect(
|
|
20
20
|
rendered.getByText(/uploadJobs.myscope.header/i),
|
|
21
21
|
).toBeInTheDocument();
|
|
22
|
+
expect(
|
|
23
|
+
rendered.getByText(/uploadJobs.myscope.header/i).closest("a"),
|
|
24
|
+
).toBeNull();
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
it("renders breadcrumbs with filename", async () => {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { render } from "@truedat/test/render";
|
|
2
|
+
import UploadJobHistory, { getLifecycleEvents } from "../UploadJobHistory";
|
|
3
|
+
|
|
4
|
+
const messages = {
|
|
5
|
+
en: {
|
|
6
|
+
"uploadJob.history.PENDING": "File received and pending processing.",
|
|
7
|
+
"uploadJob.history.STARTED": "Record reading and validation started.",
|
|
8
|
+
"uploadJob.history.COMPLETED": "File processed successfully.",
|
|
9
|
+
"uploadJob.history.FAILED": "The file could not be processed.",
|
|
10
|
+
"uploadJob.history.title": "File processing history",
|
|
11
|
+
"uploadJob.history.empty": "No processing history found.",
|
|
12
|
+
"uploadJob.table.status": "Status",
|
|
13
|
+
"uploadJob.table.inserted_at": "Processed at",
|
|
14
|
+
"uploadJob.table.response": "Detail",
|
|
15
|
+
"uploadJob.parser.event.status.PENDING": "Pending",
|
|
16
|
+
"uploadJob.parser.event.status.STARTED": "Started",
|
|
17
|
+
"uploadJob.parser.event.status.COMPLETED": "Completed",
|
|
18
|
+
"uploadJob.parser.event.status.FAILED": "Failed",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("<UploadJobHistory />", () => {
|
|
23
|
+
it("shows lifecycle only with generic final messages", () => {
|
|
24
|
+
const events = [
|
|
25
|
+
{ status: "PENDING", inserted_at: "2026-01-01T10:00:00Z" },
|
|
26
|
+
{ status: "STARTED", inserted_at: "2026-01-01T10:01:00Z" },
|
|
27
|
+
{ status: "INFO", response: { type: "created" } },
|
|
28
|
+
{ status: "ERROR", response: { type: "invalid_template_name" } },
|
|
29
|
+
{
|
|
30
|
+
status: "COMPLETED",
|
|
31
|
+
inserted_at: "2026-01-01T10:02:00Z",
|
|
32
|
+
response: { insert_count: 10 },
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
status: "FAILED",
|
|
36
|
+
inserted_at: "2026-01-01T10:03:00Z",
|
|
37
|
+
response: { message: "invalid_format" },
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const rendered = render(<UploadJobHistory events={events} />, { messages });
|
|
42
|
+
|
|
43
|
+
expect(getLifecycleEvents(events)).toHaveLength(4);
|
|
44
|
+
expect(rendered.queryByText(/created: 10/i)).not.toBeInTheDocument();
|
|
45
|
+
expect(
|
|
46
|
+
rendered.getByText("File received and pending processing."),
|
|
47
|
+
).toBeInTheDocument();
|
|
48
|
+
expect(
|
|
49
|
+
rendered.getByText("Record reading and validation started."),
|
|
50
|
+
).toBeInTheDocument();
|
|
51
|
+
expect(
|
|
52
|
+
rendered.getByText("File processed successfully."),
|
|
53
|
+
).toBeInTheDocument();
|
|
54
|
+
expect(
|
|
55
|
+
rendered.getByText("The file could not be processed."),
|
|
56
|
+
).toBeInTheDocument();
|
|
57
|
+
expect(
|
|
58
|
+
rendered.queryByText(/invalid_template_name/i),
|
|
59
|
+
).not.toBeInTheDocument();
|
|
60
|
+
expect(rendered.queryByText(/invalid_format/i)).not.toBeInTheDocument();
|
|
61
|
+
expect(rendered.getByRole("columnheader", { name: "Status" })).toHaveClass(
|
|
62
|
+
"upload-job-visually-hidden",
|
|
63
|
+
);
|
|
64
|
+
expect(
|
|
65
|
+
rendered.getByRole("columnheader", { name: "Processed at" }),
|
|
66
|
+
).toHaveClass("upload-job-visually-hidden");
|
|
67
|
+
expect(rendered.getByRole("columnheader", { name: "Detail" })).toHaveClass(
|
|
68
|
+
"upload-job-visually-hidden",
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
});
|