feat: amélioration majeure de la qualité du code et des tests - ajout de 20 nouveaux tests et fonctions utilitaires

This commit is contained in:
Yannick Le Duc
2025-08-28 20:53:53 +02:00
parent cea3b81994
commit 74189ac037
4 changed files with 243 additions and 1 deletions

View File

@@ -0,0 +1,59 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { BaseModal } from '../../components/base/BaseModal';
describe('BaseModal', () => {
const defaultProps = {
isOpen: true,
onClose: jest.fn(),
title: 'Test Modal',
children: <div>Modal Content</div>,
};
it('should render modal when open', () => {
render(<BaseModal {...defaultProps} />);
expect(screen.getByText('Test Modal')).toBeInTheDocument();
expect(screen.getByText('Modal Content')).toBeInTheDocument();
});
it('should not render modal when closed', () => {
render(<BaseModal {...defaultProps} isOpen={false} />);
expect(screen.queryByText('Test Modal')).not.toBeInTheDocument();
expect(screen.queryByText('Modal Content')).not.toBeInTheDocument();
});
it('should render with custom maxWidth and maxHeight', () => {
render(
<BaseModal
{...defaultProps}
maxWidth="sm:max-w-[800px]"
maxHeight="max-h-[80vh]"
/>
);
const modalContent = screen.getByTestId('modal-content');
expect(modalContent).toHaveClass('sm:max-w-[800px]');
expect(modalContent).toHaveClass('max-h-[80vh]');
});
it('should render with description when provided', () => {
render(
<BaseModal
{...defaultProps}
description="Test description"
/>
);
expect(screen.getByText('Test description')).toBeInTheDocument();
});
it('should render footer when provided', () => {
const footer = <button>Save</button>;
render(<BaseModal {...defaultProps} footer={footer} />);
expect(screen.getByText('Save')).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,120 @@
import {
generateSlug,
generateShortId,
formatCurrency,
formatDate,
validateEmail,
sanitizeHtml
} from '../../lib/utils';
describe('Utils Module', () => {
describe('generateSlug', () => {
it('should generate valid slug from title', () => {
const title = 'Test Campaign Title';
const slug = generateSlug(title);
expect(slug).toBe('test-campaign-title');
});
it('should handle special characters', () => {
const title = 'Campagne avec des caractères spéciaux @#$%';
const slug = generateSlug(title);
expect(slug).toBe('campagne-avec-des-caracteres-speciaux-');
});
it('should handle empty string', () => {
const slug = generateSlug('');
expect(slug).toBe('');
});
it('should handle multiple spaces', () => {
const title = 'Multiple Spaces';
const slug = generateSlug(title);
expect(slug).toBe('multiple-spaces');
});
});
describe('generateShortId', () => {
it('should generate short ID with correct length', () => {
const shortId = generateShortId();
expect(shortId).toHaveLength(8);
expect(shortId).toMatch(/^[A-Z0-9]+$/);
});
it('should generate different IDs', () => {
const id1 = generateShortId();
const id2 = generateShortId();
expect(id1).not.toBe(id2);
});
});
describe('formatCurrency', () => {
it('should format currency correctly', () => {
const result1 = formatCurrency(1000);
const result2 = formatCurrency(1234.56);
const result3 = formatCurrency(0);
expect(result1).toMatch(/1\s*000,00\s*€/);
expect(result2).toMatch(/1\s*234,56\s*€/);
expect(result3).toMatch(/0,00\s*€/);
});
it('should handle negative values', () => {
const result = formatCurrency(-1000);
expect(result).toMatch(/-1\s*000,00\s*€/);
});
});
describe('formatDate', () => {
it('should format date correctly', () => {
const date = new Date('2024-01-15T10:30:00');
const formatted = formatDate(date);
expect(formatted).toBe('15/01/2024');
});
it('should handle string date', () => {
const formatted = formatDate('2024-01-15');
expect(formatted).toBe('15/01/2024');
});
});
describe('validateEmail', () => {
it('should validate correct email addresses', () => {
expect(validateEmail('test@example.com')).toBe(true);
expect(validateEmail('user.name+tag@domain.co.uk')).toBe(true);
expect(validateEmail('123@test.org')).toBe(true);
});
it('should reject invalid email addresses', () => {
expect(validateEmail('invalid-email')).toBe(false);
expect(validateEmail('test@')).toBe(false);
expect(validateEmail('@example.com')).toBe(false);
expect(validateEmail('')).toBe(false);
});
});
describe('sanitizeHtml', () => {
it('should remove dangerous HTML tags', () => {
const input = '<script>alert("xss")</script><p>Safe content</p>';
const sanitized = sanitizeHtml(input);
expect(sanitized).toBe('<p>Safe content</p>');
});
it('should allow safe HTML tags', () => {
const input = '<p>Paragraph</p><strong>Bold</strong><em>Italic</em>';
const sanitized = sanitizeHtml(input);
expect(sanitized).toBe(input);
});
it('should handle empty string', () => {
expect(sanitizeHtml('')).toBe('');
});
});
});

View File

@@ -24,7 +24,10 @@ export function BaseModal({
}: BaseModalProps) {
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className={`${maxWidth} ${maxHeight} overflow-y-auto`}>
<DialogContent
className={`${maxWidth} ${maxHeight} overflow-y-auto`}
data-testid="modal-content"
>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
{description && <DialogDescription>{description}</DialogDescription>}

View File

@@ -43,3 +43,63 @@ export function parseFooterMessage(message: string, repositoryUrl: string): { te
return { text: processedText, links };
}
/**
* Génère un slug à partir d'un titre
*/
export function generateSlug(title: string): string {
return title
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '') // Supprime les accents
.replace(/[^a-z0-9\s-]/g, '') // Garde seulement lettres, chiffres, espaces et tirets
.replace(/\s+/g, '-') // Remplace les espaces par des tirets
.replace(/-+/g, '-') // Remplace les tirets multiples par un seul
.trim();
}
/**
* Génère un ID court aléatoire
*/
export function generateShortId(): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let result = '';
for (let i = 0; i < 8; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
/**
* Formate un montant en euros
*/
export function formatCurrency(amount: number): string {
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: 'EUR',
}).format(amount);
}
/**
* Formate une date
*/
export function formatDate(date: Date | string): string {
const dateObj = typeof date === 'string' ? new Date(date) : date;
return dateObj.toLocaleDateString('fr-FR');
}
/**
* Valide une adresse email
*/
export function validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Nettoie le HTML pour éviter les attaques XSS
*/
export function sanitizeHtml(html: string): string {
// Supprime les balises dangereuses
return html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
}