@@ -329,6 +329,200 @@ describe('MatTabHeader', () => {
329329 } ) ;
330330 } ) ;
331331
332+ describe ( 'scrolling when holding paginator' , ( ) => {
333+ let nextButton : HTMLElement ;
334+ let prevButton : HTMLElement ;
335+ let header : MatTabHeader ;
336+ let headerElement : HTMLElement ;
337+
338+ beforeEach ( ( ) => {
339+ fixture = TestBed . createComponent ( SimpleTabHeaderApp ) ;
340+ fixture . componentInstance . disableRipple = true ;
341+ fixture . detectChanges ( ) ;
342+
343+ fixture . componentInstance . addTabsForScrolling ( 50 ) ;
344+ fixture . detectChanges ( ) ;
345+
346+ nextButton = fixture . nativeElement . querySelector ( '.mat-tab-header-pagination-after' ) ;
347+ prevButton = fixture . nativeElement . querySelector ( '.mat-tab-header-pagination-before' ) ;
348+ header = fixture . componentInstance . tabHeader ;
349+ headerElement = fixture . nativeElement . querySelector ( '.mat-tab-header' ) ;
350+ } ) ;
351+
352+ it ( 'should scroll towards the end while holding down the next button using a mouse' ,
353+ fakeAsync ( ( ) => {
354+ assertNextButtonScrolling ( 'mousedown' , 'click' ) ;
355+ } ) ) ;
356+
357+ it ( 'should scroll towards the start while holding down the prev button using a mouse' ,
358+ fakeAsync ( ( ) => {
359+ assertPrevButtonScrolling ( 'mousedown' , 'click' ) ;
360+ } ) ) ;
361+
362+ it ( 'should scroll towards the end while holding down the next button using touch' ,
363+ fakeAsync ( ( ) => {
364+ assertNextButtonScrolling ( 'touchstart' , 'touchend' ) ;
365+ } ) ) ;
366+
367+ it ( 'should scroll towards the start while holding down the prev button using touch' ,
368+ fakeAsync ( ( ) => {
369+ assertPrevButtonScrolling ( 'touchstart' , 'touchend' ) ;
370+ } ) ) ;
371+
372+ it ( 'should not scroll if the sequence is interrupted quickly' , fakeAsync ( ( ) => {
373+ expect ( header . scrollDistance ) . toBe ( 0 , 'Expected to start off not scrolled.' ) ;
374+
375+ dispatchFakeEvent ( nextButton , 'mousedown' ) ;
376+ fixture . detectChanges ( ) ;
377+
378+ tick ( 100 ) ;
379+
380+ dispatchFakeEvent ( headerElement , 'mouseleave' ) ;
381+ fixture . detectChanges ( ) ;
382+
383+ tick ( 3000 ) ;
384+
385+ expect ( header . scrollDistance ) . toBe ( 0 , 'Expected not to have scrolled after a while.' ) ;
386+ } ) ) ;
387+
388+ it ( 'should clear the timeouts on destroy' , fakeAsync ( ( ) => {
389+ dispatchFakeEvent ( nextButton , 'mousedown' ) ;
390+ fixture . detectChanges ( ) ;
391+ fixture . destroy ( ) ;
392+
393+ // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared.
394+ } ) ) ;
395+
396+ it ( 'should clear the timeouts on click' , fakeAsync ( ( ) => {
397+ dispatchFakeEvent ( nextButton , 'mousedown' ) ;
398+ fixture . detectChanges ( ) ;
399+
400+ dispatchFakeEvent ( nextButton , 'click' ) ;
401+ fixture . detectChanges ( ) ;
402+
403+ // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared.
404+ } ) ) ;
405+
406+ it ( 'should clear the timeouts on touchend' , fakeAsync ( ( ) => {
407+ dispatchFakeEvent ( nextButton , 'touchstart' ) ;
408+ fixture . detectChanges ( ) ;
409+
410+ dispatchFakeEvent ( nextButton , 'touchend' ) ;
411+ fixture . detectChanges ( ) ;
412+
413+ // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared.
414+ } ) ) ;
415+
416+ it ( 'should clear the timeouts when reaching the end' , fakeAsync ( ( ) => {
417+ dispatchFakeEvent ( nextButton , 'mousedown' ) ;
418+ fixture . detectChanges ( ) ;
419+
420+ // Simulate a very long timeout.
421+ tick ( 60000 ) ;
422+
423+ // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared.
424+ } ) ) ;
425+
426+ it ( 'should clear the timeouts when reaching the start' , fakeAsync ( ( ) => {
427+ header . scrollDistance = Infinity ;
428+ fixture . detectChanges ( ) ;
429+
430+ dispatchFakeEvent ( prevButton , 'mousedown' ) ;
431+ fixture . detectChanges ( ) ;
432+
433+ // Simulate a very long timeout.
434+ tick ( 60000 ) ;
435+
436+ // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared.
437+ } ) ) ;
438+
439+ it ( 'should stop scrolling if the pointer leaves the header' , fakeAsync ( ( ) => {
440+ expect ( header . scrollDistance ) . toBe ( 0 , 'Expected to start off not scrolled.' ) ;
441+
442+ dispatchFakeEvent ( nextButton , 'mousedown' ) ;
443+ fixture . detectChanges ( ) ;
444+ tick ( 300 ) ;
445+
446+ expect ( header . scrollDistance ) . toBe ( 0 , 'Expected not to scroll after short amount of time.' ) ;
447+
448+ tick ( 1000 ) ;
449+
450+ expect ( header . scrollDistance ) . toBeGreaterThan ( 0 , 'Expected to scroll after some time.' ) ;
451+
452+ let previousDistance = header . scrollDistance ;
453+
454+ dispatchFakeEvent ( headerElement , 'mouseleave' ) ;
455+ fixture . detectChanges ( ) ;
456+ tick ( 100 ) ;
457+
458+ expect ( header . scrollDistance ) . toBe ( previousDistance ) ;
459+ } ) ) ;
460+
461+ /**
462+ * Asserts that auto scrolling using the next button works.
463+ * @param startEventName Name of the event that is supposed to start the scrolling.
464+ * @param endEventName Name of the event that is supposed to end the scrolling.
465+ */
466+ function assertNextButtonScrolling ( startEventName : string , endEventName : string ) {
467+ expect ( header . scrollDistance ) . toBe ( 0 , 'Expected to start off not scrolled.' ) ;
468+
469+ dispatchFakeEvent ( nextButton , startEventName ) ;
470+ fixture . detectChanges ( ) ;
471+ tick ( 300 ) ;
472+
473+ expect ( header . scrollDistance ) . toBe ( 0 , 'Expected not to scroll after short amount of time.' ) ;
474+
475+ tick ( 1000 ) ;
476+
477+ expect ( header . scrollDistance ) . toBeGreaterThan ( 0 , 'Expected to scroll after some time.' ) ;
478+
479+ let previousDistance = header . scrollDistance ;
480+
481+ tick ( 100 ) ;
482+
483+ expect ( header . scrollDistance )
484+ . toBeGreaterThan ( previousDistance , 'Expected to scroll again after some more time.' ) ;
485+
486+ dispatchFakeEvent ( nextButton , endEventName ) ;
487+ }
488+
489+ /**
490+ * Asserts that auto scrolling using the previous button works.
491+ * @param startEventName Name of the event that is supposed to start the scrolling.
492+ * @param endEventName Name of the event that is supposed to end the scrolling.
493+ */
494+ function assertPrevButtonScrolling ( startEventName : string , endEventName : string ) {
495+ header . scrollDistance = Infinity ;
496+ fixture . detectChanges ( ) ;
497+
498+ let currentScroll = header . scrollDistance ;
499+
500+ expect ( currentScroll ) . toBeGreaterThan ( 0 , 'Expected to start off scrolled.' ) ;
501+
502+ dispatchFakeEvent ( prevButton , startEventName ) ;
503+ fixture . detectChanges ( ) ;
504+ tick ( 300 ) ;
505+
506+ expect ( header . scrollDistance )
507+ . toBe ( currentScroll , 'Expected not to scroll after short amount of time.' ) ;
508+
509+ tick ( 1000 ) ;
510+
511+ expect ( header . scrollDistance )
512+ . toBeLessThan ( currentScroll , 'Expected to scroll after some time.' ) ;
513+
514+ currentScroll = header . scrollDistance ;
515+
516+ tick ( 100 ) ;
517+
518+ expect ( header . scrollDistance )
519+ . toBeLessThan ( currentScroll , 'Expected to scroll again after some more time.' ) ;
520+
521+ dispatchFakeEvent ( nextButton , endEventName ) ;
522+ }
523+
524+ } ) ;
525+
332526 it ( 'should re-align the ink bar when the direction changes' , fakeAsync ( ( ) => {
333527 fixture = TestBed . createComponent ( SimpleTabHeaderApp ) ;
334528
@@ -453,7 +647,9 @@ class SimpleTabHeaderApp {
453647 this . tabs [ this . disabledTabIndex ] . disabled = true ;
454648 }
455649
456- addTabsForScrolling ( ) {
457- this . tabs . push ( { label : 'new' } , { label : 'new' } , { label : 'new' } , { label : 'new' } ) ;
650+ addTabsForScrolling ( amount = 4 ) {
651+ for ( let i = 0 ; i < amount ; i ++ ) {
652+ this . tabs . push ( { label : 'new' } ) ;
653+ }
458654 }
459655}
0 commit comments