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