/**
 * @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 {Class, Component, EventEmitter, Inject, NgModule, OpaqueToken, Testability, destroyPlatform} from '@angular/core';
import {async} from '@angular/core/testing';
import {AsyncTestCompleter, ddescribe, describe, expect, iit, inject, it} from '@angular/core/testing/testing_internal';
import {BrowserModule} from '@angular/platform-browser';
import {UpgradeAdapter} from '@angular/upgrade';
import * as angular from '@angular/upgrade/src/angular_js';
export function main() {
  describe('adapter: ng1 to ng2', () => {
    beforeEach(() => destroyPlatform());
    afterEach(() => destroyPlatform());
    it('should have angular 1 loaded', () => expect(angular.version.major).toBe(1));
    it('should instantiate ng2 in ng1 template and project content',
       inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
         var ng1Module = angular.module('ng1', []);
         var Ng2 = Component({selector: 'ng2', template: `{{ 'NG2' }}()`})
                       .Class({constructor: function() {}});
         var element =
             html('
{{ \'ng1[\' }}~{{ \'ng-content\' }}~{{ \']\' }}
');
         var adapter: UpgradeAdapter = new UpgradeAdapter();
         ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
         adapter.bootstrap(element, ['ng1']).ready((ref) => {
           expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]');
           ref.dispose();
           async.done();
         });
       }));
    it('should instantiate ng1 in ng2 template and project content',
       inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
         var adapter: UpgradeAdapter = new UpgradeAdapter();
         var ng1Module = angular.module('ng1', []);
         var Ng2 = Component({
                     selector: 'ng2',
                     template: `{{ 'ng2(' }}{{'transclude'}}{{ ')' }}`,
                     directives: [adapter.upgradeNg1Component('ng1')]
                   }).Class({constructor: function() {}});
         ng1Module.directive('ng1', () => {
           return {transclude: true, template: '{{ "ng1" }}()'};
         });
         ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
         var element = html('{{\'ng1(\'}}{{\')\'}}
');
         adapter.bootstrap(element, ['ng1']).ready((ref) => {
           expect(document.body.textContent).toEqual('ng1(ng2(ng1(transclude)))');
           ref.dispose();
           async.done();
         });
       }));
    describe('scope/component change-detection', () => {
      it('should interleave scope and component expressions',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var ng1Module = angular.module('ng1', []);
           var log: any[] /** TODO #9100 */ = [];
           var l = function(value: any /** TODO #9100 */) {
             log.push(value);
             return value + ';';
           };
           var adapter: UpgradeAdapter = new UpgradeAdapter();
           ng1Module.directive('ng1a', () => { return {template: '{{ l(\'ng1a\') }}'}; });
           ng1Module.directive('ng1b', () => { return {template: '{{ l(\'ng1b\') }}'}; });
           ng1Module.run(($rootScope: any /** TODO #9100 */) => {
             $rootScope.l = l;
             $rootScope.reset = () => log.length = 0;
           });
           var Ng2 =
               Component({
                 selector: 'ng2',
                 template: `{{l('2A')}}{{l('2B')}}{{l('2C')}}`,
                 directives:
                     [adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b')]
               }).Class({constructor: function() { this.l = l; }});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element =
               html('{{reset(); l(\'1A\');}}{{l(\'1B\')}}{{l(\'1C\')}}
');
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;');
             // https://github.com/angular/angular.js/issues/12983
             expect(log).toEqual(['1A', '1B', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
             ref.dispose();
             async.done();
           });
         }));
    });
    describe('downgrade ng2 component', () => {
      it('should bind properties, events',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter: UpgradeAdapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           ng1Module.run(($rootScope: any /** TODO #9100 */) => {
             $rootScope.dataA = 'A';
             $rootScope.dataB = 'B';
             $rootScope.modelA = 'initModelA';
             $rootScope.modelB = 'initModelB';
             $rootScope.eventA = '?';
             $rootScope.eventB = '?';
           });
           var Ng2 = Component({
                       selector: 'ng2',
                       inputs:
                           ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
                       outputs: [
                         'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange',
                         'twoWayBEmitter: twoWayBChange'
                       ],
                       template: 'ignore: {{ignore}}; ' +
                           'literal: {{literal}}; interpolate: {{interpolate}}; ' +
                           'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
                           'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
                     }).Class({
             constructor: function() {
               this.ngOnChangesCount = 0;
               this.ignore = '-';
               this.literal = '?';
               this.interpolate = '?';
               this.oneWayA = '?';
               this.oneWayB = '?';
               this.twoWayA = '?';
               this.twoWayB = '?';
               this.eventA = new EventEmitter();
               this.eventB = new EventEmitter();
               this.twoWayAEmitter = new EventEmitter();
               this.twoWayBEmitter = new EventEmitter();
             },
             ngOnChanges: function(changes: any /** TODO #9100 */) {
               var assert = (prop: any /** TODO #9100 */, value: any /** TODO #9100 */) => {
                 if (this[prop] != value) {
                   throw new Error(`Expected: '${prop}' to be '${value}' but was '${this[prop]}'`);
                 }
               };
               var assertChange = (prop: any /** TODO #9100 */, value: any /** TODO #9100 */) => {
                 assert(prop, value);
                 if (!changes[prop]) {
                   throw new Error(`Changes record for '${prop}' not found.`);
                 }
                 var actValue = changes[prop].currentValue;
                 if (actValue != value) {
                   throw new Error(
                       `Expected changes record for'${prop}' to be '${value}' but was '${actValue}'`);
                 }
               };
               switch (this.ngOnChangesCount++) {
                 case 0:
                   assert('ignore', '-');
                   assertChange('literal', 'Text');
                   assertChange('interpolate', 'Hello world');
                   assertChange('oneWayA', 'A');
                   assertChange('oneWayB', 'B');
                   assertChange('twoWayA', 'initModelA');
                   assertChange('twoWayB', 'initModelB');
                   this.twoWayAEmitter.emit('newA');
                   this.twoWayBEmitter.emit('newB');
                   this.eventA.emit('aFired');
                   this.eventB.emit('bFired');
                   break;
                 case 1:
                   assertChange('twoWayA', 'newA');
                   break;
                 case 2:
                   assertChange('twoWayB', 'newB');
                   break;
                 default:
                   throw new Error('Called too many times! ' + JSON.stringify(changes));
               }
             }
           });
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
              
              | modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
              
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent))
                 .toEqual(
                     'ignore: -; ' +
                     'literal: Text; interpolate: Hello world; ' +
                     'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
                     'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
             ref.dispose();
             async.done();
           });
         }));
      it('should properly run cleanup when ng1 directive is destroyed',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter: UpgradeAdapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var onDestroyed: EventEmitter = new EventEmitter();
           ng1Module.directive('ng1', () => {
             return {
               template: '
',
               controller: function(
                   $rootScope: any /** TODO #9100 */, $timeout: any /** TODO #9100 */) {
                 $timeout(function() { $rootScope.destroyIt = true; });
               }
             };
           });
           var Ng2 = Component({selector: 'ng2', template: 'test'}).Class({
             constructor: function() {},
             ngOnDestroy: function() { onDestroyed.emit('destroyed'); }
           });
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html('');
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             onDestroyed.subscribe(() => {
               ref.dispose();
               async.done();
             });
           });
         }));
      it('should fallback to the root ng2.injector when compiled outside the dom',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter: UpgradeAdapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           ng1Module.directive('ng1', [
             '$compile',
             ($compile: any /** TODO #9100 */) => {
               return {
                 link: function(
                     $scope: any /** TODO #9100 */, $element: any /** TODO #9100 */,
                     $attrs: any /** TODO #9100 */) {
                   var compiled = $compile('');
                   var template = compiled($scope);
                   $element.append(template);
                 }
               };
             }
           ]);
           var Ng2 =
               Component({selector: 'ng2', template: 'test'}).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html('');
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('test');
             ref.dispose();
             async.done();
           });
         }));
      // this test should be removed ones the deprecated @Components.directives prop is removed
      // we should rewrite all of the tests in this file to use modules instead.
      it('should downgrade ng2 component that is part of a module', async(() => {
           const ng1Module = angular.module('ng1', []);
           ng1Module.component('ng1Component', {template: ''});
           const SpecialValue = new OpaqueToken('special test value');
           const Ng2Component = Component({
                                  selector: 'ng2-component',
                                  template: 'test: {{value}}'
                                }).Class({
             constructor: [
               Inject(SpecialValue), function Ng2Component(value: number) { this.value = value; }
             ]
           });
           const Ng2AppModule =
               NgModule({
                 declarations: [Ng2Component],
                 imports: [BrowserModule],
                 providers: [{provide: SpecialValue, useValue: 23}]
               }).Class({constructor: function Ng2AppModule() {}, ngDoBootstrap: function() {}});
           const adapter = new UpgradeAdapter(Ng2AppModule);
           ng1Module.directive('ng2Component', adapter.downgradeNg2Component(Ng2Component));
           var element = html('');
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.getElementsByTagName('ng2-component')[0].innerHTML))
                 .toEqual('test: 23');
             ref.dispose();
           });
         }));
    });
    describe('upgrade ng1 component', () => {
      it('should bind properties, events',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var ng1 = function() {
             return {
               template: 'Hello {{fullName}}; A: {{dataA}}; B: {{dataB}}; | ',
               scope: {fullName: '@', modelA: '=dataA', modelB: '=dataB', event: '&'},
               link: function(scope: any /** TODO #9100 */) {
                 scope.$watch('dataB', (v: any /** TODO #9100 */) => {
                   if (v == 'Savkin') {
                     scope.dataB = 'SAVKIN';
                     scope.event('WORKS');
                     // Should not update because [model-a] is uni directional
                     scope.dataA = 'VICTOR';
                   }
                 });
               }
             };
           };
           ng1Module.directive('ng1', ng1);
           var Ng2 =
               Component({
                 selector: 'ng2',
                 template:
                     '' +
                     '' +
                     '{{event}}-{{last}}, {{first}}',
                 directives: [adapter.upgradeNg1Component('ng1')]
               }).Class({
                 constructor: function() {
                   this.first = 'Victor';
                   this.last = 'Savkin';
                   this.event = '?';
                 }
               });
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
             // events, and so without this we would not see the events processed.
             setTimeout(() => {
               expect(multiTrim(document.body.textContent))
                   .toEqual(
                       'Hello SAVKIN, Victor; A: VICTOR; B: SAVKIN; | Hello TEST; A: First; B: Last; | WORKS-SAVKIN, Victor');
               ref.dispose();
               async.done();
             }, 0);
           });
         }));
      it('should bind properties, events in controller when bindToController is not used',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var ng1 = function() {
             return {
               restrict: 'E',
               template: '{{someText}} - Length: {{data.length}}',
               scope: {data: '='},
               controller: function($scope: any /** TODO #9100 */) {
                 $scope.someText = 'ng1 - Data: ' + $scope.data;
               }
             };
           };
           ng1Module.directive('ng1', ng1);
           var Ng2 =
               Component({
                 selector: 'ng2',
                 template:
                     '{{someText}} - Length: {{dataList.length}} | ',
                 directives: [adapter.upgradeNg1Component('ng1')],
               }).Class({
                 constructor: function() {
                   this.dataList = [1, 2, 3];
                   this.someText = 'ng2';
                 }
               });
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
             // events, and so without this we would not see the events processed.
             setTimeout(() => {
               expect(multiTrim(document.body.textContent))
                   .toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3');
               ref.dispose();
               async.done();
             }, 0);
           });
         }));
      it('should bind properties, events in link function',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var ng1 = function() {
             return {
               restrict: 'E',
               template: '{{someText}} - Length: {{data.length}}',
               scope: {data: '='},
               link: function($scope: any /** TODO #9100 */) {
                 $scope.someText = 'ng1 - Data: ' + $scope.data;
               }
             };
           };
           ng1Module.directive('ng1', ng1);
           var Ng2 =
               Component({
                 selector: 'ng2',
                 template:
                     '{{someText}} - Length: {{dataList.length}} | ',
                 directives: [adapter.upgradeNg1Component('ng1')],
               }).Class({
                 constructor: function() {
                   this.dataList = [1, 2, 3];
                   this.someText = 'ng2';
                 }
               });
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             // we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
             // events, and so without this we would not see the events processed.
             setTimeout(() => {
               expect(multiTrim(document.body.textContent))
                   .toEqual('ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3');
               ref.dispose();
               async.done();
             }, 0);
           });
         }));
      it('should support templateUrl fetched from $httpBackend',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           ng1Module.value(
               '$httpBackend', (method: any /** TODO #9100 */, url: any /** TODO #9100 */,
                                post: any /** TODO #9100 */,
                                cbFn: any /** TODO #9100 */) => { cbFn(200, `${method}:${url}`); });
           var ng1 = function() { return {templateUrl: 'url.html'}; };
           ng1Module.directive('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('GET:url.html');
             ref.dispose();
             async.done();
           });
         }));
      it('should support templateUrl as a function',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           ng1Module.value(
               '$httpBackend', (method: any /** TODO #9100 */, url: any /** TODO #9100 */,
                                post: any /** TODO #9100 */,
                                cbFn: any /** TODO #9100 */) => { cbFn(200, `${method}:${url}`); });
           var ng1 = function() { return {templateUrl() { return 'url.html'; }}; };
           ng1Module.directive('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('GET:url.html');
             ref.dispose();
             async.done();
           });
         }));
      it('should support empty template',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var ng1 = function() { return {template: ''}; };
           ng1Module.directive('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('');
             ref.dispose();
             async.done();
           });
         }));
      it('should support template as a function',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var ng1 = function() { return {template() { return ''; }}; };
           ng1Module.directive('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('');
             ref.dispose();
             async.done();
           });
         }));
      it('should support templateUrl fetched from $templateCache',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           ng1Module.run(
               ($templateCache: any /** TODO #9100 */) => $templateCache.put('url.html', 'WORKS'));
           var ng1 = function() { return {templateUrl: 'url.html'}; };
           ng1Module.directive('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('WORKS');
             ref.dispose();
             async.done();
           });
         }));
      it('should support controller with controllerAs',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var ng1 = function() {
             return {
               scope: true,
               template:
                   '{{ctl.scope}}; {{ctl.isClass}}; {{ctl.hasElement}}; {{ctl.isPublished()}}',
               controllerAs: 'ctl',
               controller: Class({
                 constructor: function(
                     $scope: any /** TODO #9100 */, $element: any /** TODO #9100 */) {
                   (this).verifyIAmAClass();
                   this.scope = $scope.$parent.$parent == $scope.$root ? 'scope' : 'wrong-scope';
                   this.hasElement = $element[0].nodeName;
                   this.$element = $element;
                 },
                 verifyIAmAClass: function() { this.isClass = 'isClass'; },
                 isPublished: function() {
                   return this.$element.controller('ng1') == this ? 'published' : 'not-published';
                 }
               })
             };
           };
           ng1Module.directive('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('scope; isClass; NG1; published');
             ref.dispose();
             async.done();
           });
         }));
      it('should support bindToController',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var ng1 = function() {
             return {
               scope: {title: '@'},
               bindToController: true,
               template: '{{ctl.title}}',
               controllerAs: 'ctl',
               controller: Class({constructor: function() {}})
             };
           };
           ng1Module.directive('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('WORKS');
             ref.dispose();
             async.done();
           });
         }));
      it('should support bindToController with bindings',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var ng1 = function() {
             return {
               scope: {},
               bindToController: {title: '@'},
               template: '{{ctl.title}}',
               controllerAs: 'ctl',
               controller: Class({constructor: function() {}})
             };
           };
           ng1Module.directive('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('WORKS');
             ref.dispose();
             async.done();
           });
         }));
      it('should support single require in linking fn',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var ng1 = function($rootScope: any /** TODO #9100 */) {
             return {
               scope: {title: '@'},
               bindToController: true,
               template: '{{ctl.status}}',
               require: 'ng1',
               controllerAs: 'ctrl',
               controller: Class({constructor: function() { this.status = 'WORKS'; }}),
               link: function(
                   scope: any /** TODO #9100 */, element: any /** TODO #9100 */,
                   attrs: any /** TODO #9100 */, linkController: any /** TODO #9100 */) {
                 expect(scope.$root).toEqual($rootScope);
                 expect(element[0].nodeName).toEqual('NG1');
                 expect(linkController.status).toEqual('WORKS');
                 scope.ctl = linkController;
               }
             };
           };
           ng1Module.directive('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('WORKS');
             ref.dispose();
             async.done();
           });
         }));
      it('should support array require in linking fn',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var parent = function() {
             return {controller: Class({constructor: function() { this.parent = 'PARENT'; }})};
           };
           var ng1 = function() {
             return {
               scope: {title: '@'},
               bindToController: true,
               template: '{{parent.parent}}:{{ng1.status}}',
               require: ['ng1', '^parent', '?^^notFound'],
               controllerAs: 'ctrl',
               controller: Class({constructor: function() { this.status = 'WORKS'; }}),
               link: function(
                   scope: any /** TODO #9100 */, element: any /** TODO #9100 */,
                   attrs: any /** TODO #9100 */, linkControllers: any /** TODO #9100 */) {
                 expect(linkControllers[0].status).toEqual('WORKS');
                 expect(linkControllers[1].parent).toEqual('PARENT');
                 expect(linkControllers[2]).toBe(undefined);
                 scope.ng1 = linkControllers[0];
                 scope.parent = linkControllers[1];
               }
             };
           };
           ng1Module.directive('parent', parent);
           ng1Module.directive('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(``);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('PARENT:WORKS');
             ref.dispose();
             async.done();
           });
         }));
      it('should call $onInit of components',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var valueToFind = '$onInit';
           var ng1 = {
             bindings: {},
             template: '{{$ctrl.value}}',
             controller: Class(
                 {constructor: function() {}, $onInit: function() { this.value = valueToFind; }})
           };
           ng1Module.component('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({constructor: function() {}});
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual(valueToFind);
             ref.dispose();
             async.done();
           });
         }));
      it('should bind input properties (<) of components',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var ng1 = {
             bindings: {personProfile: '<'},
             template: 'Hello {{$ctrl.personProfile.firstName}} {{$ctrl.personProfile.lastName}}',
             controller: Class({constructor: function() {}})
           };
           ng1Module.component('ng1', ng1);
           var Ng2 = Component({
                       selector: 'ng2',
                       template: '',
                       directives: [adapter.upgradeNg1Component('ng1')]
                     }).Class({
             constructor: function() { this.goku = {firstName: 'GOKU', lastName: 'SAN'}; }
           });
           ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual(`Hello GOKU SAN`);
             ref.dispose();
             async.done();
           });
         }));
      it('should support ng2 > ng1 > ng2',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var ng1 = {
             template: 'ng1()',
           };
           ng1Module.component('ng1', ng1);
           var Ng2a = Component({
                        selector: 'ng2a',
                        template: 'ng2a()',
                        directives: [adapter.upgradeNg1Component('ng1')]
                      }).Class({constructor: function() {}});
           ng1Module.directive('ng2a', adapter.downgradeNg2Component(Ng2a));
           var Ng2b = Component({selector: 'ng2b', template: 'ng2b', directives: []}).Class({
             constructor: function() {}
           });
           ng1Module.directive('ng2b', adapter.downgradeNg2Component(Ng2b));
           var element = html(`
`);
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(multiTrim(document.body.textContent)).toEqual('ng2a(ng1(ng2b))');
             async.done();
           });
         }));
    });
    describe('injection', () => {
      function SomeToken() {}
      it('should export ng2 instance to ng1', async(() => {
           var MyModule = NgModule({
                            providers: [{provide: SomeToken, useValue: 'correct_value'}],
                            imports: [BrowserModule]
                          }).Class({constructor: function() {}});
           var adapter = new UpgradeAdapter(MyModule);
           var module = angular.module('myExample', []);
           module.factory('someToken', adapter.downgradeNg2Provider(SomeToken));
           adapter.bootstrap(html(''), ['myExample']).ready((ref) => {
             expect(ref.ng1Injector.get('someToken')).toBe('correct_value');
             ref.dispose();
           });
         }));
      it('should export ng1 instance to ng2',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var module = angular.module('myExample', []);
           module.value('testValue', 'secreteToken');
           adapter.upgradeNg1Provider('testValue');
           adapter.upgradeNg1Provider('testValue', {asToken: 'testToken'});
           adapter.upgradeNg1Provider('testValue', {asToken: String});
           adapter.bootstrap(html('
'), ['myExample']).ready((ref) => {
             expect(ref.ng2Injector.get('testValue')).toBe('secreteToken');
             expect(ref.ng2Injector.get(String)).toBe('secreteToken');
             expect(ref.ng2Injector.get('testToken')).toBe('secreteToken');
             ref.dispose();
             async.done();
           });
         }));
    });
    describe('testability', () => {
      it('should handle deferred bootstrap',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter: UpgradeAdapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var bootstrapResumed: boolean = false;
           var element = html('
');
           window.name = 'NG_DEFER_BOOTSTRAP!' + window.name;
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             expect(bootstrapResumed).toEqual(true);
             ref.dispose();
             async.done();
           });
           setTimeout(() => {
             bootstrapResumed = true;
             (
window).angular.resumeBootstrap();
           }, 100);
         }));
      it('should wait for ng2 testability',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter: UpgradeAdapter = new UpgradeAdapter();
           var ng1Module = angular.module('ng1', []);
           var element = html('');
           adapter.bootstrap(element, ['ng1']).ready((ref) => {
             var ng2Testability: Testability = ref.ng2Injector.get(Testability);
             ng2Testability.increasePendingRequestCount();
             var ng2Stable = false;
             angular.getTestability(element).whenStable(function() {
               expect(ng2Stable).toEqual(true);
               ref.dispose();
               async.done();
             });
             setTimeout(() => {
               ng2Stable = true;
               ng2Testability.decreasePendingRequestCount();
             }, 100);
           });
         }));
    });
    describe('examples', () => {
      it('should verify UpgradeAdapter example',
         inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
           var adapter = new UpgradeAdapter();
           var module = angular.module('myExample', []);
           module.directive('ng1', function() {
             return {
               scope: {title: '='},
               transclude: true,
               template: 'ng1[Hello {{title}}!]()'
             };
           });
           var Ng2 =
               Component({
                 selector: 'ng2',
                 inputs: ['name'],
                 template: 'ng2[transclude]()',
                 directives: [adapter.upgradeNg1Component('ng1')]
               }).Class({constructor: function() {}});
           module.directive('ng2', adapter.downgradeNg2Component(Ng2));
           document.body.innerHTML = 'project';
           adapter.bootstrap(document.body, ['myExample']).ready((ref) => {
             expect(multiTrim(document.body.textContent))
                 .toEqual('ng2[ng1[Hello World!](transclude)](project)');
             ref.dispose();
             async.done();
           });
         }));
    });
  });
}
function multiTrim(text: string): string {
  return text.replace(/\n/g, '').replace(/\s\s+/g, ' ').trim();
}
function html(html: string): Element {
  var body = document.body;
  body.innerHTML = html;
  if (body.childNodes.length == 1 && body.firstChild instanceof HTMLElement)
    return body.firstChild;
  return body;
}