diff --git a/spec/operators/concat-spec.ts b/spec/operators/concat-spec.ts index 2feda83e1a..44a99320fc 100644 --- a/spec/operators/concat-spec.ts +++ b/spec/operators/concat-spec.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { of, Observable } from 'rxjs'; import { concat, mergeMap } from 'rxjs/operators'; import { TestScheduler } from 'rxjs/testing'; -import { hot, cold, expectObservable, expectSubscriptions } from '../helpers/marble-testing'; +import { observableMatcher } from '../helpers/observableMatcher'; declare function asDiagram(arg: string): Function; @@ -10,306 +10,371 @@ declare const rxTestScheduler: TestScheduler; /** @test {concat} */ describe('concat operator', () => { + let testScheduler: TestScheduler; + + beforeEach(() => { + testScheduler = new TestScheduler(observableMatcher); + }); + asDiagram('concat')('should concatenate two cold observables', () => { - const e1 = cold('--a--b-|'); - const e2 = cold( '--x---y--|'); - const expected = '--a--b---x---y--|'; + testScheduler.run(({ cold, expectObservable }) => { + const e1 = cold(' --a--b-|'); + const e2 = cold(' --x---y--|'); + const expected = ' --a--b---x---y--|'; - expectObservable(e1.pipe(concat(e2, rxTestScheduler))).toBe(expected); + expectObservable(e1.pipe(concat(e2, rxTestScheduler))).toBe(expected); + }); }); - it('should work properly with scalar observables', (done) => { + it('should work properly with scalar observables', done => { const results: string[] = []; - const s1 = new Observable((observer) => { - setTimeout(() => { - observer.next(1); - observer.complete(); - }); - }) - .pipe(concat(of(2))); - - s1.subscribe((x) => { - results.push('Next: ' + x); - }, (x) => { - done(new Error('should not be called')); - }, () => { - results.push('Completed'); - expect(results).to.deep.equal(['Next: 1', 'Next: 2', 'Completed']); - done(); - } - ); + const s1 = new Observable(observer => { + setTimeout(() => { + observer.next(1); + observer.complete(); + }); + }).pipe(concat(of(2))); + + s1.subscribe( + x => { + results.push('Next: ' + x); + }, + x => { + done(new Error('should not be called')); + }, + () => { + results.push('Completed'); + expect(results).to.deep.equal(['Next: 1', 'Next: 2', 'Completed']); + done(); + } + ); }); it('should complete without emit if both sources are empty', () => { - const e1 = cold('--|'); - const e1subs = '^ !'; - const e2 = cold( '----|'); - const e2subs = ' ^ !'; - const expected = '------|'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --|'); + const e1subs = ' ^-!'; + const e2 = cold(' ----|'); + const e2subs = ' --^---!'; + const expected = ' ------|'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); - it('should not complete if first source does not completes', () => { - const e1 = cold('-'); - const e1subs = '^'; - const e2 = cold('--|'); - const e2subs: string[] = []; - const expected = '-'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + it('should not complete if first source does not complete', () => { + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' -'); + const e1subs = ' ^'; + const e2 = cold(' --|'); + const e2subs: string[] = []; + const expected = ' -'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); - it('should not complete if second source does not completes', () => { - const e1 = cold('--|'); - const e1subs = '^ !'; - const e2 = cold('---'); - const e2subs = ' ^'; - const expected = '---'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + it('should not complete if second source does not complete', () => { + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --|'); + const e1subs = ' ^-!'; + const e2 = cold(' ---'); + const e2subs = ' --^'; + const expected = ' ---'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should not complete if both sources do not complete', () => { - const e1 = cold('-'); - const e1subs = '^'; - const e2 = cold('-'); - const e2subs: string[] = []; - const expected = '-'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' -'); + const e1subs = ' ^'; + const e2 = cold(' -'); + const e2subs: string[] = []; + const expected = ' -'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should raise error when first source is empty, second source raises error', () => { - const e1 = cold('--|'); - const e1subs = '^ !'; - const e2 = cold( '----#'); - const e2subs = ' ^ !'; - const expected = '------#'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --|'); + const e1subs = ' ^-!'; + const e2 = cold(' ----#'); + const e2subs = ' --^---!'; + const expected = ' ------#'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should raise error when first source raises error, second source is empty', () => { - const e1 = cold('---#'); - const e1subs = '^ !'; - const e2 = cold('----|'); - const e2subs: string[] = []; - const expected = '---#'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---#'); + const e1subs = ' ^--!'; + const e2 = cold(' ----|'); + const e2subs: string[] = []; + const expected = ' ---#'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should raise first error when both source raise error', () => { - const e1 = cold('---#'); - const e1subs = '^ !'; - const e2 = cold('------#'); - const e2subs: string[] = []; - const expected = '---#'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---#'); + const e1subs = ' ^--!'; + const e2 = cold(' ------#'); + const e2subs: string[] = []; + const expected = ' ---#'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should concat if first source emits once, second source is empty', () => { - const e1 = cold('--a--|'); - const e1subs = '^ !'; - const e2 = cold( '--------|'); - const e2subs = ' ^ !'; - const expected = '--a----------|'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --a--|'); + const e1subs = ' ^----!'; + const e2 = cold(' --------|'); + const e2subs = ' -----^-------!'; + const expected = ' --a----------|'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should concat if first source is empty, second source emits once', () => { - const e1 = cold('--|'); - const e1subs = '^ !'; - const e2 = cold( '--a--|'); - const e2subs = ' ^ !'; - const expected = '----a--|'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --|'); + const e1subs = ' ^-!'; + const e2 = cold(' --a--|'); + const e2subs = ' --^----!'; + const expected = ' ----a--|'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); - it('should emit element from first source, and should not complete if second ' + - 'source does not completes', () => { - const e1 = cold('--a--|'); - const e1subs = '^ !'; - const e2 = cold( '-'); - const e2subs = ' ^'; - const expected = '--a---'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); - }); + it( + 'should emit element from first source, and should not complete if second ' + + 'source does not complete', + () => { + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --a--|'); + const e1subs = ' ^----!'; + const e2 = cold(' -'); + const e2subs = ' -----^'; + const expected = ' --a---'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + } + ); it('should not complete if first source does not complete', () => { - const e1 = cold('-'); - const e1subs = '^'; - const e2 = cold('--a--|'); - const e2subs: string[] = []; - const expected = '-'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' -'); + const e1subs = ' ^'; + const e2 = cold(' --a--|'); + const e2subs: string[] = []; + const expected = ' -'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should emit elements from each source when source emit once', () => { - const e1 = cold('---a|'); - const e1subs = '^ !'; - const e2 = cold( '-----b--|'); - const e2subs = ' ^ !'; - const expected = '---a-----b--|'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---a|'); + const e1subs = ' ^---!'; + const e2 = cold(' -----b--|'); + const e2subs = ' ----^-------!'; + const expected = ' ---a-----b--|'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should unsubscribe to inner source if outer is unsubscribed early', () => { - const e1 = cold('---a-a--a| '); - const e1subs = '^ ! '; - const e2 = cold( '-----b-b--b-|'); - const e2subs = ' ^ ! '; - const unsub = ' ! '; - const expected = '---a-a--a-----b-b '; - - expectObservable(e1.pipe(concat(e2)), unsub).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---a-a--a| '); + const e1subs = ' ^--------! '; + const e2 = cold(' -----b-b--b-|'); + const e2subs = ' ---------^-------! '; + const unsub = ' -----------------! '; + const expected = ' ---a-a--a-----b-b '; + + expectObservable(e1.pipe(concat(e2)), unsub).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should not break unsubscription chains when result is unsubscribed explicitly', () => { - const e1 = cold('---a-a--a| '); - const e1subs = '^ ! '; - const e2 = cold( '-----b-b--b-|'); - const e2subs = ' ^ ! '; - const expected = '---a-a--a-----b-b- '; - const unsub = ' ! '; - - const result = e1.pipe( - mergeMap((x) => of(x)), - concat(e2), - mergeMap((x) => of(x)) - ); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---a-a--a| '); + const e1subs = ' ^--------! '; + const e2 = cold(' -----b-b--b-|'); + const e2subs = ' ---------^-------! '; + const expected = ' ---a-a--a-----b-b- '; + const unsub = ' -----------------! '; + + const result = e1.pipe( + mergeMap(x => of(x)), + concat(e2), + mergeMap(x => of(x)) + ); - expectObservable(result, unsub).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + expectObservable(result, unsub).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should raise error from first source and does not emit from second source', () => { - const e1 = cold('--#'); - const e1subs = '^ !'; - const e2 = cold('----a--|'); - const e2subs: string[] = []; - const expected = '--#'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --#'); + const e1subs = ' ^-!'; + const e2 = cold(' ----a--|'); + const e2subs: string[] = []; + const expected = ' --#'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should emit element from first source then raise error from second source', () => { - const e1 = cold('--a--|'); - const e1subs = '^ !'; - const e2 = cold( '-------#'); - const e2subs = ' ^ !'; - const expected = '--a---------#'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --a--|'); + const e1subs = ' ^----!'; + const e2 = cold(' -------#'); + const e2subs = ' -----^------!'; + const expected = ' --a---------#'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); - it('should emit all elements from both hot observable sources if first source ' + - 'completes before second source starts emit', () => { - const e1 = hot('--a--b-|'); - const e1subs = '^ !'; - const e2 = hot('--------x--y--|'); - const e2subs = ' ^ !'; - const expected = '--a--b--x--y--|'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); - }); - - it('should emit elements from second source regardless of completion time ' + - 'when second source is cold observable', () => { - const e1 = hot('--a--b--c---|'); - const e1subs = '^ !'; - const e2 = cold('-x-y-z-|'); - const e2subs = ' ^ !'; - const expected = '--a--b--c----x-y-z-|'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); - }); + it( + 'should emit all elements from both hot observable sources if first source ' + + 'completes before second source starts emit', + () => { + testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b-|'); + const e1subs = ' ^------!'; + const e2 = hot(' --------x--y--|'); + const e2subs = ' -------^------!'; + const expected = '--a--b--x--y--|'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + } + ); + + it( + 'should emit elements from second source regardless of completion time ' + + 'when second source is cold observable', + () => { + testScheduler.run( + ({ hot, cold, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--c---|'); + const e1subs = ' ^-----------!'; + const e2 = cold(' -x-y-z-|'); + const e2subs = ' ------------^------!'; + const expected = '--a--b--c----x-y-z-|'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + } + ); + } + ); it('should not emit collapsing element from second source', () => { - const e1 = hot('--a--b--c--|'); - const e1subs = '^ !'; - const e2 = hot('--------x--y--z--|'); - const e2subs = ' ^ !'; - const expected = '--a--b--c--y--z--|'; - - expectObservable(e1.pipe(concat(e2))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); + testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--c--|'); + const e1subs = ' ^----------!'; + const e2 = hot(' --------x--y--z--|'); + const e2subs = ' -----------^-----!'; + const expected = '--a--b--c--y--z--|'; + + expectObservable(e1.pipe(concat(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); }); it('should accept scheduler with multiple observables', () => { - const e1 = cold('---a|'); - const e1subs = '^ !'; - const e2 = cold( '---b--|'); - const e2subs = ' ^ !'; - const e3 = cold( '---c--|'); - const e3subs = ' ^ !'; - const expected = '---a---b-----c--|'; - - expectObservable(e1.pipe(concat(e2, e3, rxTestScheduler))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); - expectSubscriptions(e2.subscriptions).toBe(e2subs); - expectSubscriptions(e3.subscriptions).toBe(e3subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---a|'); + const e1subs = ' ^---!'; + const e2 = cold(' ---b--|'); + const e2subs = ' ----^-----!'; + const e3 = cold(' ---c--|'); + const e3subs = ' ----------^-----!'; + const expected = ' ---a---b-----c--|'; + + expectObservable(e1.pipe(concat(e2, e3, rxTestScheduler))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + expectSubscriptions(e3.subscriptions).toBe(e3subs); + }); }); it('should accept scheduler without observable parameters', () => { - const e1 = cold('---a-|'); - const e1subs = '^ !'; - const expected = '---a-|'; - - expectObservable(e1.pipe(concat(rxTestScheduler))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---a-|'); + const e1subs = ' ^----!'; + const expected = ' ---a-|'; + + expectObservable(e1.pipe(concat(rxTestScheduler))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); }); it('should emit self without parameters', () => { - const e1 = cold('---a-|'); - const e1subs = '^ !'; - const expected = '---a-|'; - - expectObservable(e1.pipe(concat())).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---a-|'); + const e1subs = ' ^----!'; + const expected = ' ---a-|'; + + expectObservable(e1.pipe(concat())).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); }); });