Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor from event #3499

Merged
merged 2 commits into from Mar 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
135 changes: 123 additions & 12 deletions spec/observables/fromEvent-spec.ts
@@ -1,6 +1,7 @@
import { expect } from 'chai';
import { expectObservable } from '../helpers/marble-testing';
import { fromEvent, NEVER, timer } from 'rxjs';
import { fromEvent, NEVER, timer, pipe } from 'rxjs';
import { mapTo, take, concat } from 'rxjs/operators';
import { TestScheduler } from 'rxjs/testing';

declare function asDiagram(arg: string): Function;
Expand All @@ -11,17 +12,19 @@ describe('fromEvent', () => {
asDiagram('fromEvent(element, \'click\')')
('should create an observable of click on the element', () => {
const target = {
addEventListener: (eventType, listener) => {
addEventListener: (eventType: any, listener: any) => {
timer(50, 20, rxTestScheduler)
.mapTo('ev')
.take(2)
.concat(NEVER)
.pipe(
mapTo('ev'),
take(2),
concat(NEVER)
)
.subscribe(listener);
},
removeEventListener: () => void 0,
dispatchEvent: () => void 0,
removeEventListener: (): void => void 0,
dispatchEvent: (): void => void 0,
};
const e1 = fromEvent(target, 'click');
const e1 = fromEvent(target as any, 'click');
const expected = '-----x-x---';
expectObservable(e1).toBe(expected, {x: 'ev'});
});
Expand Down Expand Up @@ -158,7 +161,7 @@ describe('fromEvent', () => {
});

it('should pass through events that occur', (done: MochaDone) => {
let send;
let send: any;
const obj = {
on: (name: string, handler: Function) => {
send = handler;
Expand All @@ -168,7 +171,7 @@ describe('fromEvent', () => {
}
};

fromEvent(obj, 'click').take(1)
fromEvent(obj, 'click').pipe(take(1))
.subscribe((e: any) => {
expect(e).to.equal('test');
}, (err: any) => {
Expand All @@ -180,8 +183,116 @@ describe('fromEvent', () => {
send('test');
});

it('should pass through events that occur and use the selector if provided', (done: MochaDone) => {
let send: any;
const obj = {
on: (name: string, handler: Function) => {
send = handler;
},
off: () => {
//noop
}
};

function selector(x: string) {
return x + '!';
}

fromEvent(obj, 'click', selector).pipe(take(1))
.subscribe((e: any) => {
expect(e).to.equal('test!');
}, (err: any) => {
done(new Error('should not be called'));
}, () => {
done();
});

send('test');
});

it('should not fail if no event arguments are passed and the selector does not return', (done: MochaDone) => {
let send: any;
const obj = {
on: (name: string, handler: Function) => {
send = handler;
},
off: () => {
//noop
}
};

function selector() {
//noop
}

fromEvent(obj, 'click', selector).pipe(take(1))
.subscribe((e: any) => {
expect(e).not.exist;
}, (err: any) => {
done(new Error('should not be called'));
}, () => {
done();
});

send();
});

it('should return a value from the selector if no event arguments are passed', (done: MochaDone) => {
let send: any;
const obj = {
on: (name: string, handler: Function) => {
send = handler;
},
off: () => {
//noop
}
};

function selector() {
return 'no arguments';
}

fromEvent(obj, 'click', selector).pipe(take(1))
.subscribe((e: any) => {
expect(e).to.equal('no arguments');
}, (err: any) => {
done(new Error('should not be called'));
}, () => {
done();
});

send();
});

it('should pass multiple arguments to selector from event emitter', (done: MochaDone) => {
let send: any;
const obj = {
on: (name: string, handler: Function) => {
send = handler;
},
off: () => {
//noop
}
};

function selector(x: number, y: number, z: number) {
return [].slice.call(arguments);
}

fromEvent(obj, 'click', selector).pipe(take(1))
.subscribe((e: any) => {
expect(e).to.deep.equal([1, 2, 3]);
}, (err: any) => {
done(new Error('should not be called'));
}, () => {
done();
});

send(1, 2, 3);
});

it('should emit multiple arguments from event as an array', (done: MochaDone) => {
let send;
let send: any;
const obj = {
on: (name: string, handler: Function) => {
send = handler;
Expand All @@ -191,7 +302,7 @@ describe('fromEvent', () => {
}
};

fromEvent(obj, 'click').take(1)
fromEvent(obj, 'click').pipe(take(1))
.subscribe((e: any) => {
expect(e).to.deep.equal([1, 2, 3]);
}, (err: any) => {
Expand Down
74 changes: 68 additions & 6 deletions spec/observables/fromEventPattern-spec.ts
Expand Up @@ -3,6 +3,7 @@ import * as sinon from 'sinon';
import { expectObservable } from '../helpers/marble-testing';

import { fromEventPattern, noop, NEVER, timer } from 'rxjs';
import { mapTo, take, concat } from 'rxjs/operators';
import { TestScheduler } from 'rxjs/testing';

declare function asDiagram(arg: string): Function;
Expand All @@ -12,12 +13,12 @@ declare const rxTestScheduler: TestScheduler;
describe('fromEventPattern', () => {
asDiagram('fromEventPattern(addHandler, removeHandler)')
('should create an observable from the handler API', () => {
function addHandler(h) {
timer(50, 20, rxTestScheduler)
.mapTo('ev')
.take(2)
.concat(NEVER)
.subscribe(h);
function addHandler(h: any) {
timer(50, 20, rxTestScheduler).pipe(
mapTo('ev'),
take(2),
concat(NEVER)
).subscribe(h);
}
const e1 = fromEventPattern(addHandler);
const expected = '-----x-x---';
Expand Down Expand Up @@ -70,4 +71,65 @@ describe('fromEventPattern', () => {
done();
}, () => done(new Error('should not be called')));
});

it('should accept a selector that maps outgoing values', (done: MochaDone) => {
let target: any;
const trigger = function (...args: any[]) {
if (target) {
target.apply(null, arguments);
}
};

const addHandler = (handler: any) => {
target = handler;
};
const removeHandler = (handler: any) => {
target = null;
};
const selector = (a: any, b: any) => {
return a + b + '!';
};

fromEventPattern(addHandler, removeHandler, selector).pipe(take(1))
.subscribe((x: any) => {
expect(x).to.equal('testme!');
}, (err: any) => {
done(new Error('should not be called'));
}, () => {
done();
});

trigger('test', 'me');
});

it('should send errors in the selector down the error path', (done: MochaDone) => {
let target: any;
const trigger = (value: any) => {
if (target) {
target(value);
}
};

const addHandler = (handler: any) => {
target = handler;
};
const removeHandler = (handler: any) => {
target = null;
};
const selector = (x: any) => {
throw 'bad';
};

fromEventPattern(addHandler, removeHandler, selector)
.subscribe((x: any) => {
done(new Error('should not be called'));
}, (err: any) => {
expect(err).to.equal('bad');
done();
}, () => {
done(new Error('should not be called'));
});

trigger('test');
});
});
22 changes: 20 additions & 2 deletions src/internal/observable/fromEvent.ts
@@ -1,6 +1,8 @@
import { Observable } from '../Observable';
import { isArray } from '../util/isArray';
import { isFunction } from '../util/isFunction';
import { Subscriber } from '../Subscriber';
import { map } from '../operators/map';

const toString: Function = Object.prototype.toString;

Expand All @@ -24,8 +26,11 @@ export type EventListenerOptions = {

/* tslint:disable:max-line-length */
export function fromEvent<T>(target: EventTargetLike, eventName: string): Observable<T>;
export function fromEvent<T>(target: EventTargetLike, eventName: string): Observable<T>;
/** @deprecated resultSelector no longer supported, pipe to map instead */
export function fromEvent<T>(target: EventTargetLike, eventName: string, resultSelector: (...args: any[]) => T): Observable<T>;
export function fromEvent<T>(target: EventTargetLike, eventName: string, options: EventListenerOptions): Observable<T>;
/** @deprecated resultSelector no longer supported, pipe to map instead */
export function fromEvent<T>(target: EventTargetLike, eventName: string, options: EventListenerOptions, resultSelector: (...args: any[]) => T): Observable<T>;
/* tslint:enable:max-line-length */

/**
Expand Down Expand Up @@ -142,9 +147,22 @@ export function fromEvent<T>(target: EventTargetLike, eventName: string, options
export function fromEvent<T>(
target: EventTargetLike,
eventName: string,
options?: EventListenerOptions
options?: EventListenerOptions | ((...args: any[]) => T),
resultSelector?: ((...args: any[]) => T)
): Observable<T> {

if (isFunction(options)) {
// DEPRECATED PATH
resultSelector = options;
options = undefined;
}
if (resultSelector) {
// DEPRECATED PATH
return fromEvent<T>(target, eventName, <EventListenerOptions | undefined>options).pipe(
map(args => isArray(args) ? resultSelector(...args) : resultSelector(args))
);
}

return new Observable<T>(subscriber => {
function handler(e: T) {
if (arguments.length > 1) {
Expand Down
24 changes: 21 additions & 3 deletions src/internal/observable/fromEventPattern.ts
@@ -1,5 +1,14 @@
import { Observable } from '../Observable';
import { isArray } from '../util/isArray';
import { isFunction } from '../util/isFunction';
import { fromEvent } from './fromEvent';
import { map } from '../operators/map';

/* tslint:disable:max-line-length */
export function fromEventPattern<T>(addHandler: (handler: Function) => any, removeHandler?: (handler: Function, signal?: any) => void): Observable<T>;
/** @deprecated resultSelector no longer supported, pipe to map instead */
export function fromEventPattern<T>(addHandler: (handler: Function) => any, removeHandler?: (handler: Function, signal?: any) => void, resultSelector?: (...args: any[]) => T): Observable<T>;
/* tslint:enable:max-line-length */

/**
* Creates an Observable from an API based on addHandler/removeHandler
Expand Down Expand Up @@ -44,9 +53,18 @@ import { isFunction } from '../util/isFunction';
* @name fromEventPattern
*/
export function fromEventPattern<T>(addHandler: (handler: Function) => any,
removeHandler?: (handler: Function, signal?: any) => void) {
return new Observable<T>(subscriber => {
const handler = (e: T) => subscriber.next(e);
removeHandler?: (handler: Function, signal?: any) => void,
resultSelector?: (...args: any[]) => T): Observable<T | T[]> {

if (resultSelector) {
// DEPRECATED PATH
return fromEventPattern<T>(addHandler, removeHandler).pipe(
map(args => isArray(args) ? resultSelector(...args) : resultSelector(args))
);
}

return new Observable<T | T[]>(subscriber => {
const handler = (...e: T[]) => subscriber.next(e.length === 1 ? e[0] : e);

let retValue: any;
try {
Expand Down