@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
|
@@ -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(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
expect(
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
172
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
});
|