@@ -422,6 +422,8 @@ describe('FileList', () => {
422422 } )
423423
424424 describe ( 'addFile' , ( ) => {
425+ var clock = null
426+
425427 beforeEach ( ( ) => {
426428 patternList = PATTERN_LIST
427429 mg = MG
@@ -435,14 +437,25 @@ describe('FileList', () => {
435437 statCache : mg . statCache
436438 } )
437439 }
440+
441+ clock = sinon . useFakeTimers ( )
442+ // This hack is needed to ensure lodash is using the fake timers
443+ // from sinon
438444 List = proxyquire ( '../../lib/file-list' , {
445+ lodash : _ . runInContext ( ) ,
439446 helper : helper ,
440447 glob : glob ,
448+ 'graceful-fs' : mockFs ,
441449 path : pathLib . posix || pathLib /* for node 0.10 */ ,
442- 'graceful-fs' : mockFs
450+ bluebird : Promise
443451 } )
444452
445- list = new List ( patterns ( '/some/*.js' , '*.txt' ) , [ '/secret/*.txt' ] , emitter , preprocess )
453+ list = new List ( patterns ( '/some/*.js' , '*.txt' ) , [ '/secret/*.txt' ] , emitter , preprocess , 100 )
454+ } )
455+
456+ afterEach ( ( ) => {
457+ clock . restore ( )
458+ Promise . setScheduler ( ( fn ) => process . nextTick ( fn ) )
446459 } )
447460
448461 it ( 'does not add excluded files' , ( ) => {
@@ -487,6 +500,7 @@ describe('FileList', () => {
487500 modified . reset ( )
488501
489502 return list . addFile ( '/some/d.js' ) . then ( ( ) => {
503+ clock . tick ( 101 )
490504 expect ( modified ) . to . have . been . calledOnce
491505 } )
492506 } )
@@ -534,9 +548,12 @@ describe('FileList', () => {
534548 } )
535549
536550 describe ( 'changeFile' , ( ) => {
551+ var clock = null
552+
537553 beforeEach ( ( ) => {
538554 patternList = PATTERN_LIST
539555 mg = MG
556+ Promise . setScheduler ( ( fn ) => fn ( ) )
540557
541558 preprocess = sinon . spy ( ( file , done ) => process . nextTick ( done ) )
542559 emitter = new EventEmitter ( )
@@ -548,20 +565,30 @@ describe('FileList', () => {
548565 } )
549566 }
550567
568+ clock = sinon . useFakeTimers ( )
569+ // This hack is needed to ensure lodash is using the fake timers
570+ // from sinon
551571 List = proxyquire ( '../../lib/file-list' , {
572+ lodash : _ . runInContext ( ) ,
552573 helper : helper ,
553574 glob : glob ,
575+ 'graceful-fs' : mockFs ,
554576 path : pathLib . posix || pathLib /* for node 0.10 */ ,
555- 'graceful-fs' : mockFs
577+ bluebird : Promise
556578 } )
557579
558580 mockFs . _touchFile ( '/some/a.js' , '2012-04-04' )
559581 mockFs . _touchFile ( '/some/b.js' , '2012-05-05' )
560582 } )
561583
584+ afterEach ( ( ) => {
585+ clock . restore ( )
586+ Promise . setScheduler ( ( fn ) => process . nextTick ( fn ) )
587+ } )
588+
562589 it ( 'updates mtime and fires "file_list_modified"' , ( ) => {
563590 // MATCH: /some/a.js, /some/b.js
564- list = new List ( patterns ( '/some/*.js' , '/a.*' ) , [ ] , emitter , preprocess )
591+ list = new List ( patterns ( '/some/*.js' , '/a.*' ) , [ ] , emitter , preprocess , 100 )
565592 var modified = sinon . stub ( )
566593 emitter . on ( 'file_list_modified' , modified )
567594
@@ -570,6 +597,7 @@ describe('FileList', () => {
570597 modified . reset ( )
571598
572599 return list . changeFile ( '/some/b.js' ) . then ( ( files ) => {
600+ clock . tick ( 101 )
573601 expect ( modified ) . to . have . been . calledOnce
574602 expect ( findFile ( '/some/b.js' , files . served ) . mtime ) . to . be . eql ( new Date ( '2020-01-01' ) )
575603 } )
@@ -627,9 +655,12 @@ describe('FileList', () => {
627655 } )
628656
629657 describe ( 'removeFile' , ( ) => {
658+ var clock = null
659+
630660 beforeEach ( ( ) => {
631661 patternList = PATTERN_LIST
632662 mg = MG
663+ Promise . setScheduler ( ( fn ) => fn ( ) )
633664
634665 preprocess = sinon . spy ( ( file , done ) => process . nextTick ( done ) )
635666 emitter = new EventEmitter ( )
@@ -641,20 +672,30 @@ describe('FileList', () => {
641672 } )
642673 }
643674
675+ clock = sinon . useFakeTimers ( )
676+ // This hack is needed to ensure lodash is using the fake timers
677+ // from sinon
644678 List = proxyquire ( '../../lib/file-list' , {
679+ lodash : _ . runInContext ( ) ,
645680 helper : helper ,
646681 glob : glob ,
682+ 'graceful-fs' : mockFs ,
647683 path : pathLib . posix || pathLib /* for node 0.10 */ ,
648- 'graceful-fs' : mockFs
684+ bluebird : Promise
649685 } )
650686
651687 modified = sinon . stub ( )
652688 emitter . on ( 'file_list_modified' , modified )
653689 } )
654690
691+ afterEach ( ( ) => {
692+ clock . restore ( )
693+ Promise . setScheduler ( ( fn ) => process . nextTick ( fn ) )
694+ } )
695+
655696 it ( 'removes the file from the list and fires "file_list_modified"' , ( ) => {
656697 // MATCH: /some/a.js, /some/b.js, /a.txt
657- list = new List ( patterns ( '/some/*.js' , '/a.*' ) , [ ] , emitter , preprocess )
698+ list = new List ( patterns ( '/some/*.js' , '/a.*' ) , [ ] , emitter , preprocess , 100 )
658699
659700 var modified = sinon . stub ( )
660701 emitter . on ( 'file_list_modified' , modified )
@@ -667,6 +708,7 @@ describe('FileList', () => {
667708 '/some/b.js' ,
668709 '/a.txt'
669710 ] )
711+ clock . tick ( 101 )
670712 expect ( modified ) . to . have . been . calledOnce
671713 } )
672714 } )
@@ -685,6 +727,15 @@ describe('FileList', () => {
685727 } )
686728
687729 describe ( 'batch interval' , ( ) => {
730+ // IMPORTANT: When writing tests for debouncing behaviour, you must wait for the promise
731+ // returned by list.changeFile or list.addFile. list.removeFile calls self._emitModified()
732+ // in a different manner and doesn't *need* to be waited on. If you use this behaviour
733+ // in your tests it can can lead to very confusing results when they are modified or
734+ // extended.
735+ //
736+ // Rule of thumb: Always wait on the promises returned by list.addFile, list.changeFile,
737+ // and list.removeFile.
738+
688739 var clock = null
689740
690741 beforeEach ( ( ) => {
@@ -723,7 +774,97 @@ describe('FileList', () => {
723774 Promise . setScheduler ( ( fn ) => process . nextTick ( fn ) )
724775 } )
725776
726- it ( 'batches multiple changes within an interval' , ( ) => {
777+ it ( 'debounces calls to emitModified' , ( ) => {
778+ list = new List ( patterns ( ) , [ ] , emitter , preprocess , 100 )
779+
780+ return list . refresh ( ) . then ( ( ) => {
781+ modified . reset ( )
782+ list . _emitModified ( )
783+ clock . tick ( 99 )
784+ expect ( modified ) . to . not . have . been . called
785+ list . _emitModified ( )
786+ clock . tick ( 2 )
787+ expect ( modified ) . to . not . have . been . called
788+ clock . tick ( 97 )
789+ expect ( modified ) . to . not . have . been . called
790+ clock . tick ( 2 )
791+ expect ( modified ) . to . have . been . calledOnce
792+ clock . tick ( 1000 )
793+ expect ( modified ) . to . have . been . calledOnce
794+ list . _emitModified ( )
795+ clock . tick ( 99 )
796+ expect ( modified ) . to . have . been . calledOnce
797+ clock . tick ( 2 )
798+ expect ( modified ) . to . have . been . calledTwice
799+ } )
800+ } )
801+
802+ it ( 'debounces a single file change' , ( ) => {
803+ list = new List ( patterns ( '/some/*.js' , '/a.*' ) , [ ] , emitter , preprocess , 100 )
804+
805+ return list . refresh ( ) . then ( ( files ) => {
806+ modified . reset ( )
807+ // Even with no changes, all these files are served
808+ list . addFile ( '/some/0.js' ) . then ( ( ) => {
809+ clock . tick ( 99 )
810+ expect ( modified ) . to . not . have . been . called
811+
812+ clock . tick ( 2 )
813+ expect ( modified ) . to . have . been . calledOnce
814+
815+ files = modified . lastCall . args [ 0 ]
816+ expect ( pathsFrom ( files . served ) ) . to . be . eql ( [
817+ '/some/0.js' ,
818+ '/some/a.js' ,
819+ '/some/b.js' ,
820+ '/a.txt'
821+ ] )
822+ } )
823+ } )
824+ } )
825+
826+ it ( 'debounces several changes to a file' , ( ) => {
827+ list = new List ( patterns ( '/some/*.js' , '/a.*' ) , [ ] , emitter , preprocess , 100 )
828+
829+ return list . refresh ( ) . then ( ( files ) => {
830+ modified . reset ( )
831+ list . addFile ( '/some/0.js' ) . then ( ( ) => {
832+ clock . tick ( 99 )
833+ expect ( modified ) . to . not . have . been . called
834+
835+ // Modify file, must change mtime too, or change is ignored
836+ mockFs . _touchFile ( '/some/0.js' , '2020-01-01' )
837+ list . changeFile ( '/some/0.js' ) . then ( ( ) => {
838+ // Ensure that the debounce timer was reset
839+ clock . tick ( 2 )
840+ expect ( modified ) . to . not . have . been . called
841+
842+ // Ensure that debounce timer fires after 100ms
843+ clock . tick ( 99 )
844+ expect ( modified ) . to . have . been . calledOnce
845+
846+ // Make sure there aren't any lingering debounce calls
847+ clock . tick ( 1000 )
848+
849+ // Modify file (one hour later mtime)
850+ expect ( modified ) . to . have . been . calledOnce
851+ mockFs . _touchFile ( '/some/0.js' , '2020-01-02' )
852+ list . changeFile ( '/some/0.js' ) . then ( ( ) => {
853+ clock . tick ( 99 )
854+ expect ( modified ) . to . have . been . calledOnce
855+ clock . tick ( 2 )
856+ expect ( modified ) . to . have . been . calledTwice
857+
858+ // Make sure there aren't any lingering calls
859+ clock . tick ( 1000 )
860+ expect ( modified ) . to . have . been . calledTwice
861+ } )
862+ } )
863+ } )
864+ } )
865+ } )
866+
867+ it ( 'debounces multiple changes until there is quiescence' , ( ) => {
727868 // MATCH: /some/a.js, /some/b.js, /a.txt
728869 list = new List ( patterns ( '/some/*.js' , '/a.*' ) , [ ] , emitter , preprocess , 100 )
729870
@@ -734,20 +875,28 @@ describe('FileList', () => {
734875 list . removeFile ( '/some/a.js' ) // /some/b.js, /a.txt
735876 list . removeFile ( '/a.txt' ) // /some/b.js
736877 list . addFile ( '/a.txt' ) // /some/b.js, /a.txt
737- list . addFile ( '/some/0.js' ) // /some/0.js, /some/b.js, /a.txt
738-
739- clock . tick ( 99 )
740- expect ( modified ) . to . not . have . been . called
741-
742- clock . tick ( 2 )
743- expect ( modified ) . to . have . been . calledOnce
744-
745- files = modified . lastCall . args [ 0 ]
746- expect ( pathsFrom ( files . served ) ) . to . be . eql ( [
747- '/some/0.js' ,
748- '/some/b.js' ,
749- '/a.txt'
750- ] )
878+ list . addFile ( '/some/0.js' ) . then ( ( ) => { // /some/0.js, /some/b.js, /a.txt
879+ clock . tick ( 99 )
880+ expect ( modified ) . to . not . have . been . called
881+ mockFs . _touchFile ( '/a.txt' , '2020-01-01' )
882+ list . changeFile ( '/a.txt' ) . then ( ( ) => {
883+ clock . tick ( 2 )
884+ expect ( modified ) . to . not . have . been . called
885+
886+ clock . tick ( 100 )
887+ expect ( modified ) . to . have . been . calledOnce
888+
889+ clock . tick ( 1000 )
890+ expect ( modified ) . to . have . been . calledOnce
891+
892+ files = modified . lastCall . args [ 0 ]
893+ expect ( pathsFrom ( files . served ) ) . to . be . eql ( [
894+ '/some/0.js' ,
895+ '/some/b.js' ,
896+ '/a.txt'
897+ ] )
898+ } )
899+ } )
751900 } )
752901 } )
753902
0 commit comments