252 lines
6.3 KiB
TypeScript
252 lines
6.3 KiB
TypeScript
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<void> {
|
|
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(); |