Skip to content

Commit 9011d72

Browse files
authored
[ui-storageBrowser] Add create bucket/volume/filesystem functionality (#4234)
* [ui-storageBrowser] Add create bucket/volume/filesystem functionality
1 parent 469edaa commit 9011d72

File tree

4 files changed

+320
-111
lines changed

4 files changed

+320
-111
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,198 @@
1+
// Licensed to Cloudera, Inc. under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. Cloudera, Inc. licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
117
import React from 'react';
2-
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
18+
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
19+
import userEvent from '@testing-library/user-event';
320
import '@testing-library/jest-dom';
421
import CreateAndUploadAction from './CreateAndUploadAction';
522
import { CREATE_DIRECTORY_API_URL, CREATE_FILE_API_URL } from '../../../api';
23+
import * as storageUtils from '../../../../../utils/storageBrowserUtils';
624

725
const mockSave = jest.fn();
826
jest.mock('../../../../../utils/hooks/useSaveData/useSaveData', () => ({
927
__esModule: true,
1028
default: jest.fn(() => ({
11-
save: mockSave
29+
save: mockSave,
30+
loading: false,
31+
error: undefined
1232
}))
1333
}));
1434

15-
jest.mock('../../../../../utils/huePubSub', () => ({
16-
__esModule: true,
17-
publish: jest.fn()
18-
}));
19-
2035
describe('CreateAndUploadAction', () => {
21-
const currentPath = '/some/path';
36+
const defaultPath = '/some/path';
2237
const onActionSuccess = jest.fn();
2338
const onActionError = jest.fn();
2439
const mockFilesUpload = jest.fn();
2540

26-
beforeEach(() => {
27-
jest.clearAllMocks();
28-
41+
const setup = (path = defaultPath) =>
2942
render(
3043
<CreateAndUploadAction
31-
currentPath={currentPath}
44+
currentPath={path}
3245
onActionSuccess={onActionSuccess}
3346
onFilesUpload={mockFilesUpload}
3447
onActionError={onActionError}
3548
/>
3649
);
50+
51+
const openDropdown = async user => {
52+
await user.click(screen.getByRole('button', { name: 'New' }));
53+
};
54+
55+
beforeEach(() => {
56+
jest.clearAllMocks();
57+
jest.spyOn(storageUtils, 'isS3').mockReturnValue(false);
58+
jest.spyOn(storageUtils, 'isGS').mockReturnValue(false);
59+
jest.spyOn(storageUtils, 'isABFS').mockReturnValue(false);
60+
jest.spyOn(storageUtils, 'isOFS').mockReturnValue(false);
61+
});
62+
63+
const clickMenuOption = async (label: string, user) => {
64+
await openDropdown(user);
65+
await user.click(screen.getByRole('menuitem', { name: label }));
66+
};
67+
68+
afterEach(() => {
69+
cleanup();
3770
});
3871

39-
it('should render the dropdown with actions', async () => {
72+
it('renders the New button', () => {
73+
setup();
4074
const newButton = screen.getByRole('button', { name: 'New' });
4175
expect(newButton).toBeInTheDocument();
76+
});
4277

43-
await act(async () => fireEvent.click(newButton));
44-
78+
it('should render the dropdown with CREATE and UPLOAD group actions', async () => {
79+
const user = userEvent.setup();
80+
setup();
81+
await openDropdown(user);
4582
// Check that the "Create" and "Upload" groups are in the dropdown
4683
expect(screen.getByText('CREATE')).toBeInTheDocument();
4784
expect(screen.getByText('UPLOAD')).toBeInTheDocument();
4885
});
4986

50-
it('should open the folder creation modal when "New Folder" is clicked', async () => {
51-
const newButton = screen.getByRole('button', { name: 'New' });
52-
await act(async () => fireEvent.click(newButton));
53-
54-
const newFolderButton = screen.getByRole('menuitem', { name: 'New Folder' });
55-
await act(async () => fireEvent.click(newFolderButton));
87+
describe('create actions', () => {
88+
it.each([
89+
{ label: 'New Folder', modalTitle: 'Create Folder', api: CREATE_DIRECTORY_API_URL },
90+
{ label: 'New File', modalTitle: 'Create File', api: CREATE_FILE_API_URL }
91+
])('opens ${label} modal and calls correct API', async ({ label, modalTitle, api }) => {
92+
const user = userEvent.setup();
93+
setup();
94+
await clickMenuOption(label, user);
5695

57-
expect(screen.getByRole('dialog', { name: 'Create Folder' })).toBeInTheDocument();
58-
});
96+
expect(screen.getByText(modalTitle)).toBeInTheDocument();
5997

60-
it('should open the file creation modal when "New File" is clicked', async () => {
61-
const newButton = screen.getByRole('button', { name: 'New' });
62-
await act(async () => fireEvent.click(newButton));
98+
const input = screen.getByRole('textbox');
99+
fireEvent.change(input, { target: { value: `Test ${label}` } });
63100

64-
const newFileButton = screen.getByRole('menuitem', { name: 'New File' });
65-
await act(async () => fireEvent.click(newFileButton));
101+
fireEvent.click(screen.getByRole('button', { name: 'Create' }));
66102

67-
expect(screen.getByRole('dialog', { name: 'Create File' })).toBeInTheDocument();
103+
await waitFor(() => {
104+
expect(mockSave).toHaveBeenCalledWith(
105+
{ path: defaultPath, name: `Test ${label}` },
106+
{ url: api }
107+
);
108+
});
109+
});
68110
});
69111

70-
it('should render hidden file input for upload functionality', async () => {
71-
const fileInput = document.querySelector('input[type="file"]');
72-
expect(fileInput).toBeInTheDocument();
73-
expect(fileInput).toHaveAttribute('hidden');
74-
expect(fileInput).toHaveAttribute('multiple');
75-
});
112+
describe('upload actions', () => {
113+
it('should render hidden file input for upload functionality', async () => {
114+
setup();
115+
const fileInput = document.querySelector('input[type="file"]');
116+
expect(fileInput).toBeInTheDocument();
117+
expect(fileInput).toHaveAttribute('hidden');
118+
expect(fileInput).toHaveAttribute('multiple');
119+
});
76120

77-
it('should handle file selection and call onFilesUpload', async () => {
78-
const fileInput = document.querySelector('input[type="file"]');
79-
expect(fileInput).toBeInTheDocument();
121+
it('should handle file selection and call onFilesUpload', async () => {
122+
setup();
123+
const fileInput = document.querySelector('input[type="file"]');
124+
expect(fileInput).toBeInTheDocument();
80125

81-
const file1 = new File(['test content 1'], 'test1.txt', { type: 'text/plain' });
82-
const file2 = new File(['test content 2'], 'test2.txt', { type: 'text/plain' });
126+
const file1 = new File(['test content 1'], 'test1.txt', { type: 'text/plain' });
127+
const file2 = new File(['test content 2'], 'test2.txt', { type: 'text/plain' });
83128

84-
fireEvent.change(fileInput!, {
85-
target: { files: [file1, file2] }
86-
});
129+
fireEvent.change(fileInput!, {
130+
target: { files: [file1, file2] }
131+
});
87132

88-
expect(mockFilesUpload).toHaveBeenCalledWith([file1, file2]);
133+
expect(mockFilesUpload).toHaveBeenCalledWith([file1, file2]);
134+
});
89135
});
90136

91-
it('should call the correct API for creating a folder', async () => {
92-
const newButton = screen.getByRole('button', { name: 'New' });
93-
await act(async () => fireEvent.click(newButton));
137+
describe('storage-specific actions', () => {
138+
it('shows New Bucket when S3 root', async () => {
139+
const user = userEvent.setup();
140+
jest.spyOn(storageUtils, 'isS3').mockReturnValue(true);
141+
jest.spyOn(storageUtils, 'isS3Root').mockReturnValue(true);
94142

95-
const newFolderButton = screen.getByRole('menuitem', { name: 'New Folder' });
96-
await act(async () => fireEvent.click(newFolderButton));
143+
setup('/');
144+
await openDropdown(user);
145+
expect(screen.getByRole('menuitem', { name: 'New Bucket' })).toBeInTheDocument();
146+
});
147+
148+
it('shows New Bucket when OFS root', async () => {
149+
const user = userEvent.setup();
150+
jest.spyOn(storageUtils, 'isOFS').mockReturnValue(true);
151+
jest.spyOn(storageUtils, 'isOFSRoot').mockReturnValue(true);
97152

98-
const input = screen.getByRole('textbox');
99-
fireEvent.change(input, { target: { value: 'Test Folder' } });
153+
setup('/');
154+
await openDropdown(user);
155+
expect(screen.getByRole('menuitem', { name: 'New Bucket' })).toBeInTheDocument();
156+
});
100157

101-
const createButton = screen.getByRole('button', { name: 'Create' });
102-
fireEvent.click(createButton);
158+
it('does not show New Bucket when not in S3 root', async () => {
159+
const user = userEvent.setup();
160+
jest.spyOn(storageUtils, 'isS3').mockReturnValue(true);
161+
jest.spyOn(storageUtils, 'isS3Root').mockReturnValue(false);
103162

104-
await waitFor(() => {
105-
expect(mockSave).toHaveBeenCalledWith(
106-
{ path: currentPath, name: 'Test Folder' },
107-
{ url: CREATE_DIRECTORY_API_URL }
108-
);
163+
setup('s3://user');
164+
await openDropdown(user);
165+
expect(screen.queryByRole('menuitem', { name: 'New Bucket' })).not.toBeInTheDocument();
109166
});
110-
});
111167

112-
it('should call the correct API for creating a file', async () => {
113-
const newButton = screen.getByRole('button', { name: 'New' });
114-
await act(async () => fireEvent.click(newButton));
168+
it('shows New File System when ABFS root', async () => {
169+
const user = userEvent.setup();
170+
jest.spyOn(storageUtils, 'isABFS').mockReturnValue(true);
171+
jest.spyOn(storageUtils, 'isABFSRoot').mockReturnValue(true);
172+
173+
setup('/');
174+
await openDropdown(user);
175+
expect(screen.getByRole('menuitem', { name: 'New File System' })).toBeInTheDocument();
176+
});
115177

116-
const newFileButton = screen.getByRole('menuitem', { name: 'New File' });
117-
await act(async () => fireEvent.click(newFileButton));
178+
it('shows New Volume when OFS service ID', async () => {
179+
const user = userEvent.setup();
180+
jest.spyOn(storageUtils, 'isOFS').mockReturnValue(true);
181+
jest.spyOn(storageUtils, 'isOFSServiceID').mockReturnValue(true);
118182

119-
// Simulate file name submission
120-
const input = screen.getByRole('textbox');
121-
fireEvent.change(input, { target: { value: 'Test File' } });
183+
setup('/ofs-service');
184+
await openDropdown(user);
185+
expect(screen.getByRole('menuitem', { name: 'New Volume' })).toBeInTheDocument();
186+
});
122187

123-
const createButton = screen.getByRole('button', { name: 'Create' });
124-
fireEvent.click(createButton);
188+
it('does not show New Volume when OFS service ID', async () => {
189+
const user = userEvent.setup();
190+
jest.spyOn(storageUtils, 'isOFS').mockReturnValue(true);
191+
jest.spyOn(storageUtils, 'isOFSServiceID').mockReturnValue(false);
125192

126-
await waitFor(() => {
127-
expect(mockSave).toHaveBeenCalledWith(
128-
{ path: currentPath, name: 'Test File' },
129-
{ url: CREATE_FILE_API_URL }
130-
);
193+
setup('/ofs-service');
194+
await openDropdown(user);
195+
expect(screen.queryByRole('menuitem', { name: 'New Volume' })).not.toBeInTheDocument();
131196
});
132197
});
133198
});

0 commit comments

Comments
 (0)