prettier import { expect } from 'chai' ; import { NEVER , timer , of , EMPTY , concat , Subject , Observable } from 'rxjs' ; import { debounce , mergeMap , mapTo , take } from 'rxjs/operators' ; import { TestScheduler } from 'rxjs/testing' ; import { observableMatcher } from '../helpers/observableMatcher' ; test describe ( 'debounce' , ( ) => { let testScheduler ; beforeEach ( ( ) => { testScheduler = new TestScheduler ( observableMatcher ) ; } ) ; function getTimerSelector ( x ) { return ( ) => timer ( x , testScheduler ) ; } it ( 'should debounce values by a specified cold Observable' , ( ) => { testScheduler . run ( ( { cold , hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  -a----bc----d-ef----|' ) ; const e1subs = '  ^-------------------!' ; const e2 = cold ( '  ---x                ' ) ; const e2subs = [ '               -^--!                ' , '               ------^!             ' , '               -------^--!          ' , '               ------------^-!      ' , '               --------------^!     ' , '               ---------------^--!  ' , ] ; const expected = '----a-----c-------f-|' ; const result = e1 . pipe ( debounce ( ( ) => e2 ) ) ; expectObservable ( result ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; expectSubscriptions ( e2 . subscriptions ) . toBe ( e2subs ) ; } ) ; } ) ; it ( 'should delay all element by selector observable' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --a--b--c--d---------|' ) ; const e1subs = '  ^--------------------!' ; const expected = '----a--b--c--d-------|' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 2 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should debounce by selector observable' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --a--bc--d----|' ) ; const e1subs = '  ^-------------!' ; const expected = '----a---c--d--|' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 2 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should support a scalar selector observable' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --a--bc--d----|' ) ; const e1subs = '  ^-------------!' ; const expected = '--a--bc--d----|' ; expectObservable ( e1 . pipe ( debounce ( ( ) => of ( 0 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should complete when source does not emit' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  -----|' ) ; const e1subs = '  ^----!' ; const expected = '-----|' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 2 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should complete when source is empty' , ( ) => { testScheduler . run ( ( { cold , expectObservable , expectSubscriptions } ) => { const e1 = cold ( ' |' ) ; const e1subs = '  (^!)' ; const expected = '|' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 2 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should raise error when source does not emit and raises error' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  -----#' ) ; const e1subs = '  ^----!' ; const expected = '-----#' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 2 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should raise error when source throws' , ( ) => { testScheduler . run ( ( { cold , expectObservable , expectSubscriptions } ) => { const e1 = cold ( ' #' ) ; const e1subs = '  (^!)' ; const expected = '#' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 2 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should allow unsubscribing early and explicitly' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --a--bc--d----|' ) ; const e1subs = '  ^------!       ' ; const expected = '----a---       ' ; const unsub = '   -------!       ' ; const result = e1 . pipe ( debounce ( getTimerSelector ( 2 ) ) ) ; expectObservable ( result , unsub ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should not break unsubscription chains when unsubscribed explicitly' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --a--bc--d----|' ) ; const e1subs = '  ^------!       ' ; const expected = '----a---       ' ; const unsub = '   -------!       ' ; const result = e1 . pipe ( mergeMap ( ( x ) => of ( x ) ) , debounce ( getTimerSelector ( 2 ) ) , mergeMap ( ( x ) => of ( x ) ) ) ; expectObservable ( result , unsub ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should debounce and does not complete when source does not completes' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --a--bc--d---' ) ; const e1subs = '  ^            ' ; const expected = '----a---c--d-' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 2 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should not complete when source does not complete' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  -' ) ; const e1subs = '  ^' ; const expected = '-' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 2 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should not completes when source never completes' , ( ) => { testScheduler . run ( ( { cold , expectObservable , expectSubscriptions } ) => { const e1 = cold ( ' -' ) ; const e1subs = '  ^' ; const expected = '-' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 2 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should delay all element until source raises error' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --a--b--c--d---------#' ) ; const e1subs = '  ^--------------------!' ; const expected = '----a--b--c--d-------#' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 2 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should debounce all elements while source emits by selector observable' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  ---a---b---c---d---e|' ) ; const e1subs = '  ^-------------------!' ; const expected = '--------------------(e|)' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 4 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should debounce all element while source emits by selector observable until raises error' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --a--b--c--d-#' ) ; const e1subs = '  ^------------!' ; const expected = '-------------#' ; expectObservable ( e1 . pipe ( debounce ( getTimerSelector ( 5 ) ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should delay element by same selector observable emits multiple' , ( ) => { testScheduler . run ( ( { cold , hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '       ----a--b--c----d-----e-------|' ) ; const e1subs = '       ^----------------------------!' ; const expected = '     ------a--b--c----d-----e-----|' ; const selector = cold ( '--x-y-' ) ; const selectorSubs = [ '                    ----^-!                      ' , '                    -------^-!                   ' , '                    ----------^-!                ' , '                    ---------------^-!           ' , '                    ---------------------^-!     ' , ] ; expectObservable ( e1 . pipe ( debounce ( ( ) => selector ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; expectSubscriptions ( selector . subscriptions ) . toBe ( selectorSubs ) ; } ) ; } ) ; it ( 'should debounce by selector observable emits multiple' , ( ) => { testScheduler . run ( ( { cold , hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  ----a--b--c----d-----e-------|' ) ; const e1subs = '  ^----------------------------!' ; const expected = '------a-----c----------e-----|' ; const selector = [ cold ( '              --x-y-                    ' ) , cold ( '                 ----x-y-               ' ) , cold ( '                    --x-y-              ' ) , cold ( '                         ------x-y-     ' ) , cold ( '                               --x-y-   ' ) , ] ; const selectorSubs = [ '               ----^-!                       ' , '               -------^--!                   ' , '               ----------^-!                 ' , '               ---------------^-----!        ' , '               ---------------------^-!      ' , ] ; expectObservable ( e1 . pipe ( debounce ( ( ) => selector . shift ( ) ! ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; for ( let i = 0 ; i < selectorSubs . length ; i ++ ) { expectSubscriptions ( selector [ i ] . subscriptions ) . toBe ( selectorSubs [ i ] ) ; } } ) ; } ) ; it ( 'should debounce by selector observable until source completes' , ( ) => { testScheduler . run ( ( { cold , hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  ----a--b--c----d----e|    ' ) ; const e1subs = '  ^--------------------!    ' ; const expected = '------a-----c--------(e|) ' ; const selector = [ cold ( '              --x-y-                ' ) , cold ( '                 ----x-y-           ' ) , cold ( '                    --x-y-          ' ) , cold ( '                         ------x-y- ' ) , cold ( '                              --x-y-' ) , ] ; const selectorSubs = [ '               ----^-!                   ' , '               -------^--!               ' , '               ----------^-!             ' , '               ---------------^----!     ' , '               --------------------^!    ' , ] ; expectObservable ( e1 . pipe ( debounce ( ( ) => selector . shift ( ) ! ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; for ( let i = 0 ; i < selectorSubs . length ; i ++ ) { expectSubscriptions ( selector [ i ] . subscriptions ) . toBe ( selectorSubs [ i ] ) ; } } ) ; } ) ; it ( 'should raise error when selector observable raises error' , ( ) => { testScheduler . run ( ( { cold , hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --------a--------b--------c---------|' ) ; const e1subs = '  ^----------------------------!       ' ; const expected = '---------a---------b---------#       ' ; const selector = [ cold ( '                  -x-y-                        ' ) , cold ( '                           --x-y-              ' ) , cold ( '                                    ---#       ' ) , ] ; const selectorSubs = [ '               --------^!                           ' , '               -----------------^-!                 ' , '               --------------------------^--!       ' , ] ; expectObservable ( e1 . pipe ( debounce ( ( ) => selector . shift ( ) ! ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; for ( let i = 0 ; i < selectorSubs . length ; i ++ ) { expectSubscriptions ( selector [ i ] . subscriptions ) . toBe ( selectorSubs [ i ] ) ; } } ) ; } ) ; it ( 'should raise error when source raises error with selector observable' , ( ) => { testScheduler . run ( ( { cold , hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --------a--------b--------c---------d#      ' ) ; const e1subs = '  ^------------------------------------!      ' ; const expected = '---------a---------b---------c-------#      ' ; const selector = [ cold ( '                  -x-y-                               ' ) , cold ( '                           --x-y-                     ' ) , cold ( '                                    ---x-y-           ' ) , cold ( '                                              ----x-y-' ) , ] ; const selectorSubs = [ '               --------^!                                  ' , '               -----------------^-!                        ' , '               --------------------------^--!              ' , '               ------------------------------------^!      ' , ] ; expectObservable ( e1 . pipe ( debounce ( ( ) => selector . shift ( ) ! ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; for ( let i = 0 ; i < selectorSubs . length ; i ++ ) { expectSubscriptions ( selector [ i ] . subscriptions ) . toBe ( selectorSubs [ i ] ) ; } } ) ; } ) ; it ( 'should raise error when selector function throws' , ( ) => { testScheduler . run ( ( { hot , cold , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --------a--------b--------c---------|' ) ; const e1subs = '  ^-------------------------!          ' ; const expected = '---------a---------b------#          ' ; const selector = [ cold ( '                  -x-y-                        ' ) , cold ( '                           --x-y-              ' ) , ] ; const selectorSubs = [ '               --------^!                           ' , '               -----------------^-!                 ' , ] ; function selectorFunction ( x ) { if ( x !== 'c' ) { return selector . shift ( ) ; } else { throw 'error' ; } } expectObservable ( e1 . pipe ( debounce ( selectorFunction as any ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; for ( let i = 0 ; i < selectorSubs . length ; i ++ ) { expectSubscriptions ( selector [ i ] . subscriptions ) . toBe ( selectorSubs [ i ] ) ; } } ) ; } ) ; it ( 'should ignore all values except last, when given an empty selector Observable' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --------a-x-yz---bxy---z--c--x--y--z|   ' ) ; const e1subs = '  ^-----------------------------------!   ' ; const expected = '------------------------------------(z|)' ; function selectorFunction ( x ) { return EMPTY ; } expectObservable ( e1 . pipe ( debounce ( selectorFunction ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should ignore all values except last, when given a never selector Observable' , ( ) => { testScheduler . run ( ( { hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --------a-x-yz---bxy---z--c--x--y--z|  ' ) ; const e1subs = '  ^-----------------------------------!  ' ; const expected = '------------------------------------(z|)' ; function selectorFunction ( ) { return NEVER ; } expectObservable ( e1 . pipe ( debounce ( selectorFunction ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; } ) ; } ) ; it ( 'should not delay by selector observable completes when it does not emits' , ( ) => { testScheduler . run ( ( { cold , hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  --------a--------b--------c---------|   ' ) ; const e1subs = '  ^-----------------------------------!   ' ; const expected = '------------------------------------(c|)' ; const selector = [ cold ( '                  -|                              ' ) , cold ( '                           --|                    ' ) , cold ( '                                    ---|          ' ) , ] ; const selectorSubs = [ '               --------^!                              ' , '               -----------------^-!                    ' , '               --------------------------^--!          ' , ] ; expectObservable ( e1 . pipe ( debounce ( ( ) => selector . shift ( ) ! ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; for ( let i = 0 ; i < selectorSubs . length ; i ++ ) { expectSubscriptions ( selector [ i ] . subscriptions ) . toBe ( selectorSubs [ i ] ) ; } } ) ; } ) ; it ( 'should not debounce by selector observable completes when it does not emits' , ( ) => { testScheduler . run ( ( { cold , hot , expectObservable , expectSubscriptions } ) => { const e1 = hot ( '  ----a--b-c---------de-------------|   ' ) ; const e1subs = '  ^---------------------------------!   ' ; const expected = '----------------------------------(e|)' ; const selector = [ cold ( '              -|                                ' ) , cold ( '                 --|                            ' ) , cold ( '                   ---|                         ' ) , cold ( '                             ----|              ' ) , cold ( '                              -----|            ' ) , ] ; const selectorSubs = [ '               ----^!                                ' , '               -------^-!                            ' , '               ---------^--!                         ' , '               -------------------^!                 ' , '               --------------------^----!            ' , ] ; expectObservable ( e1 . pipe ( debounce ( ( ) => selector . shift ( ) ! ) ) ) . toBe ( expected ) ; expectSubscriptions ( e1 . subscriptions ) . toBe ( e1subs ) ; for ( let i = 0 ; i < selectorSubs . length ; i ++ ) { expectSubscriptions ( selector [ i ] . subscriptions ) . toBe ( selectorSubs [ i ] ) ; } } ) ; } ) ; it ( 'should delay by promise resolves' , ( done ) => { const e1 = concat ( of ( 1 ) , timer ( 10 ) . pipe ( mapTo ( 2 ) ) , timer ( 10 ) . pipe ( mapTo ( 3 ) ) , timer ( 100 ) . pipe ( mapTo ( 4 ) ) ) ; const expected = [ 1 , 2 , 3 , 4 ] ; e1 . pipe ( debounce ( ( ) => { return new Promise ( ( resolve ) => { resolve ( 42 ) ; } ) ; } ) ) . subscribe ( { next : ( x ) => { expect ( x ) . to . equal ( expected . shift ( ) ) ; } , error : ( x ) => { done ( new Error ( 'should not be called' ) ) ; } , complete : ( ) => { expect ( expected . length ) . to . equal ( 0 ) ; done ( ) ; } , } ) ; } ) ; it ( 'should raises error when promise rejects' , ( done ) => { const e1 = concat ( of ( 1 ) , timer ( 10 ) . pipe ( mapTo ( 2 ) ) , timer ( 10 ) . pipe ( mapTo ( 3 ) ) , timer ( 100 ) . pipe ( mapTo ( 4 ) ) ) ; const expected = [ 1 , 2 ] ; const error = new Error ( 'error' ) ; e1 . pipe ( debounce ( ( x ) => { if ( x === 3 ) { return new Promise ( ( resolve , reject ) => { reject ( error ) ; } ) ; } else { return new Promise ( ( resolve ) => { resolve ( 42 ) ; } ) ; } } ) ) . subscribe ( { next : ( x ) => { expect ( x ) . to . equal ( expected . shift ( ) ) ; } , error : ( err ) => { expect ( err ) . to . be . an ( 'error' , 'error' ) ; expect ( expected . length ) . to . equal ( 0 ) ; done ( ) ; } , complete : ( ) => { done ( new Error ( 'should not be called' ) ) ; } , } ) ; } ) ; it ( 'should debounce correctly when synchronously reentered' , ( ) => { const results = [ ] ; const source = new Subject < number > ( ) ; source . pipe ( debounce ( ( ) => of ( null ) ) ) . subscribe ( ( value ) => { results . push ( value ) ; if ( value === 1 ) { source . next ( 2 ) ; } } ) ; source . next ( 1 ) ; expect ( results ) . to . deep . equal ( [ 1 , 2 ] ) ; } ) ; it ( 'should stop listening to a synchronous observable when unsubscribed' , ( ) => { const sideEffects = [ ] ; const synchronousObservable = new Observable ( ( subscriber ) => { for ( let i = 0 ; ! subscriber . closed && i < 10 ; i ++ ) { sideEffects . push ( i ) ; subscriber . next ( i ) ; } } ) ; synchronousObservable . pipe ( debounce ( ( ) => of ( 0 ) ) , take ( 3 ) ) . subscribe ( ( ) => { } ) ; expect ( sideEffects ) . to . deep . equal ( [ 0 , 1 , 2 ] ) ; } ) ; } ) ;