Skip to content

Commit

Permalink
Overload take while to accept a type guard (#4085)
Browse files Browse the repository at this point in the history
* feat(takeWhile): overload takeWhile to accept a type guard

* test(takeWhile): add type guard tests for takeWhile

* test(dtslint): add takeWhile
  • Loading branch information
chrisguttandin authored and benlesh committed Sep 7, 2018
1 parent 70fa26e commit af7b619
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 2 deletions.
10 changes: 10 additions & 0 deletions spec-dtslint/operators/takeWhile-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { of } from 'rxjs';
import { takeWhile } from 'rxjs/operators';

it('should support a user-defined type guard', () => {
const o = of('foo').pipe(takeWhile((s): s is 'foo' => true)); // $ExpectType Observable<"foo">
});

it('should support a predicate', () => {
const o = of('foo').pipe(takeWhile(s => true)); // $ExpectType Observable<string>
});
48 changes: 47 additions & 1 deletion spec/operators/takeWhile-spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from 'chai';
import { hot, cold, expectObservable, expectSubscriptions } from '../helpers/marble-testing';
import { takeWhile, tap, mergeMap } from 'rxjs/operators';
import { of } from 'rxjs';
import { of, Observable, from } from 'rxjs';

declare function asDiagram(arg: string): Function;

Expand Down Expand Up @@ -205,4 +205,50 @@ describe('takeWhile operator', () => {
expectObservable(result, unsub).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

it('should support type guards without breaking previous behavior', () => {
// type guards with interfaces and classes
{
interface Bar { bar?: string; }
interface Baz { baz?: number; }
class Foo implements Bar, Baz { constructor(public bar: string = 'name', public baz: number = 42) {} }

const isBar = (x: any): x is Bar => x && (<Bar>x).bar !== undefined;

const foo: Foo = new Foo();
of(foo).pipe(takeWhile(foo => foo.baz === 42))
.subscribe(x => x.baz); // x is still Foo
of(foo).pipe(takeWhile(isBar))
.subscribe(x => x.bar); // x is Bar!

const foobar: Bar = new Foo(); // type is interface, not the class
of(foobar).pipe(takeWhile(foobar => foobar.bar === 'name'))
.subscribe(x => x.bar); // <-- x is still Bar
of(foobar).pipe(takeWhile(isBar))
.subscribe(x => x.bar); // <--- x is Bar!

const barish = { bar: 'quack', baz: 42 }; // type can quack like a Bar
of(barish).pipe(takeWhile(x => x.bar === 'quack'))
.subscribe(x => x.bar); // x is still { bar: string; baz: number; }
of(barish).pipe(takeWhile(isBar))
.subscribe(bar => bar.bar); // x is Bar!
}

// type guards with primitive types
{
const xs: Observable<string | number> = from([ 1, 'aaa', 3, 'bb' ]);

// This type guard will narrow a `string | number` to a string in the examples below
const isString = (x: string | number): x is string => typeof x === 'string';

xs.pipe(takeWhile(isString))
.subscribe(s => s.length); // s is string

// In contrast, this type of regular boolean predicate still maintains the original type
xs.pipe(takeWhile(x => typeof x === 'number'))
.subscribe(x => x); // x is still string | number
xs.pipe(takeWhile((x, i) => typeof x === 'number' && x > i))
.subscribe(x => x); // x is still string | number
}
});
});
5 changes: 4 additions & 1 deletion src/internal/operators/takeWhile.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Operator } from '../Operator';
import { Observable } from '../Observable';
import { Subscriber } from '../Subscriber';
import { MonoTypeOperatorFunction, TeardownLogic } from '../types';
import { OperatorFunction, MonoTypeOperatorFunction, TeardownLogic } from '../types';

export function takeWhile<T, S extends T>(predicate: (value: T, index: number) => value is S): OperatorFunction<T, S>;
export function takeWhile<T>(predicate: (value: T, index: number) => boolean): MonoTypeOperatorFunction<T>;

/**
* Emits values emitted by the source Observable so long as each value satisfies
Expand Down

0 comments on commit af7b619

Please sign in to comment.