187 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			187 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/**
 | 
						|
 * @license
 | 
						|
 * Copyright Google Inc. All Rights Reserved.
 | 
						|
 *
 | 
						|
 * Use of this source code is governed by an MIT-style license that can be
 | 
						|
 * found in the LICENSE file at https://angular.io/license
 | 
						|
 */
 | 
						|
 | 
						|
import {fakeAsync, tick} from '@angular/core/testing';
 | 
						|
import {DefaultUrlSerializer, NavigationEnd, NavigationStart, RouterEvent} from '@angular/router';
 | 
						|
import {Subject} from 'rxjs';
 | 
						|
import {filter, switchMap} from 'rxjs/operators';
 | 
						|
 | 
						|
import {Scroll} from '../src/events';
 | 
						|
import {RouterScroller} from '../src/router_scroller';
 | 
						|
 | 
						|
describe('RouterScroller', () => {
 | 
						|
  it('defaults to disabled', () => {
 | 
						|
    const events = new Subject<RouterEvent>();
 | 
						|
    const router = <any>{
 | 
						|
      events,
 | 
						|
      parseUrl: (url: any) => new DefaultUrlSerializer().parse(url),
 | 
						|
      triggerEvent: (e: any) => events.next(e)
 | 
						|
    };
 | 
						|
 | 
						|
    const viewportScroller = jasmine.createSpyObj(
 | 
						|
        'viewportScroller',
 | 
						|
        ['getScrollPosition', 'scrollToPosition', 'scrollToAnchor', 'setHistoryScrollRestoration']);
 | 
						|
    setScroll(viewportScroller, 0, 0);
 | 
						|
    const scroller = new RouterScroller(router, router);
 | 
						|
 | 
						|
    expect((scroller as any).options.scrollPositionRestoration).toBe('disabled');
 | 
						|
    expect((scroller as any).options.anchorScrolling).toBe('disabled');
 | 
						|
  });
 | 
						|
 | 
						|
  describe('scroll to top', () => {
 | 
						|
    it('should scroll to the top', () => {
 | 
						|
      const {events, viewportScroller} =
 | 
						|
          createRouterScroller({scrollPositionRestoration: 'top', anchorScrolling: 'disabled'});
 | 
						|
 | 
						|
      events.next(new NavigationStart(1, '/a'));
 | 
						|
      events.next(new NavigationEnd(1, '/a', '/a'));
 | 
						|
      expect(viewportScroller.scrollToPosition).toHaveBeenCalledWith([0, 0]);
 | 
						|
 | 
						|
      events.next(new NavigationStart(2, '/a'));
 | 
						|
      events.next(new NavigationEnd(2, '/b', '/b'));
 | 
						|
      expect(viewportScroller.scrollToPosition).toHaveBeenCalledWith([0, 0]);
 | 
						|
 | 
						|
      events.next(new NavigationStart(3, '/a', 'popstate'));
 | 
						|
      events.next(new NavigationEnd(3, '/a', '/a'));
 | 
						|
      expect(viewportScroller.scrollToPosition).toHaveBeenCalledWith([0, 0]);
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe('scroll to the stored position', () => {
 | 
						|
    it('should scroll to the stored position on popstate', () => {
 | 
						|
      const {events, viewportScroller} =
 | 
						|
          createRouterScroller({scrollPositionRestoration: 'enabled', anchorScrolling: 'disabled'});
 | 
						|
 | 
						|
      events.next(new NavigationStart(1, '/a'));
 | 
						|
      events.next(new NavigationEnd(1, '/a', '/a'));
 | 
						|
      setScroll(viewportScroller, 10, 100);
 | 
						|
      expect(viewportScroller.scrollToPosition).toHaveBeenCalledWith([0, 0]);
 | 
						|
 | 
						|
      events.next(new NavigationStart(2, '/b'));
 | 
						|
      events.next(new NavigationEnd(2, '/b', '/b'));
 | 
						|
      setScroll(viewportScroller, 20, 200);
 | 
						|
      expect(viewportScroller.scrollToPosition).toHaveBeenCalledWith([0, 0]);
 | 
						|
 | 
						|
      events.next(new NavigationStart(3, '/a', 'popstate', {navigationId: 1}));
 | 
						|
      events.next(new NavigationEnd(3, '/a', '/a'));
 | 
						|
      expect(viewportScroller.scrollToPosition).toHaveBeenCalledWith([10, 100]);
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe('anchor scrolling', () => {
 | 
						|
    it('should work (scrollPositionRestoration is disabled)', () => {
 | 
						|
      const {events, viewportScroller} =
 | 
						|
          createRouterScroller({scrollPositionRestoration: 'disabled', anchorScrolling: 'enabled'});
 | 
						|
      events.next(new NavigationStart(1, '/a#anchor'));
 | 
						|
      events.next(new NavigationEnd(1, '/a#anchor', '/a#anchor'));
 | 
						|
      expect(viewportScroller.scrollToAnchor).toHaveBeenCalledWith('anchor');
 | 
						|
 | 
						|
      events.next(new NavigationStart(2, '/a#anchor2'));
 | 
						|
      events.next(new NavigationEnd(2, '/a#anchor2', '/a#anchor2'));
 | 
						|
      expect(viewportScroller.scrollToAnchor).toHaveBeenCalledWith('anchor2');
 | 
						|
      viewportScroller.scrollToAnchor.calls.reset();
 | 
						|
 | 
						|
      // we never scroll to anchor when navigating back.
 | 
						|
      events.next(new NavigationStart(3, '/a#anchor', 'popstate'));
 | 
						|
      events.next(new NavigationEnd(3, '/a#anchor', '/a#anchor'));
 | 
						|
      expect(viewportScroller.scrollToAnchor).not.toHaveBeenCalled();
 | 
						|
      expect(viewportScroller.scrollToPosition).not.toHaveBeenCalled();
 | 
						|
    });
 | 
						|
 | 
						|
    it('should work (scrollPositionRestoration is enabled)', () => {
 | 
						|
      const {events, viewportScroller} =
 | 
						|
          createRouterScroller({scrollPositionRestoration: 'enabled', anchorScrolling: 'enabled'});
 | 
						|
      events.next(new NavigationStart(1, '/a#anchor'));
 | 
						|
      events.next(new NavigationEnd(1, '/a#anchor', '/a#anchor'));
 | 
						|
      expect(viewportScroller.scrollToAnchor).toHaveBeenCalledWith('anchor');
 | 
						|
 | 
						|
      events.next(new NavigationStart(2, '/a#anchor2'));
 | 
						|
      events.next(new NavigationEnd(2, '/a#anchor2', '/a#anchor2'));
 | 
						|
      expect(viewportScroller.scrollToAnchor).toHaveBeenCalledWith('anchor2');
 | 
						|
      viewportScroller.scrollToAnchor.calls.reset();
 | 
						|
 | 
						|
      // we never scroll to anchor when navigating back
 | 
						|
      events.next(new NavigationStart(3, '/a#anchor', 'popstate', {navigationId: 1}));
 | 
						|
      events.next(new NavigationEnd(3, '/a#anchor', '/a#anchor'));
 | 
						|
      expect(viewportScroller.scrollToAnchor).not.toHaveBeenCalled();
 | 
						|
      expect(viewportScroller.scrollToPosition).toHaveBeenCalledWith([0, 0]);
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe('extending a scroll service', () => {
 | 
						|
    it('work', fakeAsync(() => {
 | 
						|
         const {events, viewportScroller, router} = createRouterScroller(
 | 
						|
             {scrollPositionRestoration: 'disabled', anchorScrolling: 'disabled'});
 | 
						|
 | 
						|
         router.events
 | 
						|
             .pipe(filter(e => e instanceof Scroll && !!e.position), switchMap(p => {
 | 
						|
                     // can be any delay (e.g., we can wait for NgRx store to emit an event)
 | 
						|
                     const r = new Subject<any>();
 | 
						|
                     setTimeout(() => {
 | 
						|
                       r.next(p);
 | 
						|
                       r.complete();
 | 
						|
                     }, 1000);
 | 
						|
                     return r;
 | 
						|
                   }))
 | 
						|
             .subscribe((e: Scroll) => { viewportScroller.scrollToPosition(e.position); });
 | 
						|
 | 
						|
         events.next(new NavigationStart(1, '/a'));
 | 
						|
         events.next(new NavigationEnd(1, '/a', '/a'));
 | 
						|
         setScroll(viewportScroller, 10, 100);
 | 
						|
 | 
						|
         events.next(new NavigationStart(2, '/b'));
 | 
						|
         events.next(new NavigationEnd(2, '/b', '/b'));
 | 
						|
         setScroll(viewportScroller, 20, 200);
 | 
						|
 | 
						|
         events.next(new NavigationStart(3, '/c'));
 | 
						|
         events.next(new NavigationEnd(3, '/c', '/c'));
 | 
						|
         setScroll(viewportScroller, 30, 300);
 | 
						|
 | 
						|
         events.next(new NavigationStart(4, '/a', 'popstate', {navigationId: 1}));
 | 
						|
         events.next(new NavigationEnd(4, '/a', '/a'));
 | 
						|
 | 
						|
         tick(500);
 | 
						|
         expect(viewportScroller.scrollToPosition).not.toHaveBeenCalled();
 | 
						|
 | 
						|
         events.next(new NavigationStart(5, '/a', 'popstate', {navigationId: 1}));
 | 
						|
         events.next(new NavigationEnd(5, '/a', '/a'));
 | 
						|
 | 
						|
         tick(5000);
 | 
						|
         expect(viewportScroller.scrollToPosition).toHaveBeenCalledWith([10, 100]);
 | 
						|
       }));
 | 
						|
  });
 | 
						|
 | 
						|
 | 
						|
  function createRouterScroller({scrollPositionRestoration, anchorScrolling}: {
 | 
						|
    scrollPositionRestoration: 'disabled' | 'enabled' | 'top',
 | 
						|
    anchorScrolling: 'disabled' | 'enabled'
 | 
						|
  }) {
 | 
						|
    const events = new Subject<RouterEvent>();
 | 
						|
    const router = <any>{
 | 
						|
      events,
 | 
						|
      parseUrl: (url: any) => new DefaultUrlSerializer().parse(url),
 | 
						|
      triggerEvent: (e: any) => events.next(e)
 | 
						|
    };
 | 
						|
 | 
						|
    const viewportScroller = jasmine.createSpyObj(
 | 
						|
        'viewportScroller',
 | 
						|
        ['getScrollPosition', 'scrollToPosition', 'scrollToAnchor', 'setHistoryScrollRestoration']);
 | 
						|
    setScroll(viewportScroller, 0, 0);
 | 
						|
 | 
						|
    const scroller =
 | 
						|
        new RouterScroller(router, viewportScroller, {scrollPositionRestoration, anchorScrolling});
 | 
						|
    scroller.init();
 | 
						|
 | 
						|
    return {events, viewportScroller, router};
 | 
						|
  }
 | 
						|
 | 
						|
  function setScroll(viewportScroller: any, x: number, y: number) {
 | 
						|
    viewportScroller.getScrollPosition.and.returnValue([x, y]);
 | 
						|
  }
 | 
						|
});
 |