/** * Tests for Event/CustomEvent/EventTarget polyfills */ import { CustomEvent } from '../polyfills/events/CustomEvent' // Set up global polyfill for tests import Event from '../polyfills/events/Event' import EventTarget from '../polyfills/events/EventTarget' ;(global as any).Event = Event ;(global as any).CustomEvent = CustomEvent describe('Event Polyfill', () => { describe('Event', () => { it('should create a basic event', () => { const event = new Event('test') expect(event.type).toBe('test') expect(event.bubbles).toBe(false) expect(event.cancelable).toBe(false) expect(event.composed).toBe(false) expect(event.defaultPrevented).toBe(false) expect(event.isTrusted).toBe(false) expect(event.eventPhase).toBe(Event.NONE) expect(typeof event.timeStamp).toBe('number') }) it('should create an event with options', () => { const event = new Event('test', { bubbles: true, cancelable: true, composed: true, }) expect(event.bubbles).toBe(true) expect(event.cancelable).toBe(true) expect(event.composed).toBe(true) }) it('should have correct phase constants', () => { expect(Event.NONE).toBe(0) expect(Event.CAPTURING_PHASE).toBe(1) expect(Event.AT_TARGET).toBe(2) expect(Event.BUBBLING_PHASE).toBe(3) }) it('should prevent default when cancelable', () => { const event = new Event('test', { cancelable: true }) expect(event.defaultPrevented).toBe(false) event.preventDefault() expect(event.defaultPrevented).toBe(true) }) it('should not prevent default when not cancelable', () => { const event = new Event('test', { cancelable: false }) event.preventDefault() expect(event.defaultPrevented).toBe(false) }) it('should support stopPropagation', () => { const event = new Event('test') // No error thrown - internal flags are set event.stopPropagation() event.stopImmediatePropagation() }) }) describe('CustomEvent', () => { it('should create a custom event with detail', () => { const detail = { foo: 'bar', count: 42 } const event = new CustomEvent('custom', { detail }) expect(event.type).toBe('custom') expect(event.detail).toEqual(detail) }) it('should inherit from Event', () => { const event = new CustomEvent('custom', { bubbles: true, cancelable: true, detail: 'test', }) expect(event instanceof Event).toBe(true) expect(event.bubbles).toBe(true) expect(event.cancelable).toBe(true) }) it('should handle undefined detail', () => { const event = new CustomEvent('custom') expect(event.detail).toBeUndefined() }) }) describe('EventTarget', () => { let target: EventTarget beforeEach(() => { target = new EventTarget() }) it('should add and remove event listeners', () => { const listener = jest.fn() target.addEventListener('test', listener) const event = new Event('test') const result = target.dispatchEvent(event) expect(listener).toHaveBeenCalledWith(event) expect(listener).toHaveBeenCalledTimes(1) expect(result).toBe(true) // not prevented target.removeEventListener('test', listener) target.dispatchEvent(new Event('test')) expect(listener).toHaveBeenCalledTimes(1) // not called again }) it('should handle multiple listeners for the same event', () => { const listener1 = jest.fn() const listener2 = jest.fn() target.addEventListener('test', listener1) target.addEventListener('test', listener2) target.dispatchEvent(new Event('test')) expect(listener1).toHaveBeenCalledTimes(1) expect(listener2).toHaveBeenCalledTimes(1) }) it('should handle once option', () => { const listener = jest.fn() target.addEventListener('test', listener, { once: true }) target.dispatchEvent(new Event('test')) target.dispatchEvent(new Event('test')) expect(listener).toHaveBeenCalledTimes(1) }) it('should handle capture option', () => { const listener = jest.fn() target.addEventListener('test', listener, { capture: true }) target.dispatchEvent(new Event('test')) expect(listener).toHaveBeenCalledTimes(1) target.removeEventListener('test', listener, { capture: true }) target.dispatchEvent(new Event('test')) expect(listener).toHaveBeenCalledTimes(1) }) it('should handle passive option', () => { const listener = jest.fn() target.addEventListener('test', listener, { passive: true }) target.dispatchEvent(new Event('test')) expect(listener).toHaveBeenCalledTimes(1) }) it('should handle object listeners with handleEvent', () => { const listener = { handleEvent: jest.fn(), } target.addEventListener('test', listener) target.dispatchEvent(new Event('test')) expect(listener.handleEvent).toHaveBeenCalledTimes(1) }) it('should return false when event is prevented', () => { const listener = (event: Event) => { event.preventDefault() } target.addEventListener('test', listener) const event = new Event('test', { cancelable: true }) const result = target.dispatchEvent(event) expect(result).toBe(false) expect(event.defaultPrevented).toBe(true) }) it('should set event properties during dispatch', () => { let capturedEvent: Event | null = null const listener = (event: Event) => { capturedEvent = event expect(event.target).toBe(target) expect(event.currentTarget).toBe(target) expect(event.eventPhase).toBe(Event.AT_TARGET) } target.addEventListener('test', listener) target.dispatchEvent(new Event('test')) expect(capturedEvent).not.toBeNull() // After dispatch, these should be reset expect(capturedEvent!.currentTarget).toBeNull() expect(capturedEvent!.eventPhase).toBe(Event.NONE) }) it('should handle bubbling events', () => { const listener = jest.fn() target.addEventListener('test', listener) const bubblingEvent = new Event('test', { bubbles: true }) target.dispatchEvent(bubblingEvent) expect(listener).toHaveBeenCalledWith(bubblingEvent) }) it('should handle error in listener gracefully', () => { const errorListener = () => { throw new Error('Test error') } const normalListener = jest.fn() // Mock console.error to avoid test output noise const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation() target.addEventListener('test', errorListener) target.addEventListener('test', normalListener) target.dispatchEvent(new Event('test')) expect(normalListener).toHaveBeenCalledTimes(1) expect(consoleErrorSpy).toHaveBeenCalled() consoleErrorSpy.mockRestore() }) it('should ignore null listeners', () => { // Should not throw target.addEventListener('test', null) target.removeEventListener('test', null) target.dispatchEvent(new Event('test')) }) it('should handle composed path', () => { let composedPath: readonly EventTarget[] | null = null const listener = (event: Event) => { composedPath = event.composedPath() } target.addEventListener('test', listener) target.dispatchEvent(new Event('test')) expect(composedPath).toEqual([target]) }) it('should prevent duplicate listeners', () => { const listener = jest.fn() target.addEventListener('test', listener) target.addEventListener('test', listener) // Same listener target.dispatchEvent(new Event('test')) expect(listener).toHaveBeenCalledTimes(1) // Only called once }) }) describe('Event flow integration', () => { it('should work with CustomEvent dispatch', () => { const target = new EventTarget() const listener = jest.fn() const detail = { message: 'hello' } target.addEventListener('custom', listener) const event = new CustomEvent('custom', { detail, bubbles: true, cancelable: true, }) const result = target.dispatchEvent(event) expect(listener).toHaveBeenCalledWith(event) expect(listener.mock.calls[0][0].detail).toEqual(detail) expect(result).toBe(true) }) it('should handle stopImmediatePropagation', () => { const target = new EventTarget() const listener1 = jest.fn((event: Event) => { event.stopImmediatePropagation() }) const listener2 = jest.fn() target.addEventListener('test', listener1) target.addEventListener('test', listener2) target.dispatchEvent(new Event('test')) expect(listener1).toHaveBeenCalledTimes(1) expect(listener2).toHaveBeenCalledTimes(0) // Stopped }) }) describe('Global polyfill installation', () => { it('should make Event available globally', () => { // This tests that our polyfill setup works expect(typeof (global as any).Event).toBe('function') expect(typeof (global as any).CustomEvent).toBe('function') const globalEvent = new (global as any).Event('test') expect(globalEvent.type).toBe('test') const globalCustomEvent = new (global as any).CustomEvent('custom', { detail: 'test', }) expect(globalCustomEvent.detail).toBe('test') }) }) })