diff --git a/packages/examples/upgrade/static/ts/lite-multi/module.ts b/packages/examples/upgrade/static/ts/lite-multi/module.ts index d53a066e6d..272db218fa 100644 --- a/packages/examples/upgrade/static/ts/lite-multi/module.ts +++ b/packages/examples/upgrade/static/ts/lite-multi/module.ts @@ -103,11 +103,15 @@ const appModule = }) .directive('ng2A', downgradeComponent({ component: Ng2AComponent, + // Since there are more than one downgraded Angular module, + // specify which module this component belongs to. downgradedModule: downgradedNg2AModule, propagateDigest: false, })) .directive('ng2B', downgradeComponent({ component: Ng2BComponent, + // Since there are more than one downgraded Angular module, + // specify which module this component belongs to. downgradedModule: downgradedNg2BModule, propagateDigest: false, })) diff --git a/packages/upgrade/src/common/constants.ts b/packages/upgrade/src/common/constants.ts index 99a57f808a..61989dd880 100644 --- a/packages/upgrade/src/common/constants.ts +++ b/packages/upgrade/src/common/constants.ts @@ -23,10 +23,12 @@ export const $TEMPLATE_REQUEST = '$templateRequest'; export const $$TESTABILITY = '$$testability'; export const COMPILER_KEY = '$$angularCompiler'; +export const DOWNGRADED_MODULE_COUNT_KEY = '$$angularDowngradedModuleCount'; export const GROUP_PROJECTABLE_NODES_KEY = '$$angularGroupProjectableNodes'; export const INJECTOR_KEY = '$$angularInjector'; export const LAZY_MODULE_REF = '$$angularLazyModuleRef'; export const NG_ZONE_KEY = '$$angularNgZone'; +export const UPGRADE_APP_TYPE_KEY = '$$angularUpgradeAppType'; export const REQUIRE_INJECTOR = '?^^' + INJECTOR_KEY; export const REQUIRE_NG_MODEL = '?ngModel'; diff --git a/packages/upgrade/src/common/downgrade_component.ts b/packages/upgrade/src/common/downgrade_component.ts index aebf63a81f..344c02a0d8 100644 --- a/packages/upgrade/src/common/downgrade_component.ts +++ b/packages/upgrade/src/common/downgrade_component.ts @@ -11,7 +11,7 @@ import {ComponentFactory, ComponentFactoryResolver, Injector, NgZone, Type} from import * as angular from './angular1'; import {$COMPILE, $INJECTOR, $PARSE, INJECTOR_KEY, LAZY_MODULE_REF, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from './constants'; import {DowngradeComponentAdapter} from './downgrade_component_adapter'; -import {LazyModuleRef, controllerKey, getComponentName, isFunction} from './util'; +import {LazyModuleRef, controllerKey, getTypeName, isFunction, validateInjectionKey} from './util'; interface Thenable { @@ -104,6 +104,10 @@ export function downgradeComponent(info: { if (!parentInjector) { const downgradedModule = info.downgradedModule || ''; const lazyModuleRefKey = `${LAZY_MODULE_REF}${downgradedModule}`; + const attemptedAction = `instantiating component '${getTypeName(info.component)}'`; + + validateInjectionKey($injector, downgradedModule, lazyModuleRefKey, attemptedAction); + const lazyModuleRef = $injector.get(lazyModuleRefKey) as LazyModuleRef; needsNgZone = lazyModuleRef.needsNgZone; parentInjector = lazyModuleRef.injector || lazyModuleRef.promise as Promise; @@ -116,7 +120,7 @@ export function downgradeComponent(info: { componentFactoryResolver.resolveComponentFactory(info.component) !; if (!componentFactory) { - throw new Error('Expecting ComponentFactory for: ' + getComponentName(info.component)); + throw new Error(`Expecting ComponentFactory for: ${getTypeName(info.component)}`); } const injectorPromise = new ParentInjectorPromise(element); diff --git a/packages/upgrade/src/common/downgrade_component_adapter.ts b/packages/upgrade/src/common/downgrade_component_adapter.ts index bc41baba81..d4813554f2 100644 --- a/packages/upgrade/src/common/downgrade_component_adapter.ts +++ b/packages/upgrade/src/common/downgrade_component_adapter.ts @@ -11,7 +11,7 @@ import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, Event import * as angular from './angular1'; import {PropertyBinding} from './component_info'; import {$SCOPE} from './constants'; -import {getComponentName, hookupNgModel, strictEquals} from './util'; +import {getTypeName, hookupNgModel, strictEquals} from './util'; const INITIAL_VALUE = { __UNINITIALIZED__: true @@ -208,7 +208,7 @@ export class DowngradeComponentAdapter { }); } else { throw new Error( - `Missing emitter '${output.prop}' on component '${getComponentName(this.componentFactory.componentType)}'!`); + `Missing emitter '${output.prop}' on component '${getTypeName(this.componentFactory.componentType)}'!`); } } diff --git a/packages/upgrade/src/common/downgrade_injectable.ts b/packages/upgrade/src/common/downgrade_injectable.ts index 443070cc66..55cbb083b2 100644 --- a/packages/upgrade/src/common/downgrade_injectable.ts +++ b/packages/upgrade/src/common/downgrade_injectable.ts @@ -7,7 +7,9 @@ */ import {Injector} from '@angular/core'; -import {INJECTOR_KEY} from './constants'; +import * as angular from './angular1'; +import {$INJECTOR, INJECTOR_KEY} from './constants'; +import {getTypeName, isFunction, validateInjectionKey} from './util'; /** * @description @@ -70,8 +72,17 @@ import {INJECTOR_KEY} from './constants'; * @publicApi */ export function downgradeInjectable(token: any, downgradedModule: string = ''): Function { - const factory = function(i: Injector) { return i.get(token); }; - (factory as any)['$inject'] = [`${INJECTOR_KEY}${downgradedModule}`]; + const factory = function($injector: angular.IInjectorService) { + const injectorKey = `${INJECTOR_KEY}${downgradedModule}`; + const injectableName = isFunction(token) ? getTypeName(token) : String(token); + const attemptedAction = `instantiating injectable '${injectableName}'`; + + validateInjectionKey($injector, downgradedModule, injectorKey, attemptedAction); + + const injector: Injector = $injector.get(injectorKey); + return injector.get(token); + }; + (factory as any)['$inject'] = [$INJECTOR]; return factory; } diff --git a/packages/upgrade/src/common/util.ts b/packages/upgrade/src/common/util.ts index d505aec20c..f1cfae9627 100644 --- a/packages/upgrade/src/common/util.ts +++ b/packages/upgrade/src/common/util.ts @@ -8,6 +8,7 @@ import {Injector, Type} from '@angular/core'; import * as angular from './angular1'; +import {DOWNGRADED_MODULE_COUNT_KEY, UPGRADE_APP_TYPE_KEY} from './constants'; const DIRECTIVE_PREFIX_REGEXP = /^(?:x|data)[:\-_]/i; const DIRECTIVE_SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g; @@ -32,15 +33,66 @@ export function directiveNormalize(name: string): string { .replace(DIRECTIVE_SPECIAL_CHARS_REGEXP, (_, letter) => letter.toUpperCase()); } -export function getComponentName(component: Type): string { - // Return the name of the component or the first line of its stringified version. - return (component as any).overriddenName || component.name || component.toString().split('\n')[0]; +export function getTypeName(type: Type): string { + // Return the name of the type or the first line of its stringified version. + return (type as any).overriddenName || type.name || type.toString().split('\n')[0]; +} + +export function getDowngradedModuleCount($injector: angular.IInjectorService): number { + return $injector.has(DOWNGRADED_MODULE_COUNT_KEY) ? $injector.get(DOWNGRADED_MODULE_COUNT_KEY) : + 0; +} + +export function getUpgradeAppType($injector: angular.IInjectorService): UpgradeAppType { + return $injector.has(UPGRADE_APP_TYPE_KEY) ? $injector.get(UPGRADE_APP_TYPE_KEY) : + UpgradeAppType.None; } export function isFunction(value: any): value is Function { return typeof value === 'function'; } +export function validateInjectionKey( + $injector: angular.IInjectorService, downgradedModule: string, injectionKey: string, + attemptedAction: string): void { + const upgradeAppType = getUpgradeAppType($injector); + const downgradedModuleCount = getDowngradedModuleCount($injector); + + // Check for common errors. + switch (upgradeAppType) { + case UpgradeAppType.Dynamic: + case UpgradeAppType.Static: + if (downgradedModule) { + throw new Error( + `Error while ${attemptedAction}: 'downgradedModule' unexpectedly specified.\n` + + 'You should not specify a value for \'downgradedModule\', unless you are downgrading ' + + 'more than one Angular module (via \'downgradeModule()\').'); + } + break; + case UpgradeAppType.Lite: + if (!downgradedModule && (downgradedModuleCount >= 2)) { + throw new Error( + `Error while ${attemptedAction}: '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.'); + } + + if (!$injector.has(injectionKey)) { + throw new Error( + `Error while ${attemptedAction}: Unable to find the specified downgraded module.\n` + + 'Did you forget to downgrade an Angular module or include it in the AngularJS ' + + 'application?'); + } + + break; + default: + throw new Error( + `Error while ${attemptedAction}: Not a valid '@angular/upgrade' application.\n` + + 'Did you forget to downgrade an Angular module or include it in the AngularJS ' + + 'application?'); + } +} + export class Deferred { promise: Promise; // TODO(issue/24571): remove '!'. @@ -64,6 +116,20 @@ export interface LazyModuleRef { promise?: Promise; } +export const enum UpgradeAppType { + // App NOT using `@angular/upgrade`. (This should never happen in an `ngUpgrade` app.) + None, + + // App using the deprecated `@angular/upgrade` APIs (a.k.a. dynamic `ngUpgrade`). + Dynamic, + + // App using `@angular/upgrade/static` with `UpgradeModule`. + Static, + + // App using @angular/upgrade/static` with `downgradeModule()` (a.k.a `ngUpgrade`-lite ). + Lite, +} + /** * @return Whether the passed-in component implements the subset of the * `ControlValueAccessor` interface needed for AngularJS `ng-model` diff --git a/packages/upgrade/src/dynamic/upgrade_adapter.ts b/packages/upgrade/src/dynamic/upgrade_adapter.ts index 7f4d645108..9c7488f395 100644 --- a/packages/upgrade/src/dynamic/upgrade_adapter.ts +++ b/packages/upgrade/src/dynamic/upgrade_adapter.ts @@ -10,10 +10,10 @@ import {Compiler, CompilerOptions, Directive, Injector, NgModule, NgModuleRef, N import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import * as angular from '../common/angular1'; -import {$$TESTABILITY, $COMPILE, $INJECTOR, $ROOT_SCOPE, COMPILER_KEY, INJECTOR_KEY, LAZY_MODULE_REF, NG_ZONE_KEY} from '../common/constants'; +import {$$TESTABILITY, $COMPILE, $INJECTOR, $ROOT_SCOPE, COMPILER_KEY, INJECTOR_KEY, LAZY_MODULE_REF, NG_ZONE_KEY, UPGRADE_APP_TYPE_KEY} from '../common/constants'; import {downgradeComponent} from '../common/downgrade_component'; import {downgradeInjectable} from '../common/downgrade_injectable'; -import {Deferred, LazyModuleRef, controllerKey, onError} from '../common/util'; +import {Deferred, LazyModuleRef, UpgradeAppType, controllerKey, onError} from '../common/util'; import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter'; @@ -506,7 +506,8 @@ export class UpgradeAdapter { this.ngZone = new NgZone({enableLongStackTrace: Zone.hasOwnProperty('longStackTraceZoneSpec')}); this.ng2BootstrapDeferred = new Deferred(); - ng1Module.factory(INJECTOR_KEY, () => this.moduleRef !.injector.get(Injector)) + ng1Module.constant(UPGRADE_APP_TYPE_KEY, UpgradeAppType.Dynamic) + .factory(INJECTOR_KEY, () => this.moduleRef !.injector.get(Injector)) .factory( LAZY_MODULE_REF, [ diff --git a/packages/upgrade/src/static/downgrade_module.ts b/packages/upgrade/src/static/downgrade_module.ts index 3fa96d3f55..08c512d5a3 100644 --- a/packages/upgrade/src/static/downgrade_module.ts +++ b/packages/upgrade/src/static/downgrade_module.ts @@ -10,8 +10,8 @@ import {Injector, NgModuleFactory, NgModuleRef, StaticProvider} from '@angular/c import {platformBrowser} from '@angular/platform-browser'; import * as angular from '../common/angular1'; -import {$INJECTOR, INJECTOR_KEY, LAZY_MODULE_REF, UPGRADE_MODULE_NAME} from '../common/constants'; -import {LazyModuleRef, isFunction} from '../common/util'; +import {$INJECTOR, $PROVIDE, DOWNGRADED_MODULE_COUNT_KEY, INJECTOR_KEY, LAZY_MODULE_REF, UPGRADE_APP_TYPE_KEY, UPGRADE_MODULE_NAME} from '../common/constants'; +import {LazyModuleRef, UpgradeAppType, getDowngradedModuleCount, isFunction} from '../common/util'; import {angular1Providers, setTempInjectorRef} from './angular1_providers'; import {NgAdapterInjector} from './util'; @@ -119,31 +119,41 @@ export function downgradeModule( // Create an ng1 module to bootstrap. angular.module(lazyModuleName, []) + .constant(UPGRADE_APP_TYPE_KEY, UpgradeAppType.Lite) .factory(INJECTOR_KEY, [lazyInjectorKey, identity]) .factory( lazyInjectorKey, () => { if (!injector) { throw new Error( - 'Trying to get the Angular injector before bootstrapping an Angular module.'); + 'Trying to get the Angular injector before bootstrapping the corresponding ' + + 'Angular module.'); } return injector; }) .factory(LAZY_MODULE_REF, [lazyModuleRefKey, identity]) - .factory(lazyModuleRefKey, [ - $INJECTOR, - ($injector: angular.IInjectorService) => { - setTempInjectorRef($injector); - const result: LazyModuleRef = { - needsNgZone: true, - promise: bootstrapFn(angular1Providers).then(ref => { - injector = result.injector = new NgAdapterInjector(ref.injector); - injector.get($INJECTOR); + .factory( + lazyModuleRefKey, + [ + $INJECTOR, + ($injector: angular.IInjectorService) => { + setTempInjectorRef($injector); + const result: LazyModuleRef = { + needsNgZone: true, + promise: bootstrapFn(angular1Providers).then(ref => { + injector = result.injector = new NgAdapterInjector(ref.injector); + injector.get($INJECTOR); - return injector; - }) - }; - return result; + return injector; + }) + }; + return result; + } + ]) + .config([ + $INJECTOR, $PROVIDE, + ($injector: angular.IInjectorService, $provide: angular.IProvideService) => { + $provide.constant(DOWNGRADED_MODULE_COUNT_KEY, getDowngradedModuleCount($injector) + 1); } ]); diff --git a/packages/upgrade/src/static/upgrade_module.ts b/packages/upgrade/src/static/upgrade_module.ts index ac2fd875ac..c9ade2025f 100644 --- a/packages/upgrade/src/static/upgrade_module.ts +++ b/packages/upgrade/src/static/upgrade_module.ts @@ -9,8 +9,8 @@ import {Injector, NgModule, NgZone, Testability} from '@angular/core'; import * as angular from '../common/angular1'; -import {$$TESTABILITY, $DELEGATE, $INJECTOR, $INTERVAL, $PROVIDE, INJECTOR_KEY, LAZY_MODULE_REF, UPGRADE_MODULE_NAME} from '../common/constants'; -import {LazyModuleRef, controllerKey} from '../common/util'; +import {$$TESTABILITY, $DELEGATE, $INJECTOR, $INTERVAL, $PROVIDE, INJECTOR_KEY, LAZY_MODULE_REF, UPGRADE_APP_TYPE_KEY, UPGRADE_MODULE_NAME} from '../common/constants'; +import {LazyModuleRef, UpgradeAppType, controllerKey} from '../common/util'; import {angular1Providers, setTempInjectorRef} from './angular1_providers'; import {NgAdapterInjector} from './util'; @@ -173,6 +173,8 @@ export class UpgradeModule { angular .module(INIT_MODULE_NAME, []) + .constant(UPGRADE_APP_TYPE_KEY, UpgradeAppType.Static) + .value(INJECTOR_KEY, this.injector) .factory( diff --git a/packages/upgrade/test/common/downgrade_injectable_spec.ts b/packages/upgrade/test/common/downgrade_injectable_spec.ts index ce6167ba2d..193f552c6d 100644 --- a/packages/upgrade/test/common/downgrade_injectable_spec.ts +++ b/packages/upgrade/test/common/downgrade_injectable_spec.ts @@ -6,31 +6,46 @@ * found in the LICENSE file at https://angular.io/license */ -import {INJECTOR_KEY} from '@angular/upgrade/src/common/constants'; +import {Injector} from '@angular/core'; +import * as angular from '@angular/upgrade/src/common/angular1'; +import {$INJECTOR, INJECTOR_KEY, UPGRADE_APP_TYPE_KEY} from '@angular/upgrade/src/common/constants'; import {downgradeInjectable} from '@angular/upgrade/src/common/downgrade_injectable'; +import {UpgradeAppType} from '@angular/upgrade/src/common/util'; -{ - describe('downgradeInjectable', () => { - it('should return an AngularJS annotated factory for the token', () => { - const factory = downgradeInjectable('someToken'); - expect(factory).toEqual(jasmine.any(Function)); - expect((factory as any).$inject).toEqual([INJECTOR_KEY]); +describe('downgradeInjectable', () => { + const setupMockInjectors = (downgradedModule = '') => { + const mockNg1Injector = jasmine.createSpyObj(['get', 'has']); + mockNg1Injector.get.and.callFake((key: string) => mockDependencies[key]); + mockNg1Injector.has.and.callFake((key: string) => mockDependencies.hasOwnProperty(key)); - const injector = {get: jasmine.createSpy('get').and.returnValue('service value')}; - const value = factory(injector); - expect(injector.get).toHaveBeenCalledWith('someToken'); - expect(value).toEqual('service value'); - }); + const mockNg2Injector = jasmine.createSpyObj(['get']); + mockNg2Injector.get.and.returnValue('service value'); - it('should inject the specified module\'s injector when specifying a module name', () => { - const factory = downgradeInjectable('someToken', 'someModule'); - expect(factory).toEqual(jasmine.any(Function)); - expect((factory as any).$inject).toEqual([`${INJECTOR_KEY}someModule`]); + const mockDependencies: {[key: string]: any} = { + [UPGRADE_APP_TYPE_KEY]: downgradedModule ? UpgradeAppType.Lite : UpgradeAppType.Static, + [`${INJECTOR_KEY}${downgradedModule}`]: mockNg2Injector, + }; - const injector = {get: jasmine.createSpy('get').and.returnValue('service value')}; - const value = factory(injector); - expect(injector.get).toHaveBeenCalledWith('someToken'); - expect(value).toEqual('service value'); - }); + return {mockNg1Injector, mockNg2Injector}; + }; + + it('should return an AngularJS annotated factory for the token', () => { + const factory = downgradeInjectable('someToken'); + expect(factory).toEqual(jasmine.any(Function)); + expect((factory as any).$inject).toEqual([$INJECTOR]); + + const {mockNg1Injector, mockNg2Injector} = setupMockInjectors(); + expect(factory(mockNg1Injector)).toEqual('service value'); + expect(mockNg2Injector.get).toHaveBeenCalledWith('someToken'); }); -} + + it('should inject the specified module\'s injector when specifying a module name', () => { + const factory = downgradeInjectable('someToken', 'someModule'); + expect(factory).toEqual(jasmine.any(Function)); + expect((factory as any).$inject).toEqual([$INJECTOR]); + + const {mockNg1Injector, mockNg2Injector} = setupMockInjectors('someModule'); + expect(factory(mockNg1Injector)).toEqual('service value'); + expect(mockNg2Injector.get).toHaveBeenCalledWith('someToken'); + }); +}); diff --git a/packages/upgrade/test/static/integration/downgrade_component_spec.ts b/packages/upgrade/test/static/integration/downgrade_component_spec.ts index 3ca52b98d3..b9a51aa96e 100644 --- a/packages/upgrade/test/static/integration/downgrade_component_spec.ts +++ b/packages/upgrade/test/static/integration/downgrade_component_spec.ts @@ -784,5 +784,37 @@ withEachNg1Version(() => { }); })); + + it('should throw if `downgradedModule` is specified', async(() => { + @Component({selector: 'ng2', template: ''}) + class Ng2Component { + } + + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule, UpgradeModule], + }) + class Ng2Module { + ngDoBootstrap() {} + } + + + const ng1Module = angular.module('ng1', []).directive( + 'ng2', downgradeComponent({component: Ng2Component, downgradedModule: 'foo'})); + + const element = html(''); + + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module) + .then( + () => { throw new Error('Expected bootstraping to fail.'); }, + err => + expect(err.message) + .toBe( + 'Error while instantiating component \'Ng2Component\': \'downgradedModule\' ' + + 'unexpectedly specified.\n' + + 'You should not specify a value for \'downgradedModule\', unless you are ' + + 'downgrading more than one Angular module (via \'downgradeModule()\').')); + })); }); }); diff --git a/packages/upgrade/test/static/integration/downgrade_module_spec.ts b/packages/upgrade/test/static/integration/downgrade_module_spec.ts index c142e2678a..ac93be22d5 100644 --- a/packages/upgrade/test/static/integration/downgrade_module_spec.ts +++ b/packages/upgrade/test/static/integration/downgrade_module_spec.ts @@ -13,7 +13,7 @@ import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {browserDetection} from '@angular/platform-browser/testing/src/browser_util'; import {downgradeComponent, downgradeModule} from '@angular/upgrade/static'; import * as angular from '@angular/upgrade/static/src/common/angular1'; -import {$ROOT_SCOPE, INJECTOR_KEY, LAZY_MODULE_REF} from '@angular/upgrade/static/src/common/constants'; +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'; @@ -661,6 +661,138 @@ withEachNg1Version(() => { // Wait for the module to be bootstrapped. setTimeout(() => expect($injectorFromNg2).toBe($injectorFromNg1)); })); + + describe('(common error)', () => { + let Ng2CompA: Type; + let Ng2CompB: Type; + let downModA: string; + let downModB: string; + let errorSpy: jasmine.Spy; + + const doDowngradeModule = (module: Type) => { + 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(' | '); + 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?'), + ''); + 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?'), + ''); + })); + + 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(' | '); + 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?'), + ''); + })); + + 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(' | '); + 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.'), + ''); + })); + }); }); }); });