angular-docs-cn/packages/core/test/render3/component_spec.ts

223 lines
6.6 KiB
TypeScript

/**
* @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 {ViewEncapsulation, ɵɵdefineInjectable, ɵɵdefineInjector} from '../../src/core';
import {createInjector} from '../../src/di/r3_injector';
import {AttributeMarker, markDirty, ɵɵadvance, ɵɵdefineComponent, ɵɵdirectiveInject, ɵɵproperty, ɵɵtemplate} from '../../src/render3/index';
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {NgIf} from './common_with_def';
import {ComponentFixture, containerEl, createComponent, MockRendererFactory, renderComponent, requestAnimationFrame, toHtml} from './render_util';
describe('component', () => {
class CounterComponent {
count = 0;
increment() {
this.count++;
}
static ɵfac = () => new CounterComponent;
static ɵcmp = ɵɵdefineComponent({
type: CounterComponent,
encapsulation: ViewEncapsulation.None,
selectors: [['counter']],
decls: 1,
vars: 1,
template:
function(rf: RenderFlags, ctx: CounterComponent) {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.count);
}
},
inputs: {count: 'count'},
});
}
describe('renderComponent', () => {
it('should render on initial call', () => {
renderComponent(CounterComponent);
expect(toHtml(containerEl)).toEqual('0');
});
it('should re-render on input change or method invocation', () => {
const component = renderComponent(CounterComponent);
expect(toHtml(containerEl)).toEqual('0');
component.count = 123;
markDirty(component);
expect(toHtml(containerEl)).toEqual('0');
requestAnimationFrame.flush();
expect(toHtml(containerEl)).toEqual('123');
component.increment();
markDirty(component);
expect(toHtml(containerEl)).toEqual('123');
requestAnimationFrame.flush();
expect(toHtml(containerEl)).toEqual('124');
});
class MyService {
constructor(public value: string) {}
static ɵprov = ɵɵdefineInjectable({
token: MyService,
providedIn: 'root',
factory: () => new MyService('no-injector'),
});
}
class MyComponent {
constructor(public myService: MyService) {}
static ɵfac = () => new MyComponent(ɵɵdirectiveInject(MyService));
static ɵcmp = ɵɵdefineComponent({
type: MyComponent,
encapsulation: ViewEncapsulation.None,
selectors: [['my-component']],
decls: 1,
vars: 1,
template:
function(fs: RenderFlags, ctx: MyComponent) {
if (fs & RenderFlags.Create) {
ɵɵtext(0);
}
if (fs & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.myService.value);
}
}
});
}
class MyModule {
static ɵinj = ɵɵdefineInjector({
factory: () => new MyModule(),
providers: [{provide: MyService, useValue: new MyService('injector')}]
});
}
it('should support bootstrapping without injector', () => {
const fixture = new ComponentFixture(MyComponent);
expect(fixture.html).toEqual('no-injector');
});
it('should support bootstrapping with injector', () => {
const fixture = new ComponentFixture(MyComponent, {injector: createInjector(MyModule)});
expect(fixture.html).toEqual('injector');
});
});
it('should instantiate components at high indices', () => {
// {{ name }}
class Comp {
// @Input
name = '';
static ɵfac = () => new Comp();
static ɵcmp = ɵɵdefineComponent({
type: Comp,
selectors: [['comp']],
decls: 1,
vars: 1,
template:
(rf: RenderFlags, ctx: Comp) => {
if (rf & RenderFlags.Create) {
ɵɵtext(0);
}
if (rf & RenderFlags.Update) {
ɵɵtextInterpolate(ctx.name);
}
},
inputs: {name: 'name'}
});
}
// Artificially inflating the slot IDs of this app component to mimic an app
// with a very large view
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
ɵɵelement(4097, 'comp');
}
if (rf & RenderFlags.Update) {
ɵɵadvance(4097);
ɵɵproperty('name', ctx.name);
}
}, 4098, 1, [Comp]);
const fixture = new ComponentFixture(App);
expect(fixture.html).toEqual('<comp></comp>');
fixture.component.name = 'some name';
fixture.update();
expect(fixture.html).toEqual('<comp>some name</comp>');
});
});
it('should not invoke renderer destroy method for embedded views', () => {
let comp: Comp;
function MyComponent_div_Template_2(rf: any, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'div');
ɵɵtext(1, 'Child view');
ɵɵelementEnd();
}
}
class Comp {
visible = true;
static ɵfac =
() => {
comp = new Comp();
return comp;
}
static ɵcmp = ɵɵdefineComponent({
type: Comp,
selectors: [['comp']],
decls: 3,
vars: 1,
directives: [NgIf],
consts: [[AttributeMarker.Template, 'ngIf']],
/**
* <div>Root view</div>
* <div *ngIf="visible">Child view</div>
*/
template:
function(rf: RenderFlags, ctx: Comp) {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'div');
ɵɵtext(1, 'Root view');
ɵɵelementEnd();
ɵɵtemplate(2, MyComponent_div_Template_2, 2, 0, 'div', 0);
}
if (rf & RenderFlags.Update) {
ɵɵadvance(2);
ɵɵproperty('ngIf', ctx.visible);
}
}
});
}
const rendererFactory = new MockRendererFactory(['destroy']);
const fixture = new ComponentFixture(Comp, {rendererFactory});
comp!.visible = false;
fixture.update();
comp!.visible = true;
fixture.update();
const renderer = rendererFactory.lastRenderer!;
const destroySpy = renderer.spies['destroy'];
// we should never see `destroy` method being called
// in case child views are created/removed
expect(destroySpy.calls.count()).toBe(0);
});