From bdd52f31a61385f7a29b6241a43d8fcf3d11d4c5 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Mon, 1 Mar 2021 16:54:17 -0600 Subject: [PATCH 1/5] fix(defaultIfEmpty): Allow `undefined` as an argument, require an argument BREAKING CHANGE: `defaultIfEmpty` requires a value be passed. Will no longer convert `undefined` to `null` for no good reason. Resolves #6064 --- api_guard/dist/types/operators/index.d.ts | 5 +++-- spec-dtslint/operators/defaultIfEmpty-spec.ts | 10 ++++++++-- spec/operators/defaultIfEmpty-spec.ts | 19 ++++--------------- src/internal/operators/defaultIfEmpty.ts | 10 +++------- 4 files changed, 18 insertions(+), 26 deletions(-) diff --git a/api_guard/dist/types/operators/index.d.ts b/api_guard/dist/types/operators/index.d.ts index 6fe502923a..886f3b437e 100644 --- a/api_guard/dist/types/operators/index.d.ts +++ b/api_guard/dist/types/operators/index.d.ts @@ -68,7 +68,7 @@ export declare function debounce(durationSelector: (value: T) => ObservableIn export declare function debounceTime(dueTime: number, scheduler?: SchedulerLike): MonoTypeOperatorFunction; -export declare function defaultIfEmpty(defaultValue?: R): OperatorFunction; +export declare function defaultIfEmpty(defaultValue: R): OperatorFunction; export declare function delay(due: number | Date, scheduler?: SchedulerLike): MonoTypeOperatorFunction; @@ -86,7 +86,8 @@ export declare function distinctUntilChanged(comparator: (previous: K, cur export declare function distinctUntilKeyChanged(key: keyof T): MonoTypeOperatorFunction; export declare function distinctUntilKeyChanged(key: K, compare: (x: T[K], y: T[K]) => boolean): MonoTypeOperatorFunction; -export declare function elementAt(index: number, defaultValue?: T): MonoTypeOperatorFunction; +export declare function elementAt(index: number): MonoTypeOperatorFunction; +export declare function elementAt(index: number, defaultValue: D): OperatorFunction; export declare function endWith(scheduler: SchedulerLike): MonoTypeOperatorFunction; export declare function endWith(v1: A, scheduler: SchedulerLike): OperatorFunction; diff --git a/spec-dtslint/operators/defaultIfEmpty-spec.ts b/spec-dtslint/operators/defaultIfEmpty-spec.ts index 84ee0877f0..00be547bfa 100644 --- a/spec-dtslint/operators/defaultIfEmpty-spec.ts +++ b/spec-dtslint/operators/defaultIfEmpty-spec.ts @@ -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 + const o = of(1, 2, 3).pipe(defaultIfEmpty()); // $ExpectError + const o2 = of(undefined).pipe(defaultIfEmpty(undefined)); // $ExpectType Observable }); it('should infer correctly with a defaultValue', () => { @@ -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('carbonara')); // $ExpectType Observable + const o2 = of(1, 2, 3).pipe(defaultIfEmpty('carbonara')); // $ExpectType Observable }); it('should infer correctly with a subtype passed through parameters', () => { @@ -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 appropriately', () => { + const o = EMPTY.pipe(defaultIfEmpty('blah')); // $ExpectType Observable +}) diff --git a/spec/operators/defaultIfEmpty-spec.ts b/spec/operators/defaultIfEmpty-spec.ts index db501ac07b..efb5d01408 100644 --- a/spec/operators/defaultIfEmpty-spec.ts +++ b/spec/operators/defaultIfEmpty-spec.ts @@ -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--|'); @@ -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); }); }); diff --git a/src/internal/operators/defaultIfEmpty.ts b/src/internal/operators/defaultIfEmpty.ts index aaf8887710..ceba8d4962 100644 --- a/src/internal/operators/defaultIfEmpty.ts +++ b/src/internal/operators/defaultIfEmpty.ts @@ -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(defaultValue?: R): OperatorFunction; -/* tslint:enable:max-line-length */ - /** * Emits a given value if the source Observable completes without emitting any * `next` value, otherwise mirrors the source Observable. @@ -34,13 +30,13 @@ export function defaultIfEmpty(defaultValue?: R): OperatorFunction(defaultValue: R | null = null): OperatorFunction { +export function defaultIfEmpty(defaultValue: R): OperatorFunction { return operate((source, subscriber) => { let hasValue = false; source.subscribe( From 9988a189132a547eb6c824effe2c81ad0012310e Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Mon, 1 Mar 2021 16:55:54 -0600 Subject: [PATCH 2/5] fix(last): Allow `defaultValue` of `undefined`. --- spec/operators/last-spec.ts | 11 +++++++++++ src/internal/operators/last.ts | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/operators/last-spec.ts b/spec/operators/last-spec.ts index 15d093d7c3..040359cfad 100644 --- a/spec/operators/last-spec.ts +++ b/spec/operators/last-spec.ts @@ -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--| '); diff --git a/src/internal/operators/last.ts b/src/internal/operators/last.ts index 11c6ba649c..ad8803a447 100644 --- a/src/internal/operators/last.ts +++ b/src/internal/operators/last.ts @@ -77,6 +77,6 @@ export function last( source.pipe( predicate ? filter((v, i) => predicate(v, i, source)) : identity, takeLast(1), - hasDefaultValue ? defaultIfEmpty(defaultValue) : throwIfEmpty(() => new EmptyError()) + hasDefaultValue ? defaultIfEmpty(defaultValue!) : throwIfEmpty(() => new EmptyError()) ); } From 5b1f798be5e318dd9e801be1e66bbfd8bb1c714a Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Mon, 1 Mar 2021 16:56:24 -0600 Subject: [PATCH 3/5] fix(first): Allow `defaultValue` of `undefined`. --- spec/operators/first-spec.ts | 11 +++++++++++ src/internal/operators/first.ts | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/operators/first-spec.ts b/spec/operators/first-spec.ts index a8b7b30fac..10d238c803 100644 --- a/spec/operators/first-spec.ts +++ b/spec/operators/first-spec.ts @@ -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--^----|'); diff --git a/src/internal/operators/first.ts b/src/internal/operators/first.ts index 0598817e93..ee801042cc 100644 --- a/src/internal/operators/first.ts +++ b/src/internal/operators/first.ts @@ -85,6 +85,6 @@ export function first( source.pipe( predicate ? filter((v, i) => predicate(v, i, source)) : identity, take(1), - hasDefaultValue ? defaultIfEmpty(defaultValue) : throwIfEmpty(() => new EmptyError()) + hasDefaultValue ? defaultIfEmpty(defaultValue!) : throwIfEmpty(() => new EmptyError()) ); } From 068cc1bcb98bcfcd899360b10063d15e225022b9 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Mon, 1 Mar 2021 16:57:06 -0600 Subject: [PATCH 4/5] fix(elementAt): Allow `defaultValue` of `undefined`. --- spec-dtslint/operators/elementAt-spec.ts | 8 ++++++-- spec/operators/elementAt-spec.ts | 11 +++++++++++ src/internal/operators/elementAt.ts | 24 ++++++++++++++---------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/spec-dtslint/operators/elementAt-spec.ts b/spec-dtslint/operators/elementAt-spec.ts index 286bfeb349..d3d206808a 100644 --- a/spec-dtslint/operators/elementAt-spec.ts +++ b/spec-dtslint/operators/elementAt-spec.ts @@ -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 +}); + +it('should allow undefined default', () => { + const o = of('foo').pipe(elementAt(100, undefined)); // $ExpectType Observable }); diff --git a/spec/operators/elementAt-spec.ts b/spec/operators/elementAt-spec.ts index 18820699ca..5a5bd19f56 100644 --- a/spec/operators/elementAt-spec.ts +++ b/spec/operators/elementAt-spec.ts @@ -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--|'); diff --git a/src/internal/operators/elementAt.ts b/src/internal/operators/elementAt.ts index 83bbb08f2a..6b3d7bdfde 100644 --- a/src/internal/operators/elementAt.ts +++ b/src/internal/operators/elementAt.ts @@ -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(index: number): MonoTypeOperatorFunction; +export function elementAt(index: number, defaultValue: D): OperatorFunction; + /** * Emits the single value at the specified `index` in a sequence of emissions * from the source Observable. @@ -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(index: number, defaultValue?: T): MonoTypeOperatorFunction { - if (index < 0) { throw new ArgumentOutOfRangeError(); } +export function elementAt(index: number, defaultValue?: D): OperatorFunction { + if (index < 0) { + throw new ArgumentOutOfRangeError(); + } const hasDefaultValue = arguments.length >= 2; - return (source: Observable) => source.pipe( - filter((v, i) => i === index), - take(1), - hasDefaultValue - ? defaultIfEmpty(defaultValue) - : throwIfEmpty(() => new ArgumentOutOfRangeError()), - ); + return (source: Observable) => + source.pipe( + filter((v, i) => i === index), + take(1), + hasDefaultValue ? defaultIfEmpty(defaultValue!) : throwIfEmpty(() => new ArgumentOutOfRangeError()) + ); } From 57292f655e3e2e939ebee366e141376a1ac1a618 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Mon, 1 Mar 2021 17:58:04 -0600 Subject: [PATCH 5/5] chore: remove unused type argument --- api_guard/dist/types/operators/index.d.ts | 2 +- src/internal/operators/elementAt.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api_guard/dist/types/operators/index.d.ts b/api_guard/dist/types/operators/index.d.ts index 886f3b437e..8aefb5f1f9 100644 --- a/api_guard/dist/types/operators/index.d.ts +++ b/api_guard/dist/types/operators/index.d.ts @@ -86,7 +86,7 @@ export declare function distinctUntilChanged(comparator: (previous: K, cur export declare function distinctUntilKeyChanged(key: keyof T): MonoTypeOperatorFunction; export declare function distinctUntilKeyChanged(key: K, compare: (x: T[K], y: T[K]) => boolean): MonoTypeOperatorFunction; -export declare function elementAt(index: number): MonoTypeOperatorFunction; +export declare function elementAt(index: number): MonoTypeOperatorFunction; export declare function elementAt(index: number, defaultValue: D): OperatorFunction; export declare function endWith(scheduler: SchedulerLike): MonoTypeOperatorFunction; diff --git a/src/internal/operators/elementAt.ts b/src/internal/operators/elementAt.ts index 6b3d7bdfde..f62a0e57ea 100644 --- a/src/internal/operators/elementAt.ts +++ b/src/internal/operators/elementAt.ts @@ -6,7 +6,7 @@ import { throwIfEmpty } from './throwIfEmpty'; import { defaultIfEmpty } from './defaultIfEmpty'; import { take } from './take'; -export function elementAt(index: number): MonoTypeOperatorFunction; +export function elementAt(index: number): MonoTypeOperatorFunction; export function elementAt(index: number, defaultValue: D): OperatorFunction; /**