Skip to content

Commit e9774ad

Browse files
authored
fix: Accept all HasEventHandling types when creating event Promise (#2550)
- Shouldn't have limited this function to just table types
1 parent 5ef19c2 commit e9774ad

2 files changed

Lines changed: 95 additions & 10 deletions

File tree

packages/jsapi-utils/src/TableUtils.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2815,6 +2815,63 @@ describe('makeCancelableTableEventPromise', () => {
28152815
});
28162816
});
28172817

2818+
describe('makeCancelableEventPromise', () => {
2819+
const DEFAULT_EVENT = 'DEFAULT_EVENT';
2820+
const DEFAULT_EMITTER = {
2821+
removeEventListener: jest.fn(),
2822+
addEventListener: jest.fn(() => DEFAULT_EMITTER.removeEventListener),
2823+
};
2824+
function makeCancelableEventPromise(
2825+
table = DEFAULT_EMITTER,
2826+
event = DEFAULT_EVENT,
2827+
timeout = 0,
2828+
matcher = undefined
2829+
) {
2830+
const promise = TableUtils.makeCancelableEventPromise(table, event, {
2831+
timeout,
2832+
matcher,
2833+
});
2834+
promise.catch(() => null);
2835+
return promise;
2836+
}
2837+
beforeAll(() => {
2838+
jest.useFakeTimers();
2839+
});
2840+
afterAll(() => {
2841+
jest.useRealTimers();
2842+
});
2843+
afterEach(() => {
2844+
jest.clearAllTimers();
2845+
DEFAULT_EMITTER.removeEventListener.mockClear();
2846+
});
2847+
2848+
it('Subscribes to a given event, returns a cancelable promise', () => {
2849+
const cancelablePromise = makeCancelableEventPromise();
2850+
expect(DEFAULT_EMITTER.addEventListener).toHaveBeenCalled();
2851+
expect(cancelablePromise).toBeInstanceOf(Promise);
2852+
expect(cancelablePromise).toHaveProperty('cancel');
2853+
});
2854+
it('Multiple cancel calls clean up subscription only once', () => {
2855+
const cancelablePromise = makeCancelableEventPromise();
2856+
cancelablePromise.cancel();
2857+
cancelablePromise.cancel();
2858+
expect(DEFAULT_EMITTER.removeEventListener).toHaveBeenCalledTimes(1);
2859+
});
2860+
it('Timeout rejects promise and cleans up subscription', () => {
2861+
const cancelablePromise = makeCancelableEventPromise();
2862+
jest.runOnlyPendingTimers();
2863+
expect.assertions(2);
2864+
expect(DEFAULT_EMITTER.removeEventListener).toHaveBeenCalledTimes(1);
2865+
expect(cancelablePromise).rejects.not.toBeNull();
2866+
});
2867+
it('Cancel after timeout cleans up subscription only once', () => {
2868+
const cancelablePromise = makeCancelableEventPromise();
2869+
jest.runOnlyPendingTimers();
2870+
cancelablePromise.cancel();
2871+
expect(DEFAULT_EMITTER.removeEventListener).toHaveBeenCalledTimes(1);
2872+
});
2873+
});
2874+
28182875
describe('converts filter type to appropriate string value for quick filters', () => {
28192876
function testFilterType(filterType: FilterTypeValue, expectedResult: string) {
28202877
expect(TableUtils.getFilterOperatorString(filterType)).toBe(expectedResult);

packages/jsapi-utils/src/TableUtils.ts

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@ export class TableUtils {
622622
* @param eventName Event to listen for
623623
* @param timeout Event timeout in milliseconds, defaults to 0
624624
* @param matcher Optional function to determine if the promise can be resolved or stays pending
625+
* @deprecated Use `makeCancelableEventPromise` instead.
625626
* @returns Resolves with the event data
626627
*/
627628
static makeCancelableTableEventPromise<TEventDetails = unknown>(
@@ -630,6 +631,30 @@ export class TableUtils {
630631
timeout = 0,
631632
matcher: ((event: DhType.Event<TEventDetails>) => boolean) | null = null
632633
): CancelablePromise<DhType.Event<TEventDetails>> {
634+
return TableUtils.makeCancelableEventPromise(table, eventName, {
635+
timeout,
636+
matcher: matcher ?? undefined,
637+
});
638+
}
639+
640+
/**
641+
* Make a cancelable promise for a one-shot event with a timeout.
642+
* @param emitter The object emitting the event
643+
* @param eventName Event to listen for
644+
* @param options Optional options for listening
645+
* @param options.timeout Event timeout in milliseconds, defaults to 0
646+
* @param options.matcher Optional function to determine if the promise can be resolved or stays pending
647+
* @returns Resolves with the event data
648+
*/
649+
static makeCancelableEventPromise<TEventDetails = unknown>(
650+
emitter: DhType.HasEventHandling,
651+
eventName: string,
652+
options: {
653+
timeout?: number;
654+
matcher?: (event: DhType.Event<TEventDetails>) => boolean;
655+
} = {}
656+
): CancelablePromise<DhType.Event<TEventDetails>> {
657+
const { timeout = 0, matcher } = options;
633658
let eventCleanup: () => void;
634659
let timeoutId: ReturnType<typeof setTimeout>;
635660
let isPending = true;
@@ -639,17 +664,20 @@ export class TableUtils {
639664
isPending = false;
640665
reject(new TimeoutError(`Event "${eventName}" timed out.`));
641666
}, timeout);
642-
eventCleanup = table.addEventListener<TEventDetails>(eventName, event => {
643-
if (matcher != null && !matcher(event)) {
644-
log.debug2('Event triggered, but matcher returned false.');
645-
return;
667+
eventCleanup = emitter.addEventListener<TEventDetails>(
668+
eventName,
669+
event => {
670+
if (matcher != null && !matcher(event)) {
671+
log.debug2('Event triggered, but matcher returned false.');
672+
return;
673+
}
674+
log.debug2('Event triggered, resolving.');
675+
eventCleanup();
676+
clearTimeout(timeoutId);
677+
isPending = false;
678+
resolve(event);
646679
}
647-
log.debug2('Event triggered, resolving.');
648-
eventCleanup();
649-
clearTimeout(timeoutId);
650-
isPending = false;
651-
resolve(event);
652-
});
680+
);
653681
}) as CancelablePromise<DhType.Event<TEventDetails>>;
654682
wrappedPromise.cancel = () => {
655683
if (isPending) {

0 commit comments

Comments
 (0)