/** * Tests for the export utility functions * @jest-environment jsdom */ import { exportToCSV, exportToJSON, formatDateForExport } from '@/utils/export'; describe('Export Utilities', () => { // Mock DOM methods for file download let mockClick: jest.Mock; let mockLink: Partial; const originalCreateObjectURL = URL.createObjectURL; const originalRevokeObjectURL = URL.revokeObjectURL; beforeAll(() => { // Mock URL methods globally URL.createObjectURL = jest.fn(() => 'blob:test-url'); URL.revokeObjectURL = jest.fn(); }); afterAll(() => { URL.createObjectURL = originalCreateObjectURL; URL.revokeObjectURL = originalRevokeObjectURL; }); beforeEach(() => { mockClick = jest.fn(); mockLink = { href: '', download: '', style: { display: '' } as CSSStyleDeclaration, click: mockClick, }; jest.spyOn(document, 'createElement').mockReturnValue(mockLink as HTMLAnchorElement); jest.spyOn(document.body, 'appendChild').mockImplementation(() => mockLink as Node); jest.spyOn(document.body, 'removeChild').mockImplementation(() => mockLink as Node); }); afterEach(() => { jest.restoreAllMocks(); }); describe('exportToCSV', () => { it('should create and download a CSV file', () => { const data = [ ['Header1', 'Header2'], ['Value1', 'Value2'], ]; exportToCSV(data, 'test-file'); expect(document.createElement).toHaveBeenCalledWith('a'); expect(URL.createObjectURL).toHaveBeenCalled(); expect(document.body.appendChild).toHaveBeenCalled(); expect(mockClick).toHaveBeenCalled(); expect(document.body.removeChild).toHaveBeenCalled(); expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:test-url'); }); it('should include date in filename by default', () => { const data = [['test']]; const today = new Date().toISOString().split('T')[0]; exportToCSV(data, 'test-file'); expect(mockLink.download).toContain('test-file'); expect(mockLink.download).toContain(today); expect(mockLink.download).toContain('.csv'); }); it('should exclude date when includeDate is false', () => { const data = [['test']]; exportToCSV(data, 'test-file', { includeDate: false }); expect(mockLink.download).toBe('test-file.csv'); }); it('should use custom separator', () => { const data = [['A', 'B'], ['C', 'D']]; // We can't easily verify the content of the blob, but we can verify the function runs expect(() => exportToCSV(data, 'test', { separator: ';' })).not.toThrow(); expect(mockClick).toHaveBeenCalled(); }); it('should handle null and undefined values', () => { const data = [[null, undefined, 'value']]; expect(() => exportToCSV(data, 'test')).not.toThrow(); expect(mockClick).toHaveBeenCalled(); }); it('should escape values containing separator', () => { const data = [['value,with,commas', 'normal']]; expect(() => exportToCSV(data, 'test')).not.toThrow(); expect(mockClick).toHaveBeenCalled(); }); it('should escape values containing quotes', () => { const data = [['value"with"quotes', 'normal']]; expect(() => exportToCSV(data, 'test')).not.toThrow(); expect(mockClick).toHaveBeenCalled(); }); it('should escape values containing newlines', () => { const data = [['value\nwith\nnewlines', 'normal']]; expect(() => exportToCSV(data, 'test')).not.toThrow(); expect(mockClick).toHaveBeenCalled(); }); it('should handle boolean values', () => { const data = [[true, false]]; expect(() => exportToCSV(data, 'test')).not.toThrow(); expect(mockClick).toHaveBeenCalled(); }); it('should handle number values', () => { const data = [[123, 456.78]]; expect(() => exportToCSV(data, 'test')).not.toThrow(); expect(mockClick).toHaveBeenCalled(); }); it('should set link style to hidden', () => { const data = [['test']]; exportToCSV(data, 'test'); expect(mockLink.style?.display).toBe('none'); }); }); describe('exportToJSON', () => { it('should create and download a JSON file', () => { const data = { key: 'value', nested: { a: 1 } }; exportToJSON(data, 'test-file'); expect(document.createElement).toHaveBeenCalledWith('a'); expect(URL.createObjectURL).toHaveBeenCalled(); expect(document.body.appendChild).toHaveBeenCalled(); expect(mockClick).toHaveBeenCalled(); expect(document.body.removeChild).toHaveBeenCalled(); expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:test-url'); }); it('should include date in filename', () => { const data = { test: true }; const today = new Date().toISOString().split('T')[0]; exportToJSON(data, 'test-file'); expect(mockLink.download).toContain('test-file'); expect(mockLink.download).toContain(today); expect(mockLink.download).toContain('.json'); }); it('should handle arrays', () => { const data = [1, 2, 3, { a: 'b' }]; expect(() => exportToJSON(data, 'test')).not.toThrow(); expect(mockClick).toHaveBeenCalled(); }); it('should handle nested objects', () => { const data = { level1: { level2: { level3: 'deep', }, }, }; expect(() => exportToJSON(data, 'test')).not.toThrow(); expect(mockClick).toHaveBeenCalled(); }); it('should set link style to hidden', () => { const data = { test: true }; exportToJSON(data, 'test'); expect(mockLink.style?.display).toBe('none'); }); }); describe('formatDateForExport', () => { it('should format date string correctly', () => { const result = formatDateForExport('2024-01-15T10:30:00Z'); // French format: DD/MM/YYYY HH:MM expect(result).toMatch(/\d{2}\/\d{2}\/\d{4}/); }); it('should format Date object correctly', () => { const date = new Date('2024-01-15T10:30:00Z'); const result = formatDateForExport(date); expect(result).toMatch(/\d{2}\/\d{2}\/\d{4}/); }); it('should return empty string for null', () => { expect(formatDateForExport(null)).toBe(''); }); it('should return empty string for undefined', () => { expect(formatDateForExport(undefined)).toBe(''); }); it('should return empty string for empty string', () => { expect(formatDateForExport('')).toBe(''); }); it('should include time in the format', () => { const result = formatDateForExport('2024-01-15T10:30:00Z'); // Should contain time component expect(result).toMatch(/\d{2}:\d{2}/); }); }); });