/**
* @license
* Copyright Google LLC 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
*/
import {ApplicationRef, COMPILER_OPTIONS, Component, destroyPlatform, NgModule, NgZone, TestabilityRegistry, ViewEncapsulation} from '@angular/core';
import {expect} from '@angular/core/testing/src/testing_internal';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {onlyInIvy, withBody} from '@angular/private/testing';
describe('bootstrap', () => {
beforeEach(destroyPlatform);
afterEach(destroyPlatform);
it('should bootstrap using #id selector',
withBody('
before|
', async () => {
try {
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(IdSelectorAppModule);
expect(document.body.textContent).toEqual('before|works!');
ngModuleRef.destroy();
} catch (err) {
console.error(err);
}
}));
it('should bootstrap using one of selectors from the list',
withBody('
before|
', async () => {
try {
const ngModuleRef =
await platformBrowserDynamic().bootstrapModule(MultipleSelectorsAppModule);
expect(document.body.textContent).toEqual('before|works!');
ngModuleRef.destroy();
} catch (err) {
console.error(err);
}
}));
describe('options', () => {
function createComponentAndModule(
options:
{encapsulation?: ViewEncapsulation; preserveWhitespaces?: boolean;
selector?: string} = {}) {
@Component({
selector: options.selector || 'my-app',
styles: [''],
template: 'a b',
encapsulation: options.encapsulation,
preserveWhitespaces: options.preserveWhitespaces,
jit: true,
})
class TestComponent {
}
@NgModule({
imports: [BrowserModule],
declarations: [TestComponent],
bootstrap: [TestComponent],
jit: true,
})
class TestModule {
}
return TestModule;
}
it('should use ViewEncapsulation.Emulated as default',
withBody('', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(TestModule);
expect(document.body.innerHTML).toContain('', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(
TestModule, {defaultEncapsulation: ViewEncapsulation.None});
expect(document.body.innerHTML).toContain('');
expect(document.body.innerHTML).not.toContain('_ngcontent-');
ngModuleRef.destroy();
}));
it('should allow setting defaultEncapsulation using compiler option',
withBody('', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic([{
provide: COMPILER_OPTIONS,
useValue: {defaultEncapsulation: ViewEncapsulation.None},
multi: true
}]).bootstrapModule(TestModule);
expect(document.body.innerHTML).toContain('');
expect(document.body.innerHTML).not.toContain('_ngcontent-');
ngModuleRef.destroy();
}));
it('should prefer encapsulation on component over bootstrap option',
withBody('', async () => {
const TestModule = createComponentAndModule({encapsulation: ViewEncapsulation.Emulated});
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(
TestModule, {defaultEncapsulation: ViewEncapsulation.None});
expect(document.body.innerHTML).toContain('', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(TestModule);
expect(document.body.innerHTML).toContain('a b');
ngModuleRef.destroy();
}));
it('should allow setting preserveWhitespaces using bootstrap option',
withBody('', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(
TestModule, {preserveWhitespaces: true});
expect(document.body.innerHTML).toContain('a b');
ngModuleRef.destroy();
}));
it('should allow setting preserveWhitespaces using compiler option',
withBody('', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef =
await platformBrowserDynamic([
{provide: COMPILER_OPTIONS, useValue: {preserveWhitespaces: true}, multi: true}
]).bootstrapModule(TestModule);
expect(document.body.innerHTML).toContain('a b');
ngModuleRef.destroy();
}));
it('should prefer preserveWhitespaces on component over bootstrap option',
withBody('', async () => {
const TestModule = createComponentAndModule({preserveWhitespaces: false});
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(
TestModule, {preserveWhitespaces: true});
expect(document.body.innerHTML).toContain('a b');
ngModuleRef.destroy();
}));
describe('ApplicationRef cleanup', () => {
it('should cleanup ApplicationRef when Injector is destroyed',
withBody('', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(TestModule);
const appRef = ngModuleRef.injector.get(ApplicationRef);
const testabilityRegistry = ngModuleRef.injector.get(TestabilityRegistry);
expect(appRef.components.length).toBe(1);
expect(testabilityRegistry.getAllRootElements().length).toBe(1);
ngModuleRef.destroy(); // also destroys an Injector instance.
expect(appRef.components.length).toBe(0);
expect(testabilityRegistry.getAllRootElements().length).toBe(0);
}));
it('should cleanup ApplicationRef when ComponentRef is destroyed',
withBody('', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(TestModule);
const appRef = ngModuleRef.injector.get(ApplicationRef);
const testabilityRegistry = ngModuleRef.injector.get(TestabilityRegistry);
const componentRef = appRef.components[0];
expect(appRef.components.length).toBe(1);
expect(testabilityRegistry.getAllRootElements().length).toBe(1);
componentRef.destroy();
expect(appRef.components.length).toBe(0);
expect(testabilityRegistry.getAllRootElements().length).toBe(0);
}));
it('should not throw in case ComponentRef is destroyed and Injector is destroyed after that',
withBody('', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(TestModule);
const appRef = ngModuleRef.injector.get(ApplicationRef);
const testabilityRegistry = ngModuleRef.injector.get(TestabilityRegistry);
const componentRef = appRef.components[0];
expect(appRef.components.length).toBe(1);
expect(testabilityRegistry.getAllRootElements().length).toBe(1);
componentRef.destroy();
ngModuleRef.destroy(); // also destroys an Injector instance.
expect(appRef.components.length).toBe(0);
expect(testabilityRegistry.getAllRootElements().length).toBe(0);
}));
it('should not throw in case Injector is destroyed and ComponentRef is destroyed after that',
withBody('', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(TestModule);
const appRef = ngModuleRef.injector.get(ApplicationRef);
const testabilityRegistry = ngModuleRef.injector.get(TestabilityRegistry);
const componentRef = appRef.components[0];
expect(appRef.components.length).toBe(1);
expect(testabilityRegistry.getAllRootElements().length).toBe(1);
ngModuleRef.destroy(); // also destroys an Injector instance.
componentRef.destroy();
expect(appRef.components.length).toBe(0);
expect(testabilityRegistry.getAllRootElements().length).toBe(0);
}));
});
describe('PlatformRef cleanup', () => {
it('should unsubscribe from `onError` when Injector is destroyed',
withBody('', async () => {
const TestModule = createComponentAndModule();
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(TestModule);
const ngZone = ngModuleRef.injector.get(NgZone);
expect(ngZone.onError.observers.length).toBe(1);
ngModuleRef.destroy();
expect(ngZone.onError.observers.length).toBe(0);
}));
});
onlyInIvy('options cannot be changed in Ivy').describe('changing bootstrap options', () => {
beforeEach(() => {
spyOn(console, 'error');
});
it('should log an error when changing defaultEncapsulation bootstrap options',
withBody('', async () => {
const platformRef = platformBrowserDynamic();
const TestModuleA = createComponentAndModule({selector: 'my-app-a'});
const ngModuleRefA = await platformRef.bootstrapModule(
TestModuleA, {defaultEncapsulation: ViewEncapsulation.None});
ngModuleRefA.destroy();
const TestModuleB = createComponentAndModule({selector: 'my-app-b'});
const ngModuleRefB = await platformRef.bootstrapModule(
TestModuleB, {defaultEncapsulation: ViewEncapsulation.ShadowDom});
expect(console.error)
.toHaveBeenCalledWith(
'Provided value for `defaultEncapsulation` can not be changed once it has been set.');
// The options should not have been changed
expect(document.body.innerHTML).not.toContain('_ngcontent-');
ngModuleRefB.destroy();
}));
it('should log an error when changing preserveWhitespaces bootstrap options',
withBody('', async () => {
const platformRef = platformBrowserDynamic();
const TestModuleA = createComponentAndModule({selector: 'my-app-a'});
const ngModuleRefA =
await platformRef.bootstrapModule(TestModuleA, {preserveWhitespaces: true});
ngModuleRefA.destroy();
const TestModuleB = createComponentAndModule({selector: 'my-app-b'});
const ngModuleRefB =
await platformRef.bootstrapModule(TestModuleB, {preserveWhitespaces: false});
expect(console.error)
.toHaveBeenCalledWith(
'Provided value for `preserveWhitespaces` can not be changed once it has been set.');
// The options should not have been changed
expect(document.body.innerHTML).toContain('a b');
ngModuleRefB.destroy();
}));
it('should log an error when changing defaultEncapsulation to its default',
withBody('', async () => {
const platformRef = platformBrowserDynamic();
const TestModuleA = createComponentAndModule({selector: 'my-app-a'});
const ngModuleRefA = await platformRef.bootstrapModule(TestModuleA);
ngModuleRefA.destroy();
const TestModuleB = createComponentAndModule({selector: 'my-app-b'});
const ngModuleRefB = await platformRef.bootstrapModule(
TestModuleB, {defaultEncapsulation: ViewEncapsulation.Emulated});
// Although the configured value may be identical to the default, the provided set of
// options has still been changed compared to the previously provided options.
expect(console.error)
.toHaveBeenCalledWith(
'Provided value for `defaultEncapsulation` can not be changed once it has been set.');
ngModuleRefB.destroy();
}));
it('should log an error when changing preserveWhitespaces to its default',
withBody('', async () => {
const platformRef = platformBrowserDynamic();
const TestModuleA = createComponentAndModule({selector: 'my-app-a'});
const ngModuleRefA = await platformRef.bootstrapModule(TestModuleA);
ngModuleRefA.destroy();
const TestModuleB = createComponentAndModule({selector: 'my-app-b'});
const ngModuleRefB =
await platformRef.bootstrapModule(TestModuleB, {preserveWhitespaces: false});
// Although the configured value may be identical to the default, the provided set of
// options has still been changed compared to the previously provided options.
expect(console.error)
.toHaveBeenCalledWith(
'Provided value for `preserveWhitespaces` can not be changed once it has been set.');
ngModuleRefB.destroy();
}));
it('should not log an error when passing identical bootstrap options',
withBody('', async () => {
const platformRef = platformBrowserDynamic();
const TestModuleA = createComponentAndModule({selector: 'my-app-a'});
const ngModuleRefA = await platformRef.bootstrapModule(
TestModuleA,
{defaultEncapsulation: ViewEncapsulation.None, preserveWhitespaces: true});
ngModuleRefA.destroy();
// Bootstrapping multiple modules using the exact same options should be allowed.
const TestModuleB = createComponentAndModule({selector: 'my-app-b'});
const ngModuleRefB = await platformRef.bootstrapModule(
TestModuleB,
{defaultEncapsulation: ViewEncapsulation.None, preserveWhitespaces: true});
ngModuleRefB.destroy();
}));
});
});
});
@Component({
selector: '#my-app',
template: 'works!',
})
export class IdSelectorAppComponent {
}
@NgModule({
imports: [BrowserModule],
declarations: [IdSelectorAppComponent],
bootstrap: [IdSelectorAppComponent],
})
export class IdSelectorAppModule {
}
@Component({
selector: '[foo],span,.bar',
template: 'works!',
})
export class MultipleSelectorsAppComponent {
}
@NgModule({
imports: [BrowserModule],
declarations: [MultipleSelectorsAppComponent],
bootstrap: [MultipleSelectorsAppComponent],
})
export class MultipleSelectorsAppModule {
}