import { tap, expect } from '@git.zone/tstest/tapbundle'; import { LifecycleComponent } from '../../../ts/core/utils/lifecycle-component.js'; import { EventEmitter } from 'events'; // Test implementation of LifecycleComponent class TestComponent extends LifecycleComponent { public timerCallCount = 0; public intervalCallCount = 0; public cleanupCalled = false; public testEmitter = new EventEmitter(); public listenerCallCount = 0; constructor() { super(); this.setupTimers(); this.setupListeners(); } private setupTimers() { // Set up a timeout this.setTimeout(() => { this.timerCallCount++; }, 100); // Set up an interval this.setInterval(() => { this.intervalCallCount++; }, 50); } private setupListeners() { this.addEventListener(this.testEmitter, 'test-event', () => { this.listenerCallCount++; }); } protected async onCleanup(): Promise { this.cleanupCalled = true; } // Expose protected methods for testing public testSetTimeout(handler: Function, timeout: number): NodeJS.Timeout { return this.setTimeout(handler, timeout); } public testSetInterval(handler: Function, interval: number): NodeJS.Timeout { return this.setInterval(handler, interval); } public testClearTimeout(timer: NodeJS.Timeout): void { return this.clearTimeout(timer); } public testClearInterval(timer: NodeJS.Timeout): void { return this.clearInterval(timer); } public testAddEventListener(target: any, event: string, handler: Function, options?: { once?: boolean }): void { return this.addEventListener(target, event, handler, options); } public testIsShuttingDown(): boolean { return this.isShuttingDownState(); } } tap.test('should manage timers properly', async () => { const component = new TestComponent(); // Wait for timers to fire await new Promise(resolve => setTimeout(resolve, 200)); expect(component.timerCallCount).toEqual(1); expect(component.intervalCallCount).toBeGreaterThan(2); await component.cleanup(); }); tap.test('should manage event listeners properly', async () => { const component = new TestComponent(); // Emit events component.testEmitter.emit('test-event'); component.testEmitter.emit('test-event'); expect(component.listenerCallCount).toEqual(2); // Cleanup and verify listeners are removed await component.cleanup(); component.testEmitter.emit('test-event'); expect(component.listenerCallCount).toEqual(2); // Should not increase }); tap.test('should prevent timer execution after cleanup', async () => { const component = new TestComponent(); let laterCallCount = 0; component.testSetTimeout(() => { laterCallCount++; }, 100); // Cleanup immediately await component.cleanup(); // Wait for timer that would have fired await new Promise(resolve => setTimeout(resolve, 150)); expect(laterCallCount).toEqual(0); }); tap.test('should handle child components', async () => { class ParentComponent extends LifecycleComponent { public child: TestComponent; constructor() { super(); this.child = new TestComponent(); this.registerChildComponent(this.child); } } const parent = new ParentComponent(); // Wait for child timers await new Promise(resolve => setTimeout(resolve, 100)); expect(parent.child.timerCallCount).toEqual(1); // Cleanup parent should cleanup child await parent.cleanup(); expect(parent.child.cleanupCalled).toBeTrue(); expect(parent.child.testIsShuttingDown()).toBeTrue(); }); tap.test('should handle multiple cleanup calls gracefully', async () => { const component = new TestComponent(); // Call cleanup multiple times const promises = [ component.cleanup(), component.cleanup(), component.cleanup() ]; await Promise.all(promises); // Should only clean up once expect(component.cleanupCalled).toBeTrue(); }); tap.test('should clear specific timers', async () => { const component = new TestComponent(); let callCount = 0; const timer = component.testSetTimeout(() => { callCount++; }, 100); // Clear the timer component.testClearTimeout(timer); // Wait and verify it didn't fire await new Promise(resolve => setTimeout(resolve, 150)); expect(callCount).toEqual(0); await component.cleanup(); }); tap.test('should clear specific intervals', async () => { const component = new TestComponent(); let callCount = 0; const interval = component.testSetInterval(() => { callCount++; }, 50); // Let it run a bit await new Promise(resolve => setTimeout(resolve, 120)); const countBeforeClear = callCount; expect(countBeforeClear).toBeGreaterThan(1); // Clear the interval component.testClearInterval(interval); // Wait and verify it stopped await new Promise(resolve => setTimeout(resolve, 100)); expect(callCount).toEqual(countBeforeClear); await component.cleanup(); }); tap.test('should handle once event listeners', async () => { const component = new TestComponent(); const emitter = new EventEmitter(); let callCount = 0; const handler = () => { callCount++; }; component.testAddEventListener(emitter, 'once-event', handler, { once: true }); // Check listener count before emit const beforeCount = emitter.listenerCount('once-event'); expect(beforeCount).toEqual(1); // Emit once - the listener should fire and auto-remove emitter.emit('once-event'); expect(callCount).toEqual(1); // Check listener was auto-removed const afterCount = emitter.listenerCount('once-event'); expect(afterCount).toEqual(0); // Emit again - should not increase count emitter.emit('once-event'); expect(callCount).toEqual(1); await component.cleanup(); }); tap.test('should not create timers when shutting down', async () => { const component = new TestComponent(); // Start cleanup const cleanupPromise = component.cleanup(); // Try to create timers during shutdown let timerFired = false; let intervalFired = false; component.testSetTimeout(() => { timerFired = true; }, 10); component.testSetInterval(() => { intervalFired = true; }, 10); await cleanupPromise; await new Promise(resolve => setTimeout(resolve, 50)); expect(timerFired).toBeFalse(); expect(intervalFired).toBeFalse(); }); tap.start();