2016-06-23 09:47:54 -07:00
/**
* @license
2020-05-19 12:08:49 -07:00
* Copyright Google LLC All Rights Reserved.
2016-06-23 09:47:54 -07:00
*
* 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
*/
2019-08-22 19:16:25 -07:00
import { DOCUMENT , ɵ getDOM as getDOM } from '@angular/common' ;
2019-03-03 18:19:27 +01:00
import { ResourceLoader } from '@angular/compiler' ;
2019-05-17 16:13:31 +02:00
import { APP_BOOTSTRAP_LISTENER , APP_INITIALIZER , Compiler , CompilerFactory , Component , InjectionToken , LOCALE_ID , NgModule , NgZone , PlatformRef , TemplateRef , Type , ViewChild , ViewContainerRef } from '@angular/core' ;
2017-09-12 20:45:02 +02:00
import { 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' ;
2019-05-17 16:13:31 +02:00
import { getLocaleId } from '@angular/core/src/render3' ;
2016-07-29 06:47:40 -07:00
import { BrowserModule } from '@angular/platform-browser' ;
2019-08-23 13:28:33 -07:00
import { createTemplate , dispatchEvent , getContent } from '@angular/platform-browser/testing/src/browser_util' ;
2017-03-02 12:12:46 -08:00
import { expect } from '@angular/platform-browser/testing/src/matchers' ;
2019-05-17 16:13:31 +02:00
import { onlyInIvy } from '@angular/private/testing' ;
2018-11-05 20:31:54 -08:00
2017-06-01 14:45:49 -07:00
import { NoopNgZone } from '../src/zone/ng_zone' ;
2020-08-01 04:43:18 +09:00
import { ComponentFixtureNoNgZone , inject , TestBed , waitForAsync , 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
2017-12-15 16:28:41 -08:00
{
2018-11-26 21:56:29 +01:00
describe ( 'bootstrap' , ( ) = > {
2016-11-12 14:08:58 +01:00
let mockConsole : MockConsole ;
2016-04-14 14:52:35 -07:00
2020-04-13 16:40:21 -07:00
beforeEach ( ( ) = > {
mockConsole = new MockConsole ( ) ;
} ) ;
2017-02-17 08:56:36 -08:00
2017-03-31 16:37:20 +02:00
function createRootEl ( selector = 'bootstrap-app' ) {
2019-08-28 16:22:36 -07:00
const doc = TestBed . inject ( DOCUMENT ) ;
2019-08-24 08:01:24 -07:00
const rootEl =
< HTMLElement > getContent ( createTemplate ( ` < ${ selector } ></ ${ selector } > ` ) ) . firstChild ;
2019-08-30 12:52:48 -07:00
const oldRoots = doc . querySelectorAll ( selector ) ;
2017-02-17 08:56:36 -08:00
for ( let i = 0 ; i < oldRoots . length ; i ++ ) {
getDOM ( ) . remove ( oldRoots [ i ] ) ;
}
2019-08-30 12:52:48 -07:00
doc . body . appendChild ( rootEl ) ;
2017-02-17 08:56:36 -08:00
}
2015-12-02 20:25:24 -08:00
2019-03-03 18:19:27 +01:00
type CreateModuleOptions =
{ providers? : any [ ] , ngDoBootstrap? : any , bootstrap? : any [ ] , component? : Type < any > } ;
2016-08-02 06:54:08 -07:00
2016-08-10 18:21:28 -07:00
function createModule ( providers? : any [ ] ) : Type < any > ;
function createModule ( options : CreateModuleOptions ) : Type < any > ;
2020-04-13 16:40:21 -07:00
function createModule ( providersOrOptions : any [ ] | CreateModuleOptions | undefined ) : Type < any > {
2016-08-02 06:54:08 -07:00
let options : CreateModuleOptions = { } ;
2019-10-10 12:03:27 +03:00
if ( Array . isArray ( providersOrOptions ) ) {
2016-08-02 06:54:08 -07:00
options = { providers : providersOrOptions } ;
} else {
options = providersOrOptions || { } ;
}
2017-03-16 12:58:41 -07:00
const errorHandler = new ErrorHandler ( ) ;
2017-12-06 06:56:49 -08:00
( errorHandler as any ) . _console = mockConsole as any ;
2016-08-02 06:54:08 -07:00
2021-04-03 09:52:53 -04:00
const platformModule = getDOM ( ) . supportsDOMEvents ?
2017-12-15 16:28:41 -08:00
BrowserModule :
require ( '@angular/platform-server' ) . ServerModule ;
2016-11-02 16:59:24 -07:00
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 ] ,
2019-03-03 18:19:27 +01:00
declarations : [ options . component || SomeComponent ] ,
entryComponents : [ options . component || SomeComponent ] ,
2016-08-02 06:54:08 -07:00
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 ;
}
2019-01-16 15:42:13 +01:00
it ( 'should bootstrap a component from a child module' ,
2020-08-01 04:43:18 +09:00
waitForAsync (
inject ( [ ApplicationRef , Compiler ] , ( app : ApplicationRef , compiler : Compiler ) = > {
@Component ( {
selector : 'bootstrap-app' ,
template : '' ,
} )
class SomeComponent {
}
const helloToken = new InjectionToken < string > ( 'hello' ) ;
@NgModule ( {
providers : [ { provide : helloToken , useValue : 'component' } ] ,
declarations : [ SomeComponent ] ,
entryComponents : [ SomeComponent ] ,
} )
class SomeModule {
}
createRootEl ( ) ;
const modFactory = compiler . compileModuleSync ( SomeModule ) ;
const module = modFactory . create ( TestBed ) ;
const cmpFactory =
module . componentFactoryResolver . resolveComponentFactory ( SomeComponent ) ! ;
const component = app . bootstrap ( cmpFactory ) ;
// The component should see the child module providers
expect ( component . injector . get ( helloToken ) ) . toEqual ( 'component' ) ;
} ) ) ) ;
2019-01-16 15:42:13 +01:00
it ( 'should bootstrap a component with a custom selector' ,
2020-08-01 04:43:18 +09:00
waitForAsync (
inject ( [ ApplicationRef , Compiler ] , ( app : ApplicationRef , compiler : Compiler ) = > {
@Component ( {
selector : 'bootstrap-app' ,
template : '' ,
} )
class SomeComponent {
}
const helloToken = new InjectionToken < string > ( 'hello' ) ;
@NgModule ( {
providers : [ { provide : helloToken , 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 ( helloToken ) ) . toEqual ( 'component' ) ;
} ) ) ) ;
2017-03-31 16:37:20 +02:00
2016-06-08 16:38:52 -07:00
describe ( 'ApplicationRef' , ( ) = > {
2020-04-13 16:40:21 -07:00
beforeEach ( ( ) = > {
TestBed . configureTestingModule ( { imports : [ createModule ( ) ] } ) ;
} ) ;
2016-08-02 05:22:44 -07:00
2018-11-30 17:14:53 +01:00
it ( 'should throw when reentering tick' , ( ) = > {
2017-04-28 11:50:45 -07:00
@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 ) ;
2019-08-28 16:22:36 -07:00
const appRef = TestBed . inject ( ApplicationRef ) ;
2017-04-28 11:50:45 -07:00
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 ,
2020-04-13 16:40:21 -07:00
useValue : ( compRef : any ) = > {
capturedCompRefs . push ( compRef ) ;
}
2016-08-02 05:22:44 -07:00
} ]
} ) ;
2016-07-29 06:47:40 -07:00
} ) ;
2016-08-02 05:22:44 -07:00
2018-12-03 17:39:01 +01:00
it ( 'should be called when a component is bootstrapped' ,
inject ( [ ApplicationRef ] , ( ref : ApplicationRef ) = > {
createRootEl ( ) ;
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' , ( ) = > {
2018-11-30 17:14:53 +01:00
it ( 'should throw if an APP_INITIIALIZER is not yet resolved' ,
withModule (
{
providers : [
{ provide : APP_INITIALIZER , useValue : ( ) = > new Promise ( ( ) = > { } ) , multi : true }
]
} ,
inject ( [ ApplicationRef ] , ( ref : ApplicationRef ) = > {
createRootEl ( ) ;
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
2020-08-01 04:43:18 +09:00
it ( 'should wait for asynchronous app initializers' , waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
let resolve : ( result : any ) = > void ;
2020-04-13 16:40:21 -07:00
const promise : Promise < any > = new Promise ( ( res ) = > {
resolve = res ;
} ) ;
2018-11-30 17:14:53 +01:00
let initializerDone = false ;
setTimeout ( ( ) = > {
resolve ( true ) ;
initializerDone = true ;
} , 1 ) ;
defaultPlatform
. bootstrapModule (
createModule ( [ { provide : APP_INITIALIZER , useValue : ( ) = > promise , multi : true } ] ) )
2020-04-13 16:40:21 -07:00
. then ( _ = > {
expect ( initializerDone ) . toBe ( true ) ;
} ) ;
2018-11-30 17:14:53 +01:00
} ) ) ;
2020-08-01 04:43:18 +09:00
it ( 'should rethrow sync errors even if the exceptionHandler is not rethrowing' ,
waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
defaultPlatform
2020-04-13 16:40:21 -07:00
. bootstrapModule ( createModule ( [ {
provide : APP_INITIALIZER ,
useValue : ( ) = > {
throw 'Test' ;
} ,
multi : true
} ] ) )
2018-11-30 17:14:53 +01:00
. then ( ( ) = > expect ( false ) . toBe ( true ) , ( e ) = > {
expect ( e ) . toBe ( 'Test' ) ;
// Error rethrown will be seen by the exception handler since it's after
// construction.
expect ( mockConsole . res [ 0 ] . join ( '#' ) ) . toEqual ( 'ERROR#Test' ) ;
} ) ;
} ) ) ;
it ( 'should rethrow promise errors even if the exceptionHandler is not rethrowing' ,
2020-08-01 04:43:18 +09:00
waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
defaultPlatform
. bootstrapModule ( createModule ( [
{ provide : APP_INITIALIZER , useValue : ( ) = > Promise . reject ( 'Test' ) , multi : true }
] ) )
. then ( ( ) = > expect ( false ) . toBe ( true ) , ( e ) = > {
expect ( e ) . toBe ( 'Test' ) ;
expect ( mockConsole . res [ 0 ] . join ( '#' ) ) . toEqual ( 'ERROR#Test' ) ;
} ) ;
} ) ) ;
2018-11-26 21:56:29 +01:00
2020-08-01 04:43:18 +09:00
it ( 'should throw useful error when ApplicationRef is not configured' , waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
@NgModule ( )
class EmptyModule {
}
2018-11-26 21:56:29 +01:00
2018-11-30 17:14:53 +01:00
return defaultPlatform . bootstrapModule ( EmptyModule )
. then ( ( ) = > fail ( 'expecting error' ) , ( error ) = > {
expect ( error . message )
. toEqual ( 'No ErrorHandler. Is platform module (BrowserModule) included?' ) ;
} ) ;
} ) ) ;
it ( 'should call the `ngDoBootstrap` method with `ApplicationRef` on the main module' ,
2020-08-01 04:43:18 +09:00
waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
const ngDoBootstrap = jasmine . createSpy ( 'ngDoBootstrap' ) ;
defaultPlatform . bootstrapModule ( createModule ( { ngDoBootstrap : ngDoBootstrap } ) )
. then ( ( moduleRef ) = > {
const appRef = moduleRef . injector . get ( ApplicationRef ) ;
expect ( ngDoBootstrap ) . toHaveBeenCalledWith ( appRef ) ;
} ) ;
} ) ) ;
2018-11-26 21:56:29 +01:00
2020-08-01 04:43:18 +09:00
it ( 'should auto bootstrap components listed in @NgModule.bootstrap' , waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
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' ,
2020-08-01 04:43:18 +09:00
waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
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 ) ;
expect ( mockConsole . res [ 0 ] . join ( '#' ) ) . toEqual ( 'ERROR#Error: ' + expectedErrMsg ) ;
} ) ;
} ) ) ;
2020-08-01 04:43:18 +09:00
it ( 'should add bootstrapped module into platform modules list' , waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
defaultPlatform . bootstrapModule ( createModule ( { bootstrap : [ SomeComponent ] } ) )
. then ( module = > expect ( ( < any > defaultPlatform ) . _modules ) . toContain ( module ) ) ;
} ) ) ;
2020-08-01 04:43:18 +09:00
it ( 'should bootstrap with NoopNgZone' , waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
defaultPlatform
. bootstrapModule ( createModule ( { bootstrap : [ SomeComponent ] } ) , { ngZone : 'noop' } )
. then ( ( module ) = > {
const ngZone = module . injector . get ( NgZone ) ;
expect ( ngZone instanceof NoopNgZone ) . toBe ( true ) ;
} ) ;
} ) ) ;
2019-03-03 18:19:27 +01:00
2020-04-13 16:40:21 -07:00
it ( 'should resolve component resources when creating module factory' , async ( ) = > {
2019-03-03 18:19:27 +01:00
@Component ( {
selector : 'with-templates-app' ,
templateUrl : '/test-template.html' ,
} )
class WithTemplateUrlComponent {
}
const loadResourceSpy = jasmine . createSpy ( 'load resource' ) . and . returnValue ( 'fakeContent' ) ;
const testModule = createModule ( { component : WithTemplateUrlComponent } ) ;
await defaultPlatform . bootstrapModule ( testModule , {
providers : [
{ provide : ResourceLoader , useValue : { get : loadResourceSpy } } ,
]
} ) ;
expect ( loadResourceSpy ) . toHaveBeenCalledTimes ( 1 ) ;
expect ( loadResourceSpy ) . toHaveBeenCalledWith ( '/test-template.html' ) ;
} ) ;
2019-05-17 16:13:31 +02:00
onlyInIvy ( 'We only need to define `LOCALE_ID` for runtime i18n' )
2020-04-13 16:40:21 -07:00
. it ( 'should define `LOCALE_ID`' , async ( ) = > {
2019-05-17 16:13:31 +02:00
@Component ( {
selector : 'i18n-app' ,
templateUrl : '' ,
} )
class I18nComponent {
}
const testModule = createModule (
{ component : I18nComponent , providers : [ { provide : LOCALE_ID , useValue : 'ro' } ] } ) ;
await defaultPlatform . bootstrapModule ( testModule ) ;
expect ( getLocaleId ( ) ) . toEqual ( 'ro' ) ;
} ) ;
2019-07-15 15:28:07 +02:00
2020-04-13 16:40:21 -07:00
it ( 'should wait for APP_INITIALIZER to set providers for `LOCALE_ID`' , async ( ) = > {
2019-07-15 15:28:07 +02:00
let locale : string = '' ;
const testModule = createModule ( {
providers : [
2020-01-17 12:52:59 +00:00
{ provide : APP_INITIALIZER , useValue : ( ) = > locale = 'fr-FR' , multi : true } ,
2019-07-15 15:28:07 +02:00
{ provide : LOCALE_ID , useFactory : ( ) = > locale }
]
} ) ;
const app = await defaultPlatform . bootstrapModule ( testModule ) ;
expect ( app . injector . get ( LOCALE_ID ) ) . toEqual ( 'fr-FR' ) ;
} ) ;
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 ;
} ) ) ;
2020-08-01 04:43:18 +09:00
it ( 'should wait for asynchronous app initializers' , waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
let resolve : ( result : any ) = > void ;
2020-04-13 16:40:21 -07:00
const promise : Promise < any > = new Promise ( ( res ) = > {
resolve = res ;
} ) ;
2018-11-30 17:14:53 +01:00
let initializerDone = false ;
setTimeout ( ( ) = > {
resolve ( true ) ;
initializerDone = true ;
} , 1 ) ;
const compilerFactory : CompilerFactory =
2020-04-13 16:40:21 -07:00
defaultPlatform . injector . get ( CompilerFactory , null ) ! ;
2018-11-30 17:14:53 +01:00
const moduleFactory = compilerFactory . createCompiler ( ) . compileModuleSync (
createModule ( [ { provide : APP_INITIALIZER , useValue : ( ) = > promise , multi : true } ] ) ) ;
defaultPlatform . bootstrapModuleFactory ( moduleFactory ) . then ( _ = > {
expect ( initializerDone ) . toBe ( true ) ;
} ) ;
} ) ) ;
2020-08-01 04:43:18 +09:00
it ( 'should rethrow sync errors even if the exceptionHandler is not rethrowing' ,
waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
const compilerFactory : CompilerFactory =
2020-04-13 16:40:21 -07:00
defaultPlatform . injector . get ( CompilerFactory , null ) ! ;
const moduleFactory = compilerFactory . createCompiler ( ) . compileModuleSync ( createModule ( [ {
provide : APP_INITIALIZER ,
useValue : ( ) = > {
throw 'Test' ;
} ,
multi : true
} ] ) ) ;
2018-11-30 17:14:53 +01:00
expect ( ( ) = > defaultPlatform . bootstrapModuleFactory ( moduleFactory ) ) . toThrow ( 'Test' ) ;
// Error rethrown will be seen by the exception handler since it's after
// construction.
expect ( mockConsole . res [ 0 ] . join ( '#' ) ) . toEqual ( 'ERROR#Test' ) ;
} ) ) ;
it ( 'should rethrow promise errors even if the exceptionHandler is not rethrowing' ,
2020-08-01 04:43:18 +09:00
waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
const compilerFactory : CompilerFactory =
2020-04-13 16:40:21 -07:00
defaultPlatform . injector . get ( CompilerFactory , null ) ! ;
2018-11-30 17:14:53 +01:00
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' ) ;
expect ( mockConsole . res [ 0 ] . join ( '#' ) ) . toEqual ( 'ERROR#Test' ) ;
2016-07-29 06:47:40 -07:00
} ) ;
2018-11-30 17:14:53 +01: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 {
2018-06-18 16:38:33 -07:00
// TODO(issue/24571): remove '!'.
2020-04-13 16:40:21 -07:00
@ViewChild ( 'vc' , { read : ViewContainerRef } ) vc ! : ViewContainerRef ;
2016-11-04 11:58:06 -07:00
}
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 {
2018-06-18 16:38:33 -07:00
// TODO(issue/24571): remove '!'.
2020-04-13 16:40:21 -07:00
@ViewChild ( TemplateRef , { static : true } ) tplRef ! : TemplateRef < Object > ;
2016-12-14 17:33:29 +01:00
}
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 ) ;
2019-08-28 16:22:36 -07:00
const appRef : ApplicationRef = TestBed . inject ( ApplicationRef ) ;
2016-11-04 11:58:06 -07:00
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 ) ;
2019-08-28 16:22:36 -07:00
const appRef : ApplicationRef = TestBed . inject ( ApplicationRef ) ;
2016-11-04 11:58:06 -07:00
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 ) ;
2019-08-28 16:22:36 -07:00
const appRef : ApplicationRef = TestBed . inject ( ApplicationRef ) ;
2016-11-04 11:58:06 -07:00
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 ) ;
2019-08-28 16:22:36 -07:00
const appRef : ApplicationRef = TestBed . inject ( ApplicationRef ) ;
2018-12-18 16:58:51 -08:00
2016-12-14 17:33:29 +01:00
const embeddedViewRef = comp . componentInstance . tplRef . createEmbeddedView ( { } ) ;
appRef . attachView ( embeddedViewRef ) ;
embeddedViewRef . destroy ( ) ;
expect ( appRef . viewCount ) . toBe ( 0 ) ;
} ) ;
2018-12-04 11:56:24 +01:00
it ( 'should not allow to attach a view to both, a view container and the ApplicationRef' ,
( ) = > {
const comp = TestBed . createComponent ( MyComp ) ;
let hostView = comp . componentRef . hostView ;
const containerComp = TestBed . createComponent ( ContainerComp ) ;
containerComp . detectChanges ( ) ;
const vc = containerComp . componentInstance . vc ;
2019-08-28 16:22:36 -07:00
const appRef : ApplicationRef = TestBed . inject ( ApplicationRef ) ;
2018-12-04 11:56:24 +01:00
vc . insert ( hostView ) ;
expect ( ( ) = > appRef . attachView ( hostView ) )
. toThrowError ( 'This view is already attached to a ViewContainer!' ) ;
2020-04-13 16:40:21 -07:00
hostView = vc . detach ( 0 ) ! ;
2018-12-04 11:56:24 +01:00
appRef . attachView ( hostView ) ;
expect ( ( ) = > vc . insert ( hostView ) )
. toThrowError ( 'This view is already attached directly to the ApplicationRef!' ) ;
} ) ;
2016-11-04 11:58:06 -07:00
} ) ;
2015-12-02 20:25:24 -08:00
} ) ;
2018-11-26 21:56:29 +01:00
describe ( 'AppRef' , ( ) = > {
2017-02-03 05:42:22 -08:00
@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' ;
2020-04-13 16:40:21 -07:00
onClick() {
this . text += '1' ;
}
2017-02-03 05:42:22 -08:00
}
@Component ( { selector : 'micro-task-comp' , template : ` <span>{{text}}</span> ` } )
class MicroTaskComp {
text : string = '1' ;
ngOnInit() {
2020-04-13 16:40:21 -07:00
Promise . resolve ( null ) . then ( ( _ ) = > {
this . text += '1' ;
} ) ;
2017-02-03 05:42:22 -08:00
}
}
@Component ( { selector : 'macro-task-comp' , template : ` <span>{{text}}</span> ` } )
class MacroTaskComp {
text : string = '1' ;
ngOnInit() {
2020-04-13 16:40:21 -07:00
setTimeout ( ( ) = > {
this . text += '1' ;
} , 10 ) ;
2017-02-03 05:42:22 -08:00
}
}
@Component ( { selector : 'micro-macro-task-comp' , template : ` <span>{{text}}</span> ` } )
class MicroMacroTaskComp {
text : string = '1' ;
ngOnInit() {
Promise . resolve ( null ) . then ( ( _ ) = > {
this . text += '1' ;
2020-04-13 16:40:21 -07:00
setTimeout ( ( ) = > {
this . text += '1' ;
} , 10 ) ;
2017-02-03 05:42:22 -08:00
} ) ;
}
}
@Component ( { selector : 'macro-micro-task-comp' , template : ` <span>{{text}}</span> ` } )
class MacroMicroTaskComp {
text : string = '1' ;
ngOnInit() {
setTimeout ( ( ) = > {
this . text += '1' ;
2020-04-13 16:40:21 -07:00
Promise . resolve ( null ) . then ( ( _ : any ) = > {
this . text += '1' ;
} ) ;
2017-02-03 05:42:22 -08:00
} , 10 ) ;
}
}
let stableCalled = false ;
beforeEach ( ( ) = > {
stableCalled = false ;
TestBed . configureTestingModule ( {
declarations : [
SyncComp , MicroTaskComp , MacroTaskComp , MicroMacroTaskComp , MacroMicroTaskComp , ClickComp
] ,
} ) ;
} ) ;
2020-04-13 16:40:21 -07:00
afterEach ( ( ) = > {
expect ( stableCalled ) . toBe ( true , 'isStable did not emit true on stable' ) ;
} ) ;
2017-02-03 05:42:22 -08:00
function expectStableTexts ( component : Type < any > , expected : string [ ] ) {
const fixture = TestBed . createComponent ( component ) ;
2019-08-28 16:22:36 -07:00
const appRef : ApplicationRef = TestBed . inject ( ApplicationRef ) ;
const zone : NgZone = TestBed . inject ( NgZone ) ;
2017-02-03 05:42:22 -08:00
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 ;
}
}
} ) ;
}
2020-08-01 04:43:18 +09:00
it ( 'isStable should fire on synchronous component loading' , waitForAsync ( ( ) = > {
2020-04-13 16:40:21 -07:00
expectStableTexts ( SyncComp , [ '1' ] ) ;
} ) ) ;
2017-02-03 05:42:22 -08:00
2020-08-01 04:43:18 +09:00
it ( 'isStable should fire after a microtask on init is completed' , waitForAsync ( ( ) = > {
2020-04-13 16:40:21 -07:00
expectStableTexts ( MicroTaskComp , [ '11' ] ) ;
} ) ) ;
2017-02-03 05:42:22 -08:00
2020-08-01 04:43:18 +09:00
it ( 'isStable should fire after a macrotask on init is completed' , waitForAsync ( ( ) = > {
2020-04-13 16:40:21 -07:00
expectStableTexts ( MacroTaskComp , [ '11' ] ) ;
} ) ) ;
2017-02-03 05:42:22 -08:00
2018-11-30 17:14:53 +01:00
it ( 'isStable should fire only after chain of micro and macrotasks on init are completed' ,
2020-08-01 04:43:18 +09:00
waitForAsync ( ( ) = > {
2020-04-13 16:40:21 -07:00
expectStableTexts ( MicroMacroTaskComp , [ '111' ] ) ;
} ) ) ;
2017-02-03 05:42:22 -08:00
2018-11-30 17:14:53 +01:00
it ( 'isStable should fire only after chain of macro and microtasks on init are completed' ,
2020-08-01 04:43:18 +09:00
waitForAsync ( ( ) = > {
2020-04-13 16:40:21 -07:00
expectStableTexts ( MacroMicroTaskComp , [ '111' ] ) ;
} ) ) ;
2017-02-03 05:42:22 -08:00
describe ( 'unstable' , ( ) = > {
let unstableCalled = false ;
2020-04-13 16:40:21 -07:00
afterEach ( ( ) = > {
expect ( unstableCalled ) . toBe ( true , 'isStable did not emit false on unstable' ) ;
} ) ;
2017-02-03 05:42:22 -08:00
function expectUnstable ( appRef : ApplicationRef ) {
appRef . isStable . subscribe ( {
next : ( stable : boolean ) = > {
if ( stable ) {
stableCalled = true ;
}
if ( ! stable ) {
unstableCalled = true ;
}
}
} ) ;
}
2020-08-01 04:43:18 +09:00
it ( 'should be fired after app becomes unstable' , waitForAsync ( ( ) = > {
2018-11-30 17:14:53 +01:00
const fixture = TestBed . createComponent ( ClickComp ) ;
2019-08-28 16:22:36 -07:00
const appRef : ApplicationRef = TestBed . inject ( ApplicationRef ) ;
const zone : NgZone = TestBed . inject ( NgZone ) ;
2018-11-30 17:14:53 +01:00
appRef . attachView ( fixture . componentRef . hostView ) ;
zone . run ( ( ) = > appRef . tick ( ) ) ;
fixture . whenStable ( ) . then ( ( ) = > {
expectUnstable ( appRef ) ;
const element = fixture . debugElement . children [ 0 ] ;
dispatchEvent ( element . nativeElement , 'click' ) ;
} ) ;
} ) ) ;
2017-02-03 05:42:22 -08:00
} ) ;
} ) ;
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 [ ] [ ] = [ ] ;
2017-07-20 22:34:19 -07:00
log ( . . . args : any [ ] ) : void {
// Logging from ErrorHandler should run outside of the Angular Zone.
NgZone . assertNotInAngularZone ( ) ;
this . res . push ( args ) ;
}
error ( . . . args : any [ ] ) : void {
// Logging from ErrorHandler should run outside of the Angular Zone.
NgZone . assertNotInAngularZone ( ) ;
this . res . push ( args ) ;
}
2015-10-28 10:34:13 -07:00
}