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 ;
2015-12-02 20:25:24 -08: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
}
2016-04-14 14:52:35 -07: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 {
}
2017-03-14 16:26:17 -07:00
2020-08-01 04:43:18 +09:00
const helloToken = new InjectionToken < string >( 'hello' );
2017-03-14 16:26:17 -07:00
2020-08-01 04:43:18 +09:00
@NgModule ({
providers : [{ provide : helloToken , useValue : 'component' }],
declarations : [ SomeComponent ],
entryComponents : [ SomeComponent ],
})
class SomeModule {
}
2017-03-14 16:26:17 -07:00
2020-08-01 04:43:18 +09:00
createRootEl ();
const modFactory = compiler . compileModuleSync ( SomeModule );
const module = modFactory . create ( TestBed );
const cmpFactory =
module . componentFactoryResolver . resolveComponentFactory ( SomeComponent ) ! ;
const component = app . bootstrap ( cmpFactory );
2017-03-14 16:26:17 -07:00
2020-08-01 04:43:18 +09:00
// The component should see the child module providers
expect ( component . injector . get ( helloToken )). toEqual ( 'component' );
})));
2017-03-31 16:37:20 +02:00
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 {
}
2017-03-31 16:37:20 +02:00
2020-08-01 04:43:18 +09:00
const helloToken = new InjectionToken < string >( 'hello' );
2017-03-31 16:37:20 +02:00
2020-08-01 04:43:18 +09:00
@NgModule ({
providers : [{ provide : helloToken , useValue : 'component' }],
declarations : [ SomeComponent ],
entryComponents : [ SomeComponent ],
})
class SomeModule {
}
2019-01-16 15:42:13 +01:00
2020-08-01 04:43:18 +09:00
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' );
2019-01-16 15:42:13 +01:00
2020-08-01 04:43:18 +09:00
// 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-07-29 06:47:40 -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
});
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
});
});
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 );
2016-07-18 03:50:31 -07:00
2018-11-30 17:14:53 +01:00
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
}));
2016-07-29 06:47:40 -07: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' );
});
}));
2016-07-29 06:47:40 -07:00
2018-11-30 17:14:53 +01:00
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' );
});
}));
2016-07-29 14:45:05 -07: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 {
}
2016-07-29 14:45:05 -07: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?' );
});
}));
2016-08-02 06:54:08 -07:00
2018-11-30 17:14:53 +01:00
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 );
});
}));
2016-08-02 06:54:08 -07: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 ]);
});
}));
2016-08-02 06:54:08 -07:00
2018-11-30 17:14:53 +01:00
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 );
});
}));
2017-01-01 20:46:53 +03:00
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 ));
}));
2017-06-01 14:45:49 -07:00
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-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 );
2016-07-29 06:47:40 -07:00
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 , 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-02 20:25:24 -08: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 );
});
2016-11-04 11:58:06 -07:00
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 );
2016-11-04 11:58:06 -07:00
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 ());
2017-02-03 05:42:22 -08:00
2018-11-30 17:14:53 +01:00
fixture . whenStable (). then (() => {
expectUnstable ( appRef );
const element = fixture . debugElement . children [ 0 ];
dispatchEvent ( element . nativeElement , 'click' );
});
}));
2017-02-03 05:42:22 -08:00
});
});
2015-12-02 20:25:24 -08: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
}