diff --git a/spec/observables/fromEvent-spec.ts b/spec/observables/fromEvent-spec.ts index 673e400b7d..fcb3251b5e 100644 --- a/spec/observables/fromEvent-spec.ts +++ b/spec/observables/fromEvent-spec.ts @@ -1,9 +1,12 @@ import { expect } from 'chai'; import { expectObservable } from '../helpers/marble-testing'; -import { fromEvent, NEVER, timer, pipe } from 'rxjs'; +import { Observable, fromEvent, NEVER, timer, pipe } from 'rxjs'; +import { NodeStyleEventEmitter, NodeCompatibleEventEmitter, NodeEventHandler } from 'rxjs/internal/observable/fromEvent'; import { mapTo, take, concat } from 'rxjs/operators'; import { TestScheduler } from 'rxjs/testing'; +declare const type: Function; + declare function asDiagram(arg: string): Function; declare const rxTestScheduler: TestScheduler; @@ -89,7 +92,7 @@ describe('fromEvent', () => { expect(offHandler).to.equal(onHandler); }); - it('should setup an event observable on objects with "addListener" and "removeListener" ', () => { + it('should setup an event observable on objects with "addListener" and "removeListener" returning event emitter', () => { let onEventName; let onHandler; let offEventName; @@ -121,6 +124,37 @@ describe('fromEvent', () => { expect(offHandler).to.equal(onHandler); }); + it('should setup an event observable on objects with "addListener" and "removeListener" returning nothing', () => { + let onEventName; + let onHandler; + let offEventName; + let offHandler; + + const obj = { + addListener(a: string, b: (...args: any[]) => any, context?: any): { context: any } { + onEventName = a; + onHandler = b; + return { context: '' }; + }, + removeListener(a: string, b: (...args: any[]) => void) { + offEventName = a; + offHandler = b; + } + }; + + const subscription = fromEvent(obj, 'click') + .subscribe(() => { + //noop + }); + + subscription.unsubscribe(); + + expect(onEventName).to.equal('click'); + expect(typeof onHandler).to.equal('function'); + expect(offEventName).to.equal(onEventName); + expect(offHandler).to.equal(onHandler); + }); + it('should setup an event observable on objects with "addListener" and "removeListener" and "length" ', () => { let onEventName; let onHandler; @@ -363,4 +397,45 @@ describe('fromEvent', () => { }).to.not.throw(TypeError); }); + type('should support node style event emitters interfaces', () => { + /* tslint:disable:no-unused-variable */ + let a: NodeStyleEventEmitter; + let b: Observable = fromEvent(a, 'mock'); + /* tslint:enable:no-unused-variable */ + }); + + type('should support node compatible event emitters interfaces', () => { + /* tslint:disable:no-unused-variable */ + let a: NodeCompatibleEventEmitter; + let b: Observable = fromEvent(a, 'mock'); + /* tslint:enable:no-unused-variable */ + }); + + type('should support node style event emitters objects', () => { + /* tslint:disable:no-unused-variable */ + interface NodeEventEmitter { + addListener(eventType: string | symbol, handler: NodeEventHandler): this; + removeListener(eventType: string | symbol, handler: NodeEventHandler): this; + } + let a: NodeEventEmitter; + let b: Observable = fromEvent(a, 'mock'); + /* tslint:enable:no-unused-variable */ + }); + + type('should support React Native event emitters', () => { + /* tslint:disable:no-unused-variable */ + interface EmitterSubscription { + context: any; + } + interface ReactNativeEventEmitterListener { + addListener(eventType: string, listener: (...args: any[]) => any, context?: any): EmitterSubscription; + } + interface ReactNativeEventEmitter extends ReactNativeEventEmitterListener { + removeListener(eventType: string, listener: (...args: any[]) => any): void; + } + let a: ReactNativeEventEmitter; + let b: Observable = fromEvent(a, 'mock'); + /* tslint:enable:no-unused-variable */ + }); + }); diff --git a/src/internal/observable/fromEvent.ts b/src/internal/observable/fromEvent.ts index e6019b0152..c8b8c6d4f0 100644 --- a/src/internal/observable/fromEvent.ts +++ b/src/internal/observable/fromEvent.ts @@ -13,6 +13,14 @@ export interface NodeStyleEventEmitter { export type NodeEventHandler = (...args: any[]) => void; +// For APIs that implement `addListener` and `removeListener` methods that may +// not use the same arguments or return EventEmitter values +// such as React Native +export interface NodeCompatibleEventEmitter { + addListener: (eventName: string, handler: NodeEventHandler) => void | {}; + removeListener: (eventName: string, handler: NodeEventHandler) => void | {}; +} + export interface JQueryStyleEventEmitter { on: (eventName: string, handler: Function) => void; off: (eventName: string, handler: Function) => void; @@ -23,7 +31,7 @@ export interface HasEventTargetAddRemove { removeEventListener(type: string, listener?: ((evt: E) => void) | null, options?: EventListenerOptions | boolean): void; } -export type EventTargetLike = HasEventTargetAddRemove | NodeStyleEventEmitter | JQueryStyleEventEmitter; +export type EventTargetLike = HasEventTargetAddRemove | NodeStyleEventEmitter | NodeCompatibleEventEmitter | JQueryStyleEventEmitter; export type FromEventTarget = EventTargetLike | ArrayLike>;