import 'mocha' ; import { expect } from 'chai' ; import { throttle , ITimer , TimeoutType } from '@/presentation/components/Shared/Throttle' ; import { EventSource } from '@/infrastructure/Events/EventSource' ; import { IEventSubscription } from '@/infrastructure/Events/IEventSource' ; describe ( 'throttle' , ( ) => { it ( 'throws if callback is undefined' , ( ) => { const expectedError = 'undefined callback' ; const callback = undefined ; const act = ( ) => throttle ( callback , 500 ) ; expect ( act ) . to . throw ( expectedError ) ; } ) ; describe ( 'throws if waitInMs is negative or zero' , ( ) => { const testCases = [ { value : 0 , expectedError : 'no delay to throttle' } , { value : - 2 , expectedError : 'negative delay' } , ] ; const noopCallback = ( ) => { } ; for ( const testCase of testCases ) { it ( `"${ testCase . value }" throws "${ testCase . expectedError }"` , ( ) => { const waitInMs = testCase . value ; const act = ( ) => throttle ( noopCallback , waitInMs ) ; expect ( act ) . to . throw ( testCase . expectedError ) ; } ) ; } } ) ; it ( 'should call the callback immediately' , ( ) => { const timer = new TimerMock ( ) ; let totalRuns = 0 ; const callback = ( ) => totalRuns ++ ; const throttleFunc = throttle ( callback , 500 , timer ) ; throttleFunc ( ) ; expect ( totalRuns ) . to . equal ( 1 ) ; } ) ; it ( 'should call the callback again after the timeout' , ( ) => { const timer = new TimerMock ( ) ; let totalRuns = 0 ; const callback = ( ) => totalRuns ++ ; const waitInMs = 500 ; const throttleFunc = throttle ( callback , waitInMs , timer ) ; throttleFunc ( ) ; totalRuns -- ; throttleFunc ( ) ; timer . tickNext ( waitInMs ) ; expect ( totalRuns ) . to . equal ( 1 ) ; } ) ; it ( 'should call the callback at most once at given time' , ( ) => { const timer = new TimerMock ( ) ; let totalRuns = 0 ; const callback = ( ) => totalRuns ++ ; const waitInMs = 500 ; const totalCalls = 10 ; const throttleFunc = throttle ( callback , waitInMs , timer ) ; for ( let currentCall = 0 ; currentCall < totalCalls ; currentCall ++ ) { const currentTime = ( waitInMs / totalCalls ) * currentCall ; timer . setCurrentTime ( currentTime ) ; throttleFunc ( ) ; } expect ( totalRuns ) . to . equal ( 2 ) ; } ) ; it ( 'should call the callback as long as delay is waited' , ( ) => { const timer = new TimerMock ( ) ; let totalRuns = 0 ; const callback = ( ) => totalRuns ++ ; const waitInMs = 500 ; const expectedTotalRuns = 10 ; const throttleFunc = throttle ( callback , waitInMs , timer ) ; for ( let i = 0 ; i < expectedTotalRuns ; i ++ ) { throttleFunc ( ) ; timer . tickNext ( waitInMs ) ; } expect ( totalRuns ) . to . equal ( expectedTotalRuns ) ; } ) ; it ( 'should call arguments as expected' , ( ) => { const timer = new TimerMock ( ) ; const expected = [ 1 , 2 , 3 ] ; const actual = new Array < number > ( ) ; const callback = ( arg ) => { actual . push ( arg ) ; } ; const waitInMs = 500 ; const throttleFunc = throttle ( callback , waitInMs , timer ) ; for ( const arg of expected ) { throttleFunc ( arg ) ; timer . tickNext ( waitInMs ) ; } expect ( expected ) . to . deep . equal ( actual ) ; } ) ; } ) ; class TimerMock implements ITimer { private timeChanged = new EventSource < number > ( ) ; private subscriptions = new Array < IEventSubscription > ( ) ; private currentTime = 0 ; public setTimeout ( callback , ms ) { const runTime = this . currentTime + ms ; const subscription = this . timeChanged . on ( ( time ) => { if ( time >= runTime ) { callback ( ) ; subscription . unsubscribe ( ) ; } } ) ; this . subscriptions . push ( subscription ) ; const id = this . subscriptions . length - 1 ; return TimerMock . mockTimeout ( id ) ; } public clearTimeout ( timeoutId ) { this . subscriptions [ + timeoutId ] . unsubscribe ( ) ; } public dateNow ( ) { return this . currentTime ; } public tickNext ( ms ) { this . setCurrentTime ( this . currentTime + ms ) ; } public setCurrentTime ( ms ) { this . currentTime = ms ; this . timeChanged . notify ( this . currentTime ) ; } private static mockTimeout ( subscriptionId ) { const throwNodeSpecificCode = ( ) => { throw new Error ( 'node specific code' ) ; } ; return { [ Symbol . toPrimitive ] : ( ) => subscriptionId , hasRef : throwNodeSpecificCode , refresh : throwNodeSpecificCode , ref : throwNodeSpecificCode , unref : throwNodeSpecificCode , } ; } }