Clean up listener and timeout after waitfor reject

This commit is contained in:
2026-08-10 02:32:17 +00:00
parent 296ab4aadf
commit bc28a7e96a
2 changed files with 79 additions and 7 deletions
+65
View File
@@ -551,6 +551,66 @@ const testEventEmitterWaitForRejectsOnPredicateError = async (): Promise<void> =
expect(listener).not.toHaveBeenCalled();
};
/**
* Tests that waitFor() removes its listener after the predicate function throws an error.
*/
const testEventEmitterWaitForRemovesListenerAfterPredicateError = async (): Promise<void> => {
const emitter = new EventEmitter<TestEvents>();
// Create a predicate function that throws an error.
const predicate = vi.fn().mockImplementation(() => {
throw new Error('predicate error');
});
// Wait for the predicate function to throw an error.
const waitPromise = emitter.waitFor('message', predicate);
// Emit an event, expecting the predicate function to throw an error.
emitter.emit('message', 'hello');
await expect(waitPromise).rejects.toThrow('predicate error');
// Expect the predicate function to have been called once.
expect(predicate).toHaveBeenCalledTimes(1);
// A later emit must not invoke the failed waitFor predicate again.
emitter.emit('message', 'again');
// Expect the predicate function to still have been called once.
expect(predicate).toHaveBeenCalledTimes(1);
};
/**
* Tests that waitFor() clears its timeout when the predicate function throws an error.
*/
const testEventEmitterWaitForClearsTimeoutAfterPredicateError = async (): Promise<void> => {
vi.useFakeTimers();
try {
const emitter = new EventEmitter<TestEvents>();
const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout');
const waitPromise = emitter.waitFor(
'message',
(): boolean => {
throw new Error('predicate error');
},
100,
);
emitter.emit('message', 'hello');
await expect(waitPromise).rejects.toThrow('predicate error');
// The timeout scheduled for waitFor must be cleared on predicate failure.
expect(clearTimeoutSpy).toHaveBeenCalled();
// Advancing past the original timeout must not produce a second rejection path.
await vi.advanceTimersByTimeAsync(100);
} finally {
vi.useRealTimers();
vi.restoreAllMocks();
}
};
const runTests = async (): Promise<void> => {
test('EventEmitter: calls listeners when an event is emitted', testEventEmitterCallsListeners);
test('EventEmitter: calls multiple listeners for the same event', testEventEmitterCallsMultipleListeners);
@@ -581,6 +641,11 @@ const runTests = async (): Promise<void> => {
test('EventEmitter: does not debounce when debounceMilliseconds is zero', testEventEmitterZeroDebounceDoesNotDebounce);
test('EventEmitter: debounces once listeners and invokes them only once', testEventEmitterDebouncedOnceListener);
test('EventEmitter: rejects waitFor when the predicate function throws', testEventEmitterWaitForRejectsOnPredicateError);
test(
'EventEmitter: removes the waitFor listener after it rejects due to predicate error',
testEventEmitterWaitForRemovesListenerAfterPredicateError,
);
test('EventEmitter: clears the timeout when waitFor rejects due to predicate error', testEventEmitterWaitForClearsTimeoutAfterPredicateError);
};
await runTests();