@scality/data-browser-library 1.1.5 → 1.1.7

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.
@@ -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
- mutate: mockDeleteObjects,
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
- fireEvent.click(confirmButton);
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
- }, expect.objectContaining({
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
- fireEvent.click(confirmButton);
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
- }), expect.any(Object));
249
+ }));
225
250
  });
226
- it('handles mixed objects and folders in delete request', ()=>{
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
- fireEvent.click(confirmButton);
240
- expect(mockDeleteObjects).toHaveBeenCalledWith(expect.objectContaining({
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
- }), expect.any(Object));
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
- const successCallback = mockDeleteObjects.mock.calls[0][1].onSuccess;
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('handles non-Error objects in error callback', async ()=>{
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
- const errorCallback = mockDeleteObjects.mock.calls[0][1].onError;
280
- errorCallback('string error');
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
- expect(mockDeleteObjects.mock.calls[0][1].onError).toBeDefined();
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('renders List Versions toggle', async ()=>{
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.getByText('List Versions')).toBeInTheDocument();
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 toggle is enabled', async ()=>{
243
+ it('shows Version ID column when List Versions is toggled on', async ()=>{
240
244
  renderObjectList();
241
245
  await waitFor(()=>{
242
- expect(screen.getByText('List Versions')).toBeInTheDocument();
246
+ expect(screen.getByLabelText(/show object versions/i)).toBeEnabled();
243
247
  });
244
- const toggleLabel = screen.getByText(/list versions/i);
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.getByText('file1.txt')).toBeInTheDocument();
279
+ expect(screen.getByLabelText(/show object versions/i)).toBeEnabled();
278
280
  });
279
- const toggleLabel = screen.getByText(/list versions/i);
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
- const toggleLabel = screen.getByText(/list versions/i);
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 checkboxes = screen.getAllByRole('checkbox');
454
- expect(checkboxes.length).toBeGreaterThan(0);
455
- const firstCheckbox = checkboxes[0];
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();
@@ -3,11 +3,31 @@ 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 { UploadButton } from "../objects/UploadButton.js";
6
+ const mockMutateAsync = jest.fn();
7
+ jest.mock('../../hooks', ()=>({
8
+ ...jest.requireActual('../../hooks'),
9
+ useUploadObjects: ()=>({
10
+ mutate: jest.fn(),
11
+ mutateAsync: mockMutateAsync,
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('UploadButton - Core Functionality', ()=>{
7
23
  const defaultProps = {
8
24
  bucket: 'test-bucket',
9
25
  prefix: 'test-prefix'
10
26
  };
27
+ beforeEach(()=>{
28
+ mockMutateAsync.mockClear();
29
+ mockMutateAsync.mockResolvedValue({});
30
+ });
11
31
  const renderUploadButton = (props = {})=>{
12
32
  const Wrapper = createTestWrapper();
13
33
  return render(/*#__PURE__*/ jsx(Wrapper, {
@@ -141,4 +161,64 @@ describe('UploadButton - Core Functionality', ()=>{
141
161
  expect(screen.getByText('Drag and drop files and folders here')).toBeInTheDocument();
142
162
  });
143
163
  });
164
+ const addFileAndUpload = async ()=>{
165
+ const fileInput = screen.getByRole('presentation').querySelector('input[type="file"]');
166
+ const testFile = new File([
167
+ 'test content'
168
+ ], 'test.txt', {
169
+ type: 'text/plain'
170
+ });
171
+ await user_event.upload(fileInput, testFile);
172
+ await waitFor(()=>expect(screen.getByText('test.txt')).toBeInTheDocument());
173
+ const uploadButtons = screen.getAllByRole('button', {
174
+ name: 'Upload'
175
+ });
176
+ const modalUploadButton = uploadButtons.find((button)=>!button.querySelector('svg'));
177
+ fireEvent.click(modalUploadButton);
178
+ };
179
+ it('constructs correct key when prefix has trailing slash', async ()=>{
180
+ renderUploadButton({
181
+ prefix: 'documents/'
182
+ });
183
+ fireEvent.click(screen.getByRole('button', {
184
+ name: /upload/i
185
+ }));
186
+ await waitFor(()=>expect(screen.getByText('Upload Files')).toBeInTheDocument());
187
+ await addFileAndUpload();
188
+ await waitFor(()=>{
189
+ expect(mockMutateAsync).toHaveBeenCalledWith(expect.objectContaining({
190
+ Key: 'documents/test.txt'
191
+ }));
192
+ });
193
+ });
194
+ it('constructs correct key when prefix has no trailing slash', async ()=>{
195
+ renderUploadButton({
196
+ prefix: 'documents'
197
+ });
198
+ fireEvent.click(screen.getByRole('button', {
199
+ name: /upload/i
200
+ }));
201
+ await waitFor(()=>expect(screen.getByText('Upload Files')).toBeInTheDocument());
202
+ await addFileAndUpload();
203
+ await waitFor(()=>{
204
+ expect(mockMutateAsync).toHaveBeenCalledWith(expect.objectContaining({
205
+ Key: 'documents/test.txt'
206
+ }));
207
+ });
208
+ });
209
+ it('constructs correct key when prefix is empty', async ()=>{
210
+ renderUploadButton({
211
+ prefix: ''
212
+ });
213
+ fireEvent.click(screen.getByRole('button', {
214
+ name: /upload/i
215
+ }));
216
+ await waitFor(()=>expect(screen.getByText('Upload Files')).toBeInTheDocument());
217
+ await addFileAndUpload();
218
+ await waitFor(()=>{
219
+ expect(mockMutateAsync).toHaveBeenCalledWith(expect.objectContaining({
220
+ Key: 'test.txt'
221
+ }));
222
+ });
223
+ });
144
224
  });