@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.
Files changed (40) hide show
  1. package/dist/components/DataBrowserUI.d.ts +20 -0
  2. package/dist/components/DataBrowserUI.js +64 -0
  3. package/dist/components/__tests__/BucketDetails.test.d.ts +1 -0
  4. package/dist/components/__tests__/BucketDetails.test.js +421 -0
  5. package/dist/components/__tests__/BucketList.test.js +389 -164
  6. package/dist/components/__tests__/BucketOverview.test.js +19 -63
  7. package/dist/components/__tests__/ObjectList.test.js +719 -219
  8. package/dist/components/buckets/BucketDetails.d.ts +40 -0
  9. package/dist/components/buckets/BucketDetails.js +194 -86
  10. package/dist/components/buckets/BucketList.d.ts +5 -6
  11. package/dist/components/buckets/BucketList.js +152 -97
  12. package/dist/components/buckets/BucketOverview.d.ts +6 -0
  13. package/dist/components/buckets/BucketOverview.js +363 -179
  14. package/dist/components/buckets/BucketPage.js +1 -5
  15. package/dist/components/buckets/BucketVersioning.js +3 -0
  16. package/dist/components/buckets/EmptyBucketButton.js +1 -1
  17. package/dist/components/index.d.ts +2 -1
  18. package/dist/components/index.js +2 -1
  19. package/dist/components/layouts/ArrowNavigation.js +20 -8
  20. package/dist/components/objects/CreateFolderButton.js +1 -1
  21. package/dist/components/objects/ObjectDetails/ObjectSummary.js +287 -157
  22. package/dist/components/objects/ObjectDetails/__tests__/ObjectDetails.test.d.ts +1 -0
  23. package/dist/components/objects/ObjectDetails/__tests__/ObjectDetails.test.js +516 -0
  24. package/dist/components/objects/ObjectDetails/__tests__/ObjectSummary.test.d.ts +1 -0
  25. package/dist/components/objects/ObjectDetails/__tests__/ObjectSummary.test.js +813 -0
  26. package/dist/components/objects/ObjectDetails/index.d.ts +16 -0
  27. package/dist/components/objects/ObjectDetails/index.js +132 -46
  28. package/dist/components/objects/ObjectList.d.ts +7 -5
  29. package/dist/components/objects/ObjectList.js +566 -286
  30. package/dist/components/objects/UploadButton.js +1 -1
  31. package/dist/config/types.d.ts +117 -0
  32. package/dist/contexts/DataBrowserUICustomizationContext.d.ts +27 -0
  33. package/dist/contexts/DataBrowserUICustomizationContext.js +13 -0
  34. package/dist/test/testUtils.d.ts +64 -0
  35. package/dist/test/testUtils.js +100 -1
  36. package/dist/types/index.d.ts +5 -3
  37. package/dist/utils/constants.d.ts +7 -0
  38. package/dist/utils/constants.js +8 -1
  39. package/dist/utils/useFeatures.js +1 -1
  40. 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
+ });