Add tests for exportToCSV, exportToJSON, and formatDateForExport covering file download, date formatting, and edge cases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
228 lines
6.7 KiB
TypeScript
228 lines
6.7 KiB
TypeScript
/**
|
|
* 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<HTMLAnchorElement>;
|
|
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}/);
|
|
});
|
|
});
|
|
});
|