@scality/data-browser-library 1.0.0-preview.11 → 1.0.0-preview.13
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/components/DataBrowserUI.d.ts +20 -0
- package/dist/components/DataBrowserUI.js +64 -0
- package/dist/components/__tests__/BucketDetails.test.d.ts +1 -0
- package/dist/components/__tests__/BucketDetails.test.js +421 -0
- package/dist/components/__tests__/BucketList.test.js +389 -164
- package/dist/components/__tests__/BucketOverview.test.js +19 -63
- package/dist/components/__tests__/ObjectList.test.js +719 -219
- package/dist/components/buckets/BucketDetails.d.ts +40 -0
- package/dist/components/buckets/BucketDetails.js +194 -86
- package/dist/components/buckets/BucketList.d.ts +5 -6
- package/dist/components/buckets/BucketList.js +152 -97
- package/dist/components/buckets/BucketOverview.d.ts +6 -0
- package/dist/components/buckets/BucketOverview.js +363 -179
- package/dist/components/buckets/BucketPage.js +1 -5
- package/dist/components/buckets/BucketVersioning.js +3 -0
- package/dist/components/buckets/EmptyBucketButton.js +1 -1
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.js +2 -1
- package/dist/components/layouts/ArrowNavigation.js +20 -8
- package/dist/components/objects/CreateFolderButton.js +1 -1
- package/dist/components/objects/ObjectDetails/ObjectSummary.js +287 -157
- package/dist/components/objects/ObjectDetails/__tests__/ObjectDetails.test.d.ts +1 -0
- package/dist/components/objects/ObjectDetails/__tests__/ObjectDetails.test.js +516 -0
- package/dist/components/objects/ObjectDetails/__tests__/ObjectSummary.test.d.ts +1 -0
- package/dist/components/objects/ObjectDetails/__tests__/ObjectSummary.test.js +813 -0
- package/dist/components/objects/ObjectDetails/index.d.ts +16 -0
- package/dist/components/objects/ObjectDetails/index.js +132 -46
- package/dist/components/objects/ObjectList.d.ts +7 -5
- package/dist/components/objects/ObjectList.js +566 -286
- package/dist/components/objects/UploadButton.js +1 -1
- package/dist/config/types.d.ts +117 -0
- package/dist/contexts/DataBrowserUICustomizationContext.d.ts +27 -0
- package/dist/contexts/DataBrowserUICustomizationContext.js +13 -0
- package/dist/test/testUtils.d.ts +64 -0
- package/dist/test/testUtils.js +100 -1
- package/dist/types/index.d.ts +5 -3
- package/dist/utils/constants.d.ts +7 -0
- package/dist/utils/constants.js +8 -1
- package/dist/utils/useFeatures.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import user_event from "@testing-library/user-event";
|
|
4
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
|
+
import { ObjectSummary } from "../ObjectSummary.js";
|
|
6
|
+
import { DataBrowserUICustomizationProvider } from "../../../../contexts/DataBrowserUICustomizationContext.js";
|
|
7
|
+
import { useObjectLegalHold, useObjectMetadata, useObjectRetention, useSetObjectLegalHold } from "../../../../hooks/index.js";
|
|
8
|
+
import { useToast } from "@scality/core-ui";
|
|
9
|
+
jest.mock("../../../../hooks");
|
|
10
|
+
jest.mock("@scality/core-ui", ()=>{
|
|
11
|
+
const actual = jest.requireActual("@scality/core-ui");
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
useToast: jest.fn()
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
const mockUseObjectMetadata = jest.mocked(useObjectMetadata);
|
|
18
|
+
const mockUseObjectLegalHold = jest.mocked(useObjectLegalHold);
|
|
19
|
+
const mockUseObjectRetention = jest.mocked(useObjectRetention);
|
|
20
|
+
const mockUseSetObjectLegalHold = jest.mocked(useSetObjectLegalHold);
|
|
21
|
+
const mockUseToast = jest.mocked(useToast);
|
|
22
|
+
const mockObjectMetadata = {
|
|
23
|
+
VersionId: "test-version-id",
|
|
24
|
+
ContentLength: 1024,
|
|
25
|
+
LastModified: new Date("2024-01-01"),
|
|
26
|
+
ETag: '"abc123def456"'
|
|
27
|
+
};
|
|
28
|
+
const mockLegalHoldData = {
|
|
29
|
+
LegalHold: {
|
|
30
|
+
Status: "OFF"
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const mockRetentionData = {
|
|
34
|
+
Retention: {
|
|
35
|
+
Mode: "GOVERNANCE",
|
|
36
|
+
RetainUntilDate: "2024-12-31T23:59:59Z"
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const setupMockDefaults = ()=>{
|
|
40
|
+
mockUseObjectMetadata.mockReturnValue({
|
|
41
|
+
data: mockObjectMetadata,
|
|
42
|
+
status: "success"
|
|
43
|
+
});
|
|
44
|
+
mockUseObjectLegalHold.mockReturnValue({
|
|
45
|
+
data: mockLegalHoldData,
|
|
46
|
+
status: "success"
|
|
47
|
+
});
|
|
48
|
+
mockUseObjectRetention.mockReturnValue({
|
|
49
|
+
data: mockRetentionData,
|
|
50
|
+
status: "success"
|
|
51
|
+
});
|
|
52
|
+
mockUseSetObjectLegalHold.mockReturnValue({
|
|
53
|
+
mutate: jest.fn(),
|
|
54
|
+
isPending: false
|
|
55
|
+
});
|
|
56
|
+
mockUseToast.mockReturnValue({
|
|
57
|
+
showToast: jest.fn()
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
const renderWithProviders = (ui, { customizationConfig = {} } = {})=>{
|
|
61
|
+
const queryClient = new QueryClient({
|
|
62
|
+
defaultOptions: {
|
|
63
|
+
queries: {
|
|
64
|
+
retry: false
|
|
65
|
+
},
|
|
66
|
+
mutations: {
|
|
67
|
+
retry: false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return render(/*#__PURE__*/ jsx(QueryClientProvider, {
|
|
72
|
+
client: queryClient,
|
|
73
|
+
children: /*#__PURE__*/ jsx(DataBrowserUICustomizationProvider, {
|
|
74
|
+
config: customizationConfig,
|
|
75
|
+
children: ui
|
|
76
|
+
})
|
|
77
|
+
}));
|
|
78
|
+
};
|
|
79
|
+
describe("ObjectSummary", ()=>{
|
|
80
|
+
const defaultProps = {
|
|
81
|
+
bucketName: "test-bucket",
|
|
82
|
+
objectKey: "test-object.txt"
|
|
83
|
+
};
|
|
84
|
+
beforeEach(()=>{
|
|
85
|
+
jest.clearAllMocks();
|
|
86
|
+
setupMockDefaults();
|
|
87
|
+
});
|
|
88
|
+
describe("Default Fields", ()=>{
|
|
89
|
+
describe("Information Section", ()=>{
|
|
90
|
+
it("should render all default information fields", ()=>{
|
|
91
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
92
|
+
...defaultProps
|
|
93
|
+
}));
|
|
94
|
+
expect(screen.getByText("Information")).toBeInTheDocument();
|
|
95
|
+
expect(screen.getByText("Name")).toBeInTheDocument();
|
|
96
|
+
expect(screen.getByText("Version ID")).toBeInTheDocument();
|
|
97
|
+
expect(screen.getByText("Size")).toBeInTheDocument();
|
|
98
|
+
expect(screen.getByText("Modified On")).toBeInTheDocument();
|
|
99
|
+
expect(screen.getByText("ETag")).toBeInTheDocument();
|
|
100
|
+
expect(screen.getByText("Location")).toBeInTheDocument();
|
|
101
|
+
});
|
|
102
|
+
it("should display object name", ()=>{
|
|
103
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
104
|
+
...defaultProps
|
|
105
|
+
}));
|
|
106
|
+
expect(screen.getByText("test-object.txt")).toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
it("should display version ID from metadata", ()=>{
|
|
109
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
110
|
+
...defaultProps
|
|
111
|
+
}));
|
|
112
|
+
expect(screen.getByText("test-version-id")).toBeInTheDocument();
|
|
113
|
+
});
|
|
114
|
+
it("should display formatted size", ()=>{
|
|
115
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
116
|
+
...defaultProps
|
|
117
|
+
}));
|
|
118
|
+
expect(screen.getByText(/1 KB/i)).toBeInTheDocument();
|
|
119
|
+
});
|
|
120
|
+
it("should display formatted date", ()=>{
|
|
121
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
122
|
+
...defaultProps
|
|
123
|
+
}));
|
|
124
|
+
const dateElements = screen.getAllByText(/2024/);
|
|
125
|
+
expect(dateElements.length).toBeGreaterThan(0);
|
|
126
|
+
});
|
|
127
|
+
it("should display ETag without quotes", ()=>{
|
|
128
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
129
|
+
...defaultProps
|
|
130
|
+
}));
|
|
131
|
+
expect(screen.getByText("abc123def456")).toBeInTheDocument();
|
|
132
|
+
});
|
|
133
|
+
it("should display default location", ()=>{
|
|
134
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
135
|
+
...defaultProps
|
|
136
|
+
}));
|
|
137
|
+
expect(screen.getByText("default")).toBeInTheDocument();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe("Data Protection Section", ()=>{
|
|
141
|
+
it("should render data protection section", ()=>{
|
|
142
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
143
|
+
...defaultProps
|
|
144
|
+
}));
|
|
145
|
+
expect(screen.getByText("Data protection")).toBeInTheDocument();
|
|
146
|
+
});
|
|
147
|
+
it("should display retention lock information", ()=>{
|
|
148
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
149
|
+
...defaultProps
|
|
150
|
+
}));
|
|
151
|
+
expect(screen.getByText("Lock")).toBeInTheDocument();
|
|
152
|
+
expect(screen.getByText(/GOVERNANCE/)).toBeInTheDocument();
|
|
153
|
+
expect(screen.getByText(/until/)).toBeInTheDocument();
|
|
154
|
+
});
|
|
155
|
+
it("should display legal hold toggle when available", ()=>{
|
|
156
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
157
|
+
...defaultProps
|
|
158
|
+
}));
|
|
159
|
+
expect(screen.getByText("Legal Hold")).toBeInTheDocument();
|
|
160
|
+
expect(screen.getByRole("checkbox")).toBeInTheDocument();
|
|
161
|
+
});
|
|
162
|
+
it("should show legal hold as inactive by default", ()=>{
|
|
163
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
164
|
+
...defaultProps
|
|
165
|
+
}));
|
|
166
|
+
const toggle = screen.getByRole("checkbox");
|
|
167
|
+
expect(toggle).not.toBeChecked();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe("Loading States", ()=>{
|
|
172
|
+
it("should show loader for pending metadata", ()=>{
|
|
173
|
+
mockUseObjectMetadata.mockReturnValueOnce({
|
|
174
|
+
data: void 0,
|
|
175
|
+
status: "pending"
|
|
176
|
+
});
|
|
177
|
+
const { container } = renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
178
|
+
...defaultProps
|
|
179
|
+
}));
|
|
180
|
+
const loaders = container.querySelectorAll("svg");
|
|
181
|
+
expect(loaders.length).toBeGreaterThan(0);
|
|
182
|
+
});
|
|
183
|
+
it("should show error state for failed metadata", ()=>{
|
|
184
|
+
mockUseObjectMetadata.mockReturnValueOnce({
|
|
185
|
+
data: void 0,
|
|
186
|
+
status: "error"
|
|
187
|
+
});
|
|
188
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
189
|
+
...defaultProps
|
|
190
|
+
}));
|
|
191
|
+
const errors = screen.getAllByText("Error");
|
|
192
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
193
|
+
});
|
|
194
|
+
it("should show N/A when retention is not available", ()=>{
|
|
195
|
+
mockUseObjectRetention.mockReturnValueOnce({
|
|
196
|
+
data: void 0,
|
|
197
|
+
status: "error"
|
|
198
|
+
});
|
|
199
|
+
mockUseObjectLegalHold.mockReturnValueOnce({
|
|
200
|
+
data: void 0,
|
|
201
|
+
status: "error"
|
|
202
|
+
});
|
|
203
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
204
|
+
...defaultProps
|
|
205
|
+
}));
|
|
206
|
+
expect(screen.getByText("No Retention")).toBeInTheDocument();
|
|
207
|
+
expect(screen.getAllByText("N/A").length).toBeGreaterThan(0);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
describe("Version ID Handling", ()=>{
|
|
211
|
+
it("should prefer metadata version ID over prop", ()=>{
|
|
212
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
213
|
+
...defaultProps,
|
|
214
|
+
versionId: "prop-version-id"
|
|
215
|
+
}));
|
|
216
|
+
expect(screen.getByText("test-version-id")).toBeInTheDocument();
|
|
217
|
+
expect(screen.queryByText("prop-version-id")).not.toBeInTheDocument();
|
|
218
|
+
});
|
|
219
|
+
it("should fallback to prop version ID when metadata is unavailable", ()=>{
|
|
220
|
+
mockUseObjectMetadata.mockReturnValueOnce({
|
|
221
|
+
data: {
|
|
222
|
+
...mockObjectMetadata,
|
|
223
|
+
VersionId: void 0
|
|
224
|
+
},
|
|
225
|
+
status: "success"
|
|
226
|
+
});
|
|
227
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
228
|
+
...defaultProps,
|
|
229
|
+
versionId: "fallback-version-id"
|
|
230
|
+
}));
|
|
231
|
+
expect(screen.getByText("fallback-version-id")).toBeInTheDocument();
|
|
232
|
+
});
|
|
233
|
+
it("should show N/A when no version ID is available", ()=>{
|
|
234
|
+
mockUseObjectMetadata.mockReturnValueOnce({
|
|
235
|
+
data: {
|
|
236
|
+
...mockObjectMetadata,
|
|
237
|
+
VersionId: void 0
|
|
238
|
+
},
|
|
239
|
+
status: "success"
|
|
240
|
+
});
|
|
241
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
242
|
+
...defaultProps
|
|
243
|
+
}));
|
|
244
|
+
expect(screen.getAllByText("N/A").length).toBeGreaterThan(0);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
describe("ETag Handling Edge Cases", ()=>{
|
|
248
|
+
it("should handle null ETag gracefully", ()=>{
|
|
249
|
+
mockUseObjectMetadata.mockReturnValueOnce({
|
|
250
|
+
data: {
|
|
251
|
+
...mockObjectMetadata,
|
|
252
|
+
ETag: null
|
|
253
|
+
},
|
|
254
|
+
status: "success"
|
|
255
|
+
});
|
|
256
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
257
|
+
...defaultProps
|
|
258
|
+
}));
|
|
259
|
+
expect(screen.getAllByText("N/A").length).toBeGreaterThan(0);
|
|
260
|
+
});
|
|
261
|
+
it("should handle undefined ETag gracefully", ()=>{
|
|
262
|
+
mockUseObjectMetadata.mockReturnValueOnce({
|
|
263
|
+
data: {
|
|
264
|
+
...mockObjectMetadata,
|
|
265
|
+
ETag: void 0
|
|
266
|
+
},
|
|
267
|
+
status: "success"
|
|
268
|
+
});
|
|
269
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
270
|
+
...defaultProps
|
|
271
|
+
}));
|
|
272
|
+
expect(screen.getAllByText("N/A").length).toBeGreaterThan(0);
|
|
273
|
+
});
|
|
274
|
+
it("should handle metadata error state gracefully", ()=>{
|
|
275
|
+
mockUseObjectMetadata.mockReturnValueOnce({
|
|
276
|
+
data: void 0,
|
|
277
|
+
status: "error"
|
|
278
|
+
});
|
|
279
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
280
|
+
...defaultProps
|
|
281
|
+
}));
|
|
282
|
+
expect(screen.getAllByText("Error").length).toBeGreaterThan(0);
|
|
283
|
+
});
|
|
284
|
+
it("should remove quotes from ETag", ()=>{
|
|
285
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
286
|
+
...defaultProps
|
|
287
|
+
}));
|
|
288
|
+
expect(screen.getByText("abc123def456")).toBeInTheDocument();
|
|
289
|
+
expect(screen.queryByText('"abc123def456"')).not.toBeInTheDocument();
|
|
290
|
+
});
|
|
291
|
+
it("should handle ETag without quotes", ()=>{
|
|
292
|
+
mockUseObjectMetadata.mockReturnValueOnce({
|
|
293
|
+
data: {
|
|
294
|
+
...mockObjectMetadata,
|
|
295
|
+
ETag: "unquoted-etag"
|
|
296
|
+
},
|
|
297
|
+
status: "success"
|
|
298
|
+
});
|
|
299
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
300
|
+
...defaultProps
|
|
301
|
+
}));
|
|
302
|
+
expect(screen.getByText("unquoted-etag")).toBeInTheDocument();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
describe("Custom Fields", ()=>{
|
|
306
|
+
describe("Information Section", ()=>{
|
|
307
|
+
it("should render custom information fields", ()=>{
|
|
308
|
+
const CustomField = ({ entityName })=>/*#__PURE__*/ jsxs("div", {
|
|
309
|
+
children: [
|
|
310
|
+
"Custom Field for ",
|
|
311
|
+
entityName
|
|
312
|
+
]
|
|
313
|
+
});
|
|
314
|
+
const customConfig = {
|
|
315
|
+
extraObjectSummaryInformation: [
|
|
316
|
+
{
|
|
317
|
+
id: "custom-field",
|
|
318
|
+
label: "Custom Field",
|
|
319
|
+
render: CustomField
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
};
|
|
323
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
324
|
+
...defaultProps
|
|
325
|
+
}), {
|
|
326
|
+
customizationConfig: customConfig
|
|
327
|
+
});
|
|
328
|
+
expect(screen.getByText("Custom Field")).toBeInTheDocument();
|
|
329
|
+
expect(screen.getByText(/custom field for test-object.txt/i)).toBeInTheDocument();
|
|
330
|
+
});
|
|
331
|
+
it("should replace default fields with same ID", ()=>{
|
|
332
|
+
const CustomLocationField = ()=>/*#__PURE__*/ jsx("div", {
|
|
333
|
+
children: "Custom Location Value"
|
|
334
|
+
});
|
|
335
|
+
const customConfig = {
|
|
336
|
+
extraObjectSummaryInformation: [
|
|
337
|
+
{
|
|
338
|
+
id: "location",
|
|
339
|
+
label: "Custom Location",
|
|
340
|
+
render: CustomLocationField
|
|
341
|
+
}
|
|
342
|
+
]
|
|
343
|
+
};
|
|
344
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
345
|
+
...defaultProps
|
|
346
|
+
}), {
|
|
347
|
+
customizationConfig: customConfig
|
|
348
|
+
});
|
|
349
|
+
expect(screen.getByText("Custom Location")).toBeInTheDocument();
|
|
350
|
+
expect(screen.getByText("Custom Location Value")).toBeInTheDocument();
|
|
351
|
+
expect(screen.queryByText("default")).not.toBeInTheDocument();
|
|
352
|
+
});
|
|
353
|
+
it("should add multiple custom fields", ()=>{
|
|
354
|
+
const Field1 = ()=>/*#__PURE__*/ jsx("div", {
|
|
355
|
+
children: "Field 1 Value"
|
|
356
|
+
});
|
|
357
|
+
const Field2 = ()=>/*#__PURE__*/ jsx("div", {
|
|
358
|
+
children: "Field 2 Value"
|
|
359
|
+
});
|
|
360
|
+
const Field3 = ()=>/*#__PURE__*/ jsx("div", {
|
|
361
|
+
children: "Field 3 Value"
|
|
362
|
+
});
|
|
363
|
+
const customConfig = {
|
|
364
|
+
extraObjectSummaryInformation: [
|
|
365
|
+
{
|
|
366
|
+
id: "field1",
|
|
367
|
+
label: "Field 1",
|
|
368
|
+
render: Field1
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
id: "field2",
|
|
372
|
+
label: "Field 2",
|
|
373
|
+
render: Field2
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: "field3",
|
|
377
|
+
label: "Field 3",
|
|
378
|
+
render: Field3
|
|
379
|
+
}
|
|
380
|
+
]
|
|
381
|
+
};
|
|
382
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
383
|
+
...defaultProps
|
|
384
|
+
}), {
|
|
385
|
+
customizationConfig: customConfig
|
|
386
|
+
});
|
|
387
|
+
expect(screen.getByText("Field 1")).toBeInTheDocument();
|
|
388
|
+
expect(screen.getByText("Field 2")).toBeInTheDocument();
|
|
389
|
+
expect(screen.getByText("Field 3")).toBeInTheDocument();
|
|
390
|
+
expect(screen.getByText("Field 1 Value")).toBeInTheDocument();
|
|
391
|
+
expect(screen.getByText("Field 2 Value")).toBeInTheDocument();
|
|
392
|
+
expect(screen.getByText("Field 3 Value")).toBeInTheDocument();
|
|
393
|
+
});
|
|
394
|
+
it("should pass entityName prop to custom field components", ()=>{
|
|
395
|
+
const CustomField = ({ entityName })=>/*#__PURE__*/ jsxs("div", {
|
|
396
|
+
"data-testid": "custom-field",
|
|
397
|
+
children: [
|
|
398
|
+
"EntityName: ",
|
|
399
|
+
entityName
|
|
400
|
+
]
|
|
401
|
+
});
|
|
402
|
+
const customConfig = {
|
|
403
|
+
extraObjectSummaryInformation: [
|
|
404
|
+
{
|
|
405
|
+
id: "test-field",
|
|
406
|
+
label: "Test Field",
|
|
407
|
+
render: CustomField
|
|
408
|
+
}
|
|
409
|
+
]
|
|
410
|
+
};
|
|
411
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
412
|
+
...defaultProps,
|
|
413
|
+
objectKey: "my-special-file.txt"
|
|
414
|
+
}), {
|
|
415
|
+
customizationConfig: customConfig
|
|
416
|
+
});
|
|
417
|
+
expect(screen.getByTestId("custom-field")).toHaveTextContent("EntityName: my-special-file.txt");
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
describe("Data Protection Section", ()=>{
|
|
421
|
+
it("should render custom data protection fields", ()=>{
|
|
422
|
+
const CustomProtectionField = ()=>/*#__PURE__*/ jsx("div", {
|
|
423
|
+
children: "Custom Protection Info"
|
|
424
|
+
});
|
|
425
|
+
const customConfig = {
|
|
426
|
+
extraObjectSummaryDataProtection: [
|
|
427
|
+
{
|
|
428
|
+
id: "custom-protection",
|
|
429
|
+
label: "Custom Protection",
|
|
430
|
+
render: CustomProtectionField
|
|
431
|
+
}
|
|
432
|
+
]
|
|
433
|
+
};
|
|
434
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
435
|
+
...defaultProps
|
|
436
|
+
}), {
|
|
437
|
+
customizationConfig: customConfig
|
|
438
|
+
});
|
|
439
|
+
expect(screen.getByText("Custom Protection")).toBeInTheDocument();
|
|
440
|
+
expect(screen.getByText("Custom Protection Info")).toBeInTheDocument();
|
|
441
|
+
});
|
|
442
|
+
it("should replace default data protection fields", ()=>{
|
|
443
|
+
const CustomLockField = ()=>/*#__PURE__*/ jsx("div", {
|
|
444
|
+
children: "Custom Lock Status"
|
|
445
|
+
});
|
|
446
|
+
const customConfig = {
|
|
447
|
+
extraObjectSummaryDataProtection: [
|
|
448
|
+
{
|
|
449
|
+
id: "lock",
|
|
450
|
+
label: "Custom Lock",
|
|
451
|
+
render: CustomLockField
|
|
452
|
+
}
|
|
453
|
+
]
|
|
454
|
+
};
|
|
455
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
456
|
+
...defaultProps
|
|
457
|
+
}), {
|
|
458
|
+
customizationConfig: customConfig
|
|
459
|
+
});
|
|
460
|
+
expect(screen.getByText("Custom Lock")).toBeInTheDocument();
|
|
461
|
+
expect(screen.getByText("Custom Lock Status")).toBeInTheDocument();
|
|
462
|
+
expect(screen.queryByText(/GOVERNANCE/)).not.toBeInTheDocument();
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
describe("Optional Labels", ()=>{
|
|
466
|
+
it("should handle fields without labels", ()=>{
|
|
467
|
+
const FieldWithoutLabel = ()=>/*#__PURE__*/ jsx("div", {
|
|
468
|
+
children: "Content Only"
|
|
469
|
+
});
|
|
470
|
+
const customConfig = {
|
|
471
|
+
extraObjectSummaryInformation: [
|
|
472
|
+
{
|
|
473
|
+
id: "no-label-field",
|
|
474
|
+
render: FieldWithoutLabel
|
|
475
|
+
}
|
|
476
|
+
]
|
|
477
|
+
};
|
|
478
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
479
|
+
...defaultProps
|
|
480
|
+
}), {
|
|
481
|
+
customizationConfig: customConfig
|
|
482
|
+
});
|
|
483
|
+
expect(screen.getByText("Content Only")).toBeInTheDocument();
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
describe("Legal Hold Toggle", ()=>{
|
|
488
|
+
it("should call mutation when toggle is clicked", async ()=>{
|
|
489
|
+
const user = user_event.setup();
|
|
490
|
+
const mockMutate = jest.fn();
|
|
491
|
+
mockUseObjectRetention.mockReturnValueOnce({
|
|
492
|
+
data: mockRetentionData,
|
|
493
|
+
status: "success"
|
|
494
|
+
});
|
|
495
|
+
mockUseObjectLegalHold.mockReturnValueOnce({
|
|
496
|
+
data: mockLegalHoldData,
|
|
497
|
+
status: "success"
|
|
498
|
+
});
|
|
499
|
+
mockUseSetObjectLegalHold.mockReturnValueOnce({
|
|
500
|
+
mutate: mockMutate,
|
|
501
|
+
isPending: false
|
|
502
|
+
});
|
|
503
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
504
|
+
...defaultProps
|
|
505
|
+
}));
|
|
506
|
+
const toggle = screen.getByRole("checkbox");
|
|
507
|
+
await user.click(toggle);
|
|
508
|
+
await waitFor(()=>{
|
|
509
|
+
expect(mockMutate).toHaveBeenCalled();
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
it("should call mutation with correct parameters", async ()=>{
|
|
513
|
+
const user = user_event.setup();
|
|
514
|
+
const mockMutate = jest.fn();
|
|
515
|
+
mockUseObjectRetention.mockReturnValueOnce({
|
|
516
|
+
data: mockRetentionData,
|
|
517
|
+
status: "success"
|
|
518
|
+
});
|
|
519
|
+
mockUseObjectLegalHold.mockReturnValueOnce({
|
|
520
|
+
data: mockLegalHoldData,
|
|
521
|
+
status: "success"
|
|
522
|
+
});
|
|
523
|
+
mockUseSetObjectLegalHold.mockReturnValueOnce({
|
|
524
|
+
mutate: mockMutate,
|
|
525
|
+
isPending: false
|
|
526
|
+
});
|
|
527
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
528
|
+
...defaultProps
|
|
529
|
+
}));
|
|
530
|
+
const toggle = screen.getByRole("checkbox");
|
|
531
|
+
await user.click(toggle);
|
|
532
|
+
await waitFor(()=>{
|
|
533
|
+
expect(mockMutate).toHaveBeenCalledWith(expect.objectContaining({
|
|
534
|
+
Bucket: "test-bucket",
|
|
535
|
+
Key: "test-object.txt",
|
|
536
|
+
LegalHold: {
|
|
537
|
+
Status: "ON"
|
|
538
|
+
}
|
|
539
|
+
}), expect.any(Object));
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
it("should show success toast with correct message when enabling legal hold", async ()=>{
|
|
543
|
+
const user = user_event.setup();
|
|
544
|
+
const mockShowToast = jest.fn();
|
|
545
|
+
const mockMutate = jest.fn((_params, callbacks)=>{
|
|
546
|
+
callbacks.onSuccess();
|
|
547
|
+
});
|
|
548
|
+
mockUseToast.mockReturnValue({
|
|
549
|
+
showToast: mockShowToast
|
|
550
|
+
});
|
|
551
|
+
mockUseObjectRetention.mockReturnValueOnce({
|
|
552
|
+
data: mockRetentionData,
|
|
553
|
+
status: "success"
|
|
554
|
+
});
|
|
555
|
+
mockUseObjectLegalHold.mockReturnValueOnce({
|
|
556
|
+
data: mockLegalHoldData,
|
|
557
|
+
status: "success"
|
|
558
|
+
});
|
|
559
|
+
mockUseSetObjectLegalHold.mockReturnValueOnce({
|
|
560
|
+
mutate: mockMutate,
|
|
561
|
+
isPending: false
|
|
562
|
+
});
|
|
563
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
564
|
+
...defaultProps
|
|
565
|
+
}));
|
|
566
|
+
const toggle = screen.getByRole("checkbox");
|
|
567
|
+
await user.click(toggle);
|
|
568
|
+
await waitFor(()=>{
|
|
569
|
+
expect(mockShowToast).toHaveBeenCalledWith({
|
|
570
|
+
open: true,
|
|
571
|
+
message: "Legal hold active",
|
|
572
|
+
status: "success"
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
it("should show error toast when mutation fails with Error instance", async ()=>{
|
|
577
|
+
const user = user_event.setup();
|
|
578
|
+
const mockShowToast = jest.fn();
|
|
579
|
+
const testError = new Error("Network error");
|
|
580
|
+
const mockMutate = jest.fn((_params, callbacks)=>{
|
|
581
|
+
callbacks.onError(testError);
|
|
582
|
+
});
|
|
583
|
+
mockUseToast.mockReturnValue({
|
|
584
|
+
showToast: mockShowToast
|
|
585
|
+
});
|
|
586
|
+
mockUseObjectRetention.mockReturnValueOnce({
|
|
587
|
+
data: mockRetentionData,
|
|
588
|
+
status: "success"
|
|
589
|
+
});
|
|
590
|
+
mockUseObjectLegalHold.mockReturnValueOnce({
|
|
591
|
+
data: mockLegalHoldData,
|
|
592
|
+
status: "success"
|
|
593
|
+
});
|
|
594
|
+
mockUseSetObjectLegalHold.mockReturnValueOnce({
|
|
595
|
+
mutate: mockMutate,
|
|
596
|
+
isPending: false
|
|
597
|
+
});
|
|
598
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
599
|
+
...defaultProps
|
|
600
|
+
}));
|
|
601
|
+
const toggle = screen.getByRole("checkbox");
|
|
602
|
+
await user.click(toggle);
|
|
603
|
+
await waitFor(()=>{
|
|
604
|
+
expect(mockShowToast).toHaveBeenCalledWith({
|
|
605
|
+
open: true,
|
|
606
|
+
message: "Network error",
|
|
607
|
+
status: "error"
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
it("should show generic error message when mutation fails with non-Error object", async ()=>{
|
|
612
|
+
const user = user_event.setup();
|
|
613
|
+
const mockShowToast = jest.fn();
|
|
614
|
+
const mockMutate = jest.fn((_params, callbacks)=>{
|
|
615
|
+
callbacks.onError({
|
|
616
|
+
message: "Custom error"
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
mockUseToast.mockReturnValue({
|
|
620
|
+
showToast: mockShowToast
|
|
621
|
+
});
|
|
622
|
+
mockUseObjectRetention.mockReturnValueOnce({
|
|
623
|
+
data: mockRetentionData,
|
|
624
|
+
status: "success"
|
|
625
|
+
});
|
|
626
|
+
mockUseObjectLegalHold.mockReturnValueOnce({
|
|
627
|
+
data: mockLegalHoldData,
|
|
628
|
+
status: "success"
|
|
629
|
+
});
|
|
630
|
+
mockUseSetObjectLegalHold.mockReturnValueOnce({
|
|
631
|
+
mutate: mockMutate,
|
|
632
|
+
isPending: false
|
|
633
|
+
});
|
|
634
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
635
|
+
...defaultProps
|
|
636
|
+
}));
|
|
637
|
+
const toggle = screen.getByRole("checkbox");
|
|
638
|
+
await user.click(toggle);
|
|
639
|
+
await waitFor(()=>{
|
|
640
|
+
expect(mockShowToast).toHaveBeenCalledWith({
|
|
641
|
+
open: true,
|
|
642
|
+
message: "Failed to update legal hold",
|
|
643
|
+
status: "error"
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
it("should disable toggle while updating", ()=>{
|
|
648
|
+
mockUseObjectRetention.mockReturnValueOnce({
|
|
649
|
+
data: mockRetentionData,
|
|
650
|
+
status: "success"
|
|
651
|
+
});
|
|
652
|
+
mockUseObjectLegalHold.mockReturnValueOnce({
|
|
653
|
+
data: mockLegalHoldData,
|
|
654
|
+
status: "success"
|
|
655
|
+
});
|
|
656
|
+
mockUseSetObjectLegalHold.mockReturnValueOnce({
|
|
657
|
+
mutate: jest.fn(),
|
|
658
|
+
isPending: true
|
|
659
|
+
});
|
|
660
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
661
|
+
...defaultProps
|
|
662
|
+
}));
|
|
663
|
+
const toggle = screen.getByRole("checkbox");
|
|
664
|
+
expect(toggle).toHaveAttribute("aria-disabled", "true");
|
|
665
|
+
});
|
|
666
|
+
it("should show checked toggle when legal hold is enabled", ()=>{
|
|
667
|
+
mockUseObjectRetention.mockReturnValueOnce({
|
|
668
|
+
data: mockRetentionData,
|
|
669
|
+
status: "success"
|
|
670
|
+
});
|
|
671
|
+
mockUseObjectLegalHold.mockReturnValueOnce({
|
|
672
|
+
data: {
|
|
673
|
+
LegalHold: {
|
|
674
|
+
Status: "ON"
|
|
675
|
+
}
|
|
676
|
+
},
|
|
677
|
+
status: "success"
|
|
678
|
+
});
|
|
679
|
+
mockUseSetObjectLegalHold.mockReturnValueOnce({
|
|
680
|
+
mutate: jest.fn(),
|
|
681
|
+
isPending: false
|
|
682
|
+
});
|
|
683
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
684
|
+
...defaultProps
|
|
685
|
+
}));
|
|
686
|
+
const toggle = screen.getByRole("checkbox");
|
|
687
|
+
expect(toggle).toBeChecked();
|
|
688
|
+
expect(toggle).toHaveAttribute("aria-checked", "true");
|
|
689
|
+
});
|
|
690
|
+
it("should show unchecked toggle when legal hold is disabled", ()=>{
|
|
691
|
+
mockUseObjectRetention.mockReturnValueOnce({
|
|
692
|
+
data: mockRetentionData,
|
|
693
|
+
status: "success"
|
|
694
|
+
});
|
|
695
|
+
mockUseObjectLegalHold.mockReturnValueOnce({
|
|
696
|
+
data: {
|
|
697
|
+
LegalHold: {
|
|
698
|
+
Status: "OFF"
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
status: "success"
|
|
702
|
+
});
|
|
703
|
+
mockUseSetObjectLegalHold.mockReturnValueOnce({
|
|
704
|
+
mutate: jest.fn(),
|
|
705
|
+
isPending: false
|
|
706
|
+
});
|
|
707
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
708
|
+
...defaultProps
|
|
709
|
+
}));
|
|
710
|
+
const toggle = screen.getByRole("checkbox");
|
|
711
|
+
expect(toggle).not.toBeChecked();
|
|
712
|
+
expect(toggle).toHaveAttribute("aria-checked", "false");
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
describe("Copy Buttons", ()=>{
|
|
716
|
+
it("should render copy buttons for version ID and ETag", ()=>{
|
|
717
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
718
|
+
...defaultProps
|
|
719
|
+
}));
|
|
720
|
+
const copyButtons = screen.getAllByRole("button", {
|
|
721
|
+
name: /copy/i
|
|
722
|
+
});
|
|
723
|
+
expect(copyButtons.length).toBeGreaterThanOrEqual(2);
|
|
724
|
+
});
|
|
725
|
+
it("should display version ID value correctly", ()=>{
|
|
726
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
727
|
+
...defaultProps
|
|
728
|
+
}));
|
|
729
|
+
expect(screen.getByText("test-version-id")).toBeInTheDocument();
|
|
730
|
+
const copyButtons = screen.getAllByRole("button", {
|
|
731
|
+
name: /copy/i
|
|
732
|
+
});
|
|
733
|
+
expect(copyButtons.length).toBeGreaterThan(0);
|
|
734
|
+
});
|
|
735
|
+
it("should display ETag value correctly", ()=>{
|
|
736
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
737
|
+
...defaultProps
|
|
738
|
+
}));
|
|
739
|
+
expect(screen.getByText("abc123def456")).toBeInTheDocument();
|
|
740
|
+
const copyButtons = screen.getAllByRole("button", {
|
|
741
|
+
name: /copy/i
|
|
742
|
+
});
|
|
743
|
+
expect(copyButtons.length).toBeGreaterThan(0);
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
describe("Field Ordering", ()=>{
|
|
747
|
+
it("should maintain default field order", ()=>{
|
|
748
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
749
|
+
...defaultProps
|
|
750
|
+
}));
|
|
751
|
+
expect(screen.getByText("Information")).toBeInTheDocument();
|
|
752
|
+
expect(screen.getByText("Name")).toBeInTheDocument();
|
|
753
|
+
expect(screen.getByText("Version ID")).toBeInTheDocument();
|
|
754
|
+
expect(screen.getByText("Size")).toBeInTheDocument();
|
|
755
|
+
expect(screen.getByText("Modified On")).toBeInTheDocument();
|
|
756
|
+
expect(screen.getByText("ETag")).toBeInTheDocument();
|
|
757
|
+
expect(screen.getByText("Location")).toBeInTheDocument();
|
|
758
|
+
expect(screen.getByText("Data protection")).toBeInTheDocument();
|
|
759
|
+
expect(screen.getByText("Lock")).toBeInTheDocument();
|
|
760
|
+
expect(screen.getByText("Legal Hold")).toBeInTheDocument();
|
|
761
|
+
});
|
|
762
|
+
it("should append custom fields after default fields", ()=>{
|
|
763
|
+
const CustomField = ()=>/*#__PURE__*/ jsx("div", {
|
|
764
|
+
children: "Custom Field Content"
|
|
765
|
+
});
|
|
766
|
+
const customConfig = {
|
|
767
|
+
extraObjectSummaryInformation: [
|
|
768
|
+
{
|
|
769
|
+
id: "custom-field",
|
|
770
|
+
label: "Custom Field",
|
|
771
|
+
render: CustomField
|
|
772
|
+
}
|
|
773
|
+
]
|
|
774
|
+
};
|
|
775
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
776
|
+
...defaultProps
|
|
777
|
+
}), {
|
|
778
|
+
customizationConfig: customConfig
|
|
779
|
+
});
|
|
780
|
+
const allText = document.body.textContent || "";
|
|
781
|
+
const locationIndex = allText.indexOf("Location");
|
|
782
|
+
const customFieldIndex = allText.indexOf("Custom Field");
|
|
783
|
+
expect(locationIndex).toBeLessThan(customFieldIndex);
|
|
784
|
+
});
|
|
785
|
+
});
|
|
786
|
+
describe("Performance", ()=>{
|
|
787
|
+
it("should memoize information fields", ()=>{
|
|
788
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
789
|
+
...defaultProps
|
|
790
|
+
}));
|
|
791
|
+
const firstRender = screen.getByText("test-object.txt");
|
|
792
|
+
expect(firstRender).toBeInTheDocument();
|
|
793
|
+
cleanup();
|
|
794
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
795
|
+
...defaultProps
|
|
796
|
+
}));
|
|
797
|
+
const secondRender = screen.getByText("test-object.txt");
|
|
798
|
+
expect(secondRender).toBeInTheDocument();
|
|
799
|
+
});
|
|
800
|
+
it("should not recalculate fields when unrelated props change", ()=>{
|
|
801
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
802
|
+
...defaultProps
|
|
803
|
+
}));
|
|
804
|
+
expect(screen.getByText("test-object.txt")).toBeInTheDocument();
|
|
805
|
+
cleanup();
|
|
806
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectSummary, {
|
|
807
|
+
...defaultProps,
|
|
808
|
+
versionId: "new-version"
|
|
809
|
+
}));
|
|
810
|
+
expect(screen.getByText("test-object.txt")).toBeInTheDocument();
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
});
|