Skip to content

Commit

Permalink
fix(startWith): accepts N arguments and returns correct type
Browse files Browse the repository at this point in the history
- Improves documentation a bit
- Updates type tests

NOTE: Calls with more than 7 arguments that have a Scheduler at the end will return the wrong time. This is consider a corner case and low-risk

BREAKING CHANGE: `startWith` will return incorrect types when called with more than 7 arguments and a scheduler. Passing scheduler to startWith is deprecated
  • Loading branch information
benlesh committed Jan 21, 2020
1 parent 71aa2ea commit 82e3ffc
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 56 deletions.
42 changes: 18 additions & 24 deletions spec-dtslint/operators/startWith-spec.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
import { of, asyncScheduler } from 'rxjs';
import { startWith } from 'rxjs/operators';
import { a, b, c, d, e, f, g, h } from '../helpers';

it('should infer correctly with one value', () => {
const o = of(1, 2, 3).pipe(startWith(4)); // $ExpectType Observable<number>
});

it('should infer correctly with multiple values', () => {
const o = of(1, 2, 3).pipe(startWith(4, 5, 6)); // $ExpectType Observable<number>
});

it('should infer correctly with no value', () => {
const o = of(1, 2, 3).pipe(startWith()); // $ExpectType Observable<number>
});

it('should infer correctly with a value and a scheduler', () => {
const o = of(1, 2, 3).pipe(startWith(5, asyncScheduler)); // $ExpectType Observable<number>
});

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

it('should infer correctly with multiple different types', () => {
const o = of(1, 2, 3).pipe(startWith('foo', 4, true)); // $ExpectType Observable<string | number | boolean>
it('should infer correctly with N values', () => {
const r0 = of(a).pipe(startWith()); // $ExpectType Observable<A>
const r1 = of(a).pipe(startWith(b)); // $ExpectType Observable<A | B>
const r2 = of(a).pipe(startWith(b, c)); // $ExpectType Observable<A | B | C>
const r3 = of(a).pipe(startWith(b, c, d)); // $ExpectType Observable<A | B | C | D>
const r4 = of(a).pipe(startWith(b, c, d, e)); // $ExpectType Observable<A | B | C | D | E>
const r5 = of(a).pipe(startWith(b, c, d, e, f)); // $ExpectType Observable<A | B | C | D | E | F>
const r6 = of(a).pipe(startWith(b, c, d, e, f, g)); // $ExpectType Observable<A | B | C | D | E | F | G>
const r7 = of(a).pipe(startWith(b, c, d, e, f, g, h)); // $ExpectType Observable<A | B | C | D | E | F | G | H>
});

it('should infer correctly with only a scheduler', () => {
const o = of(1, 2, 3).pipe(startWith(asyncScheduler)); // $ExpectType Observable<number>
});
const r = of(a).pipe(startWith(asyncScheduler)); // $ExpectType Observable<A>
const r1 = of(a).pipe(startWith(b, asyncScheduler)); // $ExpectType Observable<A | B>
const r2 = of(a).pipe(startWith(b, c, asyncScheduler)); // $ExpectType Observable<A | B | C>
const r3 = of(a).pipe(startWith(b, c, d, asyncScheduler)); // $ExpectType Observable<A | B | C | D>
const r4 = of(a).pipe(startWith(b, c, d, e, asyncScheduler)); // $ExpectType Observable<A | B | C | D | E>
const r5 = of(a).pipe(startWith(b, c, d, e, f, asyncScheduler)); // $ExpectType Observable<A | B | C | D | E | F>
const r6 = of(a).pipe(startWith(b, c, d, e, f, g, asyncScheduler)); // $ExpectType Observable<A | B | C | D | E | F | G>
});
58 changes: 26 additions & 32 deletions src/internal/operators/startWith.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Observable } from '../Observable';
import { concat } from '../observable/concat';
import { isScheduler } from '../util/isScheduler';
import { MonoTypeOperatorFunction, OperatorFunction, SchedulerLike } from '../types';
import { MonoTypeOperatorFunction, OperatorFunction, SchedulerLike, ValueFromArray } from '../types';

/* tslint:disable:max-line-length */
/** @deprecated use {@link scheduled} and {@link concatAll} (e.g. `scheduled([[a, b, c], source], scheduler).pipe(concatAll())`) */
Expand All @@ -19,20 +19,14 @@ export function startWith<T, D, E, F, G, H>(v1: D, v2: E, v3: F, v4: G, v5: H, s
/** @deprecated use {@link scheduled} and {@link concatAll} (e.g. `scheduled([[a, b, c], source], scheduler).pipe(concatAll())`) */
export function startWith<T, D, E, F, G, H, I>(v1: D, v2: E, v3: F, v4: G, v5: H, v6: I, scheduler: SchedulerLike): OperatorFunction<T, T | D | E | F | G | H | I>;

export function startWith<T, D>(v1: D): OperatorFunction<T, T | D>;
export function startWith<T, D, E>(v1: D, v2: E): OperatorFunction<T, T | D | E>;
export function startWith<T, D, E, F>(v1: D, v2: E, v3: F): OperatorFunction<T, T | D | E | F>;
export function startWith<T, D, E, F, G>(v1: D, v2: E, v3: F, v4: G): OperatorFunction<T, T | D | E | F | G>;
export function startWith<T, D, E, F, G, H>(v1: D, v2: E, v3: F, v4: G, v5: H): OperatorFunction<T, T | D | E | F | G | H>;
export function startWith<T, D, E, F, G, H, I>(v1: D, v2: E, v3: F, v4: G, v5: H, v6: I): OperatorFunction<T, T | D | E | F | G | H | I>;
export function startWith<T, D = T>(...array: D[]): OperatorFunction<T, T | D>;
/** @deprecated use {@link scheduled} and {@link concatAll} (e.g. `scheduled([[a, b, c], source], scheduler).pipe(concatAll())`) */
export function startWith<T, D = T>(...array: Array<D | SchedulerLike>): OperatorFunction<T, T | D>;
/* tslint:enable:max-line-length */
export function startWith<T, A extends any[]>(...values: A): OperatorFunction<T, T | ValueFromArray<A>>;

/**
* Returns an Observable that emits the items you specify as arguments before it begins to emit
* items emitted by the source Observable.
* Returns an observable that, at the moment of subscription, will synchronously emit all
* values provided to this operator, then subscribe to the source and mirror all of its emissions
* to subscribers.
*
* This is a useful way to know when subscription has occurred on an existing observable.
*
* <span class="informal">First emits its arguments in order, and then any
* emissions from the source.</span>
Expand All @@ -41,37 +35,37 @@ export function startWith<T, D = T>(...array: Array<D | SchedulerLike>): Operato
*
* ## Examples
*
* Start the chain of emissions with `"first"`, `"second"`
* Emit a value when a timer starts.
*
* ```ts
* import { of } from 'rxjs';
* import { timer } from 'rxjs';
* import { startWith } from 'rxjs/operators';
*
* of("from source")
* .pipe(startWith("first", "second"))
* timer(1000)
* .pipe(
* map(() => 'timer emit'),
* startWith('timer start')
* )
* .subscribe(x => console.log(x));
*
* // results:
* // "first"
* // "second"
* // "from source"
* // "timer start"
* // "timer emit"
* ```
*
* @param {...T} values - Items you want the modified Observable to emit first.
* @param {SchedulerLike} [scheduler] - A {@link SchedulerLike} to use for scheduling
* the emissions of the `next` notifications.
* @return {Observable} An Observable that emits the items in the specified Iterable and then emits the items
* emitted by the source Observable.
* @method startWith
* @owner Observable
* @param values - Items you want the modified Observable to emit first.
*
* @see endWith
* @see finalize
* @see concat
*/
export function startWith<T, D>(...array: Array<T | SchedulerLike>): OperatorFunction<T, T | D> {
const scheduler = array[array.length - 1] as SchedulerLike;
export function startWith<T, D>(...values: Array<T | SchedulerLike>): OperatorFunction<T, T | D> {
const scheduler = values[values.length - 1] as SchedulerLike;
if (isScheduler(scheduler)) {
// deprecated path
array.pop();
return (source: Observable<T>) => concat(array as T[], source, scheduler);
values.pop();
return (source: Observable<T>) => concat(values as T[], source, scheduler);
} else {
return (source: Observable<T>) => concat(array as T[], source);
return (source: Observable<T>) => concat(values as T[], source);
}
}

0 comments on commit 82e3ffc

Please sign in to comment.