Phase 6.6: Mail-Settings-Forms on RHF + Zod

- MailSettings account form: RHF+Zod (email valid, password required, imap/smtp host required, ports numeric)
- SignatureManager: RHF+Zod (name required, body_html, is_default)
- RuleEditor: RHF+Zod (name required, priority numeric)
- LabelManager: RHF+Zod (name required, color optional)
- VacationResponder: RHF+Zod (enabled, dates with end>start validation, subject, body)
- Error display under each field
- Preserved all existing functionality: CRUD, templates, variables
- Added 2 validation tests for MailSettings account form
This commit is contained in:
Agent Zero
2026-07-24 00:38:40 +02:00
parent 3e8038b75e
commit 761f8d88dc
6 changed files with 277 additions and 138 deletions
@@ -0,0 +1,77 @@
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', () => ({
fetchAccounts: vi.fn().mockResolvedValue([]),
createAccount: vi.fn().mockResolvedValue({ id: 'a1', email: 'test@example.com', display_name: '', imap_host: '', imap_port: 993, smtp_host: '', smtp_port: 587, is_shared: false, is_active: true }),
deleteAccount: vi.fn(),
testConnection: vi.fn(),
triggerSync: vi.fn(),
updateAccount: vi.fn(),
fetchFolders: vi.fn().mockResolvedValue([]),
}));
vi.mock('@/components/ui/Toast', () => ({
useToast: () => ({ success: vi.fn(), error: vi.fn(), info: vi.fn(), warning: vi.fn() }),
}));
vi.mock('@/components/mail/SignatureManager', () => ({ SignatureManager: () => null }));
vi.mock('@/components/mail/RuleEditor', () => ({ RuleEditor: () => null }));
vi.mock('@/components/mail/LabelManager', () => ({ LabelManager: () => null }));
vi.mock('@/components/mail/VacationResponder', () => ({ VacationResponder: () => null }));
vi.mock('@/components/mail/PgpSettings', () => ({ PgpSettings: () => null }));
import { MailSettingsPage } from '@/pages/MailSettings';
import { createAccount } from '@/api/mail';
beforeEach(() => {
vi.clearAllMocks();
});
describe('MailSettings account form validation (RHF + Zod)', () => {
it('shows validation error when email is empty', async () => {
render(<MemoryRouter><MailSettingsPage /></MemoryRouter>);
await waitFor(() => {
expect(screen.getByTestId('mail-settings-page')).toBeInTheDocument();
}, { timeout: 3000 });
// Click add account button
const addBtn = screen.getByTestId('add-account-btn');
fireEvent.click(addBtn);
await waitFor(() => {
expect(screen.getByTestId('add-account-form')).toBeInTheDocument();
});
// Submit empty form
const form = screen.getByTestId('add-account-form').querySelector('form');
expect(form).toBeTruthy();
fireEvent.submit(form!);
await waitFor(() => {
const errors = screen.getAllByText(/erforderlich|required/i);
expect(errors.length).toBeGreaterThan(0);
});
});
it('calls createAccount when form is valid', async () => {
render(<MemoryRouter><MailSettingsPage /></MemoryRouter>);
await waitFor(() => {
expect(screen.getByTestId('mail-settings-page')).toBeInTheDocument();
}, { timeout: 3000 });
const addBtn = screen.getByTestId('add-account-btn');
fireEvent.click(addBtn);
await waitFor(() => {
expect(screen.getByTestId('add-account-form')).toBeInTheDocument();
});
const form = screen.getByTestId('add-account-form').querySelector('form');
const inputs = form!.querySelectorAll('input');
// Fill email, imap_host, smtp_host, password
fireEvent.change(inputs[0], { target: { value: 'test@example.com' } }); // email
fireEvent.change(inputs[3], { target: { value: 'imap.example.com' } }); // imap_host
fireEvent.change(inputs[5], { target: { value: 'smtp.example.com' } }); // smtp_host
fireEvent.change(inputs[7], { target: { value: 'password123' } }); // password
fireEvent.submit(form!);
await waitFor(() => {
expect(createAccount).toHaveBeenCalledWith(expect.objectContaining({ email: 'test@example.com', imap_host: 'imap.example.com', smtp_host: 'smtp.example.com' }));
}, { timeout: 3000 });
});
});