Files
SRCmail/components/contacts/__tests__/contact-detail.test.tsx
T
Matthieu MALVACHEandMatthieu MALVACHE fb414bf2c9 feat: add contacts phase 2, advanced search, vacation responder, Docker & TOTP 2FA
- Contact groups/lists, vCard import/export (RFC 6350), bulk operations
- Advanced search with JMAP filter panel, search chips, cross-mailbox queries
- Vacation responder with JMAP VacationResponse, settings tab, sidebar indicator
- TOTP two-factor authentication support
- Docker multi-stage build with standalone output and docker-compose
- CSP Report-Only headers and security headers via proxy middleware
- Virtual scrolling for large email lists
- Structured server-side logger (text/JSON, configurable level)
- 450+ tests (contacts, vCard, threads, headers, identity, components)
- Playwright E2E framework setup
- Updated README and ROADMAP with all new features
2026-02-16 18:51:30 +01:00

62 lines
2.5 KiB
TypeScript

import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { ContactDetail } from '../contact-detail';
import type { ContactCard } from '@/lib/jmap/types';
const contact: ContactCard = {
id: '1',
addressBookIds: {},
name: { components: [{ kind: 'given', value: 'Alice' }, { kind: 'surname', value: 'Smith' }], isOrdered: true },
emails: { e0: { address: 'alice@example.com' } },
phones: { p0: { number: '+33612345678' } },
organizations: { o0: { name: 'Acme Corp' } },
addresses: { a0: { street: '123 Main St', locality: 'Paris', country: 'France' } },
notes: { n0: { note: 'VIP customer' } },
};
describe('ContactDetail', () => {
it('shows empty state when contact is null', () => {
render(<ContactDetail contact={null} onEdit={vi.fn()} onDelete={vi.fn()} />);
expect(screen.getByText('detail.no_contact_selected')).toBeInTheDocument();
});
it('displays the contact name', () => {
render(<ContactDetail contact={contact} onEdit={vi.fn()} onDelete={vi.fn()} />);
expect(screen.getByText('Alice Smith')).toBeInTheDocument();
});
it('displays email addresses as mailto links', () => {
render(<ContactDetail contact={contact} onEdit={vi.fn()} onDelete={vi.fn()} />);
const link = screen.getByText('alice@example.com');
expect(link.closest('a')).toHaveAttribute('href', 'mailto:alice@example.com');
});
it('displays phone numbers', () => {
render(<ContactDetail contact={contact} onEdit={vi.fn()} onDelete={vi.fn()} />);
expect(screen.getByText('+33612345678')).toBeInTheDocument();
});
it('displays organization name', () => {
render(<ContactDetail contact={contact} onEdit={vi.fn()} onDelete={vi.fn()} />);
const matches = screen.getAllByText('Acme Corp');
expect(matches.length).toBeGreaterThanOrEqual(1);
});
it('calls onEdit when edit button is clicked', () => {
const onEdit = vi.fn();
render(<ContactDetail contact={contact} onEdit={onEdit} onDelete={vi.fn()} />);
fireEvent.click(screen.getByText('form.edit_title'));
expect(onEdit).toHaveBeenCalledOnce();
});
it('calls onDelete when delete button is clicked', () => {
const onDelete = vi.fn();
render(<ContactDetail contact={contact} onEdit={vi.fn()} onDelete={onDelete} />);
const trashButtons = screen.getAllByRole('button').filter(
btn => btn.querySelector('svg') && btn.textContent?.trim() === ''
);
fireEvent.click(trashButtons[trashButtons.length - 1]);
expect(onDelete).toHaveBeenCalledOnce();
});
});