812 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			812 lines
		
	
	
		
			35 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 {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ApplicationRef, Component, DoCheck, Inject, Injector, Input, NgModule, NgZone, OnChanges, OnDestroy, OnInit, StaticProvider, Type, ViewRef, destroyPlatform, getPlatform} from '@angular/core';
 | 
						|
import {async, fakeAsync, tick} from '@angular/core/testing';
 | 
						|
import {BrowserModule} from '@angular/platform-browser';
 | 
						|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
 | 
						|
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
 | 
						|
import {fixmeIvy} from '@angular/private/testing';
 | 
						|
import {downgradeComponent, downgradeModule} from '@angular/upgrade/static';
 | 
						|
import * as angular from '@angular/upgrade/static/src/common/angular1';
 | 
						|
import {$EXCEPTION_HANDLER, $ROOT_SCOPE, INJECTOR_KEY, LAZY_MODULE_REF} from '@angular/upgrade/static/src/common/constants';
 | 
						|
import {LazyModuleRef} from '@angular/upgrade/static/src/common/util';
 | 
						|
 | 
						|
import {html, multiTrim, withEachNg1Version} from '../test_helpers';
 | 
						|
 | 
						|
 | 
						|
withEachNg1Version(() => {
 | 
						|
  [true, false].forEach(propagateDigest => {
 | 
						|
    describe(`lazy-load ng2 module (propagateDigest: ${propagateDigest})`, () => {
 | 
						|
 | 
						|
      beforeEach(() => destroyPlatform());
 | 
						|
 | 
						|
      it('should support multiple downgraded modules', async(() => {
 | 
						|
           @Component({selector: 'ng2A', template: 'a'})
 | 
						|
           class Ng2ComponentA {
 | 
						|
           }
 | 
						|
 | 
						|
           @Component({selector: 'ng2B', template: 'b'})
 | 
						|
           class Ng2ComponentB {
 | 
						|
           }
 | 
						|
 | 
						|
           @NgModule({
 | 
						|
             declarations: [Ng2ComponentA],
 | 
						|
             entryComponents: [Ng2ComponentA],
 | 
						|
             imports: [BrowserModule],
 | 
						|
           })
 | 
						|
           class Ng2ModuleA {
 | 
						|
             ngDoBootstrap() {}
 | 
						|
           }
 | 
						|
 | 
						|
           @NgModule({
 | 
						|
             declarations: [Ng2ComponentB],
 | 
						|
             entryComponents: [Ng2ComponentB],
 | 
						|
             imports: [BrowserModule],
 | 
						|
           })
 | 
						|
           class Ng2ModuleB {
 | 
						|
             ngDoBootstrap() {}
 | 
						|
           }
 | 
						|
 | 
						|
           const doDowngradeModule = (module: Type<any>) => {
 | 
						|
             const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
                 (getPlatform() || platformBrowserDynamic(extraProviders)).bootstrapModule(module);
 | 
						|
             return downgradeModule(bootstrapFn);
 | 
						|
           };
 | 
						|
 | 
						|
           const downModA = doDowngradeModule(Ng2ModuleA);
 | 
						|
           const downModB = doDowngradeModule(Ng2ModuleB);
 | 
						|
           const ng1Module = angular.module('ng1', [downModA, downModB])
 | 
						|
                                 .directive('ng2A', downgradeComponent({
 | 
						|
                                              component: Ng2ComponentA,
 | 
						|
                                              downgradedModule: downModA, propagateDigest,
 | 
						|
                                            }))
 | 
						|
                                 .directive('ng2B', downgradeComponent({
 | 
						|
                                              component: Ng2ComponentB,
 | 
						|
                                              downgradedModule: downModB, propagateDigest,
 | 
						|
                                            }));
 | 
						|
 | 
						|
           const element = html('<ng2-a></ng2-a> | <ng2-b></ng2-b>');
 | 
						|
           angular.bootstrap(element, [ng1Module.name]);
 | 
						|
 | 
						|
           // Wait for the module to be bootstrapped.
 | 
						|
           setTimeout(() => expect(element.textContent).toBe('a | b'));
 | 
						|
         }));
 | 
						|
 | 
						|
      it('should support downgrading a component and propagate inputs', async(() => {
 | 
						|
           @Component(
 | 
						|
               {selector: 'ng2A', template: 'a({{ value }}) | <ng2B [value]="value"></ng2B>'})
 | 
						|
           class Ng2AComponent {
 | 
						|
             @Input() value = -1;
 | 
						|
           }
 | 
						|
 | 
						|
           @Component({selector: 'ng2B', template: 'b({{ value }})'})
 | 
						|
           class Ng2BComponent {
 | 
						|
             @Input() value = -2;
 | 
						|
           }
 | 
						|
 | 
						|
           @NgModule({
 | 
						|
             declarations: [Ng2AComponent, Ng2BComponent],
 | 
						|
             entryComponents: [Ng2AComponent],
 | 
						|
             imports: [BrowserModule],
 | 
						|
           })
 | 
						|
           class Ng2Module {
 | 
						|
             ngDoBootstrap() {}
 | 
						|
           }
 | 
						|
 | 
						|
           const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
               platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
 | 
						|
           const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
 | 
						|
           const ng1Module =
 | 
						|
               angular.module('ng1', [lazyModuleName])
 | 
						|
                   .directive(
 | 
						|
                       'ng2', downgradeComponent({component: Ng2AComponent, propagateDigest}))
 | 
						|
                   .run(($rootScope: angular.IRootScopeService) => $rootScope.value = 0);
 | 
						|
 | 
						|
           const element = html('<div><ng2 [value]="value" ng-if="loadNg2"></ng2></div>');
 | 
						|
           const $injector = angular.bootstrap(element, [ng1Module.name]);
 | 
						|
           const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
 | 
						|
 | 
						|
           expect(element.textContent).toBe('');
 | 
						|
           expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
 | 
						|
 | 
						|
           $rootScope.$apply('value = 1');
 | 
						|
           expect(element.textContent).toBe('');
 | 
						|
           expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
 | 
						|
 | 
						|
           $rootScope.$apply('loadNg2 = true');
 | 
						|
           expect(element.textContent).toBe('');
 | 
						|
           expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
 | 
						|
 | 
						|
           // Wait for the module to be bootstrapped.
 | 
						|
           setTimeout(() => {
 | 
						|
             expect(() => $injector.get(INJECTOR_KEY)).not.toThrow();
 | 
						|
 | 
						|
             // Wait for `$evalAsync()` to propagate inputs.
 | 
						|
             setTimeout(() => expect(element.textContent).toBe('a(1) | b(1)'));
 | 
						|
           });
 | 
						|
         }));
 | 
						|
 | 
						|
      fixmeIvy('FW-718: upgraded service not being initialized correctly on the injector')
 | 
						|
          .it('should support using an upgraded service', async(() => {
 | 
						|
                class Ng2Service {
 | 
						|
                  constructor(@Inject('ng1Value') private ng1Value: string) {}
 | 
						|
                  getValue = () => `${this.ng1Value}-bar`;
 | 
						|
                }
 | 
						|
 | 
						|
                @Component({selector: 'ng2', template: '{{ value }}'})
 | 
						|
                class Ng2Component {
 | 
						|
                  value: string;
 | 
						|
                  constructor(ng2Service: Ng2Service) { this.value = ng2Service.getValue(); }
 | 
						|
                }
 | 
						|
 | 
						|
                @NgModule({
 | 
						|
                  declarations: [Ng2Component],
 | 
						|
                  entryComponents: [Ng2Component],
 | 
						|
                  imports: [BrowserModule],
 | 
						|
                  providers: [
 | 
						|
                    Ng2Service,
 | 
						|
                    {
 | 
						|
                      provide: 'ng1Value',
 | 
						|
                      useFactory: (i: angular.IInjectorService) => i.get('ng1Value'),
 | 
						|
                      deps: ['$injector'],
 | 
						|
                    },
 | 
						|
                  ],
 | 
						|
                })
 | 
						|
                class Ng2Module {
 | 
						|
                  ngDoBootstrap() {}
 | 
						|
                }
 | 
						|
 | 
						|
                const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
                    platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
 | 
						|
                const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
 | 
						|
                const ng1Module =
 | 
						|
                    angular.module('ng1', [lazyModuleName])
 | 
						|
                        .directive(
 | 
						|
                            'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
 | 
						|
                        .value('ng1Value', 'foo');
 | 
						|
 | 
						|
                const element = html('<div><ng2 ng-if="loadNg2"></ng2></div>');
 | 
						|
                const $injector = angular.bootstrap(element, [ng1Module.name]);
 | 
						|
                const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
 | 
						|
 | 
						|
                expect(element.textContent).toBe('');
 | 
						|
                expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
 | 
						|
 | 
						|
                $rootScope.$apply('loadNg2 = true');
 | 
						|
                expect(element.textContent).toBe('');
 | 
						|
                expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
 | 
						|
 | 
						|
                // Wait for the module to be bootstrapped.
 | 
						|
                setTimeout(() => {
 | 
						|
                  expect(() => $injector.get(INJECTOR_KEY)).not.toThrow();
 | 
						|
 | 
						|
                  // Wait for `$evalAsync()` to propagate inputs.
 | 
						|
                  setTimeout(() => expect(element.textContent).toBe('foo-bar'));
 | 
						|
                });
 | 
						|
              }));
 | 
						|
 | 
						|
      it('should create components inside the Angular zone', async(() => {
 | 
						|
           @Component({selector: 'ng2', template: 'In the zone: {{ inTheZone }}'})
 | 
						|
           class Ng2Component {
 | 
						|
             private inTheZone = false;
 | 
						|
             constructor() { this.inTheZone = NgZone.isInAngularZone(); }
 | 
						|
           }
 | 
						|
 | 
						|
           @NgModule({
 | 
						|
             declarations: [Ng2Component],
 | 
						|
             entryComponents: [Ng2Component],
 | 
						|
             imports: [BrowserModule],
 | 
						|
           })
 | 
						|
           class Ng2Module {
 | 
						|
             ngDoBootstrap() {}
 | 
						|
           }
 | 
						|
 | 
						|
           const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
               platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
 | 
						|
           const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
 | 
						|
           const ng1Module =
 | 
						|
               angular.module('ng1', [lazyModuleName])
 | 
						|
                   .directive(
 | 
						|
                       'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
 | 
						|
 | 
						|
           const element = html('<ng2></ng2>');
 | 
						|
           angular.bootstrap(element, [ng1Module.name]);
 | 
						|
 | 
						|
           // Wait for the module to be bootstrapped.
 | 
						|
           setTimeout(() => {
 | 
						|
             // Wait for `$evalAsync()` to propagate inputs.
 | 
						|
             setTimeout(() => expect(element.textContent).toBe('In the zone: true'));
 | 
						|
           });
 | 
						|
         }));
 | 
						|
 | 
						|
      it('should destroy components inside the Angular zone', async(() => {
 | 
						|
           let destroyedInTheZone = false;
 | 
						|
 | 
						|
           @Component({selector: 'ng2', template: ''})
 | 
						|
           class Ng2Component implements OnDestroy {
 | 
						|
             ngOnDestroy() { destroyedInTheZone = NgZone.isInAngularZone(); }
 | 
						|
           }
 | 
						|
 | 
						|
           @NgModule({
 | 
						|
             declarations: [Ng2Component],
 | 
						|
             entryComponents: [Ng2Component],
 | 
						|
             imports: [BrowserModule],
 | 
						|
           })
 | 
						|
           class Ng2Module {
 | 
						|
             ngDoBootstrap() {}
 | 
						|
           }
 | 
						|
 | 
						|
           const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
               platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
 | 
						|
           const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
 | 
						|
           const ng1Module =
 | 
						|
               angular.module('ng1', [lazyModuleName])
 | 
						|
                   .directive(
 | 
						|
                       'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
 | 
						|
 | 
						|
           const element = html('<ng2 ng-if="!hideNg2"></ng2>');
 | 
						|
           const $injector = angular.bootstrap(element, [ng1Module.name]);
 | 
						|
           const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
 | 
						|
 | 
						|
           // Wait for the module to be bootstrapped.
 | 
						|
           setTimeout(() => {
 | 
						|
             $rootScope.$apply('hideNg2 = true');
 | 
						|
             expect(destroyedInTheZone).toBe(true);
 | 
						|
           });
 | 
						|
         }));
 | 
						|
 | 
						|
      fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
 | 
						|
          .it('should propagate input changes inside the Angular zone', async(() => {
 | 
						|
                let ng2Component: Ng2Component;
 | 
						|
 | 
						|
                @Component({selector: 'ng2', template: ''})
 | 
						|
                class Ng2Component implements OnChanges {
 | 
						|
                  @Input() attrInput = 'foo';
 | 
						|
                  @Input() propInput = 'foo';
 | 
						|
 | 
						|
                  constructor() { ng2Component = this; }
 | 
						|
                  ngOnChanges() {}
 | 
						|
                }
 | 
						|
 | 
						|
                @NgModule({
 | 
						|
                  declarations: [Ng2Component],
 | 
						|
                  entryComponents: [Ng2Component],
 | 
						|
                  imports: [BrowserModule],
 | 
						|
                })
 | 
						|
                class Ng2Module {
 | 
						|
                  ngDoBootstrap() {}
 | 
						|
                }
 | 
						|
 | 
						|
                const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
                    platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
 | 
						|
                const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
 | 
						|
                const ng1Module =
 | 
						|
                    angular.module('ng1', [lazyModuleName])
 | 
						|
                        .directive(
 | 
						|
                            'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
 | 
						|
                        .run(($rootScope: angular.IRootScopeService) => {
 | 
						|
                          $rootScope.attrVal = 'bar';
 | 
						|
                          $rootScope.propVal = 'bar';
 | 
						|
                        });
 | 
						|
 | 
						|
                const element =
 | 
						|
                    html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>');
 | 
						|
                const $injector = angular.bootstrap(element, [ng1Module.name]);
 | 
						|
                const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
 | 
						|
 | 
						|
                setTimeout(() => {    // Wait for the module to be bootstrapped.
 | 
						|
                  setTimeout(() => {  // Wait for `$evalAsync()` to propagate inputs.
 | 
						|
                    const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true);
 | 
						|
                    const changesSpy =
 | 
						|
                        spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone);
 | 
						|
 | 
						|
                    expect(ng2Component.attrInput).toBe('bar');
 | 
						|
                    expect(ng2Component.propInput).toBe('bar');
 | 
						|
 | 
						|
                    $rootScope.$apply('attrVal = "baz"');
 | 
						|
                    expect(ng2Component.attrInput).toBe('baz');
 | 
						|
                    expect(ng2Component.propInput).toBe('bar');
 | 
						|
                    expect(changesSpy).toHaveBeenCalledTimes(1);
 | 
						|
 | 
						|
                    $rootScope.$apply('propVal = "qux"');
 | 
						|
                    expect(ng2Component.attrInput).toBe('baz');
 | 
						|
                    expect(ng2Component.propInput).toBe('qux');
 | 
						|
                    expect(changesSpy).toHaveBeenCalledTimes(2);
 | 
						|
                  });
 | 
						|
                });
 | 
						|
              }));
 | 
						|
 | 
						|
      it('should wire up the component for change detection', async(() => {
 | 
						|
           @Component(
 | 
						|
               {selector: 'ng2', template: '{{ count }}<button (click)="increment()"></button>'})
 | 
						|
           class Ng2Component {
 | 
						|
             private count = 0;
 | 
						|
             increment() { ++this.count; }
 | 
						|
           }
 | 
						|
 | 
						|
           @NgModule({
 | 
						|
             declarations: [Ng2Component],
 | 
						|
             entryComponents: [Ng2Component],
 | 
						|
             imports: [BrowserModule],
 | 
						|
           })
 | 
						|
           class Ng2Module {
 | 
						|
             ngDoBootstrap() {}
 | 
						|
           }
 | 
						|
 | 
						|
           const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
               platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
 | 
						|
           const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
 | 
						|
           const ng1Module =
 | 
						|
               angular.module('ng1', [lazyModuleName])
 | 
						|
                   .directive(
 | 
						|
                       'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
 | 
						|
 | 
						|
           const element = html('<ng2></ng2>');
 | 
						|
           angular.bootstrap(element, [ng1Module.name]);
 | 
						|
 | 
						|
           setTimeout(() => {    // Wait for the module to be bootstrapped.
 | 
						|
             setTimeout(() => {  // Wait for `$evalAsync()` to propagate inputs.
 | 
						|
               const button = element.querySelector('button') !;
 | 
						|
               expect(element.textContent).toBe('0');
 | 
						|
 | 
						|
               button.click();
 | 
						|
               expect(element.textContent).toBe('1');
 | 
						|
 | 
						|
               button.click();
 | 
						|
               expect(element.textContent).toBe('2');
 | 
						|
             });
 | 
						|
           });
 | 
						|
         }));
 | 
						|
 | 
						|
      fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
 | 
						|
          .fixmeIvy('FW-714: ng1 projected content is not being rendered')
 | 
						|
          .it('should run the lifecycle hooks in the correct order', async(() => {
 | 
						|
                const logs: string[] = [];
 | 
						|
                let rootScope: angular.IRootScopeService;
 | 
						|
 | 
						|
                @Component({
 | 
						|
                  selector: 'ng2',
 | 
						|
                  template: `
 | 
						|
               {{ value }}
 | 
						|
               <button (click)="value = 'qux'"></button>
 | 
						|
               <ng-content></ng-content>
 | 
						|
             `
 | 
						|
                })
 | 
						|
                class Ng2Component implements AfterContentChecked,
 | 
						|
                    AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges,
 | 
						|
                    OnDestroy, OnInit {
 | 
						|
                  @Input() value = 'foo';
 | 
						|
 | 
						|
                  ngAfterContentChecked() { this.log('AfterContentChecked'); }
 | 
						|
                  ngAfterContentInit() { this.log('AfterContentInit'); }
 | 
						|
                  ngAfterViewChecked() { this.log('AfterViewChecked'); }
 | 
						|
                  ngAfterViewInit() { this.log('AfterViewInit'); }
 | 
						|
                  ngDoCheck() { this.log('DoCheck'); }
 | 
						|
                  ngOnChanges() { this.log('OnChanges'); }
 | 
						|
                  ngOnDestroy() { this.log('OnDestroy'); }
 | 
						|
                  ngOnInit() { this.log('OnInit'); }
 | 
						|
 | 
						|
                  private log(hook: string) { logs.push(`${hook}(${this.value})`); }
 | 
						|
                }
 | 
						|
 | 
						|
                @NgModule({
 | 
						|
                  declarations: [Ng2Component],
 | 
						|
                  entryComponents: [Ng2Component],
 | 
						|
                  imports: [BrowserModule],
 | 
						|
                })
 | 
						|
                class Ng2Module {
 | 
						|
                  ngDoBootstrap() {}
 | 
						|
                }
 | 
						|
 | 
						|
                const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
                    platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
 | 
						|
                const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
 | 
						|
                const ng1Module =
 | 
						|
                    angular.module('ng1', [lazyModuleName])
 | 
						|
                        .directive(
 | 
						|
                            'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
 | 
						|
                        .run(($rootScope: angular.IRootScopeService) => {
 | 
						|
                          rootScope = $rootScope;
 | 
						|
                          rootScope.value = 'bar';
 | 
						|
                        });
 | 
						|
 | 
						|
                const element =
 | 
						|
                    html('<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>');
 | 
						|
                angular.bootstrap(element, [ng1Module.name]);
 | 
						|
 | 
						|
                setTimeout(() => {    // Wait for the module to be bootstrapped.
 | 
						|
                  setTimeout(() => {  // Wait for `$evalAsync()` to propagate inputs.
 | 
						|
                    const button = element.querySelector('button') !;
 | 
						|
 | 
						|
                    // Once initialized.
 | 
						|
                    expect(multiTrim(element.textContent)).toBe('bar Content');
 | 
						|
                    expect(logs).toEqual([
 | 
						|
                      // `ngOnChanges()` call triggered directly through the `inputChanges`
 | 
						|
                      // $watcher.
 | 
						|
                      'OnChanges(bar)',
 | 
						|
                      // Initial CD triggered directly through the `detectChanges()` or
 | 
						|
                      // `inputChanges`
 | 
						|
                      // $watcher (for `propagateDigest` true/false respectively).
 | 
						|
                      'OnInit(bar)',
 | 
						|
                      'DoCheck(bar)',
 | 
						|
                      'AfterContentInit(bar)',
 | 
						|
                      'AfterContentChecked(bar)',
 | 
						|
                      'AfterViewInit(bar)',
 | 
						|
                      'AfterViewChecked(bar)',
 | 
						|
                      ...(propagateDigest ?
 | 
						|
                              [
 | 
						|
                                // CD triggered directly through the `detectChanges()` $watcher (2nd
 | 
						|
                                // $digest).
 | 
						|
                                'DoCheck(bar)',
 | 
						|
                                'AfterContentChecked(bar)',
 | 
						|
                                'AfterViewChecked(bar)',
 | 
						|
                              ] :
 | 
						|
                              []),
 | 
						|
                      // CD triggered due to entering/leaving the NgZone (in `downgradeFn()`).
 | 
						|
                      'DoCheck(bar)',
 | 
						|
                      'AfterContentChecked(bar)',
 | 
						|
                      'AfterViewChecked(bar)',
 | 
						|
                    ]);
 | 
						|
                    logs.length = 0;
 | 
						|
 | 
						|
                    // Change inputs and run `$digest`.
 | 
						|
                    rootScope.$apply('value = "baz"');
 | 
						|
                    expect(multiTrim(element.textContent)).toBe('baz Content');
 | 
						|
                    expect(logs).toEqual([
 | 
						|
                      // `ngOnChanges()` call triggered directly through the `inputChanges`
 | 
						|
                      // $watcher.
 | 
						|
                      'OnChanges(baz)',
 | 
						|
                      // `propagateDigest: true` (3 CD runs):
 | 
						|
                      //   - CD triggered due to entering/leaving the NgZone (in `inputChanges`
 | 
						|
                      //   $watcher).
 | 
						|
                      //   - CD triggered directly through the `detectChanges()` $watcher.
 | 
						|
                      //   - CD triggered due to entering/leaving the NgZone (in `detectChanges`
 | 
						|
                      //   $watcher).
 | 
						|
                      // `propagateDigest: false` (2 CD runs):
 | 
						|
                      //   - CD triggered directly through the `inputChanges` $watcher.
 | 
						|
                      //   - CD triggered due to entering/leaving the NgZone (in `inputChanges`
 | 
						|
                      //   $watcher).
 | 
						|
                      'DoCheck(baz)',
 | 
						|
                      'AfterContentChecked(baz)',
 | 
						|
                      'AfterViewChecked(baz)',
 | 
						|
                      'DoCheck(baz)',
 | 
						|
                      'AfterContentChecked(baz)',
 | 
						|
                      'AfterViewChecked(baz)',
 | 
						|
                      ...(propagateDigest ?
 | 
						|
                              [
 | 
						|
                                'DoCheck(baz)',
 | 
						|
                                'AfterContentChecked(baz)',
 | 
						|
                                'AfterViewChecked(baz)',
 | 
						|
                              ] :
 | 
						|
                              []),
 | 
						|
                    ]);
 | 
						|
                    logs.length = 0;
 | 
						|
 | 
						|
                    // Run `$digest` (without changing inputs).
 | 
						|
                    rootScope.$digest();
 | 
						|
                    expect(multiTrim(element.textContent)).toBe('baz Content');
 | 
						|
                    expect(logs).toEqual(
 | 
						|
                        propagateDigest ?
 | 
						|
                            [
 | 
						|
                              // CD triggered directly through the `detectChanges()` $watcher.
 | 
						|
                              'DoCheck(baz)',
 | 
						|
                              'AfterContentChecked(baz)',
 | 
						|
                              'AfterViewChecked(baz)',
 | 
						|
                              // CD triggered due to entering/leaving the NgZone (in the above
 | 
						|
                              // $watcher).
 | 
						|
                              'DoCheck(baz)',
 | 
						|
                              'AfterContentChecked(baz)',
 | 
						|
                              'AfterViewChecked(baz)',
 | 
						|
                            ] :
 | 
						|
                            []);
 | 
						|
                    logs.length = 0;
 | 
						|
 | 
						|
                    // Trigger change detection (without changing inputs).
 | 
						|
                    button.click();
 | 
						|
                    expect(multiTrim(element.textContent)).toBe('qux Content');
 | 
						|
                    expect(logs).toEqual([
 | 
						|
                      'DoCheck(qux)',
 | 
						|
                      'AfterContentChecked(qux)',
 | 
						|
                      'AfterViewChecked(qux)',
 | 
						|
                    ]);
 | 
						|
                    logs.length = 0;
 | 
						|
 | 
						|
                    // Destroy the component.
 | 
						|
                    rootScope.$apply('hideNg2 = true');
 | 
						|
                    expect(logs).toEqual([
 | 
						|
                      'OnDestroy(qux)',
 | 
						|
                    ]);
 | 
						|
                    logs.length = 0;
 | 
						|
                  });
 | 
						|
                });
 | 
						|
              }));
 | 
						|
 | 
						|
      it('should detach hostViews from the ApplicationRef once destroyed', async(() => {
 | 
						|
           let ng2Component: Ng2Component;
 | 
						|
 | 
						|
           @Component({selector: 'ng2', template: ''})
 | 
						|
           class Ng2Component {
 | 
						|
             constructor(public appRef: ApplicationRef) {
 | 
						|
               ng2Component = this;
 | 
						|
               spyOn(appRef, 'attachView').and.callThrough();
 | 
						|
               spyOn(appRef, 'detachView').and.callThrough();
 | 
						|
             }
 | 
						|
           }
 | 
						|
 | 
						|
           @NgModule({
 | 
						|
             declarations: [Ng2Component],
 | 
						|
             entryComponents: [Ng2Component],
 | 
						|
             imports: [BrowserModule],
 | 
						|
           })
 | 
						|
           class Ng2Module {
 | 
						|
             ngDoBootstrap() {}
 | 
						|
           }
 | 
						|
 | 
						|
           const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
               platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
 | 
						|
           const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
 | 
						|
           const ng1Module =
 | 
						|
               angular.module('ng1', [lazyModuleName])
 | 
						|
                   .directive(
 | 
						|
                       'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
 | 
						|
 | 
						|
           const element = html('<ng2 ng-if="!hideNg2"></ng2>');
 | 
						|
           const $injector = angular.bootstrap(element, [ng1Module.name]);
 | 
						|
           const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
 | 
						|
 | 
						|
           setTimeout(() => {    // Wait for the module to be bootstrapped.
 | 
						|
             setTimeout(() => {  // Wait for the hostView to be attached (during the `$digest`).
 | 
						|
               const hostView: ViewRef =
 | 
						|
                   (ng2Component.appRef.attachView as jasmine.Spy).calls.mostRecent().args[0];
 | 
						|
 | 
						|
               expect(hostView.destroyed).toBe(false);
 | 
						|
 | 
						|
               $rootScope.$apply('hideNg2 = true');
 | 
						|
 | 
						|
               expect(hostView.destroyed).toBe(true);
 | 
						|
               expect(ng2Component.appRef.detachView).toHaveBeenCalledWith(hostView);
 | 
						|
             });
 | 
						|
           });
 | 
						|
         }));
 | 
						|
 | 
						|
      it('should only retrieve the Angular zone once (and cache it for later use)',
 | 
						|
         fakeAsync(() => {
 | 
						|
           let count = 0;
 | 
						|
           let getNgZoneCount = 0;
 | 
						|
 | 
						|
           @Component(
 | 
						|
               {selector: 'ng2', template: 'Count: {{ count }} | In the zone: {{ inTheZone }}'})
 | 
						|
           class Ng2Component {
 | 
						|
             private count = ++count;
 | 
						|
             private inTheZone = false;
 | 
						|
             constructor() { this.inTheZone = NgZone.isInAngularZone(); }
 | 
						|
           }
 | 
						|
 | 
						|
           @NgModule({
 | 
						|
             declarations: [Ng2Component],
 | 
						|
             entryComponents: [Ng2Component],
 | 
						|
             imports: [BrowserModule],
 | 
						|
           })
 | 
						|
           class Ng2Module {
 | 
						|
             constructor(injector: Injector) {
 | 
						|
               const originalGet = injector.get;
 | 
						|
               injector.get = function(token: any) {
 | 
						|
                 if (token === NgZone) ++getNgZoneCount;
 | 
						|
                 return originalGet.apply(injector, arguments);
 | 
						|
               };
 | 
						|
             }
 | 
						|
             ngDoBootstrap() {}
 | 
						|
           }
 | 
						|
 | 
						|
           const tickDelay = browserDetection.isIE ? 100 : 0;
 | 
						|
           const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
               platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
 | 
						|
           const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
 | 
						|
           const ng1Module =
 | 
						|
               angular.module('ng1', [lazyModuleName])
 | 
						|
                   .directive(
 | 
						|
                       'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
 | 
						|
 | 
						|
           const element = html('<div><ng2 ng-if="showNg2"></ng2></div>');
 | 
						|
           const $injector = angular.bootstrap(element, [ng1Module.name]);
 | 
						|
           const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
 | 
						|
 | 
						|
           $rootScope.$apply('showNg2 = true');
 | 
						|
           tick(tickDelay);  // Wait for the module to be bootstrapped and `$evalAsync()` to
 | 
						|
                             // propagate inputs.
 | 
						|
 | 
						|
           const injector = ($injector.get(LAZY_MODULE_REF) as LazyModuleRef).injector !;
 | 
						|
           const injectorGet = injector.get;
 | 
						|
           spyOn(injector, 'get').and.callFake((...args: any[]) => {
 | 
						|
             expect(args[0]).not.toBe(NgZone);
 | 
						|
             return injectorGet.apply(injector, args);
 | 
						|
           });
 | 
						|
 | 
						|
           expect(element.textContent).toBe('Count: 1 | In the zone: true');
 | 
						|
 | 
						|
           $rootScope.$apply('showNg2 = false');
 | 
						|
           expect(element.textContent).toBe('');
 | 
						|
 | 
						|
           $rootScope.$apply('showNg2 = true');
 | 
						|
           tick(tickDelay);  // Wait for `$evalAsync()` to propagate inputs.
 | 
						|
           expect(element.textContent).toBe('Count: 2 | In the zone: true');
 | 
						|
 | 
						|
           $rootScope.$destroy();
 | 
						|
         }));
 | 
						|
 | 
						|
      it('should give access to both injectors in the Angular module\'s constructor', async(() => {
 | 
						|
           let $injectorFromNg2: angular.IInjectorService|null = null;
 | 
						|
 | 
						|
           @Component({selector: 'ng2', template: ''})
 | 
						|
           class Ng2Component {
 | 
						|
           }
 | 
						|
 | 
						|
           @NgModule({
 | 
						|
             declarations: [Ng2Component],
 | 
						|
             entryComponents: [Ng2Component],
 | 
						|
             imports: [BrowserModule],
 | 
						|
           })
 | 
						|
           class Ng2Module {
 | 
						|
             constructor(injector: Injector) {
 | 
						|
               $injectorFromNg2 = injector.get<angular.IInjectorService>('$injector' as any);
 | 
						|
             }
 | 
						|
 | 
						|
             ngDoBootstrap() {}
 | 
						|
           }
 | 
						|
 | 
						|
           const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
               platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
 | 
						|
           const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
 | 
						|
           const ng1Module =
 | 
						|
               angular.module('ng1', [lazyModuleName])
 | 
						|
                   .directive(
 | 
						|
                       'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
 | 
						|
 | 
						|
           const element = html('<ng2></ng2>');
 | 
						|
           const $injectorFromNg1 = angular.bootstrap(element, [ng1Module.name]);
 | 
						|
 | 
						|
           // Wait for the module to be bootstrapped.
 | 
						|
           setTimeout(() => expect($injectorFromNg2).toBe($injectorFromNg1));
 | 
						|
         }));
 | 
						|
 | 
						|
      describe('(common error)', () => {
 | 
						|
        let Ng2CompA: Type<any>;
 | 
						|
        let Ng2CompB: Type<any>;
 | 
						|
        let downModA: string;
 | 
						|
        let downModB: string;
 | 
						|
        let errorSpy: jasmine.Spy;
 | 
						|
 | 
						|
        const doDowngradeModule = (module: Type<any>) => {
 | 
						|
          const bootstrapFn = (extraProviders: StaticProvider[]) =>
 | 
						|
              (getPlatform() || platformBrowserDynamic(extraProviders)).bootstrapModule(module);
 | 
						|
          return downgradeModule(bootstrapFn);
 | 
						|
        };
 | 
						|
 | 
						|
        beforeEach(() => {
 | 
						|
          @Component({selector: 'ng2A', template: 'a'})
 | 
						|
          class Ng2ComponentA {
 | 
						|
          }
 | 
						|
 | 
						|
          @Component({selector: 'ng2B', template: 'b'})
 | 
						|
          class Ng2ComponentB {
 | 
						|
          }
 | 
						|
 | 
						|
          @NgModule({
 | 
						|
            declarations: [Ng2ComponentA],
 | 
						|
            entryComponents: [Ng2ComponentA],
 | 
						|
            imports: [BrowserModule],
 | 
						|
          })
 | 
						|
          class Ng2ModuleA {
 | 
						|
            ngDoBootstrap() {}
 | 
						|
          }
 | 
						|
 | 
						|
          @NgModule({
 | 
						|
            declarations: [Ng2ComponentB],
 | 
						|
            entryComponents: [Ng2ComponentB],
 | 
						|
            imports: [BrowserModule],
 | 
						|
          })
 | 
						|
          class Ng2ModuleB {
 | 
						|
            ngDoBootstrap() {}
 | 
						|
          }
 | 
						|
 | 
						|
          Ng2CompA = Ng2ComponentA;
 | 
						|
          Ng2CompB = Ng2ComponentB;
 | 
						|
          downModA = doDowngradeModule(Ng2ModuleA);
 | 
						|
          downModB = doDowngradeModule(Ng2ModuleB);
 | 
						|
          errorSpy = jasmine.createSpy($EXCEPTION_HANDLER);
 | 
						|
        });
 | 
						|
 | 
						|
        it('should throw if no downgraded module is included', async(() => {
 | 
						|
             const ng1Module = angular.module('ng1', [])
 | 
						|
                                   .value($EXCEPTION_HANDLER, errorSpy)
 | 
						|
                                   .directive('ng2A', downgradeComponent({
 | 
						|
                                                component: Ng2CompA,
 | 
						|
                                                downgradedModule: downModA, propagateDigest,
 | 
						|
                                              }))
 | 
						|
                                   .directive('ng2B', downgradeComponent({
 | 
						|
                                                component: Ng2CompB,
 | 
						|
                                                propagateDigest,
 | 
						|
                                              }));
 | 
						|
 | 
						|
             const element = html('<ng2-a></ng2-a> | <ng2-b></ng2-b>');
 | 
						|
             angular.bootstrap(element, [ng1Module.name]);
 | 
						|
 | 
						|
             expect(errorSpy).toHaveBeenCalledTimes(2);
 | 
						|
             expect(errorSpy).toHaveBeenCalledWith(
 | 
						|
                 new Error(
 | 
						|
                     'Error while instantiating component \'Ng2ComponentA\': Not a valid ' +
 | 
						|
                     '\'@angular/upgrade\' application.\n' +
 | 
						|
                     'Did you forget to downgrade an Angular module or include it in the AngularJS ' +
 | 
						|
                     'application?'),
 | 
						|
                 '<ng2-a>');
 | 
						|
             expect(errorSpy).toHaveBeenCalledWith(
 | 
						|
                 new Error(
 | 
						|
                     'Error while instantiating component \'Ng2ComponentB\': Not a valid ' +
 | 
						|
                     '\'@angular/upgrade\' application.\n' +
 | 
						|
                     'Did you forget to downgrade an Angular module or include it in the AngularJS ' +
 | 
						|
                     'application?'),
 | 
						|
                 '<ng2-b>');
 | 
						|
           }));
 | 
						|
 | 
						|
        it('should throw if the corresponding downgraded module is not included', async(() => {
 | 
						|
             const ng1Module = angular.module('ng1', [downModA])
 | 
						|
                                   .value($EXCEPTION_HANDLER, errorSpy)
 | 
						|
                                   .directive('ng2A', downgradeComponent({
 | 
						|
                                                component: Ng2CompA,
 | 
						|
                                                downgradedModule: downModA, propagateDigest,
 | 
						|
                                              }))
 | 
						|
                                   .directive('ng2B', downgradeComponent({
 | 
						|
                                                component: Ng2CompB,
 | 
						|
                                                downgradedModule: downModB, propagateDigest,
 | 
						|
                                              }));
 | 
						|
 | 
						|
             const element = html('<ng2-a></ng2-a> | <ng2-b></ng2-b>');
 | 
						|
             angular.bootstrap(element, [ng1Module.name]);
 | 
						|
 | 
						|
             expect(errorSpy).toHaveBeenCalledTimes(1);
 | 
						|
             expect(errorSpy).toHaveBeenCalledWith(
 | 
						|
                 new Error(
 | 
						|
                     'Error while instantiating component \'Ng2ComponentB\': Unable to find the ' +
 | 
						|
                     'specified downgraded module.\n' +
 | 
						|
                     'Did you forget to downgrade an Angular module or include it in the AngularJS ' +
 | 
						|
                     'application?'),
 | 
						|
                 '<ng2-b>');
 | 
						|
           }));
 | 
						|
 | 
						|
        it('should throw if `downgradedModule` is not specified and there are multiple downgraded modules',
 | 
						|
           async(() => {
 | 
						|
             const ng1Module = angular.module('ng1', [downModA, downModB])
 | 
						|
                                   .value($EXCEPTION_HANDLER, errorSpy)
 | 
						|
                                   .directive('ng2A', downgradeComponent({
 | 
						|
                                                component: Ng2CompA,
 | 
						|
                                                downgradedModule: downModA, propagateDigest,
 | 
						|
                                              }))
 | 
						|
                                   .directive('ng2B', downgradeComponent({
 | 
						|
                                                component: Ng2CompB,
 | 
						|
                                                propagateDigest,
 | 
						|
                                              }));
 | 
						|
 | 
						|
             const element = html('<ng2-a></ng2-a> | <ng2-b></ng2-b>');
 | 
						|
             angular.bootstrap(element, [ng1Module.name]);
 | 
						|
 | 
						|
             expect(errorSpy).toHaveBeenCalledTimes(1);
 | 
						|
             expect(errorSpy).toHaveBeenCalledWith(
 | 
						|
                 new Error(
 | 
						|
                     'Error while instantiating component \'Ng2ComponentB\': \'downgradedModule\' not ' +
 | 
						|
                     'specified.\n' +
 | 
						|
                     'This application contains more than one downgraded Angular module, thus you need ' +
 | 
						|
                     'to always specify \'downgradedModule\' when downgrading components and ' +
 | 
						|
                     'injectables.'),
 | 
						|
                 '<ng2-b>');
 | 
						|
           }));
 | 
						|
      });
 | 
						|
    });
 | 
						|
  });
 | 
						|
});
 |