2016-06-23 09:47:54 -07:00
/**
* @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
*/
2017-02-22 16:49:46 -08:00
import { isPlatformBrowser } from '@angular/common' ;
import { APP_INITIALIZER , CUSTOM_ELEMENTS_SCHEMA , Compiler , Component , Directive , ErrorHandler , Inject , Input , LOCALE_ID , NgModule , OnDestroy , PLATFORM_ID , PLATFORM_INITIALIZER , Pipe , Provider , VERSION , createPlatformFactory } from '@angular/core' ;
2016-08-12 14:15:37 -07:00
import { ApplicationRef , destroyPlatform } from '@angular/core/src/application_ref' ;
2016-06-08 16:38:52 -07:00
import { Console } from '@angular/core/src/console' ;
import { ComponentRef } from '@angular/core/src/linker/component_factory' ;
import { Testability , TestabilityRegistry } from '@angular/core/src/testability/testability' ;
2017-02-17 08:56:36 -08:00
import { AsyncTestCompleter , Log , afterEach , beforeEach , beforeEachProviders , ddescribe , describe , iit , inject , it } from '@angular/core/testing/testing_internal' ;
2016-07-18 03:50:31 -07:00
import { BrowserModule } from '@angular/platform-browser' ;
2016-08-16 11:15:01 -07:00
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' ;
2016-06-08 16:38:52 -07:00
import { getDOM } from '@angular/platform-browser/src/dom/dom_adapter' ;
import { DOCUMENT } from '@angular/platform-browser/src/dom/dom_tokens' ;
2016-07-22 09:20:51 -07:00
import { expect } from '@angular/platform-browser/testing/matchers' ;
2016-08-16 11:15:01 -07:00
2016-06-08 16:38:52 -07:00
import { stringify } from '../../src/facade/lang' ;
2015-05-26 09:45:15 -07:00
2017-02-17 08:56:36 -08:00
@Component ({ selector : 'non-existent' , template : '' })
class NonExistentComp {
}
2016-03-08 13:36:48 -08:00
@Component ({ selector : 'hello-app' , template : '{{greeting}} world!' })
2015-05-26 09:45:15 -07:00
class HelloRootCmp {
greeting : string ;
constructor () { this . greeting = 'hello' ; }
}
2016-03-08 13:36:48 -08:00
@Component ({ selector : 'hello-app' , template : 'before: <ng-content></ng-content> after: done' })
2015-05-26 09:45:15 -07:00
class HelloRootCmpContent {
constructor () {}
}
2016-03-08 13:36:48 -08:00
@Component ({ selector : 'hello-app-2' , template : '{{greeting}} world, again!' })
2015-05-26 09:45:15 -07:00
class HelloRootCmp2 {
greeting : string ;
constructor () { this . greeting = 'hello' ; }
}
2016-03-08 13:36:48 -08:00
@Component ({ selector : 'hello-app' , template : '' })
2015-05-26 09:45:15 -07:00
class HelloRootCmp3 {
2016-06-08 15:45:15 -07:00
appBinding : any /** TODO #9100 */ ;
2015-05-26 09:45:15 -07:00
2016-06-08 16:38:52 -07:00
constructor ( @Inject ( 'appBinding' ) appBinding : any /** TODO #9100 */ ) {
this . appBinding = appBinding ;
}
2015-05-26 09:45:15 -07:00
}
2016-03-08 13:36:48 -08:00
@Component ({ selector : 'hello-app' , template : '' })
2015-05-26 09:45:15 -07:00
class HelloRootCmp4 {
2016-06-08 15:45:15 -07:00
appRef : any /** TODO #9100 */ ;
2015-05-26 09:45:15 -07:00
2016-06-09 11:04:15 -07:00
constructor ( @Inject ( ApplicationRef ) appRef : ApplicationRef ) { this . appRef = appRef ; }
2015-05-26 09:45:15 -07:00
}
@Component ({ selector : 'hello-app' })
class HelloRootMissingTemplate {
}
@Directive ({ selector : 'hello-app' })
class HelloRootDirectiveIsNotCmp {
}
2016-03-08 13:36:48 -08:00
@Component ({ selector : 'hello-app' , template : '' })
2015-11-10 15:42:22 -08:00
class HelloOnDestroyTickCmp implements OnDestroy {
appRef : ApplicationRef ;
2016-06-09 11:04:15 -07:00
constructor ( @Inject ( ApplicationRef ) appRef : ApplicationRef ) { this . appRef = appRef ; }
2015-11-10 15:42:22 -08:00
2015-11-16 17:04:36 -08:00
ngOnDestroy () : void { this . appRef . tick (); }
2015-11-10 15:42:22 -08:00
}
2016-06-30 13:07:17 -07:00
@Component ({ selector : 'hello-app' , templateUrl : './sometemplate.html' })
class HelloUrlCmp {
greeting = 'hello' ;
}
2016-07-07 11:57:11 -07:00
@Directive ({ selector : '[someDir]' , host : { '[title]' : 'someDir' }})
class SomeDirective {
@Input ()
someDir : string ;
}
@Pipe ({ name : 'somePipe' })
class SomePipe {
transform ( value : string ) : any { return `transformed ${ value } ` ; }
}
@Component ({ selector : 'hello-app' , template : `<div [someDir]="'someValue' | somePipe"></div>` })
2016-06-30 13:07:17 -07:00
class HelloCmpUsingPlatformDirectiveAndPipe {
show : boolean = false ;
}
2016-07-25 03:02:57 -07:00
@Component ({ selector : 'hello-app' , template : '<some-el [someProp]="true">hello world!</some-el>' })
class HelloCmpUsingCustomElement {
}
2016-08-25 00:50:16 -07:00
class MockConsole {
2015-07-27 15:47:42 -07:00
res : any [] = [];
2016-08-25 00:50:16 -07:00
error ( s : any ) : void { this . res . push ( s ); }
2015-07-23 18:00:19 -07:00
}
2015-07-27 15:47:42 -07:00
2015-12-15 08:34:44 -08:00
class DummyConsole implements Console {
2016-07-07 11:57:11 -07:00
public warnings : string [] = [];
log ( message : string ) {}
warn ( message : string ) { this . warnings . push ( message ); }
2015-12-15 08:34:44 -08:00
}
2016-08-16 11:15:01 -07:00
class TestModule {}
2017-01-05 20:24:37 +03:00
function bootstrap (
cmpType : any , providers : Provider [] = [], platformProviders : Provider [] = []) : Promise < any > {
2016-08-16 11:15:01 -07:00
@NgModule ({
imports : [ BrowserModule ],
declarations : [ cmpType ],
bootstrap : [ cmpType ],
providers : providers ,
schemas : [ CUSTOM_ELEMENTS_SCHEMA ]
})
class TestModule {
}
2017-01-05 20:24:37 +03:00
return platformBrowserDynamic ( platformProviders ). bootstrapModule ( TestModule );
2016-08-16 11:15:01 -07:00
}
2015-05-26 09:45:15 -07:00
export function main() {
2017-02-17 08:56:36 -08:00
let el : any /** TODO #9100 */ , el2 : any /** TODO #9100 */ , testProviders : Provider [],
lightDom : any /** TODO #9100 */ ;
2015-05-26 09:45:15 -07:00
describe ( 'bootstrap factory method' , () => {
2016-07-07 11:57:11 -07:00
let compilerConsole : DummyConsole ;
2016-06-23 17:10:22 -07:00
beforeEachProviders (() => { return [ Log ]; });
2017-02-17 08:56:36 -08:00
beforeEach ( inject ([ DOCUMENT ], ( doc : any ) => {
2016-08-12 14:15:37 -07:00
destroyPlatform ();
2017-02-17 08:56:36 -08:00
compilerConsole = new DummyConsole ();
testProviders = [{ provide : Console , useValue : compilerConsole }];
const oldRoots = getDOM (). querySelectorAll ( doc , 'hello-app,hello-app-2,light-dom-el' );
for ( let i = 0 ; i < oldRoots . length ; i ++ ) {
getDOM (). remove ( oldRoots [ i ]);
}
el = getDOM (). createElement ( 'hello-app' , doc );
el2 = getDOM (). createElement ( 'hello-app-2' , doc );
lightDom = getDOM (). createElement ( 'light-dom-el' , doc );
getDOM (). appendChild ( doc . body , el );
getDOM (). appendChild ( doc . body , el2 );
2016-04-28 17:50:03 -07:00
getDOM (). appendChild ( el , lightDom );
getDOM (). setText ( lightDom , 'loading' );
2017-02-17 08:56:36 -08:00
}));
2015-06-24 13:46:39 -07:00
2016-08-12 14:15:37 -07:00
afterEach ( destroyPlatform );
2015-11-18 09:18:37 -08:00
2016-11-10 14:07:30 -08:00
it ( 'should throw if bootstrapped Directive is not a Component' ,
inject ([ AsyncTestCompleter ], ( done : AsyncTestCompleter ) => {
2016-11-12 14:08:58 +01:00
const logger = new MockConsole ();
const errorHandler = new ErrorHandler ( false );
2016-11-10 14:07:30 -08:00
errorHandler . _console = logger as any ;
2017-02-06 17:55:58 -08:00
expect (
() => bootstrap (
HelloRootDirectiveIsNotCmp , [{ provide : ErrorHandler , useValue : errorHandler }]))
. toThrowError ( `HelloRootDirectiveIsNotCmp cannot be used as an entry component.` );
done . done ();
2016-11-10 14:07:30 -08:00
}));
2015-05-26 09:45:15 -07:00
2016-06-08 16:38:52 -07:00
it ( 'should throw if no element is found' ,
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
2016-11-12 14:08:58 +01:00
const logger = new MockConsole ();
const errorHandler = new ErrorHandler ( false );
2016-08-25 00:50:16 -07:00
errorHandler . _console = logger as any ;
2017-02-17 08:56:36 -08:00
bootstrap ( NonExistentComp , [
2016-08-25 00:50:16 -07:00
{ provide : ErrorHandler , useValue : errorHandler }
2016-08-16 11:15:01 -07:00
]). then ( null , ( reason ) => {
2017-02-17 08:56:36 -08:00
expect ( reason . message )
. toContain ( 'The selector "non-existent" did not match any elements' );
2015-05-26 09:45:15 -07:00
async . done ();
return null ;
});
}));
2016-04-28 17:50:03 -07:00
if ( getDOM (). supportsDOMEvents ()) {
2016-02-25 14:24:17 -08:00
it ( 'should forward the error to promise when bootstrap fails' ,
2016-06-09 11:04:15 -07:00
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
2016-11-12 14:08:58 +01:00
const logger = new MockConsole ();
const errorHandler = new ErrorHandler ( false );
2016-08-25 00:50:16 -07:00
errorHandler . _console = logger as any ;
2016-02-25 14:24:17 -08:00
2016-11-12 14:08:58 +01:00
const refPromise =
2017-02-17 08:56:36 -08:00
bootstrap ( NonExistentComp , [{ provide : ErrorHandler , useValue : errorHandler }]);
2016-08-02 15:53:34 -07:00
refPromise . then ( null , ( reason : any ) => {
2016-02-25 14:24:17 -08:00
expect ( reason . message )
2017-02-17 08:56:36 -08:00
. toContain ( 'The selector "non-existent" did not match any elements' );
2016-02-25 14:24:17 -08:00
async . done ();
});
}));
2015-07-27 15:47:42 -07:00
it ( 'should invoke the default exception handler when bootstrap fails' ,
2016-06-09 11:04:15 -07:00
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
2016-11-12 14:08:58 +01:00
const logger = new MockConsole ();
const errorHandler = new ErrorHandler ( false );
2016-08-25 00:50:16 -07:00
errorHandler . _console = logger as any ;
2015-07-27 15:47:42 -07:00
2016-11-12 14:08:58 +01:00
const refPromise =
2017-02-17 08:56:36 -08:00
bootstrap ( NonExistentComp , [{ provide : ErrorHandler , useValue : errorHandler }]);
2016-08-02 15:53:34 -07:00
refPromise . then ( null , ( reason ) => {
2016-06-08 16:38:52 -07:00
expect ( logger . res . join ( '' ))
2017-02-17 08:56:36 -08:00
. toContain ( 'The selector "non-existent" did not match any elements' );
2015-07-27 15:47:42 -07:00
async . done ();
return null ;
});
}));
}
2015-05-26 09:45:15 -07:00
it ( 'should create an injector promise' , () => {
2016-11-12 14:08:58 +01:00
const refPromise = bootstrap ( HelloRootCmp , testProviders );
2015-05-26 09:45:15 -07:00
expect ( refPromise ). not . toBe ( null );
});
2017-02-22 16:49:46 -08:00
it ( 'should set platform name to browser' ,
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
const refPromise = bootstrap ( HelloRootCmp , testProviders );
refPromise . then (( ref ) => {
expect ( isPlatformBrowser ( ref . injector . get ( PLATFORM_ID ))). toBe ( true );
async . done ();
});
}));
2016-06-09 11:04:15 -07:00
it ( 'should display hello world' , inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
2016-11-12 14:08:58 +01:00
const refPromise = bootstrap ( HelloRootCmp , testProviders );
2015-05-26 09:45:15 -07:00
refPromise . then (( ref ) => {
expect ( el ). toHaveText ( 'hello world!' );
2016-12-06 16:21:07 -08:00
expect ( el . getAttribute ( 'ng-version' )). toEqual ( VERSION . full );
2015-05-26 09:45:15 -07:00
async . done ();
});
}));
2016-08-18 13:34:28 -07:00
it ( 'should throw a descriptive error if BrowserModule is installed again via a lazily loaded module' ,
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
@NgModule ({ imports : [ BrowserModule ]})
class AsyncModule {
}
bootstrap ( HelloRootCmp , testProviders )
. then (( ref : ComponentRef < HelloRootCmp >) => {
2016-11-12 14:08:58 +01:00
const compiler : Compiler = ref . injector . get ( Compiler );
2016-08-18 13:34:28 -07:00
return compiler . compileModuleAsync ( AsyncModule ). then ( factory => {
expect (() => factory . create ( ref . injector ))
. toThrowError (
`BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead.` );
});
})
. then (() => async . done (), err => async . fail ( err ));
}));
2016-06-08 16:38:52 -07:00
it ( 'should support multiple calls to bootstrap' ,
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
2016-11-12 14:08:58 +01:00
const refPromise1 = bootstrap ( HelloRootCmp , testProviders );
const refPromise2 = bootstrap ( HelloRootCmp2 , testProviders );
2016-08-02 15:53:34 -07:00
Promise . all ([ refPromise1 , refPromise2 ]). then (( refs ) => {
2016-06-08 16:38:52 -07:00
expect ( el ). toHaveText ( 'hello world!' );
expect ( el2 ). toHaveText ( 'hello world, again!' );
async . done ();
});
2015-05-26 09:45:15 -07:00
}));
2015-11-13 11:21:16 -08:00
2015-11-10 15:42:22 -08:00
it ( 'should not crash if change detection is invoked when the root component is disposed' ,
2016-06-09 11:04:15 -07:00
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
2016-06-08 16:38:52 -07:00
bootstrap ( HelloOnDestroyTickCmp , testProviders ). then (( ref ) => {
expect (() => ref . destroy ()). not . toThrow ();
async . done ();
});
2015-11-10 15:42:22 -08:00
}));
2015-11-10 10:40:33 -08:00
it ( 'should unregister change detectors when components are disposed' ,
2016-06-09 11:04:15 -07:00
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
2016-07-18 03:50:31 -07:00
bootstrap ( HelloRootCmp , testProviders ). then (( ref ) => {
const appRef = ref . injector . get ( ApplicationRef );
2016-06-08 16:38:52 -07:00
ref . destroy ();
2016-07-18 03:50:31 -07:00
expect (() => appRef . tick ()). not . toThrow ();
2016-06-08 16:38:52 -07:00
async . done ();
});
2015-11-10 10:40:33 -08:00
}));
2015-05-26 09:45:15 -07:00
2016-06-08 16:38:52 -07:00
it ( 'should make the provided bindings available to the application component' ,
2016-06-09 11:04:15 -07:00
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
2016-11-12 14:08:58 +01:00
const refPromise = bootstrap (
2016-06-08 16:38:52 -07:00
HelloRootCmp3 , [ testProviders , { provide : 'appBinding' , useValue : 'BoundValue' }]);
2015-05-26 09:45:15 -07:00
refPromise . then (( ref ) => {
2016-08-16 11:15:01 -07:00
expect ( ref . injector . get ( 'appBinding' )). toEqual ( 'BoundValue' );
2015-05-26 09:45:15 -07:00
async . done ();
});
}));
2017-01-05 20:24:37 +03:00
it ( 'should not override locale provided during bootstrap' ,
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
const refPromise =
bootstrap ( HelloRootCmp , [ testProviders ], [{ provide : LOCALE_ID , useValue : 'fr-FR' }]);
refPromise . then ( ref => {
expect ( ref . injector . get ( LOCALE_ID )). toEqual ( 'fr-FR' );
async . done ();
});
}));
2016-06-08 16:38:52 -07:00
it ( 'should avoid cyclic dependencies when root component requires Lifecycle through DI' ,
2016-06-09 11:04:15 -07:00
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
2016-11-12 14:08:58 +01:00
const refPromise = bootstrap ( HelloRootCmp4 , testProviders );
2015-05-26 09:45:15 -07:00
refPromise . then (( ref ) => {
2016-08-16 11:15:01 -07:00
const appRef = ref . injector . get ( ApplicationRef );
expect ( appRef ). toBeDefined ();
2015-05-26 09:45:15 -07:00
async . done ();
});
}));
2016-07-18 03:50:31 -07:00
it ( 'should run platform initializers' ,
inject ([ Log , AsyncTestCompleter ], ( log : Log , async : AsyncTestCompleter ) => {
2016-11-12 14:08:58 +01:00
const p = createPlatformFactory ( platformBrowserDynamic , 'someName' , [
2016-06-08 16:38:52 -07:00
{ provide : PLATFORM_INITIALIZER , useValue : log.fn ( 'platform_init1' ), multi : true },
{ provide : PLATFORM_INITIALIZER , useValue : log.fn ( 'platform_init2' ), multi : true }
2016-07-18 03:50:31 -07:00
])();
@NgModule ({
imports : [ BrowserModule ],
providers : [
{ provide : APP_INITIALIZER , useValue : log.fn ( 'app_init1' ), multi : true },
{ provide : APP_INITIALIZER , useValue : log.fn ( 'app_init2' ), multi : true }
]
})
class SomeModule {
2016-08-02 06:54:08 -07:00
ngDoBootstrap() { }
2016-07-18 03:50:31 -07:00
}
2016-06-08 16:38:52 -07:00
expect ( log . result ()). toEqual ( 'platform_init1; platform_init2' );
2015-11-18 09:18:37 -08:00
log . clear ();
2016-07-26 05:21:19 -07:00
p . bootstrapModule ( SomeModule ). then (() => {
2016-07-18 03:50:31 -07:00
expect ( log . result ()). toEqual ( 'app_init1; app_init2' );
async . done ();
});
2015-11-18 09:18:37 -08:00
}));
2017-02-22 16:06:21 -08:00
it ( 'should remove styles when transitioning from a server render' ,
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
@Component ({
selector : 'root' ,
template : 'root' ,
})
class RootCmp {
}
@NgModule ({
bootstrap : [ RootCmp ],
declarations : [ RootCmp ],
imports : [ BrowserModule . withServerTransition ({ appId : 'my-app' })],
})
class TestModule {
}
// First, set up styles to be removed.
const dom = getDOM ();
const platform = platformBrowserDynamic ();
const document = platform . injector . get ( DOCUMENT );
const style = dom . createElement ( 'style' , document );
dom . setAttribute ( style , 'ng-transition' , 'my-app' );
dom . appendChild ( document . head , style );
const root = dom . createElement ( 'root' , document );
dom . appendChild ( document . body , root );
platform . bootstrapModule ( TestModule ). then (() => {
const styles : HTMLElement [] =
Array . prototype . slice . apply ( dom . getElementsByTagName ( document , 'style' ) || []);
styles . forEach (
style => { expect ( dom . getAttribute ( style , 'ng-transition' )). not . toBe ( 'my-app' ); });
async . done ();
});
}));
2015-05-26 09:45:15 -07:00
it ( 'should register each application with the testability registry' ,
2016-06-09 11:04:15 -07:00
inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
2016-11-12 14:08:58 +01:00
const refPromise1 : Promise < ComponentRef < any >> = bootstrap ( HelloRootCmp , testProviders );
const refPromise2 : Promise < ComponentRef < any >> = bootstrap ( HelloRootCmp2 , testProviders );
2015-05-26 09:45:15 -07:00
2016-08-02 15:53:34 -07:00
Promise . all ([ refPromise1 , refPromise2 ]). then (( refs : ComponentRef < any >[]) => {
2016-11-12 14:08:58 +01:00
const registry = refs [ 0 ]. injector . get ( TestabilityRegistry );
const testabilities =
2016-06-08 16:38:52 -07:00
[ refs [ 0 ]. injector . get ( Testability ), refs [ 1 ]. injector . get ( Testability )];
2016-08-02 15:53:34 -07:00
Promise . all ( testabilities ). then (( testabilities : Testability []) => {
2016-06-08 16:38:52 -07:00
expect ( registry . findTestabilityInTree ( el )). toEqual ( testabilities [ 0 ]);
expect ( registry . findTestabilityInTree ( el2 )). toEqual ( testabilities [ 1 ]);
async . done ();
});
});
2015-05-26 09:45:15 -07:00
}));
2016-06-30 13:07:17 -07:00
2016-07-25 03:02:57 -07:00
it ( 'should allow to pass schemas' , inject ([ AsyncTestCompleter ], ( async : AsyncTestCompleter ) => {
2016-08-08 09:36:09 -07:00
bootstrap ( HelloCmpUsingCustomElement , testProviders ). then (( compRef ) => {
2016-07-25 03:02:57 -07:00
expect ( el ). toHaveText ( 'hello world!' );
async . done ();
});
}));
2015-05-26 09:45:15 -07:00
});
}