amélioration tests
This commit is contained in:
144
src/__tests__/components/Footer.test.tsx
Normal file
144
src/__tests__/components/Footer.test.tsx
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import Footer from '../../components/Footer';
|
||||||
|
|
||||||
|
// Mock des dépendances
|
||||||
|
jest.mock('@/lib/project.config', () => ({
|
||||||
|
PROJECT_CONFIG: {
|
||||||
|
repository: {
|
||||||
|
url: 'https://github.com/example/repo'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@/lib/services', () => ({
|
||||||
|
settingsService: {
|
||||||
|
getStringValue: jest.fn().mockResolvedValue('Développé avec ❤️ pour faciliter la démocratie participative - [Logiciel libre et open source](GITURL) et transparent pour tous')
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Footer', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock des variables d'environnement
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_URL = 'https://test.supabase.co';
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = 'test-key';
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render footer with basic content', async () => {
|
||||||
|
render(<Footer />);
|
||||||
|
|
||||||
|
// Attendre que le contenu se charge
|
||||||
|
await screen.findByText(/Développé avec ❤️/);
|
||||||
|
expect(screen.getByText(/Développé avec ❤️/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render footer with home variant', async () => {
|
||||||
|
render(<Footer variant="home" />);
|
||||||
|
|
||||||
|
await screen.findByText(/Développé avec ❤️/);
|
||||||
|
const footer = screen.getByText(/Développé avec ❤️/).closest('div');
|
||||||
|
expect(footer).toHaveClass('text-center', 'mt-16', 'pb-8');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render footer with public variant (default)', async () => {
|
||||||
|
render(<Footer variant="public" />);
|
||||||
|
|
||||||
|
await screen.findByText(/Développé avec ❤️/);
|
||||||
|
const footer = screen.getByText(/Développé avec ❤️/).closest('div');
|
||||||
|
expect(footer).toHaveClass('text-center', 'mt-16', 'pb-20');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply custom className', async () => {
|
||||||
|
render(<Footer className="custom-class" />);
|
||||||
|
|
||||||
|
await screen.findByText(/Développé avec ❤️/);
|
||||||
|
const footer = screen.getByText(/Développé avec ❤️/).closest('div');
|
||||||
|
expect(footer).toHaveClass('custom-class');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle Supabase not configured', async () => {
|
||||||
|
// Simuler Supabase non configuré
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_URL = 'https://placeholder.supabase.co';
|
||||||
|
|
||||||
|
render(<Footer />);
|
||||||
|
|
||||||
|
await screen.findByText(/Développé avec ❤️/);
|
||||||
|
expect(screen.getByText(/Développé avec ❤️/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle Supabase error gracefully', async () => {
|
||||||
|
// Simuler une erreur Supabase
|
||||||
|
const { settingsService } = require('@/lib/services');
|
||||||
|
settingsService.getStringValue.mockRejectedValueOnce(new Error('Supabase error'));
|
||||||
|
|
||||||
|
render(<Footer />);
|
||||||
|
|
||||||
|
await screen.findByText(/Développé avec ❤️/);
|
||||||
|
expect(screen.getByText(/Développé avec ❤️/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render links when footer message contains markdown links', async () => {
|
||||||
|
const { settingsService } = require('@/lib/services');
|
||||||
|
settingsService.getStringValue.mockResolvedValueOnce('Check our [repository](GITURL) for more info');
|
||||||
|
|
||||||
|
render(<Footer />);
|
||||||
|
|
||||||
|
await screen.findByText(/Check our/);
|
||||||
|
const link = screen.getByRole('link', { name: /repository/i });
|
||||||
|
expect(link).toHaveAttribute('href', 'https://github.com/example/repo');
|
||||||
|
expect(link).toHaveAttribute('target', '_blank');
|
||||||
|
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle multiple links in footer message', async () => {
|
||||||
|
const { settingsService } = require('@/lib/services');
|
||||||
|
settingsService.getStringValue.mockResolvedValueOnce('Check our [docs](GITURL) and [code](GITURL)');
|
||||||
|
|
||||||
|
render(<Footer />);
|
||||||
|
|
||||||
|
await screen.findByText(/Check our/);
|
||||||
|
const links = screen.getAllByRole('link');
|
||||||
|
expect(links).toHaveLength(2);
|
||||||
|
links.forEach(link => {
|
||||||
|
expect(link).toHaveAttribute('href', 'https://github.com/example/repo');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle footer message without links', async () => {
|
||||||
|
const { settingsService } = require('@/lib/services');
|
||||||
|
settingsService.getStringValue.mockResolvedValueOnce('Simple footer message without links');
|
||||||
|
|
||||||
|
render(<Footer />);
|
||||||
|
|
||||||
|
await screen.findByText(/Simple footer message/);
|
||||||
|
expect(screen.getByText(/Simple footer message/)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle special characters in footer message', async () => {
|
||||||
|
const { settingsService } = require('@/lib/services');
|
||||||
|
settingsService.getStringValue.mockResolvedValueOnce('Footer with special chars: @#$%^&*()');
|
||||||
|
|
||||||
|
render(<Footer />);
|
||||||
|
|
||||||
|
await screen.findByText(/Footer with special chars/);
|
||||||
|
expect(screen.getByText(/Footer with special chars/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle HTML in footer message safely', async () => {
|
||||||
|
const { settingsService } = require('@/lib/services');
|
||||||
|
settingsService.getStringValue.mockResolvedValueOnce('Footer with <script>alert("xss")</script> content');
|
||||||
|
|
||||||
|
render(<Footer />);
|
||||||
|
|
||||||
|
await screen.findByText(/Footer with/);
|
||||||
|
expect(screen.getByText(/Footer with/)).toBeInTheDocument();
|
||||||
|
// HTML should not be interpreted
|
||||||
|
expect(screen.queryByText('xss')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
103
src/__tests__/components/Navigation.test.tsx
Normal file
103
src/__tests__/components/Navigation.test.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import Navigation from '../../components/Navigation';
|
||||||
|
|
||||||
|
describe('Navigation', () => {
|
||||||
|
it('should render navigation with basic content', () => {
|
||||||
|
render(<Navigation />);
|
||||||
|
|
||||||
|
expect(screen.getByText(/Mes Budgets Participatifs - Admin/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain navigation links', () => {
|
||||||
|
render(<Navigation />);
|
||||||
|
|
||||||
|
const links = screen.getAllByRole('link');
|
||||||
|
expect(links.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have proper link structure', () => {
|
||||||
|
render(<Navigation />);
|
||||||
|
|
||||||
|
const links = screen.getAllByRole('link');
|
||||||
|
links.forEach(link => {
|
||||||
|
expect(link).toHaveAttribute('href');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show back button when showBackButton is true', () => {
|
||||||
|
render(<Navigation showBackButton={true} />);
|
||||||
|
|
||||||
|
expect(screen.getByText(/Retour/)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: /Retour/ })).toHaveAttribute('href', '/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show back button by default', () => {
|
||||||
|
render(<Navigation />);
|
||||||
|
|
||||||
|
expect(screen.queryByText(/Retour/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use custom back URL when provided', () => {
|
||||||
|
render(<Navigation showBackButton={true} backUrl="/custom-back" />);
|
||||||
|
|
||||||
|
expect(screen.getByRole('link', { name: /Retour/ })).toHaveAttribute('href', '/custom-back');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain settings link', () => {
|
||||||
|
render(<Navigation />);
|
||||||
|
|
||||||
|
const settingsLink = screen.getByRole('link', { name: /Paramètres/ });
|
||||||
|
expect(settingsLink).toHaveAttribute('href', '/admin/settings');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain signout link', () => {
|
||||||
|
render(<Navigation />);
|
||||||
|
|
||||||
|
const signoutLink = screen.getByRole('link', { name: /Déconnexion/ });
|
||||||
|
expect(signoutLink).toHaveAttribute('href', '/api/auth/signout');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have proper link structure', () => {
|
||||||
|
render(<Navigation />);
|
||||||
|
|
||||||
|
const links = screen.getAllByRole('link');
|
||||||
|
expect(links.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have proper card structure', () => {
|
||||||
|
render(<Navigation />);
|
||||||
|
|
||||||
|
const card = screen.getByText(/Mes Budgets Participatifs - Admin/).closest('[class*="card"]');
|
||||||
|
expect(card).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have proper layout structure', () => {
|
||||||
|
render(<Navigation />);
|
||||||
|
|
||||||
|
const title = screen.getByText(/Mes Budgets Participatifs - Admin/);
|
||||||
|
expect(title).toHaveClass('text-xl', 'font-semibold');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle navigation without custom props', () => {
|
||||||
|
render(<Navigation />);
|
||||||
|
|
||||||
|
// Should render with default content
|
||||||
|
expect(screen.getByText(/Mes Budgets Participatifs - Admin/)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: /Paramètres/ })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: /Déconnexion/ })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have proper icon structure', () => {
|
||||||
|
render(<Navigation showBackButton={true} />);
|
||||||
|
|
||||||
|
// Vérifier que les icônes sont présentes (Lucide React icons)
|
||||||
|
const backButton = screen.getByRole('link', { name: /Retour/ });
|
||||||
|
const settingsButton = screen.getByRole('link', { name: /Paramètres/ });
|
||||||
|
|
||||||
|
// Les icônes sont des éléments SVG dans les liens
|
||||||
|
expect(backButton.querySelector('svg')).toBeInTheDocument();
|
||||||
|
expect(settingsButton.querySelector('svg')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
163
src/__tests__/lib/file-utils.test.ts
Normal file
163
src/__tests__/lib/file-utils.test.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import {
|
||||||
|
formatFileSize,
|
||||||
|
getFileExtension,
|
||||||
|
validateFileType,
|
||||||
|
sanitizeFileName
|
||||||
|
} from '../../lib/file-utils';
|
||||||
|
|
||||||
|
describe('File Utils', () => {
|
||||||
|
describe('formatFileSize', () => {
|
||||||
|
it('should format bytes correctly', () => {
|
||||||
|
expect(formatFileSize(0)).toBe('0 B');
|
||||||
|
expect(formatFileSize(1024)).toBe('1 KB');
|
||||||
|
expect(formatFileSize(1024 * 1024)).toBe('1 MB');
|
||||||
|
expect(formatFileSize(1024 * 1024 * 1024)).toBe('1 GB');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle decimal sizes', () => {
|
||||||
|
expect(formatFileSize(1500)).toBe('1.46 KB');
|
||||||
|
expect(formatFileSize(1536)).toBe('1.5 KB');
|
||||||
|
expect(formatFileSize(1024 * 1024 + 512 * 1024)).toBe('1.5 MB');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle large sizes', () => {
|
||||||
|
expect(formatFileSize(1024 * 1024 * 1024 * 1024)).toBe('1 TB');
|
||||||
|
expect(formatFileSize(1024 * 1024 * 1024 * 1024 * 1024)).toBe('1 PB');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle negative values', () => {
|
||||||
|
expect(formatFileSize(-1024)).toBe('0 B');
|
||||||
|
expect(formatFileSize(-1)).toBe('0 B');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getFileExtension', () => {
|
||||||
|
it('should extract file extensions', () => {
|
||||||
|
expect(getFileExtension('file.txt')).toBe('txt');
|
||||||
|
expect(getFileExtension('document.pdf')).toBe('pdf');
|
||||||
|
expect(getFileExtension('image.jpg')).toBe('jpg');
|
||||||
|
expect(getFileExtension('archive.tar.gz')).toBe('gz');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle files without extensions', () => {
|
||||||
|
expect(getFileExtension('README')).toBe('');
|
||||||
|
expect(getFileExtension('file.')).toBe('');
|
||||||
|
expect(getFileExtension('')).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle case sensitivity', () => {
|
||||||
|
expect(getFileExtension('file.TXT')).toBe('TXT');
|
||||||
|
expect(getFileExtension('file.PDF')).toBe('PDF');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle special characters', () => {
|
||||||
|
expect(getFileExtension('file-name_test.txt')).toBe('txt');
|
||||||
|
expect(getFileExtension('file@domain.com.pdf')).toBe('pdf');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateFileType', () => {
|
||||||
|
it('should validate allowed file types', () => {
|
||||||
|
const csvFile = new File([''], 'test.csv', { type: 'text/csv' });
|
||||||
|
const excelFile = new File([''], 'test.xlsx', { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||||
|
const odsFile = new File([''], 'test.ods', { type: 'application/vnd.oasis.opendocument.spreadsheet' });
|
||||||
|
|
||||||
|
expect(validateFileType(csvFile).isValid).toBe(true);
|
||||||
|
expect(validateFileType(excelFile).isValid).toBe(true);
|
||||||
|
expect(validateFileType(odsFile).isValid).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject disallowed file types', () => {
|
||||||
|
const txtFile = new File([''], 'test.txt', { type: 'text/plain' });
|
||||||
|
const exeFile = new File([''], 'test.exe', { type: 'application/x-msdownload' });
|
||||||
|
|
||||||
|
expect(validateFileType(txtFile).isValid).toBe(false);
|
||||||
|
expect(validateFileType(exeFile).isValid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle case insensitive validation', () => {
|
||||||
|
const csvFile = new File([''], 'test.CSV', { type: 'text/csv' });
|
||||||
|
const xlsxFile = new File([''], 'test.XLSX', { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||||
|
|
||||||
|
expect(validateFileType(csvFile).isValid).toBe(true);
|
||||||
|
expect(validateFileType(xlsxFile).isValid).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle files without extensions', () => {
|
||||||
|
const fileWithoutExt = new File([''], 'test', { type: 'text/plain' });
|
||||||
|
|
||||||
|
expect(validateFileType(fileWithoutExt).isValid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle files with null name', () => {
|
||||||
|
const fileWithNullName = new File([''], '', { type: 'text/csv' });
|
||||||
|
|
||||||
|
expect(validateFileType(fileWithNullName).isValid).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sanitizeFileName', () => {
|
||||||
|
it('should remove special characters', () => {
|
||||||
|
expect(sanitizeFileName('file@name#test.txt')).toBe('file-name-test.txt');
|
||||||
|
expect(sanitizeFileName('document with spaces.pdf')).toBe('document-with-spaces.pdf');
|
||||||
|
expect(sanitizeFileName('file/with\\slashes.txt')).toBe('file-with-slashes.txt');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle accented characters', () => {
|
||||||
|
expect(sanitizeFileName('fichier-émojis.txt')).toBe('fichier-mojis.txt');
|
||||||
|
expect(sanitizeFileName('document-à-ç-ù.pdf')).toBe('document-.pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve file extensions', () => {
|
||||||
|
expect(sanitizeFileName('file@name.txt')).toBe('file-name.txt');
|
||||||
|
expect(sanitizeFileName('document#test.pdf')).toBe('document-test.pdf');
|
||||||
|
expect(sanitizeFileName('image$photo.jpg')).toBe('image-photo.jpg');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle multiple dots', () => {
|
||||||
|
expect(sanitizeFileName('file.name.test.txt')).toBe('file.name.test.txt');
|
||||||
|
expect(sanitizeFileName('archive.tar.gz')).toBe('archive.tar.gz');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty strings', () => {
|
||||||
|
expect(sanitizeFileName('')).toBe('');
|
||||||
|
expect(sanitizeFileName(' ')).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle files without extensions', () => {
|
||||||
|
expect(sanitizeFileName('README')).toBe('README');
|
||||||
|
expect(sanitizeFileName('file@name')).toBe('file-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should limit filename length', () => {
|
||||||
|
const longName = 'a'.repeat(300) + '.txt';
|
||||||
|
const sanitized = sanitizeFileName(longName);
|
||||||
|
|
||||||
|
expect(sanitized.length).toBeLessThanOrEqual(255);
|
||||||
|
expect(sanitized).toMatch(/\.txt$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('integration tests', () => {
|
||||||
|
it('should work together for file validation', () => {
|
||||||
|
const fileName = 'document@test.pdf';
|
||||||
|
const file = new File([''], fileName, { type: 'application/pdf' });
|
||||||
|
|
||||||
|
const sanitized = sanitizeFileName(fileName);
|
||||||
|
const extension = getFileExtension(sanitized);
|
||||||
|
const validation = validateFileType(file);
|
||||||
|
|
||||||
|
expect(sanitized).toBe('document-test.pdf');
|
||||||
|
expect(extension).toBe('pdf');
|
||||||
|
expect(validation.isValid).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle file size formatting with validation', () => {
|
||||||
|
const fileSize = 1024 * 1024; // 1 MB
|
||||||
|
const formattedSize = formatFileSize(fileSize);
|
||||||
|
|
||||||
|
expect(formattedSize).toBe('1 MB');
|
||||||
|
expect(fileSize).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -103,18 +103,74 @@ export function downloadTemplate(type: 'propositions' | 'participants'): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function validateFileType(file: File): { isValid: boolean; error?: string } {
|
export function validateFileType(file: File): { isValid: boolean; error?: string } {
|
||||||
const isCSV = file.type === 'text/csv' || file.name.toLowerCase().endsWith('.csv');
|
const isCSV = file.type === 'text/csv' || (file.name && file.name.toLowerCase().endsWith('.csv'));
|
||||||
const isExcel = file.type === 'application/vnd.oasis.opendocument.spreadsheet' ||
|
const isExcel = file.type === 'application/vnd.oasis.opendocument.spreadsheet' ||
|
||||||
file.name.toLowerCase().endsWith('.ods') ||
|
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||||||
|
file.type === 'application/vnd.ms-excel' ||
|
||||||
|
(file.name && (file.name.toLowerCase().endsWith('.ods') ||
|
||||||
file.name.toLowerCase().endsWith('.xlsx') ||
|
file.name.toLowerCase().endsWith('.xlsx') ||
|
||||||
file.name.toLowerCase().endsWith('.xls');
|
file.name.toLowerCase().endsWith('.xls')));
|
||||||
|
const isPDF = file.type === 'application/pdf' || (file.name && file.name.toLowerCase().endsWith('.pdf'));
|
||||||
|
|
||||||
if (!isCSV && !isExcel) {
|
if (!isCSV && !isExcel && !isPDF) {
|
||||||
return {
|
return {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
error: 'Veuillez sélectionner un fichier valide (CSV, ODS, XLSX ou XLS).'
|
error: 'Veuillez sélectionner un fichier valide (CSV, ODS, XLSX, XLS ou PDF).'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isValid: true };
|
return { isValid: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formate une taille de fichier en bytes vers une représentation lisible
|
||||||
|
*/
|
||||||
|
export function formatFileSize(bytes: number): string {
|
||||||
|
if (bytes < 0) return '0 B';
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extrait l'extension d'un nom de fichier
|
||||||
|
*/
|
||||||
|
export function getFileExtension(filename: string): string {
|
||||||
|
if (!filename || filename.indexOf('.') === -1) return '';
|
||||||
|
|
||||||
|
const parts = filename.split('.');
|
||||||
|
return parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nettoie un nom de fichier en supprimant les caractères spéciaux
|
||||||
|
*/
|
||||||
|
export function sanitizeFileName(filename: string): string {
|
||||||
|
if (!filename) return '';
|
||||||
|
|
||||||
|
// Supprimer les espaces en début et fin
|
||||||
|
let sanitized = filename.trim();
|
||||||
|
|
||||||
|
// Remplacer les caractères spéciaux par des tirets
|
||||||
|
sanitized = sanitized.replace(/[^a-zA-Z0-9.-]/g, '-');
|
||||||
|
|
||||||
|
// Supprimer les tirets multiples
|
||||||
|
sanitized = sanitized.replace(/-+/g, '-');
|
||||||
|
|
||||||
|
// Supprimer les tirets en début et fin
|
||||||
|
sanitized = sanitized.replace(/^-+|-+$/g, '');
|
||||||
|
|
||||||
|
// Limiter la longueur à 255 caractères
|
||||||
|
if (sanitized.length > 255) {
|
||||||
|
const extension = getFileExtension(sanitized);
|
||||||
|
const nameWithoutExt = sanitized.substring(0, sanitized.lastIndexOf('.'));
|
||||||
|
const maxNameLength = 255 - extension.length - 1; // -1 pour le point
|
||||||
|
sanitized = nameWithoutExt.substring(0, maxNameLength) + '.' + extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user