@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
@@ -1,21 +1,29 @@
1
- import { jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { fireEvent, render, renderHook, screen, waitFor } from "@testing-library/react";
3
3
  import { MemoryRouter } from "react-router";
4
4
  import { createTestWrapper, mockOffsetSize, setupMswServer } from "../../test/testUtils.js";
5
5
  import { useListObjectVersions, useListObjects } from "../../hooks/index.js";
6
6
  import { ObjectList } from "../objects/ObjectList.js";
7
+ import { DataBrowserUICustomizationProvider } from "../../contexts/DataBrowserUICustomizationContext.js";
7
8
  import * as __WEBPACK_EXTERNAL_MODULE__utils_useFeatures_js_1facdd0d__ from "../../utils/useFeatures.js";
8
9
  setupMswServer();
9
- const renderObjectList = (props = {})=>{
10
+ const renderObjectList = (props = {}, customization)=>{
10
11
  const Wrapper = createTestWrapper();
12
+ const defaultCustomization = {
13
+ extraObjectListColumns: customization?.extraObjectListColumns || [],
14
+ extraObjectListActions: customization?.extraObjectListActions || []
15
+ };
11
16
  return render(/*#__PURE__*/ jsx(MemoryRouter, {
12
17
  children: /*#__PURE__*/ jsx(Wrapper, {
13
- children: /*#__PURE__*/ jsx(ObjectList, {
14
- bucketName: "test-bucket",
15
- prefix: "",
16
- onObjectSelect: jest.fn(),
17
- onPrefixChange: jest.fn(),
18
- ...props
18
+ children: /*#__PURE__*/ jsx(DataBrowserUICustomizationProvider, {
19
+ config: defaultCustomization,
20
+ children: /*#__PURE__*/ jsx(ObjectList, {
21
+ bucketName: "test-bucket",
22
+ prefix: "",
23
+ onObjectSelect: jest.fn(),
24
+ onPrefixChange: jest.fn(),
25
+ ...props
26
+ })
19
27
  })
20
28
  })
21
29
  }));
@@ -29,255 +37,747 @@ describe("ObjectList", ()=>{
29
37
  afterEach(()=>{
30
38
  jest.restoreAllMocks();
31
39
  });
32
- it("shows a table with proper headers", async ()=>{
33
- renderObjectList();
34
- await waitFor(()=>{
35
- expect(screen.getByRole("grid")).toBeInTheDocument();
40
+ describe("Basic Rendering", ()=>{
41
+ it("shows a table with proper headers", async ()=>{
42
+ renderObjectList();
43
+ await waitFor(()=>{
44
+ expect(screen.getByRole("grid")).toBeInTheDocument();
45
+ });
46
+ expect(screen.getByText("Name")).toBeInTheDocument();
47
+ expect(screen.getByText("Modified on")).toBeInTheDocument();
48
+ expect(screen.getByText("Size")).toBeInTheDocument();
49
+ expect(screen.getByText("Storage Location")).toBeInTheDocument();
36
50
  });
37
- expect(screen.getByText("Name")).toBeInTheDocument();
38
- expect(screen.getByText("Modified on")).toBeInTheDocument();
39
- expect(screen.getByText("Size")).toBeInTheDocument();
40
- expect(screen.getByText("Storage Location")).toBeInTheDocument();
41
- });
42
- it("renders content and handles interactions", async ()=>{
43
- const onObjectSelect = jest.fn();
44
- const onPrefixChange = jest.fn();
45
- renderObjectList({
46
- onObjectSelect,
47
- onPrefixChange
48
- });
49
- await waitFor(()=>{
51
+ it("renders empty state for empty bucket", async ()=>{
52
+ renderObjectList({
53
+ bucketName: "empty-bucket"
54
+ });
55
+ await waitFor(()=>{
56
+ expect(screen.getByRole("grid")).toBeInTheDocument();
57
+ });
58
+ expect(screen.getByText("Name")).toBeInTheDocument();
59
+ const rows = screen.getAllByRole("row");
60
+ expect(rows.length).toBe(1);
61
+ expect(screen.queryByText("file1.txt")).not.toBeInTheDocument();
62
+ expect(screen.queryByText("folder1/")).not.toBeInTheDocument();
63
+ });
64
+ it("handles loading states correctly", async ()=>{
65
+ renderObjectList();
66
+ expect(screen.getByRole("grid")).toBeInTheDocument();
67
+ await waitFor(()=>{
68
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
69
+ });
50
70
  expect(screen.getByText("file1.txt")).toBeInTheDocument();
71
+ expect(screen.getByText("file2.txt")).toBeInTheDocument();
72
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
51
73
  });
52
- expect(screen.getByText("file1.txt")).toBeInTheDocument();
53
- expect(screen.getByText("file2.txt")).toBeInTheDocument();
54
- expect(screen.getByText("folder1/")).toBeInTheDocument();
55
- fireEvent.click(screen.getByText("file1.txt"));
56
- expect(onObjectSelect).toHaveBeenCalledWith(expect.objectContaining({
57
- Key: "file1.txt",
58
- type: "object"
59
- }));
60
- fireEvent.click(screen.getByText("folder1/"));
61
- expect(onPrefixChange).toHaveBeenCalledWith("folder1/");
62
- const rows = screen.getAllByRole("row");
63
- expect(rows.length).toBe(4);
64
74
  });
65
- it("displays data formatting correctly", async ()=>{
66
- renderObjectList();
67
- await waitFor(()=>{
75
+ describe("Content Display", ()=>{
76
+ it("renders content and handles interactions", async ()=>{
77
+ const onObjectSelect = jest.fn();
78
+ const onPrefixChange = jest.fn();
79
+ renderObjectList({
80
+ onObjectSelect,
81
+ onPrefixChange
82
+ });
83
+ await waitFor(()=>{
84
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
85
+ });
68
86
  expect(screen.getByText("file1.txt")).toBeInTheDocument();
87
+ expect(screen.getByText("file2.txt")).toBeInTheDocument();
88
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
89
+ fireEvent.click(screen.getByText("folder1/"));
90
+ expect(onPrefixChange).toHaveBeenCalledWith("folder1/");
91
+ const rows = screen.getAllByRole("row");
92
+ expect(rows.length).toBe(4);
69
93
  });
70
- const gridElement = screen.getByRole("grid");
71
- expect(gridElement).toHaveTextContent("2023");
72
- const dateElements = screen.getAllByText(/2023/);
73
- expect(dateElements.length).toBeGreaterThanOrEqual(2);
74
- expect(screen.getByText("1 KB")).toBeInTheDocument();
75
- expect(screen.getByText("512 B")).toBeInTheDocument();
76
- const defaultElements = screen.getAllByText("default");
77
- expect(defaultElements.length).toBeGreaterThan(0);
78
- const dashes = screen.getAllByText("-");
79
- expect(dashes.length).toBeGreaterThanOrEqual(2);
80
- });
81
- it("handles edge cases gracefully", async ()=>{
82
- renderObjectList({
83
- bucketName: "empty-bucket"
94
+ it("displays data formatting correctly", async ()=>{
95
+ renderObjectList();
96
+ await waitFor(()=>{
97
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
98
+ });
99
+ const gridElement = screen.getByRole("grid");
100
+ expect(gridElement).toHaveTextContent("2023");
101
+ const dateElements = screen.getAllByText(/2023/);
102
+ expect(dateElements.length).toBeGreaterThanOrEqual(2);
103
+ expect(screen.getByText("1 KB")).toBeInTheDocument();
104
+ expect(screen.getByText("512 B")).toBeInTheDocument();
105
+ const defaultElements = screen.getAllByText("default");
106
+ expect(defaultElements.length).toBeGreaterThan(0);
107
+ const dashes = screen.getAllByText("-");
108
+ expect(dashes.length).toBeGreaterThanOrEqual(2);
84
109
  });
85
- await waitFor(()=>{
86
- expect(screen.getByRole("grid")).toBeInTheDocument();
110
+ it("displays correct icons for folders and files", async ()=>{
111
+ renderObjectList();
112
+ await waitFor(()=>{
113
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
114
+ });
115
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
116
+ expect(screen.getByText("file2.txt")).toBeInTheDocument();
117
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
118
+ const nameCells = screen.getAllByRole("gridcell").filter((cell)=>cell.textContent?.includes("folder1/") || cell.textContent?.includes("file1.txt") || cell.textContent?.includes("file2.txt"));
119
+ expect(nameCells.length).toBe(3);
120
+ nameCells.forEach((cell)=>{
121
+ const iconElement = cell.querySelector("svg, img, i");
122
+ const linkElement = cell.querySelector("a");
123
+ expect(iconElement).toBeInTheDocument();
124
+ expect(linkElement).toBeInTheDocument();
125
+ });
87
126
  });
88
- expect(screen.getByText("Name")).toBeInTheDocument();
89
- const mockOnObjectSelect = jest.fn();
90
- const mockOnPrefixChange = jest.fn();
91
- renderObjectList({
92
- onObjectSelect: mockOnObjectSelect,
93
- onPrefixChange: mockOnPrefixChange
127
+ it("removes prefix from displayed names correctly", async ()=>{
128
+ renderObjectList({
129
+ prefix: "my-prefix/subfolder/",
130
+ bucketName: "test-bucket"
131
+ });
132
+ await waitFor(()=>{
133
+ expect(screen.getByRole("grid")).toBeInTheDocument();
134
+ });
135
+ expect(screen.getByText("Name")).toBeInTheDocument();
94
136
  });
95
- await waitFor(()=>{
96
- expect(screen.getByText("file1.txt")).toBeInTheDocument();
137
+ });
138
+ describe("Interaction Behaviors", ()=>{
139
+ it("prevents event propagation when clicking links", async ()=>{
140
+ const onPrefixChange = jest.fn();
141
+ renderObjectList({
142
+ onPrefixChange
143
+ });
144
+ await waitFor(()=>{
145
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
146
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
147
+ });
148
+ fireEvent.click(screen.getByText("folder1/"));
149
+ expect(onPrefixChange).toHaveBeenCalledWith("folder1/");
150
+ expect(onPrefixChange).toHaveBeenCalledTimes(1);
97
151
  });
98
- expect(()=>{
99
- fireEvent.click(screen.getByText("file1.txt"));
152
+ it("supports prefix-based navigation", async ()=>{
153
+ const onPrefixChange = jest.fn();
154
+ renderObjectList({
155
+ onPrefixChange
156
+ });
157
+ await waitFor(()=>{
158
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
159
+ });
100
160
  fireEvent.click(screen.getByText("folder1/"));
101
- }).not.toThrow();
102
- expect(mockOnObjectSelect).toHaveBeenCalled();
103
- expect(mockOnPrefixChange).toHaveBeenCalled();
161
+ expect(onPrefixChange).toHaveBeenCalledWith("folder1/");
162
+ expect(onPrefixChange).toHaveBeenCalledTimes(1);
163
+ });
164
+ it("handles folder navigation correctly", async ()=>{
165
+ const onPrefixChange = jest.fn();
166
+ renderObjectList({
167
+ onPrefixChange
168
+ });
169
+ await waitFor(()=>{
170
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
171
+ });
172
+ fireEvent.click(screen.getByText("folder1/"));
173
+ expect(onPrefixChange).toHaveBeenCalledWith("folder1/");
174
+ });
104
175
  });
105
- it("handles loading states", async ()=>{
106
- renderObjectList();
107
- expect(screen.getByRole("grid")).toBeInTheDocument();
108
- await waitFor(()=>{
176
+ describe("Pagination and Infinite Scroll", ()=>{
177
+ it("handles infinite scroll pagination correctly", async ()=>{
178
+ renderObjectList({
179
+ bucketName: "paginated-bucket"
180
+ });
181
+ await waitFor(()=>{
182
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
183
+ });
109
184
  expect(screen.getByText("file1.txt")).toBeInTheDocument();
185
+ expect(screen.getByText("file2.txt")).toBeInTheDocument();
186
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
187
+ expect(screen.queryByText("file3.txt")).not.toBeInTheDocument();
188
+ const table = screen.getByRole("grid");
189
+ expect(table).toBeInTheDocument();
190
+ const rows = screen.getAllByRole("row");
191
+ expect(rows.length).toBe(4);
192
+ expect(screen.getByText("1 KB")).toBeInTheDocument();
193
+ expect(screen.getByText("512 B")).toBeInTheDocument();
194
+ });
195
+ it("integrates with infinite query hook for data loading", async ()=>{
196
+ const { result } = renderHook(()=>useListObjects({
197
+ Bucket: "paginated-bucket",
198
+ MaxKeys: 20,
199
+ Delimiter: "/"
200
+ }), {
201
+ wrapper: createTestWrapper()
202
+ });
203
+ await waitFor(()=>{
204
+ expect(result.current.isSuccess).toBe(true);
205
+ });
206
+ expect(result.current.data).toBeDefined();
207
+ expect(result.current.hasNextPage).toBe(true);
208
+ expect(typeof result.current.fetchNextPage).toBe("function");
209
+ expect(result.current.isFetchingNextPage).toBe(false);
210
+ const firstPage = result.current.data?.pages[0];
211
+ expect(firstPage).toBeDefined();
212
+ expect(firstPage?.Contents).toHaveLength(2);
213
+ expect(firstPage?.CommonPrefixes).toHaveLength(1);
214
+ result.current.fetchNextPage();
215
+ await waitFor(()=>{
216
+ const updatedData = result.current.data;
217
+ expect(updatedData?.pages).toHaveLength(2);
218
+ });
219
+ const secondPage = result.current.data?.pages[1];
220
+ expect(secondPage).toBeDefined();
221
+ expect(secondPage?.Contents).toHaveLength(1);
222
+ expect(result.current.hasNextPage).toBe(false);
110
223
  });
111
- expect(screen.getByText("file1.txt")).toBeInTheDocument();
112
- expect(screen.getByText("file2.txt")).toBeInTheDocument();
113
- expect(screen.getByText("folder1/")).toBeInTheDocument();
114
224
  });
115
- it("prevents event propagation when clicking links", async ()=>{
116
- const onObjectSelect = jest.fn();
117
- const onPrefixChange = jest.fn();
118
- renderObjectList({
119
- onObjectSelect,
120
- onPrefixChange
121
- });
122
- await waitFor(()=>{
123
- expect(screen.getByText("file1.txt")).toBeInTheDocument();
225
+ describe("Version Management", ()=>{
226
+ it("renders List Versions toggle", async ()=>{
227
+ renderObjectList();
228
+ await waitFor(()=>{
229
+ expect(screen.getByText("List Versions")).toBeInTheDocument();
230
+ });
231
+ const toggleLabel = screen.getByText(/list versions/i);
232
+ const toggle = toggleLabel.closest("label")?.querySelector('input[type="checkbox"]');
233
+ expect(toggle).toBeInTheDocument();
234
+ expect(toggle).not.toBeChecked();
235
+ });
236
+ it("shows Version ID column when toggle is enabled", async ()=>{
237
+ renderObjectList();
238
+ await waitFor(()=>{
239
+ expect(screen.getByText("List Versions")).toBeInTheDocument();
240
+ });
241
+ const toggleLabel = screen.getByText(/list versions/i);
242
+ const toggle = toggleLabel.closest("label")?.querySelector('input[type="checkbox"]');
243
+ fireEvent.click(toggle);
244
+ await waitFor(()=>{
245
+ expect(screen.getByText("Version ID")).toBeInTheDocument();
246
+ });
247
+ });
248
+ it("does not show Version ID column by default", async ()=>{
249
+ renderObjectList();
250
+ await waitFor(()=>{
251
+ expect(screen.getByText("Name")).toBeInTheDocument();
252
+ });
253
+ expect(screen.queryByText("Version ID")).not.toBeInTheDocument();
254
+ });
255
+ it("integrates with version listing hook", async ()=>{
256
+ const { result } = renderHook(()=>useListObjectVersions({
257
+ Bucket: "test-bucket",
258
+ MaxKeys: 20,
259
+ Delimiter: "/"
260
+ }), {
261
+ wrapper: createTestWrapper()
262
+ });
263
+ await waitFor(()=>{
264
+ expect(result.current.isSuccess).toBe(true);
265
+ });
266
+ expect(result.current.data).toBeDefined();
267
+ expect(typeof result.current.fetchNextPage).toBe("function");
268
+ const firstPage = result.current.data?.pages[0];
269
+ expect(firstPage).toBeDefined();
270
+ });
271
+ it("clears selections when toggling versions", async ()=>{
272
+ renderObjectList();
273
+ await waitFor(()=>{
274
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
275
+ });
276
+ const toggleLabel = screen.getByText(/list versions/i);
277
+ const toggle = toggleLabel.closest("label")?.querySelector('input[type="checkbox"]');
278
+ fireEvent.click(toggle);
279
+ await waitFor(()=>{
280
+ expect(screen.getByText("Version ID")).toBeInTheDocument();
281
+ });
124
282
  });
125
- fireEvent.click(screen.getByText("file1.txt"));
126
- expect(onObjectSelect).toHaveBeenCalledWith(expect.objectContaining({
127
- Key: "file1.txt",
128
- type: "object"
129
- }));
130
- expect(onPrefixChange).not.toHaveBeenCalled();
131
283
  });
132
- it("supports prefix-based navigation", async ()=>{
133
- renderObjectList({
134
- prefix: "folder1/"
284
+ describe("Search Features", ()=>{
285
+ it("renders table search when metadata-search feature is disabled", async ()=>{
286
+ jest.spyOn(__WEBPACK_EXTERNAL_MODULE__utils_useFeatures_js_1facdd0d__, "useFeatures").mockReturnValue(false);
287
+ renderObjectList();
288
+ await waitFor(()=>{
289
+ expect(screen.getByRole("grid")).toBeInTheDocument();
290
+ });
291
+ expect(screen.queryByPlaceholderText(/Metadata Search/i)).not.toBeInTheDocument();
292
+ });
293
+ it("renders MetadataSearch component when metadata-search feature is enabled", async ()=>{
294
+ jest.spyOn(__WEBPACK_EXTERNAL_MODULE__utils_useFeatures_js_1facdd0d__, "useFeatures").mockReturnValue(true);
295
+ renderObjectList();
296
+ await waitFor(()=>{
297
+ expect(screen.getByRole("grid")).toBeInTheDocument();
298
+ });
299
+ expect(screen.getByPlaceholderText(/Metadata Search/i)).toBeInTheDocument();
135
300
  });
136
- await waitFor(()=>{
301
+ });
302
+ describe("Multi-Selection", ()=>{
303
+ it("integrates with MultiSelectableContent", async ()=>{
304
+ renderObjectList();
305
+ await waitFor(()=>{
306
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
307
+ });
137
308
  expect(screen.getByRole("grid")).toBeInTheDocument();
309
+ const rows = screen.getAllByRole("row");
310
+ expect(rows.length).toBe(4);
311
+ const checkboxes = screen.getAllByRole("checkbox");
312
+ expect(checkboxes.length).toBeGreaterThan(0);
313
+ });
314
+ it("handles multi-selection state changes", async ()=>{
315
+ renderObjectList();
316
+ await waitFor(()=>{
317
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
318
+ });
319
+ const checkboxes = screen.getAllByRole("checkbox");
320
+ expect(checkboxes.length).toBeGreaterThan(0);
321
+ const firstCheckbox = checkboxes[0];
322
+ expect(firstCheckbox).not.toBeChecked();
323
+ fireEvent.click(firstCheckbox);
324
+ expect(firstCheckbox).toBeChecked();
325
+ });
326
+ it("clears selections when prefix changes", async ()=>{
327
+ const { rerender } = render(/*#__PURE__*/ jsx(MemoryRouter, {
328
+ children: (()=>{
329
+ const Wrapper = createTestWrapper();
330
+ const customization = {
331
+ extraObjectListColumns: [],
332
+ extraObjectListActions: []
333
+ };
334
+ return /*#__PURE__*/ jsx(Wrapper, {
335
+ children: /*#__PURE__*/ jsx(DataBrowserUICustomizationProvider, {
336
+ config: customization,
337
+ children: /*#__PURE__*/ jsx(ObjectList, {
338
+ bucketName: "test-bucket",
339
+ prefix: "",
340
+ onObjectSelect: jest.fn(),
341
+ onPrefixChange: jest.fn()
342
+ })
343
+ })
344
+ });
345
+ })()
346
+ }));
347
+ await waitFor(()=>{
348
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
349
+ });
350
+ const checkboxesBefore = screen.getAllByRole("checkbox");
351
+ expect(checkboxesBefore.length).toBeGreaterThan(0);
352
+ const firstCheckbox = checkboxesBefore[1];
353
+ fireEvent.click(firstCheckbox);
354
+ expect(firstCheckbox).toBeChecked();
355
+ rerender(/*#__PURE__*/ jsx(MemoryRouter, {
356
+ children: (()=>{
357
+ const Wrapper = createTestWrapper();
358
+ const customization = {
359
+ extraObjectListColumns: [],
360
+ extraObjectListActions: []
361
+ };
362
+ return /*#__PURE__*/ jsx(Wrapper, {
363
+ children: /*#__PURE__*/ jsx(DataBrowserUICustomizationProvider, {
364
+ config: customization,
365
+ children: /*#__PURE__*/ jsx(ObjectList, {
366
+ bucketName: "test-bucket",
367
+ prefix: "folder1/",
368
+ onObjectSelect: jest.fn(),
369
+ onPrefixChange: jest.fn()
370
+ })
371
+ })
372
+ });
373
+ })()
374
+ }));
375
+ await waitFor(()=>{
376
+ expect(screen.getByRole("grid")).toBeInTheDocument();
377
+ });
378
+ const checkboxesAfter = screen.getAllByRole("checkbox");
379
+ checkboxesAfter.forEach((checkbox)=>{
380
+ expect(checkbox).not.toBeChecked();
381
+ });
138
382
  });
139
- expect(screen.getByText("Name")).toBeInTheDocument();
140
383
  });
141
- it("displays correct icons for folders and files", async ()=>{
142
- renderObjectList();
143
- await waitFor(()=>{
144
- expect(screen.getByText("file1.txt")).toBeInTheDocument();
384
+ describe("Action Buttons", ()=>{
385
+ it("renders upload and create folder buttons", async ()=>{
386
+ renderObjectList();
387
+ await waitFor(()=>{
388
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
389
+ });
390
+ expect(screen.getByText("Upload")).toBeInTheDocument();
391
+ expect(screen.getByRole("button", {
392
+ name: /folder/i
393
+ })).toBeInTheDocument();
145
394
  });
146
- expect(screen.getByText("file1.txt")).toBeInTheDocument();
147
- expect(screen.getByText("file2.txt")).toBeInTheDocument();
148
- expect(screen.getByText("folder1/")).toBeInTheDocument();
149
- const nameCells = screen.getAllByRole("gridcell").filter((cell)=>cell.textContent?.includes("folder1/") || cell.textContent?.includes("file1.txt") || cell.textContent?.includes("file2.txt"));
150
- expect(nameCells.length).toBe(3);
151
- nameCells.forEach((cell)=>{
152
- const iconElement = cell.querySelector("svg, img, i");
153
- const linkElement = cell.querySelector("a");
154
- expect(iconElement).toBeInTheDocument();
155
- expect(linkElement).toBeInTheDocument();
395
+ it("renders delete button", async ()=>{
396
+ renderObjectList();
397
+ await waitFor(()=>{
398
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
399
+ });
400
+ expect(screen.getByText("Delete")).toBeInTheDocument();
156
401
  });
157
402
  });
158
- it("integrates with MultiSelectableContent", async ()=>{
159
- renderObjectList();
160
- await waitFor(()=>{
403
+ describe("Error Handling", ()=>{
404
+ it("handles errors gracefully", async ()=>{
405
+ const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
406
+ expect(()=>{
407
+ renderObjectList();
408
+ }).not.toThrow();
409
+ await waitFor(()=>{
410
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
411
+ });
161
412
  expect(screen.getByText("file1.txt")).toBeInTheDocument();
413
+ consoleErrorSpy.mockRestore();
162
414
  });
163
- expect(screen.getByRole("grid")).toBeInTheDocument();
164
- const rows = screen.getAllByRole("row");
165
- expect(rows.length).toBe(4);
166
415
  });
167
- it("handles infinite scroll pagination correctly", async ()=>{
168
- renderObjectList({
169
- bucketName: "paginated-bucket"
416
+ describe("Column Sorting", ()=>{
417
+ it("sorts folders before files", async ()=>{
418
+ renderObjectList();
419
+ await waitFor(()=>{
420
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
421
+ });
422
+ const rows = screen.getAllByRole("row");
423
+ const dataRows = rows.slice(1);
424
+ const firstRowText = dataRows[0]?.textContent || "";
425
+ expect(firstRowText).toContain("folder1/");
170
426
  });
171
- await waitFor(()=>{
172
- expect(screen.getByText("file1.txt")).toBeInTheDocument();
427
+ it("sorts items alphabetically within their type", async ()=>{
428
+ renderObjectList();
429
+ await waitFor(()=>{
430
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
431
+ });
432
+ const rows = screen.getAllByRole("row");
433
+ const dataRows = rows.slice(1);
434
+ const fileRows = dataRows.filter((row)=>row.textContent?.includes("file") && !row.textContent?.includes("folder"));
435
+ expect(fileRows.length).toBeGreaterThanOrEqual(2);
436
+ const firstFileName = fileRows[0]?.textContent || "";
437
+ const secondFileName = fileRows[1]?.textContent || "";
438
+ expect(firstFileName.includes("file1")).toBe(true);
439
+ expect(secondFileName.includes("file2")).toBe(true);
173
440
  });
174
- expect(screen.getByText("file1.txt")).toBeInTheDocument();
175
- expect(screen.getByText("file2.txt")).toBeInTheDocument();
176
- expect(screen.getByText("folder1/")).toBeInTheDocument();
177
- expect(screen.queryByText("file3.txt")).not.toBeInTheDocument();
178
- const table = screen.getByRole("grid");
179
- expect(table).toBeInTheDocument();
180
- const tableContent = table.querySelector('[role="rowgroup"]') || table.querySelector('[data-testid*="multi-selectable"]');
181
- expect(tableContent).toBeInTheDocument();
182
- const rows = screen.getAllByRole("row");
183
- expect(rows.length).toBe(4);
184
- expect(()=>{
185
- screen.getByText("Name");
186
- screen.getByText("Modified on");
187
- screen.getByText("Size");
188
- screen.getByText("Storage Location");
189
- }).not.toThrow();
190
- expect(screen.getByText("1 KB")).toBeInTheDocument();
191
- expect(screen.getByText("512 B")).toBeInTheDocument();
192
441
  });
193
- it("integrates with infinite query hook for data loading", async ()=>{
194
- const { result } = renderHook(()=>useListObjects({
195
- Bucket: "paginated-bucket",
196
- MaxKeys: 20,
197
- Delimiter: "/"
198
- }), {
199
- wrapper: createTestWrapper()
200
- });
201
- await waitFor(()=>{
202
- expect(result.current.isSuccess).toBe(true);
203
- });
204
- expect(result.current.data).toBeDefined();
205
- expect(result.current.hasNextPage).toBe(true);
206
- expect(typeof result.current.fetchNextPage).toBe("function");
207
- expect(result.current.isFetchingNextPage).toBe(false);
208
- const firstPage = result.current.data?.pages[0];
209
- expect(firstPage).toBeDefined();
210
- expect(firstPage?.Contents).toHaveLength(2);
211
- expect(firstPage?.CommonPrefixes).toHaveLength(1);
212
- result.current.fetchNextPage();
213
- await waitFor(()=>{
214
- const updatedData = result.current.data;
215
- expect(updatedData?.pages).toHaveLength(2);
216
- });
217
- const secondPage = result.current.data?.pages[1];
218
- expect(secondPage).toBeDefined();
219
- expect(secondPage?.Contents).toHaveLength(1);
220
- expect(result.current.hasNextPage).toBe(false);
221
- });
222
- it("renders List Versions toggle", async ()=>{
223
- renderObjectList();
224
- await waitFor(()=>{
225
- expect(screen.getByText("List Versions")).toBeInTheDocument();
226
- });
227
- const toggleLabel = screen.getByText(/list versions/i);
228
- const toggle = toggleLabel.closest("label")?.querySelector('input[type="checkbox"]');
229
- expect(toggle).toBeInTheDocument();
230
- expect(toggle).not.toBeChecked();
231
- });
232
- it("shows Version ID column when toggle is enabled", async ()=>{
233
- renderObjectList();
234
- await waitFor(()=>{
235
- expect(screen.getByText("List Versions")).toBeInTheDocument();
442
+ describe("Customization", ()=>{
443
+ it("supports custom columns", async ()=>{
444
+ const CustomCell = ({ data })=>/*#__PURE__*/ jsxs("div", {
445
+ children: [
446
+ "Custom: ",
447
+ data.Key
448
+ ]
449
+ });
450
+ const customization = {
451
+ extraObjectListColumns: [
452
+ {
453
+ id: "custom",
454
+ header: "Custom Column",
455
+ render: CustomCell,
456
+ width: "200px"
457
+ }
458
+ ],
459
+ extraObjectListActions: []
460
+ };
461
+ renderObjectList({}, customization);
462
+ await waitFor(()=>{
463
+ expect(screen.getByText("Custom Column")).toBeInTheDocument();
464
+ });
236
465
  });
237
- const toggleLabel = screen.getByText(/list versions/i);
238
- const toggle = toggleLabel.closest("label")?.querySelector('input[type="checkbox"]');
239
- fireEvent.click(toggle);
240
- await waitFor(()=>{
241
- expect(screen.getByText("Version ID")).toBeInTheDocument();
466
+ it("supports custom actions", async ()=>{
467
+ const CustomAction = ()=>/*#__PURE__*/ jsx("button", {
468
+ children: "Custom Action"
469
+ });
470
+ const customization = {
471
+ extraObjectListColumns: [],
472
+ extraObjectListActions: [
473
+ {
474
+ id: "customAction",
475
+ render: CustomAction
476
+ }
477
+ ]
478
+ };
479
+ renderObjectList({}, customization);
480
+ await waitFor(()=>{
481
+ expect(screen.getByText("Custom Action")).toBeInTheDocument();
482
+ });
483
+ });
484
+ it("allows overriding default columns", async ()=>{
485
+ const CustomNameCell = ({ data })=>/*#__PURE__*/ jsxs("div", {
486
+ children: [
487
+ "Override: ",
488
+ data.displayName
489
+ ]
490
+ });
491
+ const customization = {
492
+ extraObjectListColumns: [
493
+ {
494
+ id: "name",
495
+ header: "Custom Name",
496
+ render: CustomNameCell
497
+ }
498
+ ],
499
+ extraObjectListActions: []
500
+ };
501
+ renderObjectList({}, customization);
502
+ await waitFor(()=>{
503
+ expect(screen.getByText("Custom Name")).toBeInTheDocument();
504
+ });
242
505
  });
243
506
  });
244
- it("does not show Version ID column by default", async ()=>{
245
- renderObjectList();
246
- await waitFor(()=>{
247
- expect(screen.getByText("Name")).toBeInTheDocument();
507
+ describe("Edge Cases", ()=>{
508
+ it("handles edge cases gracefully", async ()=>{
509
+ const mockOnPrefixChange = jest.fn();
510
+ renderObjectList({
511
+ onPrefixChange: mockOnPrefixChange
512
+ });
513
+ await waitFor(()=>{
514
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
515
+ });
516
+ expect(()=>{
517
+ fireEvent.click(screen.getByText("folder1/"));
518
+ }).not.toThrow();
519
+ expect(mockOnPrefixChange).toHaveBeenCalled();
248
520
  });
249
- expect(screen.queryByText("Version ID")).not.toBeInTheDocument();
250
521
  });
251
- it("integrates with version listing hook", async ()=>{
252
- const { result } = renderHook(()=>useListObjectVersions({
253
- Bucket: "test-bucket",
254
- MaxKeys: 20,
255
- Delimiter: "/"
256
- }), {
257
- wrapper: createTestWrapper()
258
- });
259
- await waitFor(()=>{
260
- expect(result.current.isSuccess).toBe(true);
261
- });
262
- expect(result.current.data).toBeDefined();
263
- expect(typeof result.current.fetchNextPage).toBe("function");
264
- const firstPage = result.current.data?.pages[0];
265
- expect(firstPage).toBeDefined();
522
+ describe("Callback Stability", ()=>{
523
+ it("maintains stable callback references", async ()=>{
524
+ const onPrefixChange = jest.fn();
525
+ const { rerender } = render(/*#__PURE__*/ jsx(MemoryRouter, {
526
+ children: (()=>{
527
+ const Wrapper = createTestWrapper();
528
+ const customization = {
529
+ extraObjectListColumns: [],
530
+ extraObjectListActions: []
531
+ };
532
+ return /*#__PURE__*/ jsx(Wrapper, {
533
+ children: /*#__PURE__*/ jsx(DataBrowserUICustomizationProvider, {
534
+ config: customization,
535
+ children: /*#__PURE__*/ jsx(ObjectList, {
536
+ bucketName: "test-bucket",
537
+ prefix: "",
538
+ onObjectSelect: jest.fn(),
539
+ onPrefixChange: onPrefixChange
540
+ })
541
+ })
542
+ });
543
+ })()
544
+ }));
545
+ await waitFor(()=>{
546
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
547
+ });
548
+ fireEvent.click(screen.getByText("folder1/"));
549
+ expect(onPrefixChange).toHaveBeenCalledTimes(1);
550
+ const firstCallArgs = onPrefixChange.mock.calls[0];
551
+ rerender(/*#__PURE__*/ jsx(MemoryRouter, {
552
+ children: (()=>{
553
+ const Wrapper = createTestWrapper();
554
+ const customization = {
555
+ extraObjectListColumns: [],
556
+ extraObjectListActions: []
557
+ };
558
+ return /*#__PURE__*/ jsx(Wrapper, {
559
+ children: /*#__PURE__*/ jsx(DataBrowserUICustomizationProvider, {
560
+ config: customization,
561
+ children: /*#__PURE__*/ jsx(ObjectList, {
562
+ bucketName: "test-bucket",
563
+ prefix: "",
564
+ onObjectSelect: jest.fn(),
565
+ onPrefixChange: onPrefixChange
566
+ })
567
+ })
568
+ });
569
+ })()
570
+ }));
571
+ await waitFor(()=>{
572
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
573
+ });
574
+ fireEvent.click(screen.getByText("folder1/"));
575
+ expect(onPrefixChange).toHaveBeenCalledTimes(2);
576
+ const secondCallArgs = onPrefixChange.mock.calls[1];
577
+ expect(secondCallArgs[0]).toEqual(firstCallArgs[0]);
578
+ });
266
579
  });
267
- it("renders table search when metadata-search feature is disabled", async ()=>{
268
- jest.spyOn(__WEBPACK_EXTERNAL_MODULE__utils_useFeatures_js_1facdd0d__, "useFeatures").mockReturnValue(false);
269
- renderObjectList();
270
- await waitFor(()=>{
271
- expect(screen.getByRole("grid")).toBeInTheDocument();
580
+ describe("File Download", ()=>{
581
+ beforeEach(()=>{
582
+ mockOffsetSize(800, 600);
583
+ });
584
+ it("triggers download when clicking on file name without errors", async ()=>{
585
+ renderObjectList();
586
+ await waitFor(()=>{
587
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
588
+ });
589
+ const fileLink = screen.getByText("file1.txt");
590
+ expect(()=>{
591
+ fireEvent.click(fileLink);
592
+ }).not.toThrow();
593
+ });
594
+ it("does not download folders - navigates to prefix instead", async ()=>{
595
+ const onPrefixChange = jest.fn();
596
+ renderObjectList({
597
+ onPrefixChange
598
+ });
599
+ await waitFor(()=>{
600
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
601
+ });
602
+ const folderLink = screen.getByText("folder1/");
603
+ fireEvent.click(folderLink);
604
+ expect(onPrefixChange).toHaveBeenCalledWith("folder1/");
605
+ });
606
+ it("supports clicking on different files independently", async ()=>{
607
+ renderObjectList();
608
+ await waitFor(()=>{
609
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
610
+ expect(screen.getByText("file2.txt")).toBeInTheDocument();
611
+ });
612
+ const file1Link = screen.getByText("file1.txt");
613
+ const file2Link = screen.getByText("file2.txt");
614
+ expect(()=>{
615
+ fireEvent.click(file1Link);
616
+ fireEvent.click(file2Link);
617
+ }).not.toThrow();
618
+ });
619
+ it("renders file and folder names as clickable links", async ()=>{
620
+ renderObjectList();
621
+ await waitFor(()=>{
622
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
623
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
624
+ });
625
+ const fileLink = screen.getByText("file1.txt");
626
+ const folderLink = screen.getByText("folder1/");
627
+ expect(fileLink.closest("a")).toBeInTheDocument();
628
+ expect(folderLink.closest("a")).toBeInTheDocument();
629
+ });
630
+ it("handles download errors gracefully", async ()=>{
631
+ const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
632
+ renderObjectList();
633
+ await waitFor(()=>{
634
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
635
+ });
636
+ expect(()=>{
637
+ fireEvent.click(screen.getByText("file1.txt"));
638
+ }).not.toThrow();
639
+ consoleErrorSpy.mockRestore();
272
640
  });
273
- expect(screen.queryByPlaceholderText(/Metadata Search/i)).not.toBeInTheDocument();
274
641
  });
275
- it("renders MetadataSearch component when metadata-search feature is enabled", async ()=>{
276
- jest.spyOn(__WEBPACK_EXTERNAL_MODULE__utils_useFeatures_js_1facdd0d__, "useFeatures").mockReturnValue(true);
277
- renderObjectList();
278
- await waitFor(()=>{
279
- expect(screen.getByRole("grid")).toBeInTheDocument();
642
+ describe("Accessibility", ()=>{
643
+ it("renders file and folder names as clickable links", async ()=>{
644
+ renderObjectList();
645
+ await waitFor(()=>{
646
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
647
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
648
+ });
649
+ const fileLink = screen.getByText("file1.txt").closest("a");
650
+ const folderLink = screen.getByText("folder1/").closest("a");
651
+ expect(fileLink).toBeInTheDocument();
652
+ expect(folderLink).toBeInTheDocument();
653
+ });
654
+ it("provides focusable links for keyboard navigation", async ()=>{
655
+ renderObjectList();
656
+ await waitFor(()=>{
657
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
658
+ });
659
+ const fileLink = screen.getByText("file1.txt").closest("a");
660
+ expect(fileLink).toBeInTheDocument();
661
+ expect(fileLink?.tagName.toLowerCase()).toBe("a");
662
+ });
663
+ it("ensures interactive elements are keyboard accessible", async ()=>{
664
+ renderObjectList();
665
+ await waitFor(()=>{
666
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
667
+ });
668
+ const fileLink = screen.getByText("file1.txt").closest("a");
669
+ const folderLink = screen.getByText("folder1/").closest("a");
670
+ expect(fileLink).toBeInTheDocument();
671
+ expect(folderLink).toBeInTheDocument();
672
+ if (fileLink) {
673
+ const tabIndex = fileLink.getAttribute("tabindex");
674
+ if (null !== tabIndex) expect(parseInt(tabIndex, 10)).toBeGreaterThanOrEqual(-1);
675
+ }
676
+ if (folderLink) {
677
+ const tabIndex = folderLink.getAttribute("tabindex");
678
+ if (null !== tabIndex) expect(parseInt(tabIndex, 10)).toBeGreaterThanOrEqual(-1);
679
+ }
680
+ });
681
+ it("provides keyboard interaction for Enter key on file links", async ()=>{
682
+ renderObjectList();
683
+ await waitFor(()=>{
684
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
685
+ });
686
+ const fileLink = screen.getByText("file1.txt").closest("a");
687
+ if (!fileLink) throw new Error("File link not found");
688
+ expect(()=>{
689
+ fireEvent.keyDown(fileLink, {
690
+ key: "Enter",
691
+ code: "Enter"
692
+ });
693
+ }).not.toThrow();
694
+ });
695
+ it("provides keyboard interaction for Space key on folder links", async ()=>{
696
+ const onPrefixChange = jest.fn();
697
+ renderObjectList({
698
+ onPrefixChange
699
+ });
700
+ await waitFor(()=>{
701
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
702
+ });
703
+ const folderLink = screen.getByText("folder1/").closest("a");
704
+ if (!folderLink) throw new Error("Folder link not found");
705
+ fireEvent.keyDown(folderLink, {
706
+ key: " ",
707
+ code: "Space"
708
+ });
709
+ fireEvent.click(folderLink);
710
+ expect(onPrefixChange).toHaveBeenCalledWith("folder1/");
711
+ });
712
+ it("has proper ARIA roles for the table structure", async ()=>{
713
+ renderObjectList();
714
+ await waitFor(()=>{
715
+ expect(screen.getByRole("grid")).toBeInTheDocument();
716
+ });
717
+ const gridElement = screen.getByRole("grid");
718
+ expect(gridElement).toBeInTheDocument();
719
+ const rows = screen.getAllByRole("row");
720
+ expect(rows.length).toBeGreaterThan(0);
721
+ });
722
+ });
723
+ describe("Error Scenarios", ()=>{
724
+ let consoleErrorSpy;
725
+ beforeEach(()=>{
726
+ consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
727
+ });
728
+ afterEach(()=>{
729
+ consoleErrorSpy.mockRestore();
730
+ });
731
+ it("continues to function when download initiation fails", async ()=>{
732
+ renderObjectList();
733
+ await waitFor(()=>{
734
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
735
+ });
736
+ const fileLink = screen.getByText("file1.txt");
737
+ fireEvent.click(fileLink);
738
+ fireEvent.click(fileLink);
739
+ expect(()=>{
740
+ fireEvent.click(fileLink);
741
+ }).not.toThrow();
742
+ await waitFor(()=>{
743
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
744
+ });
745
+ });
746
+ it("handles rapid clicking without breaking the UI", async ()=>{
747
+ renderObjectList();
748
+ await waitFor(()=>{
749
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
750
+ });
751
+ const fileLink = screen.getByText("file1.txt");
752
+ for(let i = 0; i < 10; i++)fireEvent.click(fileLink);
753
+ await waitFor(()=>{
754
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
755
+ });
756
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
757
+ });
758
+ it("properly invokes navigation callbacks", async ()=>{
759
+ const onPrefixChange = jest.fn();
760
+ renderObjectList({
761
+ onPrefixChange
762
+ });
763
+ await waitFor(()=>{
764
+ expect(screen.getByText("folder1/")).toBeInTheDocument();
765
+ });
766
+ const folderLink = screen.getByText("folder1/");
767
+ fireEvent.click(folderLink);
768
+ expect(onPrefixChange).toHaveBeenCalledWith("folder1/");
769
+ expect(onPrefixChange).toHaveBeenCalledTimes(1);
770
+ });
771
+ it("recovers gracefully from failed async operations", async ()=>{
772
+ renderObjectList();
773
+ await waitFor(()=>{
774
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
775
+ });
776
+ fireEvent.click(screen.getByText("file1.txt"));
777
+ await waitFor(()=>{
778
+ expect(screen.getByText("file1.txt")).toBeInTheDocument();
779
+ });
780
+ expect(screen.getByText("file2.txt")).toBeInTheDocument();
280
781
  });
281
- expect(screen.getByPlaceholderText(/Metadata Search/i)).toBeInTheDocument();
282
782
  });
283
783
  });