import { ErrorHandler } from '../ErrorHandler'; import { ConversionIQConfig } from '../../types'; describe('ErrorHandler', () => { let errorHandler: ErrorHandler; let mockConfig: ConversionIQConfig; beforeEach(() => { mockConfig = { websiteId: 'test-website-id', endpoint: 'https://api.conversioniq.com', debug: true }; errorHandler = new ErrorHandler(mockConfig); // Clear mocks jest.clearAllMocks(); // Mock console methods jest.spyOn(console, 'error').mockImplementation(); jest.spyOn(console, 'warn').mockImplementation(); jest.spyOn(console, 'log').mockImplementation(); }); afterEach(() => { errorHandler.destroy(); jest.restoreAllMocks(); }); describe('handle', () => { it('should handle errors gracefully without throwing', () => { const testError = new Error('Test error'); expect(() => { errorHandler.handle(testError, 'test-context'); }).not.toThrow(); }); it('should log errors in debug mode', () => { const testError = new Error('Test error'); const consoleSpy = jest.spyOn(console, 'error'); errorHandler.handle(testError, 'test-context'); expect(consoleSpy).toHaveBeenCalledWith( 'ConversionIQ SDK Error:', expect.objectContaining({ message: 'Test error', context: 'test-context' }) ); }); it('should not log errors when debug is false', () => { const nonDebugConfig = { ...mockConfig, debug: false }; const nonDebugHandler = new ErrorHandler(nonDebugConfig); const consoleSpy = jest.spyOn(console, 'error'); const testError = new Error('Test error'); nonDebugHandler.handle(testError); expect(consoleSpy).not.toHaveBeenCalled(); nonDebugHandler.destroy(); }); it('should shutdown after maximum errors', () => { const testError = new Error('Test error'); // Trigger 11 errors (max is 10) for (let i = 0; i < 11; i++) { errorHandler.handle(testError); } const stats = errorHandler.getStats(); expect(stats.isShutdown).toBe(true); }); it('should categorize different error types correctly', () => { const typeError = new TypeError('Type error'); const networkError = new Error('Network request failed'); const quotaError = new Error('quota exceeded'); // These should not throw and should be handled appropriately expect(() => { errorHandler.handle(typeError); errorHandler.handle(networkError); errorHandler.handle(quotaError); }).not.toThrow(); }); }); describe('warn', () => { it('should handle warnings in debug mode', () => { const consoleSpy = jest.spyOn(console, 'warn'); errorHandler.warn('Test warning', 'test-context'); expect(consoleSpy).toHaveBeenCalledWith( 'ConversionIQ Warning:', 'Test warning', expect.objectContaining({ context: 'test-context' }) ); }); it('should not log warnings when debug is false', () => { const nonDebugConfig = { ...mockConfig, debug: false }; const nonDebugHandler = new ErrorHandler(nonDebugConfig); const consoleSpy = jest.spyOn(console, 'warn'); nonDebugHandler.warn('Test warning'); expect(consoleSpy).not.toHaveBeenCalled(); nonDebugHandler.destroy(); }); }); describe('getStats', () => { it('should return correct statistics', () => { const stats = errorHandler.getStats(); expect(stats).toEqual({ errorCount: 0, isShutdown: false, bufferSize: 0 }); }); it('should update error count after handling errors', () => { const testError = new Error('Test error'); errorHandler.handle(testError); const stats = errorHandler.getStats(); expect(stats.errorCount).toBe(1); expect(stats.bufferSize).toBe(1); }); }); describe('shutdown', () => { it('should shutdown gracefully', () => { errorHandler.shutdown(); const stats = errorHandler.getStats(); expect(stats.isShutdown).toBe(true); }); it('should not handle errors after shutdown', () => { errorHandler.shutdown(); const testError = new Error('Test error'); errorHandler.handle(testError); const stats = errorHandler.getStats(); expect(stats.errorCount).toBe(0); // Should not increment }); }); describe('error reporting', () => { beforeEach(() => { // Destroy the default errorHandler and create one with faster reporting errorHandler.destroy(); errorHandler = new ErrorHandler(mockConfig, 50); // 50ms reporting interval for tests // Mock fetch for error reporting (global.fetch as jest.Mock).mockResolvedValue({ ok: true, status: 200 }); // Mock navigator.sendBeacon (window.navigator.sendBeacon as jest.Mock).mockReturnValue(true); }); it('should attempt to report errors periodically', (done) => { const testError = new Error('Test error'); errorHandler.handle(testError); // Wait for reporting interval (50ms in tests) setTimeout(() => { // Should have attempted to report via sendBeacon or fetch expect( (window.navigator.sendBeacon as jest.Mock).mock.calls.length + (global.fetch as jest.Mock).mock.calls.length ).toBeGreaterThan(0); done(); }, 100); }); }); describe('storage quota handling', () => { it('should handle storage quota errors gracefully', () => { const quotaError = new Error('QuotaExceededError: quota exceeded'); // Mock localStorage to throw quota error const setItemSpy = jest.spyOn(Storage.prototype, 'setItem').mockImplementation(() => { throw quotaError; }); expect(() => { errorHandler.handle(quotaError); }).not.toThrow(); setItemSpy.mockRestore(); }); }); });