/**
 * @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
 */

'use strict';

describe('navigation', function () {

  var elt,
      $compile,
      $rootScope,
      $rootRouter,
      $compileProvider;

  beforeEach(function () {
    module('ng');
    module('ngComponentRouter');
    module(function (_$compileProvider_) {
      $compileProvider = _$compileProvider_;
    });

    inject(function (_$compile_, _$rootScope_, _$rootRouter_) {
      $compile = _$compile_;
      $rootScope = _$rootScope_;
      $rootRouter = _$rootRouter_;
    });

    registerDirective('userCmp', {
      template: '<div>hello {{userCmp.$routeParams.name}}</div>',
      $routerOnActivate: function(next) {
        this.$routeParams = next.params;
      }
    });
    registerDirective('oneCmp', {
      template: '<div>{{oneCmp.number}}</div>',
      controller: function () {this.number = 'one'}
    });
    registerDirective('twoCmp', {
      template: '<div>{{twoCmp.number}}</div>',
      controller: function () {this.number = 'two'}
    });
    registerComponent('threeCmp', {
      template: '<div>{{$ctrl.number}}</div>',
      controller: function () {this.number = 'three'}
    });
    registerComponent('getParams', {
      template: '<div>{{$ctrl.params.x}}</div>',
      controller: function () {
        this.$routerOnActivate = function(next) {
          this.params = next.params;
        };
      }
    })
  });

  it('should work in a simple case', function () {
    compile('<ng-outlet></ng-outlet>');

    $rootRouter.config([
      { path: '/', component: 'oneCmp' }
    ]);

    $rootRouter.navigateByUrl('/');
    $rootScope.$digest();

    expect(elt.text()).toBe('one');
  });


  it('should work with components created by the `mod.component()` helper', function () {
    compile('<ng-outlet></ng-outlet>');

    $rootRouter.config([
      { path: '/', component: 'threeCmp' }
    ]);

    $rootRouter.navigateByUrl('/');
    $rootScope.$digest();

    expect(elt.text()).toBe('three');
  });


  it('should navigate between components with different parameters', function () {
    $rootRouter.config([
      { path: '/user/:name', component: 'userCmp' }
    ]);
    compile('<ng-outlet></ng-outlet>');

    $rootRouter.navigateByUrl('/user/brian');
    $rootScope.$digest();
    expect(elt.text()).toBe('hello brian');

    $rootRouter.navigateByUrl('/user/igor');
    $rootScope.$digest();
    expect(elt.text()).toBe('hello igor');
  });


  it('should reuse a parent when navigating between child components with different parameters', function () {
    var instanceCount = 0;
    function ParentController() {
      instanceCount += 1;
    }
    registerDirective('parentCmp', {
      template: 'parent { <ng-outlet></ng-outlet> }',
      $routeConfig: [
        { path: '/user/:name', component: 'userCmp' }
      ],
      controller: ParentController
    });

    $rootRouter.config([
      { path: '/parent/...', component: 'parentCmp' }
    ]);
    compile('<ng-outlet></ng-outlet>');

    $rootRouter.navigateByUrl('/parent/user/brian');
    $rootScope.$digest();
    expect(instanceCount).toBe(1);
    expect(elt.text()).toBe('parent { hello brian }');

    $rootRouter.navigateByUrl('/parent/user/igor');
    $rootScope.$digest();
    expect(instanceCount).toBe(1);
    expect(elt.text()).toBe('parent { hello igor }');
  });


  it('should work with nested outlets', function () {
    registerDirective('childCmp', {
      template: '<div>inner { <div ng-outlet></div> }</div>',
      $routeConfig: [
        { path: '/b', component: 'oneCmp' }
      ]
    });

    $rootRouter.config([
      { path: '/a/...', component: 'childCmp' }
    ]);
    compile('<div>outer { <div ng-outlet></div> }</div>');

    $rootRouter.navigateByUrl('/a/b');
    $rootScope.$digest();

    expect(elt.text()).toBe('outer { inner { one } }');
  });

  it('should work when parent route has empty path', inject(function ($location) {
    registerComponent('childCmp', {
      template: '<div>inner { <div ng-outlet></div> }</div>',
      $routeConfig: [
        { path: '/b', component: 'oneCmp' }
      ]
    });

    $rootRouter.config([
      { path: '/...', component: 'childCmp' }
    ]);
    compile('<div>outer { <div ng-outlet></div> }</div>');

    $rootRouter.navigateByUrl('/b');
    $rootScope.$digest();

    expect(elt.text()).toBe('outer { inner { one } }');
    expect($location.path()).toBe('/b');
  }));


  it('should work with recursive nested outlets', function () {
    registerDirective('recurCmp', {
      template: '<div>recur { <div ng-outlet></div> }</div>',
      $routeConfig: [
        { path: '/recur', component: 'recurCmp' },
        { path: '/end', component: 'oneCmp' }
      ]});

    $rootRouter.config([
      { path: '/recur', component: 'recurCmp' },
      { path: '/', component: 'oneCmp' }
    ]);

    compile('<div>root { <div ng-outlet></div> }</div>');
    $rootRouter.navigateByUrl('/recur/recur/end');
    $rootScope.$digest();
    expect(elt.text()).toBe('root { one }');
  });


  it('should change location path', inject(function ($location) {
    $rootRouter.config([
      { path: '/user', component: 'userCmp' }
    ]);

    compile('<div ng-outlet></div>');

    $rootRouter.navigateByUrl('/user');
    $rootScope.$digest();

    expect($location.path()).toBe('/user');
  }));


  it('should pass through query terms to the location', inject(function ($location) {
    $rootRouter.config([
      { path: '/user', component: 'userCmp' }
    ]);

    compile('<div ng-outlet></div>');

    $rootRouter.navigateByUrl('/user?x=y');
    $rootScope.$digest();

    expect($location.path()).toBe('/user');
    expect($location.search()).toEqual({ x: 'y'});
  }));


  it('should change location to the canonical route', inject(function ($location) {
    compile('<div ng-outlet></div>');

    $rootRouter.config([
      { path: '/',     redirectTo: ['/User'] },
      { path: '/user', component:  'userCmp', name: 'User' }
    ]);

    $rootRouter.navigateByUrl('/');
    $rootScope.$digest();

    expect($location.path()).toBe('/user');
  }));


  it('should change location to the canonical route with nested components', inject(function ($location) {
    registerDirective('childRouter', {
      template: '<div>inner { <div ng-outlet></div> }</div>',
      $routeConfig: [
        { path: '/new-child', component: 'oneCmp', name: 'NewChild'},
        { path: '/new-child-two', component: 'twoCmp', name: 'NewChildTwo'}
      ]
    });

    $rootRouter.config([
      { path: '/old-parent/old-child', redirectTo: ['/NewParent', 'NewChild'] },
      { path: '/old-parent/old-child-two', redirectTo: ['/NewParent', 'NewChildTwo'] },
      { path: '/new-parent/...', component:  'childRouter', name: 'NewParent' }
    ]);

    compile('<div ng-outlet></div>');

    $rootRouter.navigateByUrl('/old-parent/old-child');
    $rootScope.$digest();

    expect($location.path()).toBe('/new-parent/new-child');
    expect(elt.text()).toBe('inner { one }');

    $rootRouter.navigateByUrl('/old-parent/old-child-two');
    $rootScope.$digest();

    expect($location.path()).toBe('/new-parent/new-child-two');
    expect(elt.text()).toBe('inner { two }');
  }));


  it('should navigate when the location path changes', inject(function ($location) {
    $rootRouter.config([
      { path: '/one', component: 'oneCmp' }
    ]);
    compile('<div ng-outlet></div>');

    $location.path('/one');
    $rootScope.$digest();

    expect(elt.text()).toBe('one');
  }));


  it('should navigate when the location query changes', inject(function ($location) {
    $rootRouter.config([
      { path: '/get/params', component: 'getParams' }
    ]);
    compile('<div ng-outlet></div>');

    $location.url('/get/params?x=y');
    $rootScope.$digest();

    expect(elt.text()).toBe('y');
  }));


  it('should expose a "navigating" property on $rootRouter', inject(function ($q) {
    var defer;
    registerDirective('pendingActivate', {
      $canActivate: function () {
        defer = $q.defer();
        return defer.promise;
      }
    });
    $rootRouter.config([
      { path: '/pending-activate', component: 'pendingActivate' }
    ]);
    compile('<div ng-outlet></div>');

    $rootRouter.navigateByUrl('/pending-activate');
    $rootScope.$digest();
    expect($rootRouter.navigating).toBe(true);
    defer.resolve();
    $rootScope.$digest();
    expect($rootRouter.navigating).toBe(false);
  }));

  function registerDirective(name, options) {
    var controller = getController(options);
    function factory() {
      return {
        template: options.template || '',
        controllerAs: name,
        controller: controller
      };
    }
    applyStaticProperties(controller, options);
    $compileProvider.directive(name, factory);
  }

  function registerComponent(name, options) {

    var definition = {
      template: options.template || '',
      controller: getController(options),
    }
    applyStaticProperties(definition.controller, options);
    $compileProvider.component(name, definition);
  }

  function compile(template) {
    elt = $compile('<div>' + template + '</div>')($rootScope);
    $rootScope.$digest();
    return elt;
  }

  function getController(options) {
    var controller = options.controller || function () {};
    [
      '$routerOnActivate', '$routerOnDeactivate',
      '$routerOnReuse', '$routerCanReuse',
      '$routerCanDeactivate'
    ].forEach(function (hookName) {
      if (options[hookName]) {
        controller.prototype[hookName] = options[hookName];
      }
    });
    return controller;
  }

  function applyStaticProperties(target, options) {
    ['$canActivate', '$routeConfig'].forEach(function(property) {
      if (options[property]) {
        target[property] = options[property];
      }
    });
  }
});

