Phase 6.1: ComposeModal on RHF + Zod
- Migrated ComposeModal from useState to React Hook Form + Zod - Zod schema: to (required, email list), cc/bcc (optional, email list), subject (required), body - Object-level superRefine for comma-separated email validation - Error display under each field via Input error prop - Preserved all existing functionality: attachments, signatures, templates, drafts - Added 4 validation tests (empty to, invalid email, empty subject, valid submit)
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
vi.mock('@/api/mail', () => ({
|
||||
decodeMimeHeader: (s: string) => s,
|
||||
fetchTemplates: vi.fn().mockResolvedValue([]),
|
||||
substituteTemplate: vi.fn().mockResolvedValue({ subject: '', body: '' }),
|
||||
uploadAttachment: vi.fn(),
|
||||
replaceSignatureVariables: (s: string) => s,
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/Toast', () => ({
|
||||
useToast: () => ({ success: vi.fn(), error: vi.fn(), info: vi.fn(), warning: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('@/store/authStore', () => ({
|
||||
useAuthStore: () => ({ user: null, currentTenant: null }),
|
||||
}));
|
||||
|
||||
import { ComposeModal } from '@/components/mail/ComposeModal';
|
||||
|
||||
function renderModal(props: Partial<React.ComponentProps<typeof ComposeModal>> = {}) {
|
||||
const defaultProps = {
|
||||
open: true,
|
||||
mode: 'new' as const,
|
||||
accountId: 'acc1',
|
||||
replyToMail: null,
|
||||
forwardMail: null,
|
||||
signatures: [],
|
||||
onSend: vi.fn().mockResolvedValue(undefined),
|
||||
onClose: vi.fn(),
|
||||
};
|
||||
return render(<MemoryRouter><ComposeModal {...defaultProps} {...props} /></MemoryRouter>);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('ComposeModal validation (RHF + Zod)', () => {
|
||||
it('shows validation error when "to" is empty on submit', async () => {
|
||||
renderModal();
|
||||
fireEvent.click(screen.getByTestId('compose-send'));
|
||||
await waitFor(() => {
|
||||
const toInput = screen.getByTestId('compose-to');
|
||||
const errorEl = toInput.parentElement?.querySelector('.text-danger-500, .text-danger-600, .text-red');
|
||||
expect(errorEl).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows validation error for invalid email in "to" field', async () => {
|
||||
renderModal();
|
||||
fireEvent.change(screen.getByTestId('compose-to'), { target: { value: 'not-an-email' } });
|
||||
fireEvent.click(screen.getByTestId('compose-send'));
|
||||
await waitFor(() => {
|
||||
const toInput = screen.getByTestId('compose-to');
|
||||
const errorEl = toInput.parentElement?.querySelector('.text-danger-500, .text-danger-600, .text-red');
|
||||
expect(errorEl).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows validation error when subject is empty', async () => {
|
||||
renderModal();
|
||||
fireEvent.change(screen.getByTestId('compose-to'), { target: { value: 'valid@example.com' } });
|
||||
fireEvent.click(screen.getByTestId('compose-send'));
|
||||
await waitFor(() => {
|
||||
const subjectInput = screen.getByTestId('compose-subject');
|
||||
const errorEl = subjectInput.parentElement?.querySelector('.text-danger-500, .text-danger-600, .text-red');
|
||||
expect(errorEl).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onSend when form is valid', async () => {
|
||||
const onSend = vi.fn().mockResolvedValue(undefined);
|
||||
renderModal({ onSend });
|
||||
fireEvent.change(screen.getByTestId('compose-to'), { target: { value: 'valid@example.com' } });
|
||||
fireEvent.change(screen.getByTestId('compose-subject'), { target: { value: 'Test Subject' } });
|
||||
fireEvent.click(screen.getByTestId('compose-send'));
|
||||
await waitFor(() => {
|
||||
expect(onSend).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user