@truedat/core 8.13.0 → 8.13.2

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.
@@ -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
- describe("<UploadJob />", () => {
17
- const user = userEvent.setup({ delay: null });
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", async () => {
24
- useUploadJob.mockReturnValue({
25
- loading: true,
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 empty state when no events", async () => {
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
- events: [],
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(rendered.getByText(/no events found/i)).toBeInTheDocument();
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("renders events grouped by status", async () => {
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: "COMPLETED",
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: "INFO",
68
- inserted_at: "2023-01-01T00:00:00Z",
69
- response: { type: "info_message" },
109
+ status: "ERROR",
110
+ inserted_at: "2023-01-01T00:01:30Z",
111
+ response: { type: "invalid_template_name" },
70
112
  },
71
113
  {
72
- status: "ERROR",
73
- inserted_at: "2023-01-01T00:00:00Z",
74
- response: { type: "error_message" },
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: { type: "completed_message" },
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
- // Check group headers exist
90
- expect(rendered.getByText(/info \(1\)/i)).toBeInTheDocument();
91
- expect(rendered.getByText(/error \(1\)/i)).toBeInTheDocument();
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
- // Expand ERROR group
100
- await user.click(rendered.getByText(/error \(1\)/i));
101
- await waitFor(() => {
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.getAllByText(/uploadJob.parser.event.status.COMPLETED/i),
108
- ).toHaveLength(2);
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
+ });