/**
 * @license
 * Copyright Google LLC 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 {Location} from '@angular/common';
import {inject, TestBed} from '@angular/core/testing';
import {RouterTestingModule} from '@angular/router/testing';
import {of} from 'rxjs';

import {Routes} from '../src/config';
import {ChildActivationStart} from '../src/events';
import {checkGuards as checkGuardsOperator} from '../src/operators/check_guards';
import {resolveData as resolveDataOperator} from '../src/operators/resolve_data';
import {NavigationTransition, Router} from '../src/router';
import {ChildrenOutletContexts} from '../src/router_outlet_context';
import {createEmptyStateSnapshot, RouterStateSnapshot} from '../src/router_state';
import {DefaultUrlSerializer, UrlTree} from '../src/url_tree';
import {getAllRouteGuards} from '../src/utils/preactivation';
import {TreeNode} from '../src/utils/tree';

import {createActivatedRouteSnapshot, Logger, provideTokenLogger} from './helpers';

describe('Router', () => {
  describe('resetConfig', () => {
    class TestComponent {}

    beforeEach(() => {
      TestBed.configureTestingModule({imports: [RouterTestingModule]});
    });

    it('should copy config to avoid mutations of user-provided objects', () => {
      const r: Router = TestBed.inject(Router);
      const configs: Routes = [{
        path: 'a',
        component: TestComponent,
        children: [{path: 'b', component: TestComponent}, {path: 'c', component: TestComponent}]
      }];
      const children = configs[0].children!;

      r.resetConfig(configs);

      const rConfigs = r.config;
      const rChildren = rConfigs[0].children!;

      // routes array and shallow copy
      expect(configs).not.toBe(rConfigs);
      expect(configs[0]).not.toBe(rConfigs[0]);
      expect(configs[0].path).toBe(rConfigs[0].path);
      expect(configs[0].component).toBe(rConfigs[0].component);

      // children should be new array and routes shallow copied
      expect(children).not.toBe(rChildren);
      expect(children[0]).not.toBe(rChildren[0]);
      expect(children[0].path).toBe(rChildren[0].path);
      expect(children[1]).not.toBe(rChildren[1]);
      expect(children[1].path).toBe(rChildren[1].path);
    });
  });

  describe('resetRootComponentType', () => {
    class NewRootComponent {}

    beforeEach(() => {
      TestBed.configureTestingModule({imports: [RouterTestingModule]});
    });

    it('should not change root route when updating the root component', () => {
      const r: Router = TestBed.inject(Router);
      const root = r.routerState.root;

      (r as any).resetRootComponentType(NewRootComponent);

      expect(r.routerState.root).toBe(root);
    });
  });

  describe('setUpLocationChangeListener', () => {
    beforeEach(() => {
      TestBed.configureTestingModule({imports: [RouterTestingModule]});
    });

    it('should be idempotent', inject([Router, Location], (r: Router, location: Location) => {
         r.setUpLocationChangeListener();
         const a = (<any>r).locationSubscription;
         r.setUpLocationChangeListener();
         const b = (<any>r).locationSubscription;

         expect(a).toBe(b);

         r.dispose();
         r.setUpLocationChangeListener();
         const c = (<any>r).locationSubscription;

         expect(c).not.toBe(b);
       }));
  });

  describe('PreActivation', () => {
    const serializer = new DefaultUrlSerializer();
    const inj = {get: (token: any) => () => `${token}_value`};
    let empty: RouterStateSnapshot;
    let logger: Logger;
    let events: any[];

    const CA_CHILD = 'canActivate_child';
    const CA_CHILD_FALSE = 'canActivate_child_false';
    const CA_CHILD_REDIRECT = 'canActivate_child_redirect';
    const CAC_CHILD = 'canActivateChild_child';
    const CAC_CHILD_FALSE = 'canActivateChild_child_false';
    const CAC_CHILD_REDIRECT = 'canActivateChild_child_redirect';
    const CA_GRANDCHILD = 'canActivate_grandchild';
    const CA_GRANDCHILD_FALSE = 'canActivate_grandchild_false';
    const CA_GRANDCHILD_REDIRECT = 'canActivate_grandchild_redirect';
    const CDA_CHILD = 'canDeactivate_child';
    const CDA_CHILD_FALSE = 'canDeactivate_child_false';
    const CDA_CHILD_REDIRECT = 'canDeactivate_child_redirect';
    const CDA_GRANDCHILD = 'canDeactivate_grandchild';
    const CDA_GRANDCHILD_FALSE = 'canDeactivate_grandchild_false';
    const CDA_GRANDCHILD_REDIRECT = 'canDeactivate_grandchild_redirect';

    beforeEach(() => {
      TestBed.configureTestingModule({
        imports: [RouterTestingModule],
        providers: [
          Logger, provideTokenLogger(CA_CHILD), provideTokenLogger(CA_CHILD_FALSE, false),
          provideTokenLogger(CA_CHILD_REDIRECT, serializer.parse('/canActivate_child_redirect')),
          provideTokenLogger(CAC_CHILD), provideTokenLogger(CAC_CHILD_FALSE, false),
          provideTokenLogger(
              CAC_CHILD_REDIRECT, serializer.parse('/canActivateChild_child_redirect')),
          provideTokenLogger(CA_GRANDCHILD), provideTokenLogger(CA_GRANDCHILD_FALSE, false),
          provideTokenLogger(
              CA_GRANDCHILD_REDIRECT, serializer.parse('/canActivate_grandchild_redirect')),
          provideTokenLogger(CDA_CHILD), provideTokenLogger(CDA_CHILD_FALSE, false),
          provideTokenLogger(CDA_CHILD_REDIRECT, serializer.parse('/canDeactivate_child_redirect')),
          provideTokenLogger(CDA_GRANDCHILD), provideTokenLogger(CDA_GRANDCHILD_FALSE, false),
          provideTokenLogger(
              CDA_GRANDCHILD_REDIRECT, serializer.parse('/canDeactivate_grandchild_redirect'))
        ]
      });
    });

    beforeEach(inject([Logger], (_logger: Logger) => {
      empty = createEmptyStateSnapshot(serializer.parse('/'), null!);
      logger = _logger;
      events = [];
    }));

    describe('ChildActivation', () => {
      it('should run', () => {
        /**
         * R  -->  R (ChildActivationStart)
         *          \
         *           child
         */
        let result = false;
        const childSnapshot =
            createActivatedRouteSnapshot({component: 'child', routeConfig: {path: 'child'}});
        const futureState = new (RouterStateSnapshot as any)(
            'url', new TreeNode(empty.root, [new TreeNode(childSnapshot, [])]));
        // Since we only test the guards, we don't need to provide a full navigation
        // transition object with all properties set.
        const testTransition = {
          guards: getAllRouteGuards(futureState, empty, new ChildrenOutletContexts())
        } as NavigationTransition;

        of(testTransition)
            .pipe(checkGuardsOperator(
                TestBed,
                (evt) => {
                  events.push(evt);
                }))
            .subscribe((x) => result = !!x.guardsResult, (e) => {
              throw e;
            });

        expect(result).toBe(true);
        expect(events.length).toEqual(2);
        expect(events[0].snapshot).toBe(events[0].snapshot.root);
        expect(events[1].snapshot.routeConfig.path).toBe('child');
      });

      it('should run from top to bottom', () => {
        /**
         * R  -->  R (ChildActivationStart)
         *          \
         *           child (ChildActivationStart)
         *            \
         *             grandchild (ChildActivationStart)
         *              \
         *               great grandchild
         */
        let result = false;
        const childSnapshot =
            createActivatedRouteSnapshot({component: 'child', routeConfig: {path: 'child'}});
        const grandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'grandchild', routeConfig: {path: 'grandchild'}});
        const greatGrandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'great-grandchild', routeConfig: {path: 'great-grandchild'}});
        const futureState = new (RouterStateSnapshot as any)(
            'url',
            new TreeNode(
                empty.root, [new TreeNode(childSnapshot, [
                  new TreeNode(grandchildSnapshot, [new TreeNode(greatGrandchildSnapshot, [])])
                ])]));
        // Since we only test the guards, we don't need to provide a full navigation
        // transition object with all properties set.
        const testTransition = {
          guards: getAllRouteGuards(futureState, empty, new ChildrenOutletContexts())
        } as NavigationTransition;

        of(testTransition)
            .pipe(checkGuardsOperator(
                TestBed,
                (evt) => {
                  events.push(evt);
                }))
            .subscribe((x) => result = !!x.guardsResult, (e) => {
              throw e;
            });

        expect(result).toBe(true);
        expect(events.length).toEqual(6);
        expect(events[0].snapshot).toBe(events[0].snapshot.root);
        expect(events[2].snapshot.routeConfig.path).toBe('child');
        expect(events[4].snapshot.routeConfig.path).toBe('grandchild');
        expect(events[5].snapshot.routeConfig.path).toBe('great-grandchild');
      });

      it('should not run for unchanged routes', () => {
        /**
         *         R  -->  R
         *        / \
         *   child   child (ChildActivationStart)
         *            \
         *             grandchild
         */
        let result = false;
        const childSnapshot =
            createActivatedRouteSnapshot({component: 'child', routeConfig: {path: 'child'}});
        const grandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'grandchild', routeConfig: {path: 'grandchild'}});
        const currentState = new (RouterStateSnapshot as any)(
            'url', new TreeNode(empty.root, [new TreeNode(childSnapshot, [])]));
        const futureState = new (RouterStateSnapshot as any)(
            'url',
            new TreeNode(
                empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
        // Since we only test the guards, we don't need to provide a full navigation
        // transition object with all properties set.
        const testTransition = {
          guards: getAllRouteGuards(futureState, currentState, new ChildrenOutletContexts())
        } as NavigationTransition;

        of(testTransition)
            .pipe(checkGuardsOperator(
                TestBed,
                (evt) => {
                  events.push(evt);
                }))
            .subscribe((x) => result = !!x.guardsResult, (e) => {
              throw e;
            });

        expect(result).toBe(true);
        expect(events.length).toEqual(2);
        expect(events[0].snapshot).not.toBe(events[0].snapshot.root);
        expect(events[0].snapshot.routeConfig.path).toBe('child');
      });

      it('should skip multiple unchanged routes but fire for all changed routes', () => {
        /**
         *         R  -->  R
         *            / \
         *       child   child
         *          /     \
         * grandchild      grandchild (ChildActivationStart)
         *                  \
         *                   greatgrandchild (ChildActivationStart)
         *                    \
         *                     great-greatgrandchild
         */
        let result = false;
        const childSnapshot =
            createActivatedRouteSnapshot({component: 'child', routeConfig: {path: 'child'}});
        const grandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'grandchild', routeConfig: {path: 'grandchild'}});
        const greatGrandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'greatgrandchild', routeConfig: {path: 'greatgrandchild'}});
        const greatGreatGrandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'great-greatgrandchild', routeConfig: {path: 'great-greatgrandchild'}});
        const currentState = new (RouterStateSnapshot as any)(
            'url',
            new TreeNode(
                empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
        const futureState = new (RouterStateSnapshot as any)(
            'url',
            new TreeNode(
                empty.root,
                [new TreeNode(
                    childSnapshot, [new TreeNode(grandchildSnapshot, [
                      new TreeNode(
                          greatGrandchildSnapshot, [new TreeNode(greatGreatGrandchildSnapshot, [])])
                    ])])]));
        // Since we only test the guards, we don't need to provide a full navigation
        // transition object with all properties set.
        const testTransition = {
          guards: getAllRouteGuards(futureState, currentState, new ChildrenOutletContexts())
        } as NavigationTransition;

        of(testTransition)
            .pipe(checkGuardsOperator(
                TestBed,
                (evt) => {
                  events.push(evt);
                }))
            .subscribe((x) => result = !!x.guardsResult, (e) => {
              throw e;
            });

        expect(result).toBe(true);
        expect(events.length).toEqual(4);
        expect(events[0] instanceof ChildActivationStart).toBe(true);
        expect(events[0].snapshot).not.toBe(events[0].snapshot.root);
        expect(events[0].snapshot.routeConfig.path).toBe('grandchild');
        expect(events[2].snapshot.routeConfig.path).toBe('greatgrandchild');
      });
    });

    describe('guards', () => {
      it('should run CanActivate checks', () => {
        /**
         * R  -->  R
         *          \
         *           child (CA, CAC)
         *            \
         *             grandchild (CA)
         */

        const childSnapshot = createActivatedRouteSnapshot({
          component: 'child',
          routeConfig: {

            canActivate: [CA_CHILD],
            canActivateChild: [CAC_CHILD]
          }
        });
        const grandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});

        const futureState = new (RouterStateSnapshot as any)(
            'url',
            new TreeNode(
                empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));

        checkGuards(futureState, empty, TestBed, (result) => {
          expect(result).toBe(true);
          expect(logger.logs).toEqual([CA_CHILD, CAC_CHILD, CA_GRANDCHILD]);
        });
      });

      it('should not run grandchild guards if child fails', () => {
        /**
         * R  -->  R
         *          \
         *           child (CA: x, CAC)
         *            \
         *             grandchild (CA)
         */

        const childSnapshot = createActivatedRouteSnapshot({
          component: 'child',
          routeConfig: {canActivate: [CA_CHILD_FALSE], canActivateChild: [CAC_CHILD]}
        });
        const grandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});

        const futureState = new (RouterStateSnapshot as any)(
            'url',
            new TreeNode(
                empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));

        checkGuards(futureState, empty, TestBed, (result) => {
          expect(result).toBe(false);
          expect(logger.logs).toEqual([CA_CHILD_FALSE]);
        });
      });

      it('should not run grandchild guards if child canActivateChild fails', () => {
        /**
         * R  -->  R
         *          \
         *           child (CA, CAC: x)
         *            \
         *             grandchild (CA)
         */

        const childSnapshot = createActivatedRouteSnapshot({
          component: 'child',
          routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD_FALSE]}
        });
        const grandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});

        const futureState = new (RouterStateSnapshot as any)(
            'url',
            new TreeNode(
                empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));

        checkGuards(futureState, empty, TestBed, (result) => {
          expect(result).toBe(false);
          expect(logger.logs).toEqual([CA_CHILD, CAC_CHILD_FALSE]);
        });
      });

      it('should run deactivate guards before activate guards', () => {
        /**
         *      R  -->  R
         *     /         \
         *    prev (CDA)  child (CA)
         *                 \
         *                  grandchild (CA)
         */

        const prevSnapshot = createActivatedRouteSnapshot(
            {component: 'prev', routeConfig: {canDeactivate: [CDA_CHILD]}});

        const childSnapshot = createActivatedRouteSnapshot({
          component: 'child',
          routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
        });

        const grandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});

        const currentState = new (RouterStateSnapshot as any)(
            'prev', new TreeNode(empty.root, [new TreeNode(prevSnapshot, [])]));

        const futureState = new (RouterStateSnapshot as any)(
            'url',
            new TreeNode(
                empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));

        checkGuards(futureState, currentState, TestBed, (result) => {
          expect(logger.logs).toEqual([CDA_CHILD, CA_CHILD, CAC_CHILD, CA_GRANDCHILD]);
        });
      });

      it('should not run activate if deactivate fails guards', () => {
        /**
         *      R  -->     R
         *     /            \
         *    prev (CDA: x)  child (CA)
         *                    \
         *                     grandchild (CA)
         */

        const prevSnapshot = createActivatedRouteSnapshot(
            {component: 'prev', routeConfig: {canDeactivate: [CDA_CHILD_FALSE]}});
        const childSnapshot = createActivatedRouteSnapshot({
          component: 'child',
          routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
        });
        const grandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});

        const currentState = new (RouterStateSnapshot as any)(
            'prev', new TreeNode(empty.root, [new TreeNode(prevSnapshot, [])]));
        const futureState = new (RouterStateSnapshot as any)(
            'url',
            new TreeNode(
                empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));

        checkGuards(futureState, currentState, TestBed, (result) => {
          expect(result).toBe(false);
          expect(logger.logs).toEqual([CDA_CHILD_FALSE]);
        });
      });
      it('should deactivate from bottom up, then activate top down', () => {
        /**
         *      R     -->      R
         *     /                \
         *    prevChild (CDA)    child (CA)
         *   /                    \
         *  prevGrandchild(CDA)    grandchild (CA)
         */

        const prevChildSnapshot = createActivatedRouteSnapshot(
            {component: 'prev_child', routeConfig: {canDeactivate: [CDA_CHILD]}});
        const prevGrandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'prev_grandchild', routeConfig: {canDeactivate: [CDA_GRANDCHILD]}});
        const childSnapshot = createActivatedRouteSnapshot({
          component: 'child',
          routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
        });
        const grandchildSnapshot = createActivatedRouteSnapshot(
            {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});

        const currentState = new (RouterStateSnapshot as any)(
            'prev', new TreeNode(empty.root, [
              new TreeNode(prevChildSnapshot, [new TreeNode(prevGrandchildSnapshot, [])])
            ]));

        const futureState = new (RouterStateSnapshot as any)(
            'url',
            new TreeNode(
                empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));

        checkGuards(futureState, currentState, TestBed, (result) => {
          expect(result).toBe(true);
          expect(logger.logs).toEqual([
            CDA_GRANDCHILD, CDA_CHILD, CA_CHILD, CAC_CHILD, CA_GRANDCHILD
          ]);
        });

        logger.empty();
        checkGuards(currentState, futureState, TestBed, (result) => {
          expect(result).toBe(true);
          expect(logger.logs).toEqual([]);
        });
      });

      describe('UrlTree', () => {
        it('should allow return of UrlTree from CanActivate', () => {
          /**
           * R  -->  R
           *          \
           *           child (CA: redirect)
           */

          const childSnapshot = createActivatedRouteSnapshot({
            component: 'child',
            routeConfig: {

              canActivate: [CA_CHILD_REDIRECT]
            }
          });

          const futureState = new (RouterStateSnapshot as any)(
              'url', new TreeNode(empty.root, [new TreeNode(childSnapshot, [])]));

          checkGuards(futureState, empty, TestBed, (result) => {
            expect(serializer.serialize(result as UrlTree)).toBe('/' + CA_CHILD_REDIRECT);
            expect(logger.logs).toEqual([CA_CHILD_REDIRECT]);
          });
        });

        it('should allow return of UrlTree from CanActivateChild', () => {
          /**
           * R  -->  R
           *          \
           *           child (CAC: redirect)
           *            \
           *             grandchild (CA)
           */

          const childSnapshot = createActivatedRouteSnapshot(
              {component: 'child', routeConfig: {canActivateChild: [CAC_CHILD_REDIRECT]}});
          const grandchildSnapshot = createActivatedRouteSnapshot(
              {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});

          const futureState = new (RouterStateSnapshot as any)(
              'url', new TreeNode(empty.root, [
                new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])
              ]));

          checkGuards(futureState, empty, TestBed, (result) => {
            expect(serializer.serialize(result as UrlTree)).toBe('/' + CAC_CHILD_REDIRECT);
            expect(logger.logs).toEqual([CAC_CHILD_REDIRECT]);
          });
        });

        it('should allow return of UrlTree from a child CanActivate', () => {
          /**
           * R  -->  R
           *          \
           *           child (CAC)
           *            \
           *             grandchild (CA: redirect)
           */

          const childSnapshot = createActivatedRouteSnapshot(
              {component: 'child', routeConfig: {canActivateChild: [CAC_CHILD]}});
          const grandchildSnapshot = createActivatedRouteSnapshot(
              {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD_REDIRECT]}});

          const futureState = new (RouterStateSnapshot as any)(
              'url', new TreeNode(empty.root, [
                new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])
              ]));

          checkGuards(futureState, empty, TestBed, (result) => {
            expect(serializer.serialize(result as UrlTree)).toBe('/' + CA_GRANDCHILD_REDIRECT);
            expect(logger.logs).toEqual([CAC_CHILD, CA_GRANDCHILD_REDIRECT]);
          });
        });

        it('should allow return of UrlTree from a child CanDeactivate', () => {
          /**
           *      R  -->            R
           *     /                   \
           *    prev (CDA: redirect)  child (CA)
           *                           \
           *                            grandchild (CA)
           */

          const prevSnapshot = createActivatedRouteSnapshot(
              {component: 'prev', routeConfig: {canDeactivate: [CDA_CHILD_REDIRECT]}});
          const childSnapshot = createActivatedRouteSnapshot({
            component: 'child',
            routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
          });
          const grandchildSnapshot = createActivatedRouteSnapshot(
              {component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});

          const currentState = new (RouterStateSnapshot as any)(
              'prev', new TreeNode(empty.root, [new TreeNode(prevSnapshot, [])]));
          const futureState = new (RouterStateSnapshot as any)(
              'url', new TreeNode(empty.root, [
                new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])
              ]));

          checkGuards(futureState, currentState, TestBed, (result) => {
            expect(serializer.serialize(result as UrlTree)).toBe('/' + CDA_CHILD_REDIRECT);
            expect(logger.logs).toEqual([CDA_CHILD_REDIRECT]);
          });
        });
      });
    });

    describe('resolve', () => {
      it('should resolve data', () => {
        /**
         * R  -->  R
         *          \
         *           a
         */
        const r = {data: 'resolver'};
        const n = createActivatedRouteSnapshot({component: 'a', resolve: r});
        const s = new (RouterStateSnapshot as any)(
            'url', new TreeNode(empty.root, [new TreeNode(n, [])]));

        checkResolveData(s, empty, inj, () => {
          expect(s.root.firstChild!.data).toEqual({data: 'resolver_value'});
        });
      });

      it('should wait for the parent resolve to complete', () => {
        /**
         * R  -->  R
         *          \
         *           null (resolve: parentResolve)
         *            \
         *             b (resolve: childResolve)
         */
        const parentResolve = {data: 'resolver'};
        const childResolve = {};

        const parent = createActivatedRouteSnapshot({component: null!, resolve: parentResolve});
        const child = createActivatedRouteSnapshot({component: 'b', resolve: childResolve});

        const s = new (RouterStateSnapshot as any)(
            'url', new TreeNode(empty.root, [new TreeNode(parent, [new TreeNode(child, [])])]));

        const inj = {get: (token: any) => () => Promise.resolve(`${token}_value`)};

        checkResolveData(s, empty, inj, () => {
          expect(s.root.firstChild!.firstChild!.data).toEqual({data: 'resolver_value'});
        });
      });

      it('should copy over data when creating a snapshot', () => {
        /**
         * R  -->  R         -->         R
         *          \                     \
         *           n1 (resolve: r1)      n21 (resolve: r1)
         *                                  \
         *                                   n22 (resolve: r2)
         */
        const r1 = {data: 'resolver1'};
        const r2 = {data: 'resolver2'};

        const n1 = createActivatedRouteSnapshot({component: 'a', resolve: r1});
        const s1 = new (RouterStateSnapshot as any)(
            'url', new TreeNode(empty.root, [new TreeNode(n1, [])]));
        checkResolveData(s1, empty, inj, () => {});

        const n21 = createActivatedRouteSnapshot({component: 'a', resolve: r1});
        const n22 = createActivatedRouteSnapshot({component: 'b', resolve: r2});
        const s2 = new (RouterStateSnapshot as any)(
            'url', new TreeNode(empty.root, [new TreeNode(n21, [new TreeNode(n22, [])])]));
        checkResolveData(s2, s1, inj, () => {
          expect(s2.root.firstChild!.data).toEqual({data: 'resolver1_value'});
          expect(s2.root.firstChild!.firstChild!.data).toEqual({data: 'resolver2_value'});
        });
      });
    });
  });
});

function checkResolveData(
    future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any, check: any): void {
  // Since we only test the guards and their resolve data function, we don't need to provide
  // a full navigation transition object with all properties set.
  of({guards: getAllRouteGuards(future, curr, new ChildrenOutletContexts())} as
     NavigationTransition)
      .pipe(resolveDataOperator('emptyOnly', injector))
      .subscribe(check, (e) => {
        throw e;
      });
}

function checkGuards(
    future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any,
    check: (result: boolean|UrlTree) => void): void {
  // Since we only test the guards, we don't need to provide a full navigation
  // transition object with all properties set.
  of({guards: getAllRouteGuards(future, curr, new ChildrenOutletContexts())} as
     NavigationTransition)
      .pipe(checkGuardsOperator(injector))
      .subscribe({
        next(t) {
          if (t.guardsResult === null) throw new Error('Guard result expected');
          return check(t.guardsResult);
        },
        error(e) {
          throw e;
        }
      });
}