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

Fix/6064 default if empty and related fixes #6070

Merged
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions api_guard/dist/types/operators/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export declare function debounce<T>(durationSelector: (value: T) => ObservableIn

export declare function debounceTime<T>(dueTime: number, scheduler?: SchedulerLike): MonoTypeOperatorFunction<T>;

export declare function defaultIfEmpty<T, R = T>(defaultValue?: R): OperatorFunction<T, T | R>;
export declare function defaultIfEmpty<T, R>(defaultValue: R): OperatorFunction<T, T | R>;

export declare function delay<T>(due: number | Date, scheduler?: SchedulerLike): MonoTypeOperatorFunction<T>;

Expand All @@ -86,7 +86,8 @@ export declare function distinctUntilChanged<T, K>(comparator: (previous: K, cur
export declare function distinctUntilKeyChanged<T>(key: keyof T): MonoTypeOperatorFunction<T>;
export declare function distinctUntilKeyChanged<T, K extends keyof T>(key: K, compare: (x: T[K], y: T[K]) => boolean): MonoTypeOperatorFunction<T>;

export declare function elementAt<T>(index: number, defaultValue?: T): MonoTypeOperatorFunction<T>;
export declare function elementAt<T>(index: number): MonoTypeOperatorFunction<T>;
export declare function elementAt<T, D>(index: number, defaultValue: D): OperatorFunction<T, T | D>;

export declare function endWith<T>(scheduler: SchedulerLike): MonoTypeOperatorFunction<T>;
export declare function endWith<T, A>(v1: A, scheduler: SchedulerLike): OperatorFunction<T, T | A>;
Expand Down
10 changes: 8 additions & 2 deletions spec-dtslint/operators/defaultIfEmpty-spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { of } from 'rxjs';
import { EMPTY, of } from 'rxjs';
import { defaultIfEmpty, map } from 'rxjs/operators';

it('should infer correctly', () => {
const o = of(1, 2, 3).pipe(defaultIfEmpty()); // $ExpectType Observable<number>
const o = of(1, 2, 3).pipe(defaultIfEmpty()); // $ExpectError
const o2 = of(undefined).pipe(defaultIfEmpty(undefined)); // $ExpectType Observable<undefined>
});

it('should infer correctly with a defaultValue', () => {
Expand All @@ -11,6 +12,7 @@ it('should infer correctly with a defaultValue', () => {

it('should infer correctly with a different type of defaultValue', () => {
const o = of(1, 2, 3).pipe(defaultIfEmpty<number, string>('carbonara')); // $ExpectType Observable<string | number>
const o2 = of(1, 2, 3).pipe(defaultIfEmpty('carbonara')); // $ExpectType Observable<string | number>
});

it('should infer correctly with a subtype passed through parameters', () => {
Expand All @@ -20,3 +22,7 @@ it('should infer correctly with a subtype passed through parameters', () => {
it('should enforce types', () => {
const o = of(1, 2, 3).pipe(defaultIfEmpty(4, 5)); // $ExpectError
});

it('should handle Observable<never> appropriately', () => {
const o = EMPTY.pipe(defaultIfEmpty('blah')); // $ExpectType Observable<string>
})
8 changes: 6 additions & 2 deletions spec-dtslint/operators/elementAt-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ it('should enforce types', () => {
const o = of('foo').pipe(elementAt()); // $ExpectError
});

it('should enforce of index', () => {
it('should enforce passing the index', () => {
const o = of('foo').pipe(elementAt('foo')); // $ExpectError
});

it('should enforce of default', () => {
const o = of('foo').pipe(elementAt(5, 5)); // $ExpectError
const o = of('foo').pipe(elementAt(5, 5)); // $ExpectType Observable<string | number>
});

it('should allow undefined default', () => {
const o = of('foo').pipe(elementAt(100, undefined)); // $ExpectType Observable<string | undefined>
});
19 changes: 4 additions & 15 deletions spec/operators/defaultIfEmpty-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,6 @@ describe('defaultIfEmpty', () => {
});
});

it('should return null if the Observable is empty and no arguments', () => {
testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => {
const e1 = cold(' |');
const e1subs = ' (^!)';
const expected = '(x|)';

expectObservable(e1.pipe(defaultIfEmpty())).toBe(expected, { x: null });
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});
});

it('should return the Observable if not empty with a default value', () => {
testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {
const e1 = hot(' --a--b--|');
Expand All @@ -57,13 +46,13 @@ describe('defaultIfEmpty', () => {
});
});

it('should return the Observable if not empty with no default value', () => {
it('should allow undefined as a default value', () => {
testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {
const e1 = hot(' --a--b--|');
const e1 = hot(' --------|');
const e1subs = ' ^-------!';
const expected = '--a--b--|';
const expected = '--------(U|)';

expectObservable(e1.pipe(defaultIfEmpty())).toBe(expected);
expectObservable(e1.pipe(defaultIfEmpty(undefined))).toBe(expected, { U: undefined });
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});
});
Expand Down
11 changes: 11 additions & 0 deletions spec/operators/elementAt-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ describe('elementAt', () => {
});
});

it('should allow undefined as a default value', () => {
testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {
const e1 = hot(' -----a--a---a-| ');
const e1subs = ' ^-------------! ';
const expected = '--------------(U|)';

expectObservable(e1.pipe(elementAt(100, undefined))).toBe(expected, { U: undefined });
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});
});

it('should return non-first element by zero-based index', () => {
testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {
const e1 = hot(' --a--b--c--d--e--f--|');
Expand Down
11 changes: 11 additions & 0 deletions spec/operators/first-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ describe('first', () => {
});
});

it('should allow undefined as a default value', () => {
testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {
const e1 = hot(' -----a--a---a-| ');
const e1subs = ' ^-------------! ';
const expected = '--------------(U|)';

expectObservable(e1.pipe(first((value) => value === 'b', undefined))).toBe(expected, { U: undefined });
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});
});

it('should error on empty', () => {
testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {
const e1 = hot('--a--^----|');
Expand Down
11 changes: 11 additions & 0 deletions spec/operators/last-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ describe('last', () => {
});
});

it('should allow undefined as a default value', () => {
testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {
const e1 = hot(' -----a--a---a-| ');
const e1subs = ' ^-------------! ';
const expected = '--------------(U|)';

expectObservable(e1.pipe(last((value) => value === 'b', undefined))).toBe(expected, { U: undefined });
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});
});

it('should return last element matches with predicate', () => {
testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {
const e1 = hot(' --a--b--a--b--| ');
Expand Down
10 changes: 3 additions & 7 deletions src/internal/operators/defaultIfEmpty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import { OperatorFunction } from '../types';
import { operate } from '../util/lift';
import { OperatorSubscriber } from './OperatorSubscriber';

/* tslint:disable:max-line-length */
export function defaultIfEmpty<T, R = T>(defaultValue?: R): OperatorFunction<T, T | R>;
/* tslint:enable:max-line-length */

/**
* Emits a given value if the source Observable completes without emitting any
* `next` value, otherwise mirrors the source Observable.
Expand Down Expand Up @@ -34,13 +30,13 @@ export function defaultIfEmpty<T, R = T>(defaultValue?: R): OperatorFunction<T,
* @see {@link empty}
* @see {@link last}
*
* @param {any} [defaultValue=null] The default value used if the source
* @param defaultValue The default value used if the source
* Observable is empty.
* @return {Observable} An Observable that emits either the specified
* @return An Observable that emits either the specified
* `defaultValue` if the source Observable emits no items, or the values emitted
* by the source Observable.
*/
export function defaultIfEmpty<T, R>(defaultValue: R | null = null): OperatorFunction<T, T | R> {
export function defaultIfEmpty<T, R>(defaultValue: R): OperatorFunction<T, T | R> {
return operate((source, subscriber) => {
let hasValue = false;
source.subscribe(
Expand Down
24 changes: 14 additions & 10 deletions src/internal/operators/elementAt.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { ArgumentOutOfRangeError } from '../util/ArgumentOutOfRangeError';
import { Observable } from '../Observable';
import { MonoTypeOperatorFunction } from '../types';
import { MonoTypeOperatorFunction, OperatorFunction } from '../types';
import { filter } from './filter';
import { throwIfEmpty } from './throwIfEmpty';
import { defaultIfEmpty } from './defaultIfEmpty';
import { take } from './take';

export function elementAt<T>(index: number): MonoTypeOperatorFunction<T>;
export function elementAt<T, D>(index: number, defaultValue: D): OperatorFunction<T, T | D>;

/**
* Emits the single value at the specified `index` in a sequence of emissions
* from the source Observable.
Expand Down Expand Up @@ -52,14 +55,15 @@ import { take } from './take';
* @return {Observable} An Observable that emits a single item, if it is found.
* Otherwise, will emit the default value if given. If not, then emits an error.
*/
export function elementAt<T>(index: number, defaultValue?: T): MonoTypeOperatorFunction<T> {
if (index < 0) { throw new ArgumentOutOfRangeError(); }
export function elementAt<T, D>(index: number, defaultValue?: D): OperatorFunction<T, T | D> {
if (index < 0) {
throw new ArgumentOutOfRangeError();
}
const hasDefaultValue = arguments.length >= 2;
return (source: Observable<T>) => source.pipe(
filter((v, i) => i === index),
take(1),
hasDefaultValue
? defaultIfEmpty(defaultValue)
: throwIfEmpty(() => new ArgumentOutOfRangeError()),
);
return (source: Observable<T>) =>
source.pipe(
filter((v, i) => i === index),
take(1),
hasDefaultValue ? defaultIfEmpty(defaultValue!) : throwIfEmpty(() => new ArgumentOutOfRangeError())
);
}
2 changes: 1 addition & 1 deletion src/internal/operators/first.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,6 @@ export function first<T, D>(
source.pipe(
predicate ? filter((v, i) => predicate(v, i, source)) : identity,
take(1),
hasDefaultValue ? defaultIfEmpty<T, D>(defaultValue) : throwIfEmpty(() => new EmptyError())
hasDefaultValue ? defaultIfEmpty(defaultValue!) : throwIfEmpty(() => new EmptyError())
);
}
2 changes: 1 addition & 1 deletion src/internal/operators/last.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,6 @@ export function last<T, D>(
source.pipe(
predicate ? filter((v, i) => predicate(v, i, source)) : identity,
takeLast(1),
hasDefaultValue ? defaultIfEmpty<T, D>(defaultValue) : throwIfEmpty(() => new EmptyError())
hasDefaultValue ? defaultIfEmpty(defaultValue!) : throwIfEmpty(() => new EmptyError())
);
}