Skip to content

Commit 4d8f884

Browse files
Replace Base64 Storage with MinIO Client in CreateGroupChat.tsx (Fixes #3724) (#3821)
* Added minio client * Improve test coverage * Fix coderabbitai parsing error
1 parent f25fe79 commit 4d8f884

File tree

3 files changed

+293
-17
lines changed

3 files changed

+293
-17
lines changed

.coderabbit.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ reviews:
1111
high_level_summary: true
1212
review_status: true
1313
collapse_walkthrough: false
14-
request_changes_workflow: true
1514
auto_review:
1615
enabled: true
1716
drafts: false

src/components/UserPortal/CreateGroupChat/CreateGroupChat.spec.tsx

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import React from 'react';
2+
import { MemoryRouter, Route, Routes } from 'react-router-dom';
3+
import * as MinioUploadHook from 'utils/MinioUpload';
24
import {
35
act,
46
fireEvent,
@@ -52,6 +54,16 @@ import { vi } from 'vitest';
5254

5355
const { setItem } = useLocalStorage();
5456

57+
const mockUploadFileToMinio = vi
58+
.fn()
59+
.mockResolvedValue({ fileUrl: 'https://minio-test.com/test-image.jpg' });
60+
61+
vi.mock('utils/MinioUpload', () => ({
62+
useMinioUpload: vi.fn(() => ({
63+
uploadFileToMinio: mockUploadFileToMinio,
64+
})),
65+
}));
66+
5567
const USER_JOINED_ORG_MOCK = [
5668
{
5769
request: {
@@ -6325,3 +6337,270 @@ describe('CreateGroupChat Additional Tests', () => {
63256337
expect(fileInput).toBeInTheDocument();
63266338
});
63276339
});
6340+
6341+
describe('CreateGroupChat - handleImageChange', () => {
6342+
const mockToggleCreateGroupChatModal = vi.fn();
6343+
const mockChatsListRefetch = vi.fn().mockResolvedValue({});
6344+
6345+
const mockUserData = {
6346+
users: [
6347+
{
6348+
user: {
6349+
_id: 'user1',
6350+
firstName: 'John',
6351+
lastName: 'Doe',
6352+
6353+
},
6354+
},
6355+
],
6356+
};
6357+
6358+
const mocks = [
6359+
{
6360+
request: {
6361+
query: USERS_CONNECTION_LIST,
6362+
variables: { firstName_contains: '', lastName_contains: '' },
6363+
},
6364+
result: {
6365+
data: mockUserData,
6366+
},
6367+
},
6368+
];
6369+
6370+
beforeEach(() => {
6371+
vi.clearAllMocks();
6372+
});
6373+
6374+
afterEach(() => {
6375+
vi.restoreAllMocks();
6376+
});
6377+
6378+
test('should upload file to MinIO and set the image URL on successful upload', async () => {
6379+
// Mock the return value of useMinioUpload
6380+
const mockUploadFileToMinio = vi.fn().mockResolvedValue({
6381+
fileUrl: 'https://minio.example.com/test-image.jpg',
6382+
});
6383+
vi.mocked(MinioUploadHook.useMinioUpload).mockReturnValue({
6384+
uploadFileToMinio: mockUploadFileToMinio,
6385+
});
6386+
6387+
render(
6388+
<MockedProvider mocks={mocks} addTypename={false}>
6389+
<MemoryRouter initialEntries={['/organizations/test-org-id']}>
6390+
<Routes>
6391+
<Route
6392+
path="/organizations/:orgId"
6393+
element={
6394+
<CreateGroupChat
6395+
toggleCreateGroupChatModal={mockToggleCreateGroupChatModal}
6396+
createGroupChatModalisOpen={true}
6397+
chatsListRefetch={mockChatsListRefetch}
6398+
/>
6399+
}
6400+
/>
6401+
</Routes>
6402+
</MemoryRouter>
6403+
</MockedProvider>,
6404+
);
6405+
6406+
// Wait for component to load
6407+
await waitFor(() => {
6408+
expect(screen.getByTestId('createGroupChatModal')).toBeInTheDocument();
6409+
});
6410+
6411+
// Create a test file
6412+
const testFile = new File(['test image content'], 'test-image.jpg', {
6413+
type: 'image/jpeg',
6414+
});
6415+
6416+
// Get the file input
6417+
const fileInput = screen.getByTestId('fileInput');
6418+
6419+
// Simulate file selection
6420+
fireEvent.change(fileInput, { target: { files: [testFile] } });
6421+
6422+
// Check if uploadFileToMinio was called with the correct arguments
6423+
await waitFor(() => {
6424+
expect(mockUploadFileToMinio).toHaveBeenCalledWith(
6425+
testFile,
6426+
'test-org-id',
6427+
);
6428+
});
6429+
6430+
// Verify that the image URL was set correctly
6431+
await waitFor(() => {
6432+
// If the image is visible, we should see the image element with the correct src
6433+
const img = screen.getByAltText('');
6434+
expect(img).toHaveAttribute(
6435+
'src',
6436+
'https://minio.example.com/test-image.jpg',
6437+
);
6438+
});
6439+
});
6440+
6441+
test('should handle errors when uploading file to MinIO', async () => {
6442+
// Spy on console.error
6443+
const consoleErrorSpy = vi
6444+
.spyOn(console, 'error')
6445+
.mockImplementation(() => {});
6446+
6447+
// Mock the return value of useMinioUpload to throw an error
6448+
const mockUploadFileToMinio = vi
6449+
.fn()
6450+
.mockRejectedValue(new Error('Upload failed'));
6451+
vi.mocked(MinioUploadHook.useMinioUpload).mockReturnValue({
6452+
uploadFileToMinio: mockUploadFileToMinio,
6453+
});
6454+
6455+
render(
6456+
<MockedProvider mocks={mocks} addTypename={false}>
6457+
<MemoryRouter initialEntries={['/organizations/test-org-id']}>
6458+
<Routes>
6459+
<Route
6460+
path="/organizations/:orgId"
6461+
element={
6462+
<CreateGroupChat
6463+
toggleCreateGroupChatModal={mockToggleCreateGroupChatModal}
6464+
createGroupChatModalisOpen={true}
6465+
chatsListRefetch={mockChatsListRefetch}
6466+
/>
6467+
}
6468+
/>
6469+
</Routes>
6470+
</MemoryRouter>
6471+
</MockedProvider>,
6472+
);
6473+
6474+
// Wait for component to load
6475+
await waitFor(() => {
6476+
expect(screen.getByTestId('createGroupChatModal')).toBeInTheDocument();
6477+
});
6478+
6479+
// Create a test file
6480+
const testFile = new File(['test image content'], 'test-image.jpg', {
6481+
type: 'image/jpeg',
6482+
});
6483+
6484+
// Get the file input
6485+
const fileInput = screen.getByTestId('fileInput');
6486+
6487+
// Simulate file selection
6488+
fireEvent.change(fileInput, { target: { files: [testFile] } });
6489+
6490+
// Check if uploadFileToMinio was called with the correct arguments
6491+
await waitFor(() => {
6492+
expect(mockUploadFileToMinio).toHaveBeenCalledWith(
6493+
testFile,
6494+
'test-org-id',
6495+
);
6496+
});
6497+
6498+
// Verify that console.error was called with the correct error message
6499+
await waitFor(() => {
6500+
expect(consoleErrorSpy).toHaveBeenCalledWith(
6501+
'Error uploading image to MinIO:',
6502+
expect.any(Error),
6503+
);
6504+
});
6505+
6506+
// Verify that the image was not set (should still show the Avatar component)
6507+
await waitFor(() => {
6508+
const avatar = screen.getByTestId('editImageBtn');
6509+
expect(avatar).toBeInTheDocument();
6510+
});
6511+
6512+
consoleErrorSpy.mockRestore();
6513+
});
6514+
6515+
test('should not call uploadFileToMinio when no file is selected', async () => {
6516+
// Mock the return value of useMinioUpload
6517+
const mockUploadFileToMinio = vi.fn();
6518+
vi.mocked(MinioUploadHook.useMinioUpload).mockReturnValue({
6519+
uploadFileToMinio: mockUploadFileToMinio,
6520+
});
6521+
6522+
render(
6523+
<MockedProvider mocks={mocks} addTypename={false}>
6524+
<MemoryRouter initialEntries={['/organizations/test-org-id']}>
6525+
<Routes>
6526+
<Route
6527+
path="/organizations/:orgId"
6528+
element={
6529+
<CreateGroupChat
6530+
toggleCreateGroupChatModal={mockToggleCreateGroupChatModal}
6531+
createGroupChatModalisOpen={true}
6532+
chatsListRefetch={mockChatsListRefetch}
6533+
/>
6534+
}
6535+
/>
6536+
</Routes>
6537+
</MemoryRouter>
6538+
</MockedProvider>,
6539+
);
6540+
6541+
// Wait for component to load
6542+
await waitFor(() => {
6543+
expect(screen.getByTestId('createGroupChatModal')).toBeInTheDocument();
6544+
});
6545+
6546+
// Get the file input
6547+
const fileInput = screen.getByTestId('fileInput');
6548+
6549+
// Simulate an empty file selection
6550+
fireEvent.change(fileInput, { target: { files: [] } });
6551+
6552+
// Verify that uploadFileToMinio was not called
6553+
expect(mockUploadFileToMinio).not.toHaveBeenCalled();
6554+
});
6555+
6556+
test('should trigger handleImageChange when edit button is clicked', async () => {
6557+
// Mock the return value of useMinioUpload
6558+
const mockUploadFileToMinio = vi.fn();
6559+
vi.mocked(MinioUploadHook.useMinioUpload).mockReturnValue({
6560+
uploadFileToMinio: mockUploadFileToMinio,
6561+
});
6562+
6563+
render(
6564+
<MockedProvider mocks={mocks} addTypename={false}>
6565+
<MemoryRouter initialEntries={['/organizations/test-org-id']}>
6566+
<Routes>
6567+
<Route
6568+
path="/organizations/:orgId"
6569+
element={
6570+
<CreateGroupChat
6571+
toggleCreateGroupChatModal={mockToggleCreateGroupChatModal}
6572+
createGroupChatModalisOpen={true}
6573+
chatsListRefetch={mockChatsListRefetch}
6574+
/>
6575+
}
6576+
/>
6577+
</Routes>
6578+
</MemoryRouter>
6579+
</MockedProvider>,
6580+
);
6581+
6582+
// Wait for component to load
6583+
await waitFor(() => {
6584+
expect(screen.getByTestId('createGroupChatModal')).toBeInTheDocument();
6585+
});
6586+
6587+
// Find the edit button
6588+
const editButton = screen.getByTestId('editImageBtn');
6589+
6590+
// Create a mock click function for the file input
6591+
const mockClick = vi.fn();
6592+
6593+
// Get the file input and mock its click method
6594+
const fileInput = screen.getByTestId('fileInput');
6595+
Object.defineProperty(fileInput, 'click', {
6596+
value: mockClick,
6597+
configurable: true,
6598+
});
6599+
6600+
// Click the edit button
6601+
fireEvent.click(editButton);
6602+
6603+
// Verify that the file input's click method was called
6604+
expect(mockClick).toHaveBeenCalled();
6605+
});
6606+
});

src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import Loader from 'components/Loader/Loader';
1818
import { Search } from '@mui/icons-material';
1919
import { useTranslation } from 'react-i18next';
2020
import { useParams } from 'react-router-dom';
21-
import convertToBase64 from 'utils/convertToBase64';
2221
import Avatar from 'components/Avatar/Avatar';
2322
import { FiEdit } from 'react-icons/fi';
23+
import { useMinioUpload } from 'utils/MinioUpload';
2424

2525
interface InterfaceCreateGroupChatProps {
2626
toggleCreateGroupChatModal: () => void;
@@ -88,6 +88,10 @@ export default function CreateGroupChat({
8888
const [userIds, setUserIds] = useState<string[]>([]);
8989

9090
const [addUserModalisOpen, setAddUserModalisOpen] = useState(false);
91+
const [selectedImage, setSelectedImage] = useState<string | null>(null);
92+
const fileInputRef = useRef<HTMLInputElement>(null);
93+
const { orgId: currentOrg } = useParams();
94+
const { uploadFileToMinio } = useMinioUpload();
9195

9296
function openAddUserModal(): void {
9397
setAddUserModalisOpen(true);
@@ -96,8 +100,6 @@ export default function CreateGroupChat({
96100
const toggleAddUserModal = (): void =>
97101
setAddUserModalisOpen(!addUserModalisOpen);
98102

99-
const { orgId: currentOrg } = useParams();
100-
101103
function reset(): void {
102104
setTitle('');
103105
setUserIds([]);
@@ -139,21 +141,13 @@ export default function CreateGroupChat({
139141
const handleUserModalSearchChange = (e: React.FormEvent): void => {
140142
e.preventDefault();
141143
const [firstName, lastName] = userName.split(' ');
142-
143144
const newFilterData = {
144145
firstName_contains: firstName || '',
145146
lastName_contains: lastName || '',
146147
};
147-
148-
allUsersRefetch({
149-
...newFilterData,
150-
});
148+
allUsersRefetch({ ...newFilterData });
151149
};
152150

153-
const [selectedImage, setSelectedImage] = useState<string | null>(null);
154-
155-
const fileInputRef = useRef<HTMLInputElement>(null);
156-
157151
const handleImageClick = (): void => {
158152
fileInputRef?.current?.click();
159153
};
@@ -162,9 +156,13 @@ export default function CreateGroupChat({
162156
e: React.ChangeEvent<HTMLInputElement>,
163157
): Promise<void> => {
164158
const file = e.target.files?.[0];
165-
if (file) {
166-
const base64 = await convertToBase64(file);
167-
setSelectedImage(base64);
159+
if (file && currentOrg) {
160+
try {
161+
const { fileUrl } = await uploadFileToMinio(file, currentOrg);
162+
setSelectedImage(fileUrl);
163+
} catch (error) {
164+
console.error('Error uploading image to MinIO:', error);
165+
}
168166
}
169167
};
170168

@@ -184,7 +182,7 @@ export default function CreateGroupChat({
184182
type="file"
185183
accept="image/*"
186184
ref={fileInputRef}
187-
style={{ display: 'none' }} // Hide the input
185+
style={{ display: 'none' }}
188186
onChange={handleImageChange}
189187
data-testid="fileInput"
190188
/>

0 commit comments

Comments
 (0)