@scality/data-browser-library 1.1.4 → 1.1.6
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/__tests__/CreateFolderButton.test.js +67 -0
- package/dist/components/__tests__/DeleteObjectButton.test.js +249 -26
- package/dist/components/__tests__/ObjectList.test.js +65 -22
- package/dist/components/__tests__/UploadButton.test.js +80 -0
- package/dist/components/buckets/BucketList.js +1 -2
- package/dist/components/objects/CreateFolderButton.js +2 -1
- package/dist/components/objects/DeleteObjectButton.js +89 -43
- package/dist/components/objects/ObjectList.js +21 -0
- package/dist/components/objects/UploadButton.js +2 -1
- package/dist/components/ui/DeleteObjectModalContent.d.ts +4 -1
- package/dist/components/ui/DeleteObjectModalContent.js +57 -18
- package/dist/hooks/__tests__/useDeleteFolder.test.d.ts +1 -0
- package/dist/hooks/__tests__/useDeleteFolder.test.js +203 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +2 -1
- package/dist/hooks/useDeleteFolder.d.ts +6 -0
- package/dist/hooks/useDeleteFolder.js +40 -0
- package/package.json +1 -1
|
@@ -3,11 +3,30 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
|
3
3
|
import user_event from "@testing-library/user-event";
|
|
4
4
|
import { createTestWrapper } from "../../test/testUtils.js";
|
|
5
5
|
import { CreateFolderButton } from "../objects/CreateFolderButton.js";
|
|
6
|
+
const mockMutate = jest.fn();
|
|
7
|
+
jest.mock('../../hooks', ()=>({
|
|
8
|
+
...jest.requireActual('../../hooks'),
|
|
9
|
+
useCreateFolder: ()=>({
|
|
10
|
+
mutate: mockMutate,
|
|
11
|
+
mutateAsync: jest.fn(),
|
|
12
|
+
reset: jest.fn(),
|
|
13
|
+
isPending: false,
|
|
14
|
+
isIdle: true,
|
|
15
|
+
isError: false,
|
|
16
|
+
isSuccess: false,
|
|
17
|
+
status: 'idle',
|
|
18
|
+
data: void 0,
|
|
19
|
+
error: null
|
|
20
|
+
})
|
|
21
|
+
}));
|
|
6
22
|
describe('CreateFolderButton', ()=>{
|
|
7
23
|
const defaultProps = {
|
|
8
24
|
bucket: 'test-bucket',
|
|
9
25
|
prefix: 'test-prefix'
|
|
10
26
|
};
|
|
27
|
+
beforeEach(()=>{
|
|
28
|
+
mockMutate.mockClear();
|
|
29
|
+
});
|
|
11
30
|
const renderCreateFolderButton = (props = {})=>{
|
|
12
31
|
const Wrapper = createTestWrapper();
|
|
13
32
|
return render(/*#__PURE__*/ jsx(Wrapper, {
|
|
@@ -144,4 +163,52 @@ describe('CreateFolderButton', ()=>{
|
|
|
144
163
|
await user_event.type(input, '/invalid');
|
|
145
164
|
expect(saveButton).toBeDisabled();
|
|
146
165
|
});
|
|
166
|
+
it('constructs correct key when prefix has trailing slash', async ()=>{
|
|
167
|
+
renderCreateFolderButton({
|
|
168
|
+
prefix: 'documents/'
|
|
169
|
+
});
|
|
170
|
+
fireEvent.click(screen.getByRole('button', {
|
|
171
|
+
name: /folder/i
|
|
172
|
+
}));
|
|
173
|
+
await waitFor(()=>screen.getByText('Create a folder'));
|
|
174
|
+
await user_event.type(screen.getByRole('textbox'), 'sub-folder');
|
|
175
|
+
fireEvent.click(screen.getByRole('button', {
|
|
176
|
+
name: /save/i
|
|
177
|
+
}));
|
|
178
|
+
expect(mockMutate).toHaveBeenCalledWith(expect.objectContaining({
|
|
179
|
+
Key: 'documents/sub-folder/'
|
|
180
|
+
}), expect.anything());
|
|
181
|
+
});
|
|
182
|
+
it('constructs correct key when prefix has no trailing slash', async ()=>{
|
|
183
|
+
renderCreateFolderButton({
|
|
184
|
+
prefix: 'documents'
|
|
185
|
+
});
|
|
186
|
+
fireEvent.click(screen.getByRole('button', {
|
|
187
|
+
name: /folder/i
|
|
188
|
+
}));
|
|
189
|
+
await waitFor(()=>screen.getByText('Create a folder'));
|
|
190
|
+
await user_event.type(screen.getByRole('textbox'), 'sub-folder');
|
|
191
|
+
fireEvent.click(screen.getByRole('button', {
|
|
192
|
+
name: /save/i
|
|
193
|
+
}));
|
|
194
|
+
expect(mockMutate).toHaveBeenCalledWith(expect.objectContaining({
|
|
195
|
+
Key: 'documents/sub-folder/'
|
|
196
|
+
}), expect.anything());
|
|
197
|
+
});
|
|
198
|
+
it('constructs correct key when prefix is empty', async ()=>{
|
|
199
|
+
renderCreateFolderButton({
|
|
200
|
+
prefix: ''
|
|
201
|
+
});
|
|
202
|
+
fireEvent.click(screen.getByRole('button', {
|
|
203
|
+
name: /folder/i
|
|
204
|
+
}));
|
|
205
|
+
await waitFor(()=>screen.getByText('Create a folder'));
|
|
206
|
+
await user_event.type(screen.getByRole('textbox'), 'new-folder');
|
|
207
|
+
fireEvent.click(screen.getByRole('button', {
|
|
208
|
+
name: /save/i
|
|
209
|
+
}));
|
|
210
|
+
expect(mockMutate).toHaveBeenCalledWith(expect.objectContaining({
|
|
211
|
+
Key: 'new-folder/'
|
|
212
|
+
}), expect.anything());
|
|
213
|
+
});
|
|
147
214
|
});
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
3
3
|
import { useDeleteObjects, useGetBucketVersioning } from "../../hooks/index.js";
|
|
4
|
+
import { useDeleteFolder } from "../../hooks/useDeleteFolder.js";
|
|
4
5
|
import { createTestWrapper } from "../../test/testUtils.js";
|
|
6
|
+
import { useInvalidateQueries } from "../providers/DataBrowserProvider.js";
|
|
5
7
|
import { DeleteObjectButton } from "../objects/DeleteObjectButton.js";
|
|
6
8
|
jest.mock('../../hooks');
|
|
9
|
+
jest.mock('../../hooks/useDeleteFolder');
|
|
10
|
+
jest.mock('../providers/DataBrowserProvider', ()=>({
|
|
11
|
+
...jest.requireActual('../providers/DataBrowserProvider'),
|
|
12
|
+
useInvalidateQueries: jest.fn()
|
|
13
|
+
}));
|
|
7
14
|
const mockUseDeleteObjects = jest.mocked(useDeleteObjects);
|
|
8
15
|
const mockUseGetBucketVersioning = jest.mocked(useGetBucketVersioning);
|
|
16
|
+
const mockUseDeleteFolder = jest.mocked(useDeleteFolder);
|
|
17
|
+
const mockUseInvalidateQueries = jest.mocked(useInvalidateQueries);
|
|
9
18
|
const mockDeleteObjects = jest.fn();
|
|
19
|
+
const mockDeleteFolder = jest.fn();
|
|
20
|
+
const mockInvalidateQueries = jest.fn();
|
|
10
21
|
const createMockObjects = (count, withVersions = false)=>Array.from({
|
|
11
22
|
length: count
|
|
12
23
|
}, (_, i)=>({
|
|
@@ -27,6 +38,10 @@ const createMockFolders = (count)=>Array.from({
|
|
|
27
38
|
type: 'folder',
|
|
28
39
|
LastModified: void 0
|
|
29
40
|
}));
|
|
41
|
+
const checkConfirmationCheckbox = ()=>{
|
|
42
|
+
const checkbox = screen.getByLabelText('Confirm the deletion');
|
|
43
|
+
fireEvent.click(checkbox);
|
|
44
|
+
};
|
|
30
45
|
const renderDeleteObjectButton = (props = {})=>{
|
|
31
46
|
const defaultProps = {
|
|
32
47
|
objects: createMockObjects(2),
|
|
@@ -49,13 +64,20 @@ const mockHookDefaults = (versioningEnabled = false)=>{
|
|
|
49
64
|
error: null
|
|
50
65
|
});
|
|
51
66
|
mockUseDeleteObjects.mockReturnValue({
|
|
52
|
-
|
|
67
|
+
mutateAsync: mockDeleteObjects,
|
|
53
68
|
isPending: false
|
|
54
69
|
});
|
|
70
|
+
mockUseDeleteFolder.mockReturnValue({
|
|
71
|
+
mutateAsync: mockDeleteFolder,
|
|
72
|
+
isPending: false
|
|
73
|
+
});
|
|
74
|
+
mockUseInvalidateQueries.mockReturnValue(mockInvalidateQueries);
|
|
55
75
|
};
|
|
56
76
|
describe('DeleteObjectButton', ()=>{
|
|
57
77
|
beforeEach(()=>{
|
|
58
78
|
jest.clearAllMocks();
|
|
79
|
+
mockDeleteObjects.mockResolvedValue({});
|
|
80
|
+
mockDeleteFolder.mockResolvedValue(void 0);
|
|
59
81
|
mockHookDefaults();
|
|
60
82
|
});
|
|
61
83
|
it('renders delete button with correct label and icon', ()=>{
|
|
@@ -171,7 +193,7 @@ describe('DeleteObjectButton', ()=>{
|
|
|
171
193
|
fireEvent.click(cancelButton);
|
|
172
194
|
expect(screen.queryByText('Confirmation')).not.toBeInTheDocument();
|
|
173
195
|
});
|
|
174
|
-
it('calls delete mutation with correct parameters when deletion is confirmed', ()=>{
|
|
196
|
+
it('calls delete mutation with correct parameters when deletion is confirmed', async ()=>{
|
|
175
197
|
const objects = createMockObjects(2);
|
|
176
198
|
renderDeleteObjectButton({
|
|
177
199
|
objects,
|
|
@@ -183,7 +205,10 @@ describe('DeleteObjectButton', ()=>{
|
|
|
183
205
|
fireEvent.click(deleteButton);
|
|
184
206
|
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
185
207
|
expect(confirmButton).toBeInTheDocument();
|
|
186
|
-
|
|
208
|
+
checkConfirmationCheckbox();
|
|
209
|
+
await act(async ()=>{
|
|
210
|
+
fireEvent.click(confirmButton);
|
|
211
|
+
});
|
|
187
212
|
expect(mockDeleteObjects).toHaveBeenCalledWith({
|
|
188
213
|
Bucket: 'my-bucket',
|
|
189
214
|
Delete: {
|
|
@@ -196,12 +221,9 @@ describe('DeleteObjectButton', ()=>{
|
|
|
196
221
|
}
|
|
197
222
|
]
|
|
198
223
|
}
|
|
199
|
-
}
|
|
200
|
-
onSuccess: expect.any(Function),
|
|
201
|
-
onError: expect.any(Function)
|
|
202
|
-
}));
|
|
224
|
+
});
|
|
203
225
|
});
|
|
204
|
-
it('includes version IDs in delete request when objects have versions', ()=>{
|
|
226
|
+
it('includes version IDs in delete request when objects have versions', async ()=>{
|
|
205
227
|
const objects = createMockObjects(1, true);
|
|
206
228
|
renderDeleteObjectButton({
|
|
207
229
|
objects
|
|
@@ -210,8 +232,11 @@ describe('DeleteObjectButton', ()=>{
|
|
|
210
232
|
name: /delete/i
|
|
211
233
|
});
|
|
212
234
|
fireEvent.click(deleteButton);
|
|
235
|
+
checkConfirmationCheckbox();
|
|
213
236
|
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
214
|
-
|
|
237
|
+
await act(async ()=>{
|
|
238
|
+
fireEvent.click(confirmButton);
|
|
239
|
+
});
|
|
215
240
|
expect(mockDeleteObjects).toHaveBeenCalledWith(expect.objectContaining({
|
|
216
241
|
Delete: {
|
|
217
242
|
Objects: [
|
|
@@ -221,34 +246,77 @@ describe('DeleteObjectButton', ()=>{
|
|
|
221
246
|
}
|
|
222
247
|
]
|
|
223
248
|
}
|
|
224
|
-
})
|
|
249
|
+
}));
|
|
225
250
|
});
|
|
226
|
-
it('
|
|
251
|
+
it('deletes objects first then attempts folder deletion', async ()=>{
|
|
252
|
+
const callOrder = [];
|
|
253
|
+
mockDeleteObjects.mockImplementation(async ()=>{
|
|
254
|
+
callOrder.push('deleteObjects');
|
|
255
|
+
return {};
|
|
256
|
+
});
|
|
257
|
+
mockDeleteFolder.mockImplementation(async ()=>{
|
|
258
|
+
callOrder.push('deleteFolder');
|
|
259
|
+
});
|
|
227
260
|
const mixedObjects = [
|
|
228
261
|
...createMockObjects(1),
|
|
229
262
|
...createMockFolders(1)
|
|
230
263
|
];
|
|
231
264
|
renderDeleteObjectButton({
|
|
232
|
-
objects: mixedObjects
|
|
265
|
+
objects: mixedObjects,
|
|
266
|
+
bucketName: 'my-bucket'
|
|
233
267
|
});
|
|
234
268
|
const deleteButton = screen.getByRole('button', {
|
|
235
269
|
name: /delete/i
|
|
236
270
|
});
|
|
237
271
|
fireEvent.click(deleteButton);
|
|
272
|
+
checkConfirmationCheckbox();
|
|
238
273
|
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
239
|
-
|
|
240
|
-
|
|
274
|
+
await act(async ()=>{
|
|
275
|
+
fireEvent.click(confirmButton);
|
|
276
|
+
});
|
|
277
|
+
expect(mockDeleteObjects).toHaveBeenCalledWith({
|
|
278
|
+
Bucket: 'my-bucket',
|
|
241
279
|
Delete: {
|
|
242
280
|
Objects: [
|
|
243
281
|
{
|
|
244
282
|
Key: 'file1.txt'
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
Key: 'folder1/'
|
|
248
283
|
}
|
|
249
284
|
]
|
|
250
285
|
}
|
|
251
|
-
})
|
|
286
|
+
});
|
|
287
|
+
expect(mockDeleteFolder).toHaveBeenCalledWith({
|
|
288
|
+
Bucket: 'my-bucket',
|
|
289
|
+
FolderKey: 'folder1/'
|
|
290
|
+
});
|
|
291
|
+
expect(callOrder).toEqual([
|
|
292
|
+
'deleteObjects',
|
|
293
|
+
'deleteFolder'
|
|
294
|
+
]);
|
|
295
|
+
});
|
|
296
|
+
it('only calls deleteFolder for folder-only selection', async ()=>{
|
|
297
|
+
const folders = createMockFolders(2);
|
|
298
|
+
renderDeleteObjectButton({
|
|
299
|
+
objects: folders,
|
|
300
|
+
bucketName: 'my-bucket'
|
|
301
|
+
});
|
|
302
|
+
const deleteButton = screen.getByRole('button', {
|
|
303
|
+
name: /delete/i
|
|
304
|
+
});
|
|
305
|
+
fireEvent.click(deleteButton);
|
|
306
|
+
checkConfirmationCheckbox();
|
|
307
|
+
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
308
|
+
await act(async ()=>{
|
|
309
|
+
fireEvent.click(confirmButton);
|
|
310
|
+
});
|
|
311
|
+
expect(mockDeleteFolder).toHaveBeenCalledWith({
|
|
312
|
+
Bucket: 'my-bucket',
|
|
313
|
+
FolderKey: 'folder1/'
|
|
314
|
+
});
|
|
315
|
+
expect(mockDeleteFolder).toHaveBeenCalledWith({
|
|
316
|
+
Bucket: 'my-bucket',
|
|
317
|
+
FolderKey: 'folder2/'
|
|
318
|
+
});
|
|
319
|
+
expect(mockDeleteObjects).not.toHaveBeenCalled();
|
|
252
320
|
});
|
|
253
321
|
it('shows success toast and closes modal when deletion succeeds', async ()=>{
|
|
254
322
|
renderDeleteObjectButton();
|
|
@@ -257,29 +325,184 @@ describe('DeleteObjectButton', ()=>{
|
|
|
257
325
|
});
|
|
258
326
|
fireEvent.click(deleteButton);
|
|
259
327
|
expect(screen.getByText('Confirmation')).toBeInTheDocument();
|
|
328
|
+
checkConfirmationCheckbox();
|
|
329
|
+
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
330
|
+
await act(async ()=>{
|
|
331
|
+
fireEvent.click(confirmButton);
|
|
332
|
+
});
|
|
333
|
+
await waitFor(()=>{
|
|
334
|
+
expect(screen.queryByText('Confirmation')).not.toBeInTheDocument();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
it('shows error when S3 returns Errors in delete response', async ()=>{
|
|
338
|
+
mockDeleteObjects.mockResolvedValueOnce({
|
|
339
|
+
Errors: [
|
|
340
|
+
{
|
|
341
|
+
Key: 'file1.txt',
|
|
342
|
+
Code: 'AccessDenied',
|
|
343
|
+
Message: 'Access Denied because object protected by object lock.'
|
|
344
|
+
}
|
|
345
|
+
]
|
|
346
|
+
});
|
|
347
|
+
renderDeleteObjectButton();
|
|
348
|
+
const deleteButton = screen.getByRole('button', {
|
|
349
|
+
name: /delete/i
|
|
350
|
+
});
|
|
351
|
+
fireEvent.click(deleteButton);
|
|
352
|
+
checkConfirmationCheckbox();
|
|
353
|
+
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
354
|
+
await act(async ()=>{
|
|
355
|
+
fireEvent.click(confirmButton);
|
|
356
|
+
});
|
|
357
|
+
await waitFor(()=>{
|
|
358
|
+
expect(screen.queryByText('Confirmation')).not.toBeInTheDocument();
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
it('shows error toast when deletion fails', async ()=>{
|
|
362
|
+
mockDeleteObjects.mockRejectedValueOnce(new Error('Access Denied'));
|
|
363
|
+
renderDeleteObjectButton();
|
|
364
|
+
const deleteButton = screen.getByRole('button', {
|
|
365
|
+
name: /delete/i
|
|
366
|
+
});
|
|
367
|
+
fireEvent.click(deleteButton);
|
|
368
|
+
checkConfirmationCheckbox();
|
|
369
|
+
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
370
|
+
await act(async ()=>{
|
|
371
|
+
fireEvent.click(confirmButton);
|
|
372
|
+
});
|
|
373
|
+
await waitFor(()=>{
|
|
374
|
+
expect(screen.queryByText('Confirmation')).not.toBeInTheDocument();
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
it('shows error toast when folder deletion fails but still deletes objects', async ()=>{
|
|
378
|
+
const mixedObjects = [
|
|
379
|
+
...createMockObjects(1),
|
|
380
|
+
...createMockFolders(1)
|
|
381
|
+
];
|
|
382
|
+
mockDeleteFolder.mockRejectedValueOnce(new Error('Cannot delete folder: The folder is not empty'));
|
|
383
|
+
renderDeleteObjectButton({
|
|
384
|
+
objects: mixedObjects
|
|
385
|
+
});
|
|
386
|
+
const deleteButton = screen.getByRole('button', {
|
|
387
|
+
name: /delete/i
|
|
388
|
+
});
|
|
389
|
+
fireEvent.click(deleteButton);
|
|
390
|
+
checkConfirmationCheckbox();
|
|
260
391
|
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
261
|
-
fireEvent.click(confirmButton);
|
|
262
392
|
await act(async ()=>{
|
|
263
|
-
|
|
264
|
-
successCallback();
|
|
393
|
+
fireEvent.click(confirmButton);
|
|
265
394
|
});
|
|
395
|
+
expect(mockDeleteObjects).toHaveBeenCalled();
|
|
266
396
|
await waitFor(()=>{
|
|
267
397
|
expect(screen.queryByText('Confirmation')).not.toBeInTheDocument();
|
|
268
398
|
});
|
|
269
399
|
});
|
|
270
|
-
it('
|
|
400
|
+
it('shows folder not empty error message', async ()=>{
|
|
401
|
+
const folders = createMockFolders(1);
|
|
402
|
+
mockDeleteFolder.mockRejectedValueOnce(new Error('Cannot delete folder: The folder is not empty'));
|
|
403
|
+
renderDeleteObjectButton({
|
|
404
|
+
objects: folders
|
|
405
|
+
});
|
|
406
|
+
const deleteButton = screen.getByRole('button', {
|
|
407
|
+
name: /delete/i
|
|
408
|
+
});
|
|
409
|
+
fireEvent.click(deleteButton);
|
|
410
|
+
checkConfirmationCheckbox();
|
|
411
|
+
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
412
|
+
await act(async ()=>{
|
|
413
|
+
fireEvent.click(confirmButton);
|
|
414
|
+
});
|
|
415
|
+
await waitFor(()=>{
|
|
416
|
+
expect(screen.queryByText('Confirmation')).not.toBeInTheDocument();
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
it('invalidates queries after successful deletion', async ()=>{
|
|
271
420
|
renderDeleteObjectButton();
|
|
272
421
|
const deleteButton = screen.getByRole('button', {
|
|
273
422
|
name: /delete/i
|
|
274
423
|
});
|
|
275
424
|
fireEvent.click(deleteButton);
|
|
425
|
+
checkConfirmationCheckbox();
|
|
276
426
|
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
277
|
-
fireEvent.click(confirmButton);
|
|
278
427
|
await act(async ()=>{
|
|
279
|
-
|
|
280
|
-
|
|
428
|
+
fireEvent.click(confirmButton);
|
|
429
|
+
});
|
|
430
|
+
await waitFor(()=>{
|
|
431
|
+
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
|
432
|
+
queryKey: [
|
|
433
|
+
'ListObjects'
|
|
434
|
+
]
|
|
435
|
+
});
|
|
436
|
+
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
|
437
|
+
queryKey: [
|
|
438
|
+
'ListObjectVersions'
|
|
439
|
+
]
|
|
440
|
+
});
|
|
441
|
+
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
|
442
|
+
queryKey: [
|
|
443
|
+
'SearchObjects'
|
|
444
|
+
]
|
|
445
|
+
});
|
|
446
|
+
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
|
447
|
+
queryKey: [
|
|
448
|
+
'SearchObjectsVersions'
|
|
449
|
+
]
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
it('disables delete button until confirmation checkbox is checked for non-versioned bucket', ()=>{
|
|
454
|
+
renderDeleteObjectButton();
|
|
455
|
+
const deleteButton = screen.getByRole('button', {
|
|
456
|
+
name: /delete/i
|
|
457
|
+
});
|
|
458
|
+
fireEvent.click(deleteButton);
|
|
459
|
+
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
460
|
+
expect(confirmButton).toBeDisabled();
|
|
461
|
+
checkConfirmationCheckbox();
|
|
462
|
+
expect(confirmButton).not.toBeDisabled();
|
|
463
|
+
});
|
|
464
|
+
it('does not show confirmation checkbox for versioned bucket without specific versions', ()=>{
|
|
465
|
+
mockHookDefaults(true);
|
|
466
|
+
renderDeleteObjectButton({
|
|
467
|
+
objects: createMockObjects(2, false)
|
|
281
468
|
});
|
|
282
|
-
|
|
469
|
+
const deleteButton = screen.getByRole('button', {
|
|
470
|
+
name: /delete/i
|
|
471
|
+
});
|
|
472
|
+
fireEvent.click(deleteButton);
|
|
473
|
+
expect(screen.queryByLabelText('Confirm the deletion')).not.toBeInTheDocument();
|
|
474
|
+
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
475
|
+
expect(confirmButton).not.toBeDisabled();
|
|
476
|
+
});
|
|
477
|
+
it('shows confirmation checkbox for versioned bucket with specific versions', ()=>{
|
|
478
|
+
mockHookDefaults(true);
|
|
479
|
+
renderDeleteObjectButton({
|
|
480
|
+
objects: createMockObjects(2, true)
|
|
481
|
+
});
|
|
482
|
+
const deleteButton = screen.getByRole('button', {
|
|
483
|
+
name: /delete/i
|
|
484
|
+
});
|
|
485
|
+
fireEvent.click(deleteButton);
|
|
486
|
+
expect(screen.getByLabelText('Confirm the deletion')).toBeInTheDocument();
|
|
487
|
+
const confirmButton = document.getElementById('object-delete-delete-button');
|
|
488
|
+
expect(confirmButton).toBeDisabled();
|
|
489
|
+
checkConfirmationCheckbox();
|
|
490
|
+
expect(confirmButton).not.toBeDisabled();
|
|
491
|
+
});
|
|
492
|
+
it('resets checkbox state on cancel', ()=>{
|
|
493
|
+
renderDeleteObjectButton();
|
|
494
|
+
const deleteButton = screen.getByRole('button', {
|
|
495
|
+
name: /delete/i
|
|
496
|
+
});
|
|
497
|
+
fireEvent.click(deleteButton);
|
|
498
|
+
checkConfirmationCheckbox();
|
|
499
|
+
expect(screen.getByLabelText('Confirm the deletion')).toBeChecked();
|
|
500
|
+
const cancelButton = screen.getByRole('button', {
|
|
501
|
+
name: /cancel/i
|
|
502
|
+
});
|
|
503
|
+
fireEvent.click(cancelButton);
|
|
504
|
+
fireEvent.click(deleteButton);
|
|
505
|
+
expect(screen.getByLabelText('Confirm the deletion')).not.toBeChecked();
|
|
283
506
|
});
|
|
284
507
|
it('updates objects when props change', ()=>{
|
|
285
508
|
const { rerender } = renderDeleteObjectButton({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { act, fireEvent, render, renderHook, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { act, fireEvent, render, renderHook, screen, waitFor, within } from "@testing-library/react";
|
|
3
3
|
import { HttpResponse, http } from "msw";
|
|
4
4
|
import { MemoryRouter } from "react-router";
|
|
5
5
|
import { DataBrowserUICustomizationProvider } from "../../contexts/DataBrowserUICustomizationContext.js";
|
|
@@ -226,24 +226,26 @@ describe('ObjectList', ()=>{
|
|
|
226
226
|
});
|
|
227
227
|
});
|
|
228
228
|
describe('Version Management', ()=>{
|
|
229
|
-
it('
|
|
229
|
+
it('disables List Versions toggle when bucket has no object versions', async ()=>{
|
|
230
|
+
renderObjectList({
|
|
231
|
+
bucketName: 'empty-bucket'
|
|
232
|
+
});
|
|
233
|
+
await waitFor(()=>{
|
|
234
|
+
expect(screen.getByLabelText(/show object versions/i)).toBeDisabled();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
it('enables List Versions toggle when bucket has object versions', async ()=>{
|
|
230
238
|
renderObjectList();
|
|
231
239
|
await waitFor(()=>{
|
|
232
|
-
expect(screen.
|
|
240
|
+
expect(screen.getByLabelText(/show object versions/i)).toBeEnabled();
|
|
233
241
|
});
|
|
234
|
-
const toggleLabel = screen.getByText(/list versions/i);
|
|
235
|
-
const toggle = toggleLabel.closest('label')?.querySelector('input[type="checkbox"]');
|
|
236
|
-
expect(toggle).toBeInTheDocument();
|
|
237
|
-
expect(toggle).not.toBeChecked();
|
|
238
242
|
});
|
|
239
|
-
it('shows Version ID column when
|
|
243
|
+
it('shows Version ID column when List Versions is toggled on', async ()=>{
|
|
240
244
|
renderObjectList();
|
|
241
245
|
await waitFor(()=>{
|
|
242
|
-
expect(screen.
|
|
246
|
+
expect(screen.getByLabelText(/show object versions/i)).toBeEnabled();
|
|
243
247
|
});
|
|
244
|
-
|
|
245
|
-
const toggle = toggleLabel.closest('label')?.querySelector('input[type="checkbox"]');
|
|
246
|
-
fireEvent.click(toggle);
|
|
248
|
+
fireEvent.click(screen.getByLabelText(/show object versions/i));
|
|
247
249
|
await waitFor(()=>{
|
|
248
250
|
expect(screen.getByText('Version ID')).toBeInTheDocument();
|
|
249
251
|
});
|
|
@@ -274,11 +276,9 @@ describe('ObjectList', ()=>{
|
|
|
274
276
|
it('clears selections when toggling versions', async ()=>{
|
|
275
277
|
renderObjectList();
|
|
276
278
|
await waitFor(()=>{
|
|
277
|
-
expect(screen.
|
|
279
|
+
expect(screen.getByLabelText(/show object versions/i)).toBeEnabled();
|
|
278
280
|
});
|
|
279
|
-
|
|
280
|
-
const toggle = toggleLabel.closest('label')?.querySelector('input[type="checkbox"]');
|
|
281
|
-
fireEvent.click(toggle);
|
|
281
|
+
fireEvent.click(screen.getByLabelText(/show object versions/i));
|
|
282
282
|
await waitFor(()=>{
|
|
283
283
|
expect(screen.getByText('Version ID')).toBeInTheDocument();
|
|
284
284
|
});
|
|
@@ -300,14 +300,56 @@ describe('ObjectList', ()=>{
|
|
|
300
300
|
type: 'object'
|
|
301
301
|
}));
|
|
302
302
|
onObjectSelect.mockClear();
|
|
303
|
-
|
|
304
|
-
const toggle = toggleLabel.closest('label')?.querySelector('input[type="checkbox"]');
|
|
305
|
-
fireEvent.click(toggle);
|
|
303
|
+
fireEvent.click(screen.getByLabelText(/show object versions/i));
|
|
306
304
|
await waitFor(()=>{
|
|
307
305
|
expect(screen.getByText('Version ID')).toBeInTheDocument();
|
|
308
306
|
});
|
|
309
307
|
expect(onObjectSelect).toHaveBeenCalledWith(null);
|
|
310
308
|
});
|
|
309
|
+
it('exits version view when switching to a bucket without versions', async ()=>{
|
|
310
|
+
const Wrapper = createTestWrapper();
|
|
311
|
+
const customization = {
|
|
312
|
+
extraObjectListColumns: [],
|
|
313
|
+
extraObjectListActions: []
|
|
314
|
+
};
|
|
315
|
+
const { rerender } = render(/*#__PURE__*/ jsx(MemoryRouter, {
|
|
316
|
+
children: /*#__PURE__*/ jsx(Wrapper, {
|
|
317
|
+
children: /*#__PURE__*/ jsx(DataBrowserUICustomizationProvider, {
|
|
318
|
+
config: customization,
|
|
319
|
+
children: /*#__PURE__*/ jsx(ObjectList, {
|
|
320
|
+
bucketName: "test-bucket",
|
|
321
|
+
prefix: "",
|
|
322
|
+
onObjectSelect: jest.fn(),
|
|
323
|
+
onPrefixChange: jest.fn()
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
}));
|
|
328
|
+
await waitFor(()=>{
|
|
329
|
+
expect(screen.getByLabelText(/show object versions/i)).toBeEnabled();
|
|
330
|
+
});
|
|
331
|
+
fireEvent.click(screen.getByLabelText(/show object versions/i));
|
|
332
|
+
await waitFor(()=>{
|
|
333
|
+
expect(screen.getByText('Version ID')).toBeInTheDocument();
|
|
334
|
+
});
|
|
335
|
+
rerender(/*#__PURE__*/ jsx(MemoryRouter, {
|
|
336
|
+
children: /*#__PURE__*/ jsx(Wrapper, {
|
|
337
|
+
children: /*#__PURE__*/ jsx(DataBrowserUICustomizationProvider, {
|
|
338
|
+
config: customization,
|
|
339
|
+
children: /*#__PURE__*/ jsx(ObjectList, {
|
|
340
|
+
bucketName: "empty-bucket",
|
|
341
|
+
prefix: "",
|
|
342
|
+
onObjectSelect: jest.fn(),
|
|
343
|
+
onPrefixChange: jest.fn()
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
})
|
|
347
|
+
}));
|
|
348
|
+
await waitFor(()=>{
|
|
349
|
+
expect(screen.getByLabelText(/show object versions/i)).toBeDisabled();
|
|
350
|
+
});
|
|
351
|
+
expect(screen.queryByText('Version ID')).not.toBeInTheDocument();
|
|
352
|
+
});
|
|
311
353
|
});
|
|
312
354
|
describe('Search Features', ()=>{
|
|
313
355
|
it('renders SearchInput when metadata-search feature is disabled', async ()=>{
|
|
@@ -450,9 +492,10 @@ describe('ObjectList', ()=>{
|
|
|
450
492
|
await waitFor(()=>{
|
|
451
493
|
expect(screen.getByText('file1.txt')).toBeInTheDocument();
|
|
452
494
|
});
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
495
|
+
const grid = within(screen.getByRole('grid'));
|
|
496
|
+
const rowCheckboxes = grid.getAllByRole('checkbox');
|
|
497
|
+
expect(rowCheckboxes.length).toBeGreaterThan(0);
|
|
498
|
+
const firstCheckbox = rowCheckboxes[0];
|
|
456
499
|
expect(firstCheckbox).not.toBeChecked();
|
|
457
500
|
fireEvent.click(firstCheckbox);
|
|
458
501
|
expect(firstCheckbox).toBeChecked();
|