import { exportToCSV, exportToExcel, exportData, generateExportFilename } from '../../utils/export' import { ColumnConfig } from '../../types' import * as XLSX from 'xlsx' // Mock the XLSX library jest.mock('xlsx', () => ({ utils: { aoa_to_sheet: jest.fn(), book_new: jest.fn(), book_append_sheet: jest.fn(), }, write: jest.fn(), })) // Mock URL.createObjectURL and URL.revokeObjectURL global.URL.createObjectURL = jest.fn(() => 'blob:mock-url') global.URL.revokeObjectURL = jest.fn() // Mock document methods const mockClick = jest.fn() const mockAppendChild = jest.fn() const mockRemoveChild = jest.fn() Object.defineProperty(document, 'createElement', { writable: true, value: jest.fn().mockReturnValue({ click: mockClick, href: '', download: '', }), }) Object.defineProperty(document.body, 'appendChild', { writable: true, value: mockAppendChild, }) Object.defineProperty(document.body, 'removeChild', { writable: true, value: mockRemoveChild, }) interface TestData { id: string name: string age: number isActive: boolean company?: { name: string } tags?: string[] createdAt: Date } describe('Export Utilities', () => { beforeEach(() => { jest.clearAllMocks() }) const sampleData: TestData[] = [ { id: '1', name: 'John Doe', age: 30, isActive: true, company: { name: 'Acme Corp' }, tags: ['developer', 'manager'], createdAt: new Date('2023-01-15'), }, { id: '2', name: 'Jane Smith', age: 25, isActive: false, company: { name: 'Tech Inc' }, tags: ['designer'], createdAt: new Date('2023-02-20'), }, { id: '3', name: 'Bob Johnson', age: 35, isActive: true, createdAt: new Date('2023-03-10'), }, ] const sampleColumns: ColumnConfig[] = [ { id: 'id', header: 'ID', accessorKey: 'id', }, { id: 'name', header: 'Name', accessorKey: 'name', }, { id: 'age', header: 'Age', accessorKey: 'age', }, { id: 'isActive', header: 'Active', accessorKey: 'isActive', }, { id: 'company', header: 'Company', accessorKey: 'company.name', }, { id: 'tags', header: 'Tags', accessorFn: (row) => row.tags?.join(', ') || '', }, { id: 'createdAt', header: 'Created At', accessorKey: 'createdAt', }, ] describe('exportToCSV', () => { it('should export data to CSV format', () => { exportToCSV(sampleData, sampleColumns, 'test-export') expect(mockClick).toHaveBeenCalled() expect(mockAppendChild).toHaveBeenCalled() expect(mockRemoveChild).toHaveBeenCalled() }) it('should include headers by default', () => { const createElementSpy = jest.spyOn(document, 'createElement') exportToCSV(sampleData, sampleColumns, 'test-export') const linkElement = createElementSpy.mock.results[0]?.value expect(linkElement.download).toBe('test-export.csv') }) it('should handle empty data', () => { expect(() => { exportToCSV([], sampleColumns, 'test-export') }).toThrow('No data to export') }) it('should handle empty columns', () => { expect(() => { exportToCSV(sampleData, [], 'test-export') }).toThrow('No columns specified for export') }) it('should properly escape CSV values with quotes and commas', () => { const dataWithSpecialChars: TestData[] = [ { id: '1', name: 'John "The Boss" Doe', age: 30, isActive: true, createdAt: new Date('2023-01-15'), }, { id: '2', name: 'Smith, Jane', age: 25, isActive: false, createdAt: new Date('2023-02-20'), }, ] expect(() => { exportToCSV(dataWithSpecialChars, sampleColumns, 'test-special-chars') }).not.toThrow() }) it('should handle nested object accessors', () => { expect(() => { exportToCSV(sampleData, sampleColumns, 'test-nested') }).not.toThrow() }) it('should handle accessor functions', () => { const columnsWithFunctions: ColumnConfig[] = [ { id: 'fullInfo', header: 'Full Info', accessorFn: (row) => `${row.name} (${row.age})`, }, ] expect(() => { exportToCSV(sampleData, columnsWithFunctions, 'test-accessor-fn') }).not.toThrow() }) it('should format boolean values as Yes/No', () => { const booleanColumns: ColumnConfig[] = [ { id: 'isActive', header: 'Active', accessorKey: 'isActive', }, ] expect(() => { exportToCSV(sampleData, booleanColumns, 'test-boolean') }).not.toThrow() }) it('should handle null and undefined values', () => { const dataWithNulls: TestData[] = [ { id: '1', name: 'John Doe', age: 30, isActive: true, company: undefined, createdAt: new Date('2023-01-15'), }, ] expect(() => { exportToCSV(dataWithNulls, sampleColumns, 'test-nulls') }).not.toThrow() }) it('should format dates as ISO strings', () => { const dateColumns: ColumnConfig[] = [ { id: 'createdAt', header: 'Created', accessorKey: 'createdAt', }, ] expect(() => { exportToCSV(sampleData, dateColumns, 'test-dates') }).not.toThrow() }) }) describe('exportToExcel', () => { beforeEach(() => { ;(XLSX.utils.aoa_to_sheet as jest.Mock).mockReturnValue({}) ;(XLSX.utils.book_new as jest.Mock).mockReturnValue({}) ;(XLSX.write as jest.Mock).mockReturnValue(new ArrayBuffer(8)) }) it('should export data to Excel format', () => { exportToExcel(sampleData, sampleColumns, 'test-export') expect(XLSX.utils.aoa_to_sheet).toHaveBeenCalled() expect(XLSX.utils.book_new).toHaveBeenCalled() expect(XLSX.utils.book_append_sheet).toHaveBeenCalled() expect(XLSX.write).toHaveBeenCalled() }) it('should create worksheet with correct name', () => { exportToExcel(sampleData, sampleColumns, 'test-export') expect(XLSX.utils.book_append_sheet).toHaveBeenCalledWith( expect.anything(), expect.anything(), 'Data' ) }) it('should handle empty data', () => { expect(() => { exportToExcel([], sampleColumns, 'test-export') }).toThrow('No data to export') }) it('should handle empty columns', () => { expect(() => { exportToExcel(sampleData, [], 'test-export') }).toThrow('No columns specified for export') }) it('should set column widths', () => { const mockSheet = {} ;(XLSX.utils.aoa_to_sheet as jest.Mock).mockReturnValue(mockSheet) exportToExcel(sampleData, sampleColumns, 'test-widths') expect(mockSheet).toHaveProperty('!cols') }) it('should trigger file download', () => { exportToExcel(sampleData, sampleColumns, 'test-download') expect(mockClick).toHaveBeenCalled() expect(mockAppendChild).toHaveBeenCalled() expect(mockRemoveChild).toHaveBeenCalled() }) }) describe('exportData', () => { it('should route to CSV export when format is csv', () => { exportData(sampleData, sampleColumns, { format: 'csv', filename: 'test-export', }) expect(mockClick).toHaveBeenCalled() }) it('should route to Excel export when format is excel', () => { ;(XLSX.utils.aoa_to_sheet as jest.Mock).mockReturnValue({}) ;(XLSX.utils.book_new as jest.Mock).mockReturnValue({}) ;(XLSX.write as jest.Mock).mockReturnValue(new ArrayBuffer(8)) exportData(sampleData, sampleColumns, { format: 'excel', filename: 'test-export', }) expect(XLSX.utils.aoa_to_sheet).toHaveBeenCalled() }) it('should throw error for unsupported format', () => { expect(() => { exportData(sampleData, sampleColumns, { format: 'pdf' as any, filename: 'test-export', }) }).toThrow('Unsupported export format: pdf') }) it('should respect includeHeaders option', () => { exportData(sampleData, sampleColumns, { format: 'csv', filename: 'test-export', includeHeaders: false, }) expect(mockClick).toHaveBeenCalled() }) }) describe('generateExportFilename', () => { it('should generate filename with timestamp', () => { const filename = generateExportFilename('export') expect(filename).toMatch(/^export_\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/) }) it('should handle custom base filename', () => { const filename = generateExportFilename('my-custom-export') expect(filename).toMatch(/^my-custom-export_\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/) }) it('should generate unique filenames on consecutive calls', async () => { const filename1 = generateExportFilename('export') await new Promise((resolve) => setTimeout(resolve, 10)) const filename2 = generateExportFilename('export') // They might be the same if called within the same second // Just ensure they both match the pattern expect(filename1).toMatch(/^export_\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/) expect(filename2).toMatch(/^export_\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/) }) }) describe('Edge Cases', () => { it('should handle large datasets', () => { const largeData = Array.from({ length: 10000 }, (_, i) => ({ id: `${i}`, name: `User ${i}`, age: 20 + (i % 50), isActive: i % 2 === 0, createdAt: new Date('2023-01-01'), })) expect(() => { exportToCSV(largeData, sampleColumns, 'large-export') }).not.toThrow() }) it('should handle columns with function headers', () => { const columnsWithFunctionHeaders: ColumnConfig[] = [ { id: 'name', header: () => 'Custom Name Header', accessorKey: 'name', }, ] expect(() => { exportToCSV(sampleData, columnsWithFunctionHeaders, 'test-function-header') }).not.toThrow() }) it('should handle data with array values', () => { const dataWithArrays: TestData[] = [ { id: '1', name: 'John Doe', age: 30, isActive: true, tags: ['tag1', 'tag2', 'tag3'], createdAt: new Date('2023-01-15'), }, ] expect(() => { exportToCSV(dataWithArrays, sampleColumns, 'test-arrays') }).not.toThrow() }) it('should handle columns without accessor', () => { const columnsWithoutAccessor: ColumnConfig[] = [ { id: 'custom', header: 'Custom Column', }, ] expect(() => { exportToCSV(sampleData, columnsWithoutAccessor, 'test-no-accessor') }).not.toThrow() }) }) })