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-03-14 16:26:17 -07:00
import { APP_BOOTSTRAP_LISTENER , APP_INITIALIZER , Compiler , CompilerFactory , Component , NgModule , NgZone , PlatformRef , TemplateRef , Type , ViewChild , ViewContainerRef } from '@angular/core' ;
2016-07-29 06:47:40 -07:00
import { ApplicationRef , ApplicationRef_ } from '@angular/core/src/application_ref' ;
2016-08-25 00:50:16 -07:00
import { ErrorHandler } from '@angular/core/src/error_handler' ;
2016-07-29 06:47:40 -07:00
import { ComponentRef } from '@angular/core/src/linker/component_factory' ;
2017-02-17 08:56:36 -08:00
import { TestComponentRenderer } from '@angular/core/testing' ;
2016-07-29 06:47:40 -07:00
import { BrowserModule } from '@angular/platform-browser' ;
import { getDOM } from '@angular/platform-browser/src/dom/dom_adapter' ;
import { DOCUMENT } from '@angular/platform-browser/src/dom/dom_tokens' ;
2017-03-02 12:12:46 -08:00
import { dispatchEvent } from '@angular/platform-browser/testing/src/browser_util' ;
import { expect } from '@angular/platform-browser/testing/src/matchers' ;
2016-11-02 16:59:24 -07:00
import { ServerModule } from '@angular/platform-server' ;
2016-07-29 06:47:40 -07:00
2017-02-03 05:42:22 -08:00
import { ComponentFixture , ComponentFixtureNoNgZone , TestBed , async , inject , withModule } from '../testing' ;
2016-07-29 06:47:40 -07:00
2017-02-17 08:56:36 -08:00
@Component ( { selector : 'bootstrap-app' , template : 'hello' } )
2016-07-29 06:47:40 -07:00
class SomeComponent {
}
2015-10-28 10:34:13 -07:00
export function main() {
2016-06-08 16:38:52 -07:00
describe ( 'bootstrap' , ( ) = > {
2016-11-12 14:08:58 +01:00
let mockConsole : MockConsole ;
2016-04-14 14:52:35 -07:00
2017-02-17 08:56:36 -08:00
beforeEach ( ( ) = > { mockConsole = new MockConsole ( ) ; } ) ;
2017-03-31 16:37:20 +02:00
function createRootEl ( selector = 'bootstrap-app' ) {
2017-02-17 08:56:36 -08:00
const doc = TestBed . get ( DOCUMENT ) ;
const rootEl = < HTMLElement > getDOM ( ) . firstChild (
2017-03-31 16:37:20 +02:00
getDOM ( ) . content ( getDOM ( ) . createTemplate ( ` < ${ selector } ></ ${ selector } > ` ) ) ) ;
const oldRoots = getDOM ( ) . querySelectorAll ( doc , selector ) ;
2017-02-17 08:56:36 -08:00
for ( let i = 0 ; i < oldRoots . length ; i ++ ) {
getDOM ( ) . remove ( oldRoots [ i ] ) ;
}
getDOM ( ) . appendChild ( doc . body , rootEl ) ;
}
2015-12-02 20:25:24 -08:00
2016-08-02 06:54:08 -07:00
type CreateModuleOptions = { providers? : any [ ] , ngDoBootstrap? : any , bootstrap? : any [ ] } ;
2016-08-10 18:21:28 -07:00
function createModule ( providers? : any [ ] ) : Type < any > ;
function createModule ( options : CreateModuleOptions ) : Type < any > ;
2017-03-29 09:34:45 -07:00
function createModule ( providersOrOptions : any [ ] | CreateModuleOptions | undefined ) : Type < any > {
2016-08-02 06:54:08 -07:00
let options : CreateModuleOptions = { } ;
if ( providersOrOptions instanceof Array ) {
options = { providers : providersOrOptions } ;
} else {
options = providersOrOptions || { } ;
}
2017-03-16 12:58:41 -07:00
const errorHandler = new ErrorHandler ( ) ;
2016-08-25 00:50:16 -07:00
errorHandler . _console = mockConsole as any ;
2016-08-02 06:54:08 -07:00
2016-11-02 16:59:24 -07:00
const platformModule = getDOM ( ) . supportsDOMEvents ( ) ? BrowserModule : ServerModule ;
2016-07-29 06:47:40 -07:00
@NgModule ( {
2017-02-17 08:56:36 -08:00
providers : [ { provide : ErrorHandler , useValue : errorHandler } , options . providers || [ ] ] ,
2016-11-02 16:59:24 -07:00
imports : [ platformModule ] ,
2016-07-29 06:47:40 -07:00
declarations : [ SomeComponent ] ,
2016-08-02 06:54:08 -07:00
entryComponents : [ SomeComponent ] ,
bootstrap : options.bootstrap || [ ]
2016-07-29 06:47:40 -07:00
} )
2016-07-18 03:50:31 -07:00
class MyModule {
}
2016-08-02 06:54:08 -07:00
if ( options . ngDoBootstrap !== false ) {
( < any > MyModule . prototype ) . ngDoBootstrap = options . ngDoBootstrap || ( ( ) = > { } ) ;
}
2016-07-18 03:50:31 -07:00
return MyModule ;
}
2017-03-14 16:26:17 -07:00
it ( 'should bootstrap a component from a child module' ,
async ( inject ( [ ApplicationRef , Compiler ] , ( app : ApplicationRef , compiler : Compiler ) = > {
@Component ( {
selector : 'bootstrap-app' ,
template : '' ,
} )
class SomeComponent {
}
@NgModule ( {
providers : [ { provide : 'hello' , useValue : 'component' } ] ,
declarations : [ SomeComponent ] ,
entryComponents : [ SomeComponent ] ,
} )
class SomeModule {
}
createRootEl ( ) ;
const modFactory = compiler . compileModuleSync ( SomeModule ) ;
const module = modFactory . create ( TestBed ) ;
2017-03-29 09:34:45 -07:00
const cmpFactory =
module . componentFactoryResolver . resolveComponentFactory ( SomeComponent ) ! ;
2017-03-14 16:26:17 -07:00
const component = app . bootstrap ( cmpFactory ) ;
// The component should see the child module providers
expect ( component . injector . get ( 'hello' ) ) . toEqual ( 'component' ) ;
} ) ) ) ;
2017-03-31 16:37:20 +02:00
it ( 'should bootstrap a component with a custom selector' ,
async ( inject ( [ ApplicationRef , Compiler ] , ( app : ApplicationRef , compiler : Compiler ) = > {
@Component ( {
selector : 'bootstrap-app' ,
template : '' ,
} )
class SomeComponent {
}
@NgModule ( {
providers : [ { provide : 'hello' , useValue : 'component' } ] ,
declarations : [ SomeComponent ] ,
entryComponents : [ SomeComponent ] ,
} )
class SomeModule {
}
createRootEl ( 'custom-selector' ) ;
const modFactory = compiler . compileModuleSync ( SomeModule ) ;
const module = modFactory . create ( TestBed ) ;
const cmpFactory =
module . componentFactoryResolver . resolveComponentFactory ( SomeComponent ) ! ;
const component = app . bootstrap ( cmpFactory , 'custom-selector' ) ;
// The component should see the child module providers
expect ( component . injector . get ( 'hello' ) ) . toEqual ( 'component' ) ;
} ) ) ) ;
2016-06-08 16:38:52 -07:00
describe ( 'ApplicationRef' , ( ) = > {
2016-07-29 06:47:40 -07:00
beforeEach ( ( ) = > { TestBed . configureTestingModule ( { imports : [ createModule ( ) ] } ) ; } ) ;
2016-08-02 05:22:44 -07:00
2017-04-28 11:50:45 -07:00
it ( 'should throw when reentering tick' , ( ) = > {
@Component ( { template : '{{reenter()}}' } )
class ReenteringComponent {
reenterCount = 1 ;
reenterErr : any ;
constructor ( private appRef : ApplicationRef ) { }
reenter() {
if ( this . reenterCount -- ) {
try {
this . appRef . tick ( ) ;
} catch ( e ) {
this . reenterErr = e ;
}
}
}
}
const fixture = TestBed . configureTestingModule ( { declarations : [ ReenteringComponent ] } )
. createComponent ( ReenteringComponent ) ;
const appRef = TestBed . get ( ApplicationRef ) as ApplicationRef ;
appRef . attachView ( fixture . componentRef . hostView ) ;
appRef . tick ( ) ;
expect ( fixture . componentInstance . reenterErr . message )
. toBe ( 'ApplicationRef.tick is called recursively' ) ;
} ) ;
2015-12-20 18:10:36 -05:00
2016-08-02 05:22:44 -07:00
describe ( 'APP_BOOTSTRAP_LISTENER' , ( ) = > {
let capturedCompRefs : ComponentRef < any > [ ] ;
beforeEach ( ( ) = > {
capturedCompRefs = [ ] ;
TestBed . configureTestingModule ( {
providers : [ {
provide : APP_BOOTSTRAP_LISTENER ,
multi : true ,
useValue : ( compRef : any ) = > { capturedCompRefs . push ( compRef ) ; }
} ]
} ) ;
2016-07-29 06:47:40 -07:00
} ) ;
2016-08-02 05:22:44 -07:00
it ( 'should be called when a component is bootstrapped' ,
inject ( [ ApplicationRef ] , ( ref : ApplicationRef_ ) = > {
2017-02-17 08:56:36 -08:00
createRootEl ( ) ;
2016-08-02 05:22:44 -07:00
const compRef = ref . bootstrap ( SomeComponent ) ;
expect ( capturedCompRefs ) . toEqual ( [ compRef ] ) ;
} ) ) ;
2016-08-02 07:38:14 -07:00
} ) ;
2016-08-02 05:22:44 -07:00
2016-08-02 07:38:14 -07:00
describe ( 'bootstrap' , ( ) = > {
it ( 'should throw if an APP_INITIIALIZER is not yet resolved' ,
withModule (
{
2016-08-02 15:53:34 -07:00
providers : [
{ provide : APP_INITIALIZER , useValue : ( ) = > new Promise ( ( ) = > { } ) , multi : true }
]
2016-08-02 07:38:14 -07:00
} ,
inject ( [ ApplicationRef ] , ( ref : ApplicationRef_ ) = > {
2017-02-17 08:56:36 -08:00
createRootEl ( ) ;
2016-08-02 07:38:14 -07:00
expect ( ( ) = > ref . bootstrap ( SomeComponent ) )
. toThrowError (
'Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.' ) ;
} ) ) ) ;
2016-04-14 14:52:35 -07:00
} ) ;
} ) ;
2015-12-20 18:10:36 -05:00
2016-07-18 03:50:31 -07:00
describe ( 'bootstrapModule' , ( ) = > {
2016-11-12 14:08:58 +01:00
let defaultPlatform : PlatformRef ;
2017-02-17 08:56:36 -08:00
beforeEach ( inject ( [ PlatformRef ] , ( _platform : PlatformRef ) = > {
createRootEl ( ) ;
defaultPlatform = _platform ;
} ) ) ;
2016-07-29 06:47:40 -07:00
it ( 'should wait for asynchronous app initializers' , async ( ( ) = > {
2016-08-02 15:53:34 -07:00
let resolve : ( result : any ) = > void ;
2016-11-12 14:08:58 +01:00
const promise : Promise < any > = new Promise ( ( res ) = > { resolve = res ; } ) ;
let initializerDone = false ;
2016-07-29 06:47:40 -07:00
setTimeout ( ( ) = > {
2016-08-02 15:53:34 -07:00
resolve ( true ) ;
2016-04-14 14:52:35 -07:00
initializerDone = true ;
} , 1 ) ;
2016-07-18 03:50:31 -07:00
2016-07-26 05:21:19 -07:00
defaultPlatform
2016-08-02 15:53:34 -07:00
. bootstrapModule (
createModule ( [ { provide : APP_INITIALIZER , useValue : ( ) = > promise , multi : true } ] ) )
2016-07-29 06:47:40 -07:00
. then ( _ = > { expect ( initializerDone ) . toBe ( true ) ; } ) ;
} ) ) ;
it ( 'should rethrow sync errors even if the exceptionHandler is not rethrowing' , async ( ( ) = > {
defaultPlatform
. bootstrapModule ( createModule (
[ { provide : APP_INITIALIZER , useValue : ( ) = > { throw 'Test' ; } , multi : true } ] ) )
. then ( ( ) = > expect ( false ) . toBe ( true ) , ( e ) = > {
expect ( e ) . toBe ( 'Test' ) ;
2017-05-16 15:14:55 -07:00
// Error rethrown will be seen by the exception handler since it's after
// construction.
expect ( mockConsole . res [ 0 ] . join ( '#' ) ) . toEqual ( 'ERROR#Test' ) ;
2016-07-29 06:47:40 -07:00
} ) ;
} ) ) ;
it ( 'should rethrow promise errors even if the exceptionHandler is not rethrowing' ,
async ( ( ) = > {
defaultPlatform
. bootstrapModule ( createModule ( [
{ provide : APP_INITIALIZER , useValue : ( ) = > Promise . reject ( 'Test' ) , multi : true }
] ) )
. then ( ( ) = > expect ( false ) . toBe ( true ) , ( e ) = > {
expect ( e ) . toBe ( 'Test' ) ;
2017-03-14 09:16:15 -07:00
expect ( mockConsole . res [ 0 ] . join ( '#' ) ) . toEqual ( 'ERROR#Test' ) ;
2016-07-18 03:50:31 -07:00
} ) ;
2015-12-02 20:25:24 -08:00
} ) ) ;
2016-07-29 14:45:05 -07:00
it ( 'should throw useful error when ApplicationRef is not configured' , async ( ( ) = > {
@NgModule ( )
class EmptyModule {
}
return defaultPlatform . bootstrapModule ( EmptyModule )
. then ( ( ) = > fail ( 'expecting error' ) , ( error ) = > {
expect ( error . message )
2016-08-30 18:07:40 -07:00
. toEqual ( 'No ErrorHandler. Is platform module (BrowserModule) included?' ) ;
2016-07-29 14:45:05 -07:00
} ) ;
} ) ) ;
2016-08-02 06:54:08 -07:00
it ( 'should call the `ngDoBootstrap` method with `ApplicationRef` on the main module' ,
async ( ( ) = > {
const ngDoBootstrap = jasmine . createSpy ( 'ngDoBootstrap' ) ;
defaultPlatform . bootstrapModule ( createModule ( { ngDoBootstrap : ngDoBootstrap } ) )
. then ( ( moduleRef ) = > {
const appRef = moduleRef . injector . get ( ApplicationRef ) ;
expect ( ngDoBootstrap ) . toHaveBeenCalledWith ( appRef ) ;
} ) ;
} ) ) ;
it ( 'should auto bootstrap components listed in @NgModule.bootstrap' , async ( ( ) = > {
defaultPlatform . bootstrapModule ( createModule ( { bootstrap : [ SomeComponent ] } ) )
. then ( ( moduleRef ) = > {
const appRef : ApplicationRef = moduleRef . injector . get ( ApplicationRef ) ;
expect ( appRef . componentTypes ) . toEqual ( [ SomeComponent ] ) ;
} ) ;
} ) ) ;
it ( 'should error if neither `ngDoBootstrap` nor @NgModule.bootstrap was specified' ,
async ( ( ) = > {
defaultPlatform . bootstrapModule ( createModule ( { ngDoBootstrap : false } ) )
. then ( ( ) = > expect ( false ) . toBe ( true ) , ( e ) = > {
const expectedErrMsg =
` The module MyModule was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. Please define one of these. ` ;
expect ( e . message ) . toEqual ( expectedErrMsg ) ;
2017-03-14 09:16:15 -07:00
expect ( mockConsole . res [ 0 ] . join ( '#' ) ) . toEqual ( 'ERROR#Error: ' + expectedErrMsg ) ;
2016-08-02 06:54:08 -07:00
} ) ;
} ) ) ;
2017-01-01 20:46:53 +03:00
it ( 'should add bootstrapped module into platform modules list' , async ( ( ) = > {
defaultPlatform . bootstrapModule ( createModule ( { bootstrap : [ SomeComponent ] } ) )
. then ( module = > expect ( ( < any > defaultPlatform ) . _modules ) . toContain ( module ) ) ;
} ) ) ;
2015-12-02 20:25:24 -08:00
} ) ;
2015-12-20 18:10:36 -05:00
2016-07-29 06:47:40 -07:00
describe ( 'bootstrapModuleFactory' , ( ) = > {
2016-11-12 14:08:58 +01:00
let defaultPlatform : PlatformRef ;
2017-02-17 08:56:36 -08:00
beforeEach ( inject ( [ PlatformRef ] , ( _platform : PlatformRef ) = > {
createRootEl ( ) ;
defaultPlatform = _platform ;
} ) ) ;
2016-07-29 06:47:40 -07:00
it ( 'should wait for asynchronous app initializers' , async ( ( ) = > {
2016-08-02 15:53:34 -07:00
let resolve : ( result : any ) = > void ;
2016-11-12 14:08:58 +01:00
const promise : Promise < any > = new Promise ( ( res ) = > { resolve = res ; } ) ;
let initializerDone = false ;
2016-07-29 06:47:40 -07:00
setTimeout ( ( ) = > {
2016-08-02 15:53:34 -07:00
resolve ( true ) ;
2016-07-29 06:47:40 -07:00
initializerDone = true ;
} , 1 ) ;
const compilerFactory : CompilerFactory =
defaultPlatform . injector . get ( CompilerFactory , null ) ;
2016-08-02 15:53:34 -07:00
const moduleFactory = compilerFactory . createCompiler ( ) . compileModuleSync (
createModule ( [ { provide : APP_INITIALIZER , useValue : ( ) = > promise , multi : true } ] ) ) ;
2016-07-29 06:47:40 -07:00
defaultPlatform . bootstrapModuleFactory ( moduleFactory ) . then ( _ = > {
expect ( initializerDone ) . toBe ( true ) ;
} ) ;
} ) ) ;
it ( 'should rethrow sync errors even if the exceptionHandler is not rethrowing' , async ( ( ) = > {
const compilerFactory : CompilerFactory =
defaultPlatform . injector . get ( CompilerFactory , null ) ;
const moduleFactory = compilerFactory . createCompiler ( ) . compileModuleSync ( createModule (
[ { provide : APP_INITIALIZER , useValue : ( ) = > { throw 'Test' ; } , multi : true } ] ) ) ;
expect ( ( ) = > defaultPlatform . bootstrapModuleFactory ( moduleFactory ) ) . toThrow ( 'Test' ) ;
2017-05-16 15:14:55 -07:00
// Error rethrown will be seen by the exception handler since it's after
// construction.
expect ( mockConsole . res [ 0 ] . join ( '#' ) ) . toEqual ( 'ERROR#Test' ) ;
2016-07-29 06:47:40 -07:00
} ) ) ;
it ( 'should rethrow promise errors even if the exceptionHandler is not rethrowing' ,
async ( ( ) = > {
const compilerFactory : CompilerFactory =
defaultPlatform . injector . get ( CompilerFactory , null ) ;
const moduleFactory = compilerFactory . createCompiler ( ) . compileModuleSync ( createModule (
[ { provide : APP_INITIALIZER , useValue : ( ) = > Promise . reject ( 'Test' ) , multi : true } ] ) ) ;
defaultPlatform . bootstrapModuleFactory ( moduleFactory )
. then ( ( ) = > expect ( false ) . toBe ( true ) , ( e ) = > {
expect ( e ) . toBe ( 'Test' ) ;
2017-03-14 09:16:15 -07:00
expect ( mockConsole . res [ 0 ] . join ( '#' ) ) . toEqual ( 'ERROR#Test' ) ;
2016-07-29 06:47:40 -07:00
} ) ;
2015-12-20 18:10:36 -05:00
} ) ) ;
} ) ;
2016-11-04 11:58:06 -07:00
describe ( 'attachView / detachView' , ( ) = > {
@Component ( { template : '{{name}}' } )
class MyComp {
name = 'Initial' ;
}
@Component ( { template : '<ng-container #vc></ng-container>' } )
class ContainerComp {
@ViewChild ( 'vc' , { read : ViewContainerRef } )
vc : ViewContainerRef ;
}
2017-01-09 13:16:46 -08:00
@Component ( { template : '<ng-template #t>Dynamic content</ng-template>' } )
2016-12-14 17:33:29 +01:00
class EmbeddedViewComp {
@ViewChild ( TemplateRef )
tplRef : TemplateRef < Object > ;
}
2016-11-04 11:58:06 -07:00
beforeEach ( ( ) = > {
TestBed . configureTestingModule ( {
2016-12-14 17:33:29 +01:00
declarations : [ MyComp , ContainerComp , EmbeddedViewComp ] ,
2016-11-04 11:58:06 -07:00
providers : [ { provide : ComponentFixtureNoNgZone , useValue : true } ]
} ) ;
} ) ;
it ( 'should dirty check attached views' , ( ) = > {
const comp = TestBed . createComponent ( MyComp ) ;
const appRef : ApplicationRef = TestBed . get ( ApplicationRef ) ;
expect ( appRef . viewCount ) . toBe ( 0 ) ;
appRef . tick ( ) ;
expect ( comp . nativeElement ) . toHaveText ( '' ) ;
appRef . attachView ( comp . componentRef . hostView ) ;
appRef . tick ( ) ;
expect ( appRef . viewCount ) . toBe ( 1 ) ;
expect ( comp . nativeElement ) . toHaveText ( 'Initial' ) ;
} ) ;
it ( 'should not dirty check detached views' , ( ) = > {
const comp = TestBed . createComponent ( MyComp ) ;
const appRef : ApplicationRef = TestBed . get ( ApplicationRef ) ;
appRef . attachView ( comp . componentRef . hostView ) ;
appRef . tick ( ) ;
expect ( comp . nativeElement ) . toHaveText ( 'Initial' ) ;
appRef . detachView ( comp . componentRef . hostView ) ;
comp . componentInstance . name = 'New' ;
appRef . tick ( ) ;
expect ( appRef . viewCount ) . toBe ( 0 ) ;
expect ( comp . nativeElement ) . toHaveText ( 'Initial' ) ;
} ) ;
it ( 'should detach attached views if they are destroyed' , ( ) = > {
const comp = TestBed . createComponent ( MyComp ) ;
const appRef : ApplicationRef = TestBed . get ( ApplicationRef ) ;
appRef . attachView ( comp . componentRef . hostView ) ;
comp . destroy ( ) ;
expect ( appRef . viewCount ) . toBe ( 0 ) ;
} ) ;
2016-12-14 17:33:29 +01:00
it ( 'should detach attached embedded views if they are destroyed' , ( ) = > {
const comp = TestBed . createComponent ( EmbeddedViewComp ) ;
const appRef : ApplicationRef = TestBed . get ( ApplicationRef ) ;
const embeddedViewRef = comp . componentInstance . tplRef . createEmbeddedView ( { } ) ;
appRef . attachView ( embeddedViewRef ) ;
embeddedViewRef . destroy ( ) ;
expect ( appRef . viewCount ) . toBe ( 0 ) ;
} ) ;
2016-11-04 11:58:06 -07:00
it ( 'should not allow to attach a view to both, a view container and the ApplicationRef' ,
( ) = > {
const comp = TestBed . createComponent ( MyComp ) ;
2017-02-17 08:56:36 -08:00
let hostView = comp . componentRef . hostView ;
2016-11-04 11:58:06 -07:00
const containerComp = TestBed . createComponent ( ContainerComp ) ;
containerComp . detectChanges ( ) ;
const vc = containerComp . componentInstance . vc ;
const appRef : ApplicationRef = TestBed . get ( ApplicationRef ) ;
vc . insert ( hostView ) ;
expect ( ( ) = > appRef . attachView ( hostView ) )
. toThrowError ( 'This view is already attached to a ViewContainer!' ) ;
2017-03-29 09:34:45 -07:00
hostView = vc . detach ( 0 ) ! ;
2016-11-04 11:58:06 -07:00
appRef . attachView ( hostView ) ;
expect ( ( ) = > vc . insert ( hostView ) )
. toThrowError ( 'This view is already attached directly to the ApplicationRef!' ) ;
} ) ;
} ) ;
2015-12-02 20:25:24 -08:00
} ) ;
2017-02-03 05:42:22 -08:00
describe ( 'AppRef' , ( ) = > {
@Component ( { selector : 'sync-comp' , template : ` <span>{{text}}</span> ` } )
class SyncComp {
text : string = '1' ;
}
@Component ( { selector : 'click-comp' , template : ` <span (click)="onClick()">{{text}}</span> ` } )
class ClickComp {
text : string = '1' ;
onClick() { this . text += '1' ; }
}
@Component ( { selector : 'micro-task-comp' , template : ` <span>{{text}}</span> ` } )
class MicroTaskComp {
text : string = '1' ;
ngOnInit() {
Promise . resolve ( null ) . then ( ( _ ) = > { this . text += '1' ; } ) ;
}
}
@Component ( { selector : 'macro-task-comp' , template : ` <span>{{text}}</span> ` } )
class MacroTaskComp {
text : string = '1' ;
ngOnInit() {
setTimeout ( ( ) = > { this . text += '1' ; } , 10 ) ;
}
}
@Component ( { selector : 'micro-macro-task-comp' , template : ` <span>{{text}}</span> ` } )
class MicroMacroTaskComp {
text : string = '1' ;
ngOnInit() {
Promise . resolve ( null ) . then ( ( _ ) = > {
this . text += '1' ;
setTimeout ( ( ) = > { this . text += '1' ; } , 10 ) ;
} ) ;
}
}
@Component ( { selector : 'macro-micro-task-comp' , template : ` <span>{{text}}</span> ` } )
class MacroMicroTaskComp {
text : string = '1' ;
ngOnInit() {
setTimeout ( ( ) = > {
this . text += '1' ;
Promise . resolve ( null ) . then ( ( _ : any ) = > { this . text += '1' ; } ) ;
} , 10 ) ;
}
}
let stableCalled = false ;
beforeEach ( ( ) = > {
stableCalled = false ;
TestBed . configureTestingModule ( {
declarations : [
SyncComp , MicroTaskComp , MacroTaskComp , MicroMacroTaskComp , MacroMicroTaskComp , ClickComp
] ,
} ) ;
} ) ;
afterEach ( ( ) = > { expect ( stableCalled ) . toBe ( true , 'isStable did not emit true on stable' ) ; } ) ;
function expectStableTexts ( component : Type < any > , expected : string [ ] ) {
const fixture = TestBed . createComponent ( component ) ;
const appRef : ApplicationRef = TestBed . get ( ApplicationRef ) ;
const zone : NgZone = TestBed . get ( NgZone ) ;
appRef . attachView ( fixture . componentRef . hostView ) ;
zone . run ( ( ) = > appRef . tick ( ) ) ;
let i = 0 ;
appRef . isStable . subscribe ( {
next : ( stable : boolean ) = > {
if ( stable ) {
expect ( i ) . toBeLessThan ( expected . length ) ;
expect ( fixture . nativeElement ) . toHaveText ( expected [ i ++ ] ) ;
stableCalled = true ;
}
}
} ) ;
}
it ( 'isStable should fire on synchronous component loading' ,
async ( ( ) = > { expectStableTexts ( SyncComp , [ '1' ] ) ; } ) ) ;
it ( 'isStable should fire after a microtask on init is completed' ,
async ( ( ) = > { expectStableTexts ( MicroTaskComp , [ '11' ] ) ; } ) ) ;
it ( 'isStable should fire after a macrotask on init is completed' ,
async ( ( ) = > { expectStableTexts ( MacroTaskComp , [ '11' ] ) ; } ) ) ;
it ( 'isStable should fire only after chain of micro and macrotasks on init are completed' ,
async ( ( ) = > { expectStableTexts ( MicroMacroTaskComp , [ '111' ] ) ; } ) ) ;
it ( 'isStable should fire only after chain of macro and microtasks on init are completed' ,
async ( ( ) = > { expectStableTexts ( MacroMicroTaskComp , [ '111' ] ) ; } ) ) ;
describe ( 'unstable' , ( ) = > {
let unstableCalled = false ;
afterEach (
( ) = > { expect ( unstableCalled ) . toBe ( true , 'isStable did not emit false on unstable' ) ; } ) ;
function expectUnstable ( appRef : ApplicationRef ) {
appRef . isStable . subscribe ( {
next : ( stable : boolean ) = > {
if ( stable ) {
stableCalled = true ;
}
if ( ! stable ) {
unstableCalled = true ;
}
}
} ) ;
}
it ( 'should be fired after app becomes unstable' , async ( ( ) = > {
const fixture = TestBed . createComponent ( ClickComp ) ;
const appRef : ApplicationRef = TestBed . get ( ApplicationRef ) ;
const zone : NgZone = TestBed . get ( NgZone ) ;
appRef . attachView ( fixture . componentRef . hostView ) ;
zone . run ( ( ) = > appRef . tick ( ) ) ;
fixture . whenStable ( ) . then ( ( ) = > {
expectUnstable ( appRef ) ;
const element = fixture . debugElement . children [ 0 ] ;
dispatchEvent ( element . nativeElement , 'click' ) ;
} ) ;
} ) ) ;
} ) ;
} ) ;
2016-04-14 14:52:35 -07:00
}
2016-08-25 00:50:16 -07:00
class MockConsole {
2017-03-14 09:16:15 -07:00
res : any [ ] [ ] = [ ] ;
log ( . . . args : any [ ] ) : void { this . res . push ( args ) ; }
error ( . . . args : any [ ] ) : void { this . res . push ( args ) ; }
2015-10-28 10:34:13 -07:00
}