Add global navigation search, cancellation policies, and UI improvements
- Add global search in top bar for navigating to dashboard pages - Add cancellation policy settings (window hours, late fee, deposit refund) - Display booking policies on customer confirmation page - Filter API tokens by sandbox/live mode - Widen settings layout and full-width site builder - Add help documentation search with OpenAI integration - Add blocked time ranges API for calendar visualization - Update business hours settings with holiday management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
708
frontend/src/hooks/__tests__/useStaffEmail.test.ts
Normal file
708
frontend/src/hooks/__tests__/useStaffEmail.test.ts
Normal file
@@ -0,0 +1,708 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderHook, waitFor, act } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import {
|
||||
staffEmailKeys,
|
||||
useStaffEmailFolders,
|
||||
useCreateStaffEmailFolder,
|
||||
useUpdateStaffEmailFolder,
|
||||
useDeleteStaffEmailFolder,
|
||||
useStaffEmail,
|
||||
useStaffEmailThread,
|
||||
useStaffEmailLabels,
|
||||
useCreateLabel,
|
||||
useUpdateLabel,
|
||||
useDeleteLabel,
|
||||
useAddLabelToEmail,
|
||||
useRemoveLabelFromEmail,
|
||||
useCreateDraft,
|
||||
useUpdateDraft,
|
||||
useDeleteDraft,
|
||||
useSendEmail,
|
||||
useReplyToEmail,
|
||||
useForwardEmail,
|
||||
useMarkAsRead,
|
||||
useMarkAsUnread,
|
||||
useStarEmail,
|
||||
useUnstarEmail,
|
||||
useArchiveEmail,
|
||||
useTrashEmail,
|
||||
useRestoreEmail,
|
||||
usePermanentlyDeleteEmail,
|
||||
useMoveEmails,
|
||||
useBulkEmailAction,
|
||||
useContactSearch,
|
||||
useUploadAttachment,
|
||||
useDeleteAttachment,
|
||||
useSyncEmails,
|
||||
useFullSyncEmails,
|
||||
useUserEmailAddresses,
|
||||
} from '../useStaffEmail';
|
||||
import * as staffEmailApi from '../../api/staffEmail';
|
||||
|
||||
vi.mock('../../api/staffEmail');
|
||||
|
||||
const mockFolder = {
|
||||
id: 1,
|
||||
owner: 1,
|
||||
name: 'Inbox',
|
||||
folderType: 'inbox',
|
||||
emailCount: 10,
|
||||
unreadCount: 3,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
const mockEmail = {
|
||||
id: 1,
|
||||
folder: 1,
|
||||
fromAddress: 'sender@example.com',
|
||||
fromName: 'Sender Name',
|
||||
toAddresses: [{ email: 'recipient@example.com', name: 'Recipient' }],
|
||||
subject: 'Test Email',
|
||||
snippet: 'This is a test...',
|
||||
status: 'received',
|
||||
isRead: false,
|
||||
isStarred: false,
|
||||
isImportant: false,
|
||||
hasAttachments: false,
|
||||
attachmentCount: 0,
|
||||
threadId: 'thread-1',
|
||||
emailDate: '2024-01-01T12:00:00Z',
|
||||
createdAt: '2024-01-01T12:00:00Z',
|
||||
labels: [],
|
||||
owner: 1,
|
||||
emailAddress: 1,
|
||||
messageId: 'msg-1',
|
||||
inReplyTo: null,
|
||||
references: '',
|
||||
ccAddresses: [],
|
||||
bccAddresses: [],
|
||||
bodyText: 'This is a test email body.',
|
||||
bodyHtml: '<p>This is a test email body.</p>',
|
||||
isAnswered: false,
|
||||
isPermanentlyDeleted: false,
|
||||
deletedAt: null,
|
||||
attachments: [],
|
||||
updatedAt: '2024-01-01T12:00:00Z',
|
||||
};
|
||||
|
||||
const mockLabel = {
|
||||
id: 1,
|
||||
owner: 1,
|
||||
name: 'Important',
|
||||
color: '#ef4444',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
const mockContact = {
|
||||
id: 1,
|
||||
owner: 1,
|
||||
email: 'contact@example.com',
|
||||
name: 'Contact Name',
|
||||
useCount: 5,
|
||||
lastUsedAt: '2024-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
const mockAttachment = {
|
||||
id: 1,
|
||||
filename: 'document.pdf',
|
||||
contentType: 'application/pdf',
|
||||
size: 1024,
|
||||
url: 'https://example.com/document.pdf',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
const mockUserEmailAddress = {
|
||||
id: 1,
|
||||
email_address: 'user@example.com',
|
||||
display_name: 'User',
|
||||
color: '#3b82f6',
|
||||
is_default: true,
|
||||
last_check_at: '2024-01-01T00:00:00Z',
|
||||
emails_processed_count: 100,
|
||||
};
|
||||
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
return function Wrapper({ children }: { children: React.ReactNode }) {
|
||||
return React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
};
|
||||
};
|
||||
|
||||
describe('useStaffEmail hooks', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('staffEmailKeys', () => {
|
||||
it('generates correct query keys', () => {
|
||||
expect(staffEmailKeys.all).toEqual(['staffEmail']);
|
||||
expect(staffEmailKeys.folders()).toEqual(['staffEmail', 'folders']);
|
||||
expect(staffEmailKeys.emails()).toEqual(['staffEmail', 'emails']);
|
||||
expect(staffEmailKeys.emailDetail(1)).toEqual(['staffEmail', 'emails', 'detail', 1]);
|
||||
expect(staffEmailKeys.emailThread('thread-1')).toEqual(['staffEmail', 'emails', 'thread', 'thread-1']);
|
||||
expect(staffEmailKeys.labels()).toEqual(['staffEmail', 'labels']);
|
||||
expect(staffEmailKeys.contacts('test')).toEqual(['staffEmail', 'contacts', 'test']);
|
||||
expect(staffEmailKeys.userEmailAddresses()).toEqual(['staffEmail', 'userEmailAddresses']);
|
||||
});
|
||||
|
||||
it('generates email list key with filters', () => {
|
||||
const filters = { folderId: 1, emailAddressId: 2, search: 'test' };
|
||||
const key = staffEmailKeys.emailList(filters);
|
||||
expect(key).toContain('staffEmail');
|
||||
expect(key).toContain('emails');
|
||||
expect(key).toContain('list');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useStaffEmailFolders', () => {
|
||||
it('fetches email folders', async () => {
|
||||
vi.mocked(staffEmailApi.getFolders).mockResolvedValueOnce([mockFolder]);
|
||||
|
||||
const { result } = renderHook(() => useStaffEmailFolders(), { wrapper: createWrapper() });
|
||||
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
|
||||
expect(staffEmailApi.getFolders).toHaveBeenCalled();
|
||||
expect(result.current.data).toEqual([mockFolder]);
|
||||
});
|
||||
|
||||
it('handles error when fetching folders', async () => {
|
||||
vi.mocked(staffEmailApi.getFolders).mockRejectedValueOnce(new Error('Failed to fetch folders'));
|
||||
|
||||
const { result } = renderHook(() => useStaffEmailFolders(), { wrapper: createWrapper() });
|
||||
|
||||
await waitFor(() => expect(result.current.isError).toBe(true));
|
||||
});
|
||||
});
|
||||
|
||||
describe('useCreateStaffEmailFolder', () => {
|
||||
it('creates a new folder', async () => {
|
||||
const newFolder = { ...mockFolder, id: 2, name: 'Custom Folder' };
|
||||
vi.mocked(staffEmailApi.createFolder).mockResolvedValueOnce(newFolder);
|
||||
|
||||
const { result } = renderHook(() => useCreateStaffEmailFolder(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync('Custom Folder');
|
||||
});
|
||||
|
||||
expect(staffEmailApi.createFolder).toHaveBeenCalledWith('Custom Folder');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useUpdateStaffEmailFolder', () => {
|
||||
it('updates a folder name', async () => {
|
||||
const updatedFolder = { ...mockFolder, name: 'Updated Name' };
|
||||
vi.mocked(staffEmailApi.updateFolder).mockResolvedValueOnce(updatedFolder);
|
||||
|
||||
const { result } = renderHook(() => useUpdateStaffEmailFolder(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ id: 1, name: 'Updated Name' });
|
||||
});
|
||||
|
||||
expect(staffEmailApi.updateFolder).toHaveBeenCalledWith(1, 'Updated Name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useDeleteStaffEmailFolder', () => {
|
||||
it('deletes a folder', async () => {
|
||||
vi.mocked(staffEmailApi.deleteFolder).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useDeleteStaffEmailFolder(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.deleteFolder).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useStaffEmail', () => {
|
||||
it('fetches a single email by id', async () => {
|
||||
vi.mocked(staffEmailApi.getEmail).mockResolvedValueOnce(mockEmail);
|
||||
|
||||
const { result } = renderHook(() => useStaffEmail(1), { wrapper: createWrapper() });
|
||||
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
|
||||
expect(staffEmailApi.getEmail).toHaveBeenCalledWith(1);
|
||||
expect(result.current.data).toEqual(mockEmail);
|
||||
});
|
||||
|
||||
it('does not fetch when id is undefined', () => {
|
||||
const { result } = renderHook(() => useStaffEmail(undefined), { wrapper: createWrapper() });
|
||||
|
||||
expect(result.current.fetchStatus).toBe('idle');
|
||||
expect(staffEmailApi.getEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useStaffEmailThread', () => {
|
||||
it('fetches email thread', async () => {
|
||||
vi.mocked(staffEmailApi.getEmailThread).mockResolvedValueOnce([mockEmail]);
|
||||
|
||||
const { result } = renderHook(() => useStaffEmailThread('thread-1'), { wrapper: createWrapper() });
|
||||
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
|
||||
expect(staffEmailApi.getEmailThread).toHaveBeenCalledWith('thread-1');
|
||||
expect(result.current.data).toEqual([mockEmail]);
|
||||
});
|
||||
|
||||
it('does not fetch when threadId is undefined', () => {
|
||||
const { result } = renderHook(() => useStaffEmailThread(undefined), { wrapper: createWrapper() });
|
||||
|
||||
expect(result.current.fetchStatus).toBe('idle');
|
||||
expect(staffEmailApi.getEmailThread).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useStaffEmailLabels', () => {
|
||||
it('fetches email labels', async () => {
|
||||
vi.mocked(staffEmailApi.getLabels).mockResolvedValueOnce([mockLabel]);
|
||||
|
||||
const { result } = renderHook(() => useStaffEmailLabels(), { wrapper: createWrapper() });
|
||||
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
|
||||
expect(staffEmailApi.getLabels).toHaveBeenCalled();
|
||||
expect(result.current.data).toEqual([mockLabel]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useCreateLabel', () => {
|
||||
it('creates a new label', async () => {
|
||||
const newLabel = { ...mockLabel, id: 2, name: 'Work', color: '#10b981' };
|
||||
vi.mocked(staffEmailApi.createLabel).mockResolvedValueOnce(newLabel);
|
||||
|
||||
const { result } = renderHook(() => useCreateLabel(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ name: 'Work', color: '#10b981' });
|
||||
});
|
||||
|
||||
expect(staffEmailApi.createLabel).toHaveBeenCalledWith('Work', '#10b981');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useUpdateLabel', () => {
|
||||
it('updates a label', async () => {
|
||||
const updatedLabel = { ...mockLabel, name: 'Updated Label' };
|
||||
vi.mocked(staffEmailApi.updateLabel).mockResolvedValueOnce(updatedLabel);
|
||||
|
||||
const { result } = renderHook(() => useUpdateLabel(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ id: 1, data: { name: 'Updated Label' } });
|
||||
});
|
||||
|
||||
expect(staffEmailApi.updateLabel).toHaveBeenCalledWith(1, { name: 'Updated Label' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('useDeleteLabel', () => {
|
||||
it('deletes a label', async () => {
|
||||
vi.mocked(staffEmailApi.deleteLabel).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useDeleteLabel(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.deleteLabel).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useAddLabelToEmail', () => {
|
||||
it('adds label to email', async () => {
|
||||
vi.mocked(staffEmailApi.addLabelToEmail).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useAddLabelToEmail(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ emailId: 1, labelId: 2 });
|
||||
});
|
||||
|
||||
expect(staffEmailApi.addLabelToEmail).toHaveBeenCalledWith(1, 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useRemoveLabelFromEmail', () => {
|
||||
it('removes label from email', async () => {
|
||||
vi.mocked(staffEmailApi.removeLabelFromEmail).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useRemoveLabelFromEmail(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ emailId: 1, labelId: 2 });
|
||||
});
|
||||
|
||||
expect(staffEmailApi.removeLabelFromEmail).toHaveBeenCalledWith(1, 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useCreateDraft', () => {
|
||||
it('creates a draft email', async () => {
|
||||
vi.mocked(staffEmailApi.createDraft).mockResolvedValueOnce(mockEmail);
|
||||
|
||||
const draftData = {
|
||||
emailAddressId: 1,
|
||||
toAddresses: ['recipient@example.com'],
|
||||
subject: 'Test Draft',
|
||||
bodyText: 'Draft body',
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useCreateDraft(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(draftData);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.createDraft).toHaveBeenCalledWith(draftData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useUpdateDraft', () => {
|
||||
it('updates a draft email', async () => {
|
||||
vi.mocked(staffEmailApi.updateDraft).mockResolvedValueOnce(mockEmail);
|
||||
|
||||
const { result } = renderHook(() => useUpdateDraft(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ id: 1, data: { subject: 'Updated Subject' } });
|
||||
});
|
||||
|
||||
expect(staffEmailApi.updateDraft).toHaveBeenCalledWith(1, { subject: 'Updated Subject' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('useDeleteDraft', () => {
|
||||
it('deletes a draft', async () => {
|
||||
vi.mocked(staffEmailApi.deleteDraft).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useDeleteDraft(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.deleteDraft).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSendEmail', () => {
|
||||
it('sends an email', async () => {
|
||||
vi.mocked(staffEmailApi.sendEmail).mockResolvedValueOnce(mockEmail);
|
||||
|
||||
const { result } = renderHook(() => useSendEmail(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.sendEmail).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useReplyToEmail', () => {
|
||||
it('replies to an email', async () => {
|
||||
vi.mocked(staffEmailApi.replyToEmail).mockResolvedValueOnce(mockEmail);
|
||||
|
||||
const replyData = {
|
||||
bodyText: 'Reply body',
|
||||
bodyHtml: '<p>Reply body</p>',
|
||||
replyAll: false,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useReplyToEmail(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ id: 1, data: replyData });
|
||||
});
|
||||
|
||||
expect(staffEmailApi.replyToEmail).toHaveBeenCalledWith(1, replyData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useForwardEmail', () => {
|
||||
it('forwards an email', async () => {
|
||||
vi.mocked(staffEmailApi.forwardEmail).mockResolvedValueOnce(mockEmail);
|
||||
|
||||
const forwardData = {
|
||||
toAddresses: ['forward@example.com'],
|
||||
bodyText: 'Forwarding this email',
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useForwardEmail(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ id: 1, data: forwardData });
|
||||
});
|
||||
|
||||
expect(staffEmailApi.forwardEmail).toHaveBeenCalledWith(1, forwardData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useMarkAsRead', () => {
|
||||
it('marks email as read', async () => {
|
||||
vi.mocked(staffEmailApi.markAsRead).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useMarkAsRead(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.markAsRead).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useMarkAsUnread', () => {
|
||||
it('marks email as unread', async () => {
|
||||
vi.mocked(staffEmailApi.markAsUnread).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useMarkAsUnread(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.markAsUnread).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useStarEmail', () => {
|
||||
it('stars an email', async () => {
|
||||
vi.mocked(staffEmailApi.starEmail).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useStarEmail(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.starEmail).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useUnstarEmail', () => {
|
||||
it('unstars an email', async () => {
|
||||
vi.mocked(staffEmailApi.unstarEmail).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useUnstarEmail(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.unstarEmail).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useArchiveEmail', () => {
|
||||
it('archives an email', async () => {
|
||||
vi.mocked(staffEmailApi.archiveEmail).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useArchiveEmail(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.archiveEmail).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useTrashEmail', () => {
|
||||
it('moves email to trash', async () => {
|
||||
vi.mocked(staffEmailApi.trashEmail).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useTrashEmail(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.trashEmail).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useRestoreEmail', () => {
|
||||
it('restores an email from trash', async () => {
|
||||
vi.mocked(staffEmailApi.restoreEmail).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useRestoreEmail(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.restoreEmail).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('usePermanentlyDeleteEmail', () => {
|
||||
it('permanently deletes an email', async () => {
|
||||
vi.mocked(staffEmailApi.permanentlyDeleteEmail).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => usePermanentlyDeleteEmail(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.permanentlyDeleteEmail).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useMoveEmails', () => {
|
||||
it('moves emails to a folder', async () => {
|
||||
vi.mocked(staffEmailApi.moveEmails).mockResolvedValueOnce(undefined);
|
||||
|
||||
const moveData = { emailIds: [1, 2, 3], folderId: 2 };
|
||||
|
||||
const { result } = renderHook(() => useMoveEmails(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(moveData);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.moveEmails).toHaveBeenCalledWith(moveData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useBulkEmailAction', () => {
|
||||
it('performs bulk action on emails', async () => {
|
||||
vi.mocked(staffEmailApi.bulkAction).mockResolvedValueOnce(undefined);
|
||||
|
||||
const bulkData = { emailIds: [1, 2, 3], action: 'mark_read' as const };
|
||||
|
||||
const { result } = renderHook(() => useBulkEmailAction(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(bulkData);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.bulkAction).toHaveBeenCalledWith(bulkData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useContactSearch', () => {
|
||||
it('searches contacts with query', async () => {
|
||||
vi.mocked(staffEmailApi.searchContacts).mockResolvedValueOnce([mockContact]);
|
||||
|
||||
const { result } = renderHook(() => useContactSearch('test'), { wrapper: createWrapper() });
|
||||
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
|
||||
expect(staffEmailApi.searchContacts).toHaveBeenCalledWith('test');
|
||||
expect(result.current.data).toEqual([mockContact]);
|
||||
});
|
||||
|
||||
it('does not search with query less than 2 characters', () => {
|
||||
const { result } = renderHook(() => useContactSearch('t'), { wrapper: createWrapper() });
|
||||
|
||||
expect(result.current.fetchStatus).toBe('idle');
|
||||
expect(staffEmailApi.searchContacts).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useUploadAttachment', () => {
|
||||
it('uploads an attachment', async () => {
|
||||
vi.mocked(staffEmailApi.uploadAttachment).mockResolvedValueOnce(mockAttachment);
|
||||
|
||||
const file = new File(['test content'], 'test.pdf', { type: 'application/pdf' });
|
||||
|
||||
const { result } = renderHook(() => useUploadAttachment(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ file, emailId: 1 });
|
||||
});
|
||||
|
||||
expect(staffEmailApi.uploadAttachment).toHaveBeenCalledWith(file, 1);
|
||||
});
|
||||
|
||||
it('uploads attachment without email id', async () => {
|
||||
vi.mocked(staffEmailApi.uploadAttachment).mockResolvedValueOnce(mockAttachment);
|
||||
|
||||
const file = new File(['test content'], 'test.pdf', { type: 'application/pdf' });
|
||||
|
||||
const { result } = renderHook(() => useUploadAttachment(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ file });
|
||||
});
|
||||
|
||||
expect(staffEmailApi.uploadAttachment).toHaveBeenCalledWith(file, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useDeleteAttachment', () => {
|
||||
it('deletes an attachment', async () => {
|
||||
vi.mocked(staffEmailApi.deleteAttachment).mockResolvedValueOnce(undefined);
|
||||
|
||||
const { result } = renderHook(() => useDeleteAttachment(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(1);
|
||||
});
|
||||
|
||||
expect(staffEmailApi.deleteAttachment).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSyncEmails', () => {
|
||||
it('syncs emails', async () => {
|
||||
vi.mocked(staffEmailApi.syncEmails).mockResolvedValueOnce({ success: true, message: 'Synced' });
|
||||
|
||||
const { result } = renderHook(() => useSyncEmails(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync();
|
||||
});
|
||||
|
||||
expect(staffEmailApi.syncEmails).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useFullSyncEmails', () => {
|
||||
it('performs full email sync', async () => {
|
||||
vi.mocked(staffEmailApi.fullSyncEmails).mockResolvedValueOnce({
|
||||
status: 'started',
|
||||
tasks: [{ email_address: 'user@example.com', task_id: 'task-1' }],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useFullSyncEmails(), { wrapper: createWrapper() });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync();
|
||||
});
|
||||
|
||||
expect(staffEmailApi.fullSyncEmails).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useUserEmailAddresses', () => {
|
||||
it('fetches user email addresses', async () => {
|
||||
vi.mocked(staffEmailApi.getUserEmailAddresses).mockResolvedValueOnce([mockUserEmailAddress]);
|
||||
|
||||
const { result } = renderHook(() => useUserEmailAddresses(), { wrapper: createWrapper() });
|
||||
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
|
||||
expect(staffEmailApi.getUserEmailAddresses).toHaveBeenCalled();
|
||||
expect(result.current.data).toEqual([mockUserEmailAddress]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user