angular-cn/packages/core/test/render3/di_spec.ts

2349 lines
80 KiB
TypeScript
Raw Normal View History

/**
* @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
*/
import {Attribute, ChangeDetectorRef, ElementRef, Host, INJECTOR, Inject, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, TemplateRef, ViewContainerRef, defineInjectable, defineInjector} from '@angular/core';
import {ComponentType, RenderFlags} from '@angular/core/src/render3/interfaces/definition';
import {createInjector} from '../../src/di/r3_injector';
import {defineComponent} from '../../src/render3/definition';
import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
import {ProvidersFeature, defineDirective, elementProperty, load, templateRefExtractor, elementStart, text, elementEnd, reference, textBinding, bind, directiveInject, element, container, containerRefreshStart, embeddedViewStart, embeddedViewEnd, containerRefreshEnd, template, allocHostVars, interpolation2, elementContainerStart, elementContainerEnd, projectionDef, projection, injectAttribute} from '../../src/render3/index';
import {LContainer, NATIVE} from '../../src/render3/interfaces/container';
import {TNODE} from '../../src/render3/interfaces/injector';
import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node';
import {RElement, isProceduralRenderer} from '../../src/render3/interfaces/renderer';
import {LViewFlags} from '../../src/render3/interfaces/view';
import {enterView, getLView, leaveView} from '../../src/render3/state';
import {getNativeByIndex} from '../../src/render3/util/view_utils';
import {ViewRef} from '../../src/render3/view_ref';
import {NgIf} from './common_with_def';
import {getRendererFactory2} from './imported_renderer2';
import {ComponentFixture, createComponent, createDirective, getDirectiveOnNode, renderComponent, toHtml} from './render_util';
import {createLView, createTView, createNodeAtIndex} from '@angular/core/src/render3/instructions/shared';
describe('di', () => {
describe('no dependencies', () => {
it('should create directive with no deps', () => {
class Directive {
value: string = 'Created';
static ngDirectiveDef = defineDirective({
type: Directive,
selectors: [['', 'dir', '']],
factory: () => new Directive,
exportAs: ['dir']
});
}
/** <div dir #dir="dir"> {{ dir.value }} </div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dir', ''], ['dir', 'dir']);
{ text(2); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const tmp = reference(1) as any;
textBinding(2, bind(tmp.value));
}
}, 3, 1, [Directive]);
const fixture = new ComponentFixture(App);
expect(fixture.html).toEqual('<div dir="">Created</div>');
});
});
describe('directive injection', () => {
let log: string[] = [];
class DirB {
value = 'DirB';
constructor() { log.push(this.value); }
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirB', '']],
type: DirB,
factory: () => new DirB(),
inputs: {value: 'value'}
});
}
beforeEach(() => log = []);
it('should create directive with intra view dependencies', () => {
class DirA {
value: string = 'DirA';
static ngDirectiveDef =
defineDirective({type: DirA, selectors: [['', 'dirA', '']], factory: () => new DirA()});
}
class DirC {
value: string;
constructor(a: DirA, b: DirB) { this.value = a.value + b.value; }
static ngDirectiveDef = defineDirective({
type: DirC,
selectors: [['', 'dirC', '']],
factory: () => new DirC(directiveInject(DirA), directiveInject(DirB)),
exportAs: ['dirC']
});
}
/**
* <div dirA>
* <span dirB dirC #dir="dirC"> {{ dir.value }} </span>
* </div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirA', '']);
{
elementStart(1, 'span', ['dirB', '', 'dirC', ''], ['dir', 'dirC']);
{ text(3); }
elementEnd();
}
elementEnd();
}
if (rf & RenderFlags.Update) {
const tmp = reference(2) as any;
textBinding(3, bind(tmp.value));
}
}, 4, 1, [DirA, DirB, DirC]);
const fixture = new ComponentFixture(App);
expect(fixture.html).toEqual('<div dira=""><span dirb="" dirc="">DirADirB</span></div>');
});
it('should instantiate injected directives in dependency order', () => {
class DirA {
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirA', '']],
type: DirA,
factory: () => new DirA(directiveInject(DirB)),
});
}
/** <div dirA dirB></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirA', '', 'dirB', '']);
}
}, 1, 0, [DirA, DirB]);
new ComponentFixture(App);
expect(log).toEqual(['DirB', 'DirA (dep: DirB)']);
});
it('should fallback to the module injector', () => {
class DirA {
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirA', '']],
type: DirA,
factory: () => new DirA(directiveInject(DirB)),
});
}
// `<div dirB></div><div dirA></div>`
// - dirB is know to the node injectors (it uses the diPublic feature)
// - then when dirA tries to inject dirB, it will check the node injector first tree
// - if not found, it will check the module injector tree
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirB', '']);
element(1, 'div', ['dirA', '']);
}
}, 2, 0, [DirA, DirB]);
const fakeModuleInjector: any = {
get: function(token: any) {
const value = token === DirB ? 'module' : 'fail';
return {value: value};
}
};
new ComponentFixture(App, {injector: fakeModuleInjector});
expect(log).toEqual(['DirB', 'DirA (dep: module)']);
});
it('should instantiate injected directives before components', () => {
class Comp {
constructor(dir: DirB) { log.push(`Comp (dep: ${dir.value})`); }
static ngComponentDef = defineComponent({
selectors: [['comp']],
type: Comp,
consts: 0,
vars: 0,
factory: () => new Comp(directiveInject(DirB)),
template: (rf: RenderFlags, ctx: Comp) => {}
});
}
/** <comp dirB></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'comp', ['dirB', '']);
}
}, 1, 0, [Comp, DirB]);
new ComponentFixture(App);
expect(log).toEqual(['DirB', 'Comp (dep: DirB)']);
});
it('should inject directives in the correct order in a for loop', () => {
class DirA {
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirA', '']],
type: DirA,
factory: () => new DirA(directiveInject(DirB))
});
}
/**
* % for(let i = 0; i < 3; i++) {
* <div dirA dirB></div>
* % }
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
container(0);
}
if (rf & RenderFlags.Update) {
containerRefreshStart(0);
{
for (let i = 0; i < 3; i++) {
if (embeddedViewStart(0, 1, 0)) {
element(0, 'div', ['dirA', '', 'dirB', '']);
}
embeddedViewEnd();
}
}
containerRefreshEnd();
}
}, 1, 0, [DirA, DirB]);
new ComponentFixture(App);
expect(log).toEqual(
['DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)']);
});
it('should instantiate directives with multiple out-of-order dependencies', () => {
class DirA {
value = 'DirA';
constructor() { log.push(this.value); }
static ngDirectiveDef =
defineDirective({selectors: [['', 'dirA', '']], type: DirA, factory: () => new DirA()});
}
class DirB {
constructor(dirA: DirA, dirC: DirC) {
log.push(`DirB (deps: ${dirA.value} and ${dirC.value})`);
}
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirB', '']],
type: DirB,
factory: () => new DirB(directiveInject(DirA), directiveInject(DirC))
});
}
class DirC {
value = 'DirC';
constructor() { log.push(this.value); }
static ngDirectiveDef =
defineDirective({selectors: [['', 'dirC', '']], type: DirC, factory: () => new DirC()});
}
/** <div dirA dirB dirC></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirA', '', 'dirB', '', 'dirC', '']);
}
}, 1, 0, [DirA, DirB, DirC]);
new ComponentFixture(App);
expect(log).toEqual(['DirA', 'DirC', 'DirB (deps: DirA and DirC)']);
});
it('should instantiate in the correct order for complex case', () => {
class Comp {
constructor(dir: DirD) { log.push(`Comp (dep: ${dir.value})`); }
static ngComponentDef = defineComponent({
selectors: [['comp']],
type: Comp,
consts: 0,
vars: 0,
factory: () => new Comp(directiveInject(DirD)),
template: (ctx: any, fm: boolean) => {}
});
}
class DirA {
value = 'DirA';
constructor(dir: DirC) { log.push(`DirA (dep: ${dir.value})`); }
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirA', '']],
type: DirA,
factory: () => new DirA(directiveInject(DirC))
});
}
class DirC {
value = 'DirC';
constructor(dir: DirB) { log.push(`DirC (dep: ${dir.value})`); }
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirC', '']],
type: DirC,
factory: () => new DirC(directiveInject(DirB))
});
}
class DirD {
value = 'DirD';
constructor(dir: DirA) { log.push(`DirD (dep: ${dir.value})`); }
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirD', '']],
type: DirD,
factory: () => new DirD(directiveInject(DirA))
});
}
/** <comp dirA dirB dirC dirD></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'comp', ['dirA', '', 'dirB', '', 'dirC', '', 'dirD', '']);
}
}, 1, 0, [Comp, DirA, DirB, DirC, DirD]);
new ComponentFixture(App);
expect(log).toEqual(
['DirB', 'DirC (dep: DirB)', 'DirA (dep: DirC)', 'DirD (dep: DirA)', 'Comp (dep: DirD)']);
});
it('should instantiate in correct order with mixed parent and peer dependencies', () => {
class DirA {
constructor(dirB: DirB, app: App) {
log.push(`DirA (deps: ${dirB.value} and ${app.value})`);
}
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirA', '']],
type: DirA,
factory: () => new DirA(directiveInject(DirB), directiveInject(App)),
});
}
class App {
value = 'App';
static ngComponentDef = defineComponent({
selectors: [['app']],
type: App,
factory: () => new App(),
consts: 1,
vars: 0,
/** <div dirA dirB dirC></div> */
template: (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirA', '', 'dirB', '', 'dirC', 'dirC']);
}
},
directives: [DirA, DirB]
});
}
new ComponentFixture(App);
expect(log).toEqual(['DirB', 'DirA (deps: DirB and App)']);
});
it('should not use a parent when peer dep is available', () => {
let count = 1;
class DirA {
constructor(dirB: DirB) { log.push(`DirA (dep: DirB - ${dirB.count})`); }
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirA', '']],
type: DirA,
factory: () => new DirA(directiveInject(DirB)),
});
}
class DirB {
count: number;
constructor() {
log.push(`DirB`);
this.count = count++;
}
static ngDirectiveDef =
defineDirective({selectors: [['', 'dirB', '']], type: DirB, factory: () => new DirB()});
}
/** <div dirA dirB></div> */
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirA', '', 'dirB', '']);
}
}, 1, 0, [DirA, DirB]);
/** <parent dirB></parent> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'parent', ['dirB', '']);
}
}, 1, 0, [Parent, DirB]);
new ComponentFixture(App);
expect(log).toEqual(['DirB', 'DirB', 'DirA (dep: DirB - 2)']);
});
describe('dependencies in parent views', () => {
class DirA {
injector: Injector;
constructor(public dirB: DirB, public vcr: ViewContainerRef) {
this.injector = vcr.injector;
}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => new DirA(directiveInject(DirB), directiveInject(ViewContainerRef as any)),
exportAs: ['dirA']
});
}
/**
* <div dirA #dir="dirA">
* {{ dir.dirB.value }}
* </div>
*/
const Comp = createComponent('comp', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']);
{ text(2); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const dir = reference(1) as DirA;
textBinding(2, bind(dir.dirB.value));
}
}, 3, 1, [DirA]);
it('should find dependencies on component hosts', () => {
/** <comp dirB>/comp> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'comp', ['dirB', '']);
}
}, 1, 0, [Comp, DirB]);
const fixture = new ComponentFixture(App);
expect(fixture.hostElement.textContent).toEqual(`DirB`);
});
it('should find dependencies for directives in embedded views', () => {
function IfTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
{
elementStart(1, 'div', ['dirA', ''], ['dir', 'dirA']);
{ text(3); }
elementEnd();
}
elementEnd();
}
if (rf & RenderFlags.Update) {
const dir = reference(2) as DirA;
textBinding(3, bind(dir.dirB.value));
}
}
/**
* <div dirB>
* <div *ngIf="showing">
* <div dirA #dir="dirA"> {{ dir.dirB.value }} </div>
* </div>
* </div>
*/
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirB', '']);
fix(ivy): match attribute selectors for content projection with inline-templates (#29041) The content projection mechanism is static, in that it only looks at the static template nodes before directives are matched and change detection is run. When you have a selector-based content projection the selection is based on nodes that are available in the template. For example: ``` <ng-content selector="[some-attr]"></ng-content> ``` would match ``` <div some-attr="..."></div> ``` If you have an inline-template in your projected nodes. For example: ``` <div *ngIf="..." some-attr="..."></div> ``` This gets pre-parsed and converted to a canonical form. For example: ``` <ng-template [ngIf]="..."> <div some-attr=".."></div> </ng-template> ``` Note that only structural attributes (e.g. `*ngIf`) stay with the `<ng-template>` node. The other attributes move to the contained element inside the template. When this happens in ivy, the ng-template content is removed from the component template function and is compiled into its own template function. But this means that the information about the attributes that were on the content are lost and the projection selection mechanism is unable to match the original `<div *ngIf="..." some-attr>`. This commit adds support for this in ivy. Attributes are separated into three groups (Bindings, Templates and "other"). For inline-templates the Bindings and "other" types are hoisted back from the contained node to the `template()` instruction, so that they can be used in content projection matching. PR Close #29041
2019-03-07 03:31:31 -05:00
{ template(1, IfTemplate, 4, 1, 'div', [AttributeMarker.Template, 'ngIf']); }
elementEnd();
}
if (rf & RenderFlags.Update) {
elementProperty(1, 'ngIf', bind(ctx.showing));
}
}, 2, 1, [DirA, DirB, NgIf]);
const fixture = new ComponentFixture(App);
fixture.component.showing = true;
fixture.update();
expect(fixture.hostElement.textContent).toEqual(`DirB`);
});
it('should find dependencies of directives nested deeply in inline views', () => {
/**
* <div dirB>
* % if (!skipContent) {
* % if (!skipContent2) {
* <div dirA #dir="dirA"> {{ dir.dirB.value }} </div>
* % }
* % }
* </div>
*/
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirB', '']);
{ container(1); }
elementEnd();
}
if (rf & RenderFlags.Update) {
containerRefreshStart(1);
{
if (!ctx.skipContent) {
let rf1 = embeddedViewStart(0, 1, 0);
{
if (rf1 & RenderFlags.Create) {
container(0);
}
if (rf1 & RenderFlags.Update) {
containerRefreshStart(0);
{
if (!ctx.skipContent2) {
let rf2 = embeddedViewStart(0, 3, 1);
{
if (rf2 & RenderFlags.Create) {
elementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']);
{ text(2); }
elementEnd();
}
if (rf2 & RenderFlags.Update) {
const dir = reference(1) as DirA;
textBinding(2, bind(dir.dirB.value));
}
}
embeddedViewEnd();
}
}
containerRefreshEnd();
}
}
embeddedViewEnd();
}
}
containerRefreshEnd();
}
}, 2, 0, [DirA, DirB]);
const fixture = new ComponentFixture(App);
expect(fixture.hostElement.textContent).toEqual(`DirB`);
});
it('should find dependencies in declaration tree of ng-template (not insertion tree)', () => {
let structuralDir !: StructuralDir;
class StructuralDir {
// @Input()
tmp !: TemplateRef<any>;
constructor(public vcr: ViewContainerRef) {}
create() { this.vcr.createEmbeddedView(this.tmp); }
static ngDirectiveDef = defineDirective({
type: StructuralDir,
selectors: [['', 'structuralDir', '']],
factory: () => structuralDir =
new StructuralDir(directiveInject(ViewContainerRef as any)),
inputs: {tmp: 'tmp'}
});
}
function FooTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']);
{ text(2); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const dir = reference(1) as DirA;
textBinding(2, bind(dir.dirB.value));
}
}
/**
* <div dirB value="declaration">
* <ng-template #foo>
* <div dirA dir="dirA"> {{ dir.dirB.value }} </div>
* </ng-template>
* </div>
*
* <div dirB value="insertion">
* <div structuralDir [tmp]="foo"></div>
* // insertion point
* </div>
*/
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirB', '', 'value', 'declaration']);
{
template(
1, FooTemplate, 3, 1, 'ng-template', null, ['foo', ''], templateRefExtractor);
}
elementEnd();
elementStart(3, 'div', ['dirB', '', 'value', 'insertion']);
{ element(4, 'div', ['structuralDir', '']); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const foo = reference(2) as any;
elementProperty(4, 'tmp', bind(foo));
}
}, 5, 1, [DirA, DirB, StructuralDir]);
const fixture = new ComponentFixture(App);
structuralDir.create();
fixture.update();
expect(fixture.hostElement.textContent).toEqual(`declaration`);
});
it('should create injectors on second template pass', () => {
/**
* <comp dirB></comp>
* <comp dirB></comp>
*/
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'comp', ['dirB', '']);
element(1, 'comp', ['dirB', '']);
}
}, 2, 0, [Comp, DirB]);
const fixture = new ComponentFixture(App);
expect(fixture.hostElement.textContent).toEqual(`DirBDirB`);
});
it('should create injectors and host bindings in same view', () => {
let hostBindingDir !: HostBindingDir;
class HostBindingDir {
// @HostBinding('id')
id = 'foo';
static ngDirectiveDef = defineDirective({
type: HostBindingDir,
selectors: [['', 'hostBindingDir', '']],
factory: () => hostBindingDir = new HostBindingDir(),
hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(1);
}
if (rf & RenderFlags.Update) {
elementProperty(elementIndex, 'id', bind(ctx.id));
}
}
});
}
let dir !: DirA;
/**
* <div dirB hostBindingDir>
* <p dirA #dir="dirA">
* {{ dir.dirB.value }}
* </p>
* </div>
*/
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', [
'dirB',
'',
'hostBindingDir',
'',
]);
{
elementStart(1, 'p', ['dirA', ''], ['dir', 'dirA']);
{ text(3); }
elementEnd();
}
elementEnd();
}
if (rf & RenderFlags.Update) {
dir = reference(2) as DirA;
textBinding(3, bind(dir.dirB.value));
}
}, 4, 1, [HostBindingDir, DirA, DirB]);
const fixture = new ComponentFixture(App);
expect(fixture.hostElement.textContent).toEqual(`DirB`);
const hostDirEl = fixture.hostElement.querySelector('div') as HTMLElement;
expect(hostDirEl.id).toEqual('foo');
// The injector should not be overwritten by host bindings
expect(dir.vcr.injector).toEqual(dir.injector);
hostBindingDir.id = 'bar';
fixture.update();
expect(hostDirEl.id).toEqual('bar');
});
});
it('should create instance even when no injector present', () => {
class MyService {
value = 'MyService';
static ngInjectableDef =
defineInjectable({providedIn: 'root', factory: () => new MyService()});
}
class MyComponent {
constructor(public myService: MyService) {}
static ngComponentDef = defineComponent({
type: MyComponent,
selectors: [['my-component']],
consts: 1,
vars: 1,
factory: () => new MyComponent(directiveInject(MyService)),
template: function(rf: RenderFlags, ctx: MyComponent) {
if (rf & RenderFlags.Create) {
text(0);
}
if (rf & RenderFlags.Update) {
textBinding(0, bind(ctx.myService.value));
}
}
});
}
const fixture = new ComponentFixture(MyComponent);
fixture.update();
expect(fixture.html).toEqual('MyService');
});
it('should throw if directive is not found anywhere', () => {
class Dir {
constructor(siblingDir: OtherDir) {}
static ngDirectiveDef = defineDirective({
selectors: [['', 'dir', '']],
type: Dir,
factory: () => new Dir(directiveInject(OtherDir))
});
}
class OtherDir {
static ngDirectiveDef = defineDirective(
{selectors: [['', 'other', '']], type: OtherDir, factory: () => new OtherDir()});
}
/** <div dir></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dir', '']);
}
}, 1, 0, [Dir, OtherDir]);
expect(() => new ComponentFixture(App)).toThrowError(/Injector: NOT_FOUND \[OtherDir\]/);
});
it('should throw if directive is not found in ancestor tree', () => {
class Dir {
constructor(siblingDir: OtherDir) {}
static ngDirectiveDef = defineDirective({
selectors: [['', 'dir', '']],
type: Dir,
factory: () => new Dir(directiveInject(OtherDir))
});
}
class OtherDir {
static ngDirectiveDef = defineDirective(
{selectors: [['', 'other', '']], type: OtherDir, factory: () => new OtherDir()});
}
/**
* <div other></div>
* <div dir></div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['other', '']);
element(1, 'div', ['dir', '']);
}
}, 2, 0, [Dir, OtherDir]);
expect(() => new ComponentFixture(App)).toThrowError(/Injector: NOT_FOUND \[OtherDir\]/);
});
it('should throw if directives try to inject each other', () => {
class DirA {
constructor(dir: DirB) {}
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirA', '']],
type: DirA,
factory: () => new DirA(directiveInject(DirB))
});
}
class DirB {
constructor(dir: DirA) {}
static ngDirectiveDef = defineDirective({
selectors: [['', 'dirB', '']],
type: DirB,
factory: () => new DirB(directiveInject(DirA))
});
}
/** <div dirA dirB></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirA', '', 'dirB', '']);
}
}, 1, 0, [DirA, DirB]);
expect(() => new ComponentFixture(App)).toThrowError(/Circular dep for/);
});
it('should throw if directive tries to inject itself', () => {
class Dir {
constructor(dir: Dir) {}
static ngDirectiveDef = defineDirective({
selectors: [['', 'dir', '']],
type: Dir,
factory: () => new Dir(directiveInject(Dir))
});
}
/** <div dir></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dir', '']);
}
}, 1, 0, [Dir]);
expect(() => new ComponentFixture(App)).toThrowError(/Circular dep for/);
});
describe('flags', () => {
class DirB {
// TODO(issue/24571): remove '!'.
value !: string;
static ngDirectiveDef = defineDirective({
type: DirB,
selectors: [['', 'dirB', '']],
factory: () => new DirB(),
inputs: {value: 'dirB'}
});
}
describe('Optional', () => {
let dirA: DirA|null = null;
class DirA {
constructor(@Optional() public dirB: DirB|null) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Optional))
});
}
beforeEach(() => dirA = null);
it('should not throw if dependency is @Optional (limp mode)', () => {
/** <div dirA></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirA', '']);
}
}, 1, 0, [DirA, DirB]);
expect(() => { new ComponentFixture(App); }).not.toThrow();
expect(dirA !.dirB).toEqual(null);
});
it('should not throw if dependency is @Optional (module injector)', () => {
class SomeModule {
static ngInjectorDef = defineInjector({factory: () => new SomeModule()});
}
/** <div dirA></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirA', '']);
}
}, 1, 0, [DirA, DirB]);
expect(() => {
const injector = createInjector(SomeModule);
new ComponentFixture(App, {injector});
}).not.toThrow();
expect(dirA !.dirB).toEqual(null);
});
it('should return null if @Optional dependency has @Self flag', () => {
let dirC !: DirC;
class DirC {
constructor(@Optional() @Self() public dirB: DirB|null) {}
static ngDirectiveDef = defineDirective({
type: DirC,
selectors: [['', 'dirC', '']],
factory: () => dirC =
new DirC(directiveInject(DirB, InjectFlags.Optional|InjectFlags.Self))
});
}
/** <div dirC></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirC', '']);
}
}, 1, 0, [DirC, DirB]);
expect(() => { new ComponentFixture(App); }).not.toThrow();
expect(dirC !.dirB).toEqual(null);
});
it('should not throw if dependency is @Optional but defined elsewhere', () => {
let dirA: DirA;
class DirA {
constructor(@Optional() public dirB: DirB|null) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Optional))
});
}
/**
* <div dirB></div>
* <div dirA></div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirB', '']);
element(1, 'div', ['dirA', '']);
}
}, 2, 0, [DirA, DirB]);
expect(() => {
new ComponentFixture(App);
expect(dirA !.dirB).toEqual(null);
}).not.toThrow();
});
});
it('should skip the current node with @SkipSelf', () => {
let dirA: DirA;
class DirA {
constructor(@SkipSelf() public dirB: DirB) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.SkipSelf))
});
}
/** <div dirA dirB="self"></div> */
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirA', '', 'dirB', 'self']);
}
}, 1, 0, [DirA, DirB]);
/* <comp dirB="parent"></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'comp', ['dirB', 'parent']);
}
}, 1, 0, [Comp, DirB]);
new ComponentFixture(App);
expect(dirA !.dirB.value).toEqual('parent');
});
it('should check only the current node with @Self', () => {
let dirA: DirA;
class DirA {
constructor(@Self() public dirB: DirB) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Self))
});
}
/**
* <div dirB>
* <div dirA></div>
* </div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirB', '']);
element(1, 'div', ['dirA', '']);
elementEnd();
}
}, 2, 0, [DirA, DirB]);
expect(() => {
new ComponentFixture(App);
}).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/);
});
it('should check only the current node with @Self even with false positive', () => {
let dirA: DirA;
class DirA {
constructor(@Self() public dirB: DirB) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Self))
});
}
const DirC = createDirective('dirC');
/**
* <div dirB>
* <div dirA dirC></div>
* </div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirB', '']);
element(1, 'div', ['dirA', '', 'dirC', '']);
elementEnd();
}
}, 2, 0, [DirA, DirB, DirC]);
expect(() => {
(DirA as any)['__NG_ELEMENT_ID__'] = 1;
(DirC as any)['__NG_ELEMENT_ID__'] = 257;
new ComponentFixture(App);
}).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/);
});
describe('@Host', () => {
let dirA: DirA|null = null;
let dirString: DirString|null = null;
beforeEach(() => {
dirA = null;
dirString = null;
});
class DirA {
constructor(@Host() public dirB: DirB) {}
static ngDirectiveDef = defineDirective({
type: DirA,
selectors: [['', 'dirA', '']],
factory: () => dirA = new DirA(directiveInject(DirB, InjectFlags.Host))
});
}
class DirString {
constructor(@Host() public s: String) {}
static ngDirectiveDef = defineDirective({
type: DirString,
selectors: [['', 'dirString', '']],
factory: () => dirString = new DirString(directiveInject(String, InjectFlags.Host))
});
}
it('should find viewProviders on the host itself', () => {
/** <div dirString></div> */
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirString', '']);
}
}, 1, 0, [DirString], [], null, [], [{provide: String, useValue: 'Foo'}]);
/* <comp></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'comp');
}
}, 1, 0, [Comp]);
new ComponentFixture(App);
expect(dirString !.s).toEqual('Foo');
});
it('should find host component on the host itself', () => {
let dirComp: DirComp|null = null;
class DirComp {
constructor(@Host() public comp: any) {}
static ngDirectiveDef = defineDirective({
type: DirComp,
selectors: [['', 'dirCmp', '']],
factory: () => dirComp = new DirComp(directiveInject(Comp, InjectFlags.Host))
});
}
/** <div dirCmp></div> */
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirCmp', '']);
}
}, 1, 0, [DirComp]);
/* <comp></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'comp');
}
}, 1, 0, [Comp]);
new ComponentFixture(App);
expect(dirComp !.comp instanceof Comp).toBeTruthy();
});
it('should not find providers on the host itself', () => {
/** <div dirString></div> */
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirString', '']);
}
}, 1, 0, [DirString], [], null, [{provide: String, useValue: 'Foo'}]);
/* <comp></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'comp');
}
}, 1, 0, [Comp]);
expect(() => {
new ComponentFixture(App);
}).toThrowError(/NodeInjector: NOT_FOUND \[String\]/);
});
it('should not find other directives on the host itself', () => {
/** <div dirA></div> */
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirA', '']);
}
}, 1, 0, [DirA]);
/* <comp dirB></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'comp', ['dirB', '']);
}
}, 1, 0, [Comp, DirB]);
expect(() => {
new ComponentFixture(App);
}).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/);
});
it('should not find providers on the host itself if in inline view', () => {
let comp !: any;
/**
* % if (showing) {
* <div dirA></div>
* % }
*/
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
container(0);
}
if (rf & RenderFlags.Update) {
containerRefreshStart(0);
{
if (ctx.showing) {
let rf1 = embeddedViewStart(0, 1, 0);
if (rf1 & RenderFlags.Create) {
element(0, 'div', ['dirA', '']);
}
embeddedViewEnd();
}
}
containerRefreshEnd();
}
}, 1, 0, [DirA, DirB]);
/* <comp dirB></comp> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'comp', ['dirB', '']);
}
if (rf & RenderFlags.Update) {
comp = getDirectiveOnNode(0);
}
}, 1, 0, [Comp, DirB]);
const fixture = new ComponentFixture(App);
expect(() => {
comp.showing = true;
fixture.update();
}).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/);
});
it('should find providers across embedded views if not passing component boundary', () => {
let dirB !: DirB;
function IfTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirA', '']);
}
}
/**
* <div dirB>
* <div *ngIf="showing" dirA></div>
* </div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dirB', '']);
fix(ivy): match attribute selectors for content projection with inline-templates (#29041) The content projection mechanism is static, in that it only looks at the static template nodes before directives are matched and change detection is run. When you have a selector-based content projection the selection is based on nodes that are available in the template. For example: ``` <ng-content selector="[some-attr]"></ng-content> ``` would match ``` <div some-attr="..."></div> ``` If you have an inline-template in your projected nodes. For example: ``` <div *ngIf="..." some-attr="..."></div> ``` This gets pre-parsed and converted to a canonical form. For example: ``` <ng-template [ngIf]="..."> <div some-attr=".."></div> </ng-template> ``` Note that only structural attributes (e.g. `*ngIf`) stay with the `<ng-template>` node. The other attributes move to the contained element inside the template. When this happens in ivy, the ng-template content is removed from the component template function and is compiled into its own template function. But this means that the information about the attributes that were on the content are lost and the projection selection mechanism is unable to match the original `<div *ngIf="..." some-attr>`. This commit adds support for this in ivy. Attributes are separated into three groups (Bindings, Templates and "other"). For inline-templates the Bindings and "other" types are hoisted back from the contained node to the `template()` instruction, so that they can be used in content projection matching. PR Close #29041
2019-03-07 03:31:31 -05:00
{
template(
1, IfTemplate, 1, 0, 'div', ['dirA', '', AttributeMarker.Template, 'ngIf']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
elementProperty(1, 'ngIf', bind(ctx.showing));
// testing only
dirB = getDirectiveOnNode(0);
}
}, 2, 1, [NgIf, DirA, DirB]);
const fixture = new ComponentFixture(App);
fixture.component.showing = true;
fixture.update();
expect(dirA !.dirB).toEqual(dirB);
});
it('should not find component above the host', () => {
let dirComp: DirComp|null = null;
class DirComp {
constructor(@Host() public comp: any) {}
static ngDirectiveDef = defineDirective({
type: DirComp,
selectors: [['', 'dirCmp', '']],
factory: () => dirComp = new DirComp(directiveInject(App, InjectFlags.Host))
});
}
/** <div dirCmp></div> */
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dirCmp', '']);
}
}, 1, 0, [DirComp]);
/* <comp></comp> */
class App {
static ngComponentDef = defineComponent({
type: App,
selectors: [['app']],
consts: 1,
vars: 0,
factory: () => new App,
template: function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'comp');
}
},
directives: [Comp],
});
}
expect(() => {
new ComponentFixture(App);
}).toThrowError(/NodeInjector: NOT_FOUND \[App\]/);
});
describe('regression', () => {
// based on https://stackblitz.com/edit/angular-riss8k?file=src/app/app.component.ts
it('should allow directives with Host flag to inject view providers from containing component',
() => {
let controlContainers: ControlContainer[] = [];
let injectedControlContainer: ControlContainer|null = null;
class ControlContainer {}
/*
@Directive({
selector: '[group]',
providers: [{provide: ControlContainer, useExisting: GroupDirective}]
})
*/
class GroupDirective {
constructor() { controlContainers.push(this); }
static ngDirectiveDef = defineDirective({
type: GroupDirective,
selectors: [['', 'group', '']],
factory: () => new GroupDirective(),
features: [ProvidersFeature(
[{provide: ControlContainer, useExisting: GroupDirective}])],
});
}
// @Directive({selector: '[controlName]'})
class ControlNameDirective {
constructor(@Host() @SkipSelf() @Inject(ControlContainer) parent:
ControlContainer) {
injectedControlContainer = parent;
}
static ngDirectiveDef = defineDirective({
type: ControlNameDirective,
selectors: [['', 'controlName', '']],
factory: () => new ControlNameDirective(directiveInject(
ControlContainer, InjectFlags.Host|InjectFlags.SkipSelf))
});
}
/*
@Component({
selector: 'child',
template: `
<input controlName type="text">
`,
viewProviders: [{provide: ControlContainer, useExisting: GroupDirective}]
})
*/
class ChildComponent {
static ngComponentDef = defineComponent({
type: ChildComponent,
selectors: [['child']],
consts: 1,
vars: 0,
factory: () => new ChildComponent(),
template: function(rf: RenderFlags, ctx: ChildComponent) {
if (rf & RenderFlags.Create) {
element(0, 'input', ['controlName', '', 'type', 'text']);
}
},
directives: [ControlNameDirective],
features: [ProvidersFeature(
[], [{provide: ControlContainer, useExisting: GroupDirective}])],
});
}
/*
@Component({
selector: 'my-app',
template: `
<div group>
<child></child>
</div>
`
})
*/
class AppComponent {
static ngComponentDef = defineComponent({
type: AppComponent,
selectors: [['my-app']],
consts: 2,
vars: 0,
factory: () => new AppComponent(),
template: function(rf: RenderFlags, ctx: AppComponent) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['group', '']);
element(1, 'child');
elementEnd();
}
},
directives: [ChildComponent, GroupDirective]
});
}
const fixture = new ComponentFixture(AppComponent as ComponentType<AppComponent>);
expect(fixture.html)
.toEqual(
'<div group=""><child><input controlname="" type="text"></child></div>');
expect(controlContainers).toEqual([injectedControlContainer !]);
});
});
});
});
});
describe('Special tokens', () => {
describe('Injector', () => {
it('should inject the injector', () => {
let injectorDir !: InjectorDir;
let otherInjectorDir !: OtherInjectorDir;
let divElement !: HTMLElement;
class InjectorDir {
constructor(public injector: Injector) {}
static ngDirectiveDef = defineDirective({
type: InjectorDir,
selectors: [['', 'injectorDir', '']],
factory: () => injectorDir = new InjectorDir(directiveInject(Injector as any))
});
}
class OtherInjectorDir {
constructor(public otherDir: InjectorDir, public injector: Injector) {}
static ngDirectiveDef = defineDirective({
type: OtherInjectorDir,
selectors: [['', 'otherInjectorDir', '']],
factory: () => otherInjectorDir = new OtherInjectorDir(
directiveInject(InjectorDir), directiveInject(Injector as any))
});
}
/** <div injectorDir otherInjectorDir></div> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'div', ['injectorDir', '', 'otherInjectorDir', '']);
}
// testing only
divElement = load(0);
}, 1, 0, [InjectorDir, OtherInjectorDir]);
const fixture = new ComponentFixture(App);
expect(injectorDir.injector.get(ElementRef).nativeElement).toBe(divElement);
expect(otherInjectorDir.injector.get(ElementRef).nativeElement).toBe(divElement);
expect(otherInjectorDir.injector.get(InjectorDir)).toBe(injectorDir);
expect(injectorDir.injector).not.toBe(otherInjectorDir.injector);
});
it('should inject INJECTOR', () => {
let injectorDir !: INJECTORDir;
let divElement !: HTMLElement;
class INJECTORDir {
constructor(public injector: Injector) {}
static ngDirectiveDef = defineDirective({
type: INJECTORDir,
selectors: [['', 'injectorDir', '']],
factory: () => injectorDir = new INJECTORDir(directiveInject(INJECTOR as any))
});
}
/** <div injectorDir otherInjectorDir></div> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'div', ['injectorDir', '']);
}
// testing only
divElement = load(0);
}, 1, 0, [INJECTORDir]);
const fixture = new ComponentFixture(App);
expect(injectorDir.injector.get(ElementRef).nativeElement).toBe(divElement);
expect(injectorDir.injector.get(Injector).get(ElementRef).nativeElement).toBe(divElement);
expect(injectorDir.injector.get(INJECTOR).get(ElementRef).nativeElement).toBe(divElement);
});
});
describe('ElementRef', () => {
it('should create directive with ElementRef dependencies', () => {
let dir !: Directive;
let dirSameInstance !: DirectiveSameInstance;
let div !: RElement;
class Directive {
value: string;
constructor(public elementRef: ElementRef) {
this.value = (elementRef.constructor as any).name;
}
static ngDirectiveDef = defineDirective({
type: Directive,
selectors: [['', 'dir', '']],
factory: () => dir = new Directive(directiveInject(ElementRef)),
exportAs: ['dir']
});
}
class DirectiveSameInstance {
isSameInstance: boolean;
constructor(public elementRef: ElementRef, directive: Directive) {
this.isSameInstance = elementRef === directive.elementRef;
}
static ngDirectiveDef = defineDirective({
type: DirectiveSameInstance,
selectors: [['', 'dirSame', '']],
factory: () => dirSameInstance = new DirectiveSameInstance(
directiveInject(ElementRef), directiveInject(Directive)),
exportAs: ['dirSame']
});
}
/** <div dir dirSame></div> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dir', '', 'dirSame', '']);
elementEnd();
div = getNativeByIndex(0, getLView()) as RElement;
}
}, 1, 0, [Directive, DirectiveSameInstance]);
const fixture = new ComponentFixture(App);
expect(dir.value).toContain('ElementRef');
expect(dir.elementRef.nativeElement).toEqual(div);
expect(dirSameInstance.elementRef.nativeElement).toEqual(div);
// Each ElementRef instance should be unique
expect(dirSameInstance.isSameInstance).toBe(false);
});
it('should create ElementRef with comment if requesting directive is on <ng-template> node',
() => {
let dir !: Directive;
let lContainer !: LContainer;
class Directive {
value: string;
constructor(public elementRef: ElementRef) {
this.value = (elementRef.constructor as any).name;
}
static ngDirectiveDef = defineDirective({
type: Directive,
selectors: [['', 'dir', '']],
factory: () => dir = new Directive(directiveInject(ElementRef)),
exportAs: ['dir']
});
}
/** <ng-template dir></ng-template> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
template(0, () => {}, 0, 0, 'ng-template', ['dir', '']);
lContainer = load(0) as any;
}
}, 1, 0, [Directive]);
const fixture = new ComponentFixture(App);
expect(dir.value).toContain('ElementRef');
expect(dir.elementRef.nativeElement).toEqual(lContainer[NATIVE]);
});
});
describe('TemplateRef', () => {
class Directive {
value: string;
constructor(public templateRef: TemplateRef<any>) {
this.value = (templateRef.constructor as any).name;
}
static ngDirectiveDef = defineDirective({
type: Directive,
selectors: [['', 'dir', '']],
factory: () => new Directive(directiveInject(TemplateRef as any)),
exportAs: ['dir']
});
}
it('should create directive with TemplateRef dependencies', () => {
class DirectiveSameInstance {
isSameInstance: boolean;
constructor(templateRef: TemplateRef<any>, directive: Directive) {
this.isSameInstance = templateRef === directive.templateRef;
}
static ngDirectiveDef = defineDirective({
type: DirectiveSameInstance,
selectors: [['', 'dirSame', '']],
factory: () => new DirectiveSameInstance(
directiveInject(TemplateRef as any), directiveInject(Directive)),
exportAs: ['dirSame']
});
}
/**
* <ng-template dir dirSame #dir="dir" #dirSame="dirSame">
* {{ dir.value }} - {{ dirSame.value }}
* </ng-template>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
template(
0, function() {}, 0, 0, 'ng-template', ['dir', '', 'dirSame', ''],
['dir', 'dir', 'dirSame', 'dirSame']);
text(3);
}
if (rf & RenderFlags.Update) {
const tmp1 = reference(1) as any;
const tmp2 = reference(2) as any;
textBinding(3, interpolation2('', tmp1.value, '-', tmp2.isSameInstance, ''));
}
}, 4, 2, [Directive, DirectiveSameInstance]);
const fixture = new ComponentFixture(App);
// Each TemplateRef instance should be unique
expect(fixture.html).toContain('TemplateRef');
expect(fixture.html).toContain('false');
});
it('should throw if injected on an element', () => {
/** <div dir></div> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dir', '']);
}
}, 1, 0, [Directive]);
expect(() => new ComponentFixture(App)).toThrowError(/No provider for TemplateRef/);
});
it('should throw if injected on an ng-container', () => {
/** <ng-container dir></ng-container> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
elementContainerStart(0, ['dir', '']);
elementContainerEnd();
}
}, 1, 0, [Directive]);
expect(() => new ComponentFixture(App)).toThrowError(/No provider for TemplateRef/);
});
it('should NOT throw if optional and injected on an element', () => {
let dir !: OptionalDirective;
class OptionalDirective {
constructor(@Optional() public templateRef: TemplateRef<any>) {}
static ngDirectiveDef = defineDirective({
type: OptionalDirective,
selectors: [['', 'dir', '']],
factory: () => dir = new OptionalDirective(
directiveInject(TemplateRef as any, InjectFlags.Optional)),
exportAs: ['dir']
});
}
/** <div dir></div> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dir', '']);
}
}, 1, 0, [OptionalDirective]);
expect(() => new ComponentFixture(App)).not.toThrow();
expect(dir.templateRef).toBeNull();
});
});
describe('ViewContainerRef', () => {
it('should create directive with ViewContainerRef dependencies', () => {
class Directive {
value: string;
constructor(public viewContainerRef: ViewContainerRef) {
this.value = (viewContainerRef.constructor as any).name;
}
static ngDirectiveDef = defineDirective({
type: Directive,
selectors: [['', 'dir', '']],
factory: () => new Directive(directiveInject(ViewContainerRef as any)),
exportAs: ['dir']
});
}
class DirectiveSameInstance {
isSameInstance: boolean;
constructor(viewContainerRef: ViewContainerRef, directive: Directive) {
this.isSameInstance = viewContainerRef === directive.viewContainerRef;
}
static ngDirectiveDef = defineDirective({
type: DirectiveSameInstance,
selectors: [['', 'dirSame', '']],
factory: () => new DirectiveSameInstance(
directiveInject(ViewContainerRef as any), directiveInject(Directive)),
exportAs: ['dirSame']
});
}
/**
* <div dir dirSame #dir="dir" #dirSame="dirSame">
* {{ dir.value }} - {{ dirSame.value }}
* </div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(
0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir', 'dirSame', 'dirSame']);
{ text(3); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const tmp1 = reference(1) as any;
const tmp2 = reference(2) as any;
textBinding(3, interpolation2('', tmp1.value, '-', tmp2.isSameInstance, ''));
}
}, 4, 2, [Directive, DirectiveSameInstance]);
const fixture = new ComponentFixture(App);
// Each ViewContainerRef instance should be unique
expect(fixture.html).toContain('ViewContainerRef');
expect(fixture.html).toContain('false');
});
});
describe('ChangeDetectorRef', () => {
let dir: Directive;
let dirSameInstance: DirectiveSameInstance;
let comp: MyComp;
class MyComp {
constructor(public cdr: ChangeDetectorRef) {}
static ngComponentDef = defineComponent({
type: MyComp,
selectors: [['my-comp']],
factory: () => comp = new MyComp(directiveInject(ChangeDetectorRef as any)),
consts: 1,
vars: 0,
template: function(rf: RenderFlags, ctx: MyComp) {
if (rf & RenderFlags.Create) {
projectionDef();
projection(0);
}
}
});
}
class Directive {
value: string;
constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; }
static ngDirectiveDef = defineDirective({
type: Directive,
selectors: [['', 'dir', '']],
factory: () => dir = new Directive(directiveInject(ChangeDetectorRef as any)),
exportAs: ['dir']
});
}
class DirectiveSameInstance {
constructor(public cdr: ChangeDetectorRef) {}
static ngDirectiveDef = defineDirective({
type: DirectiveSameInstance,
selectors: [['', 'dirSame', '']],
factory: () => dirSameInstance =
new DirectiveSameInstance(directiveInject(ChangeDetectorRef as any))
});
}
const directives = [MyComp, Directive, DirectiveSameInstance, NgIf];
it('should inject current component ChangeDetectorRef into directives on the same node as components',
() => {
fix(ivy): match attribute selectors for content projection with inline-templates (#29041) The content projection mechanism is static, in that it only looks at the static template nodes before directives are matched and change detection is run. When you have a selector-based content projection the selection is based on nodes that are available in the template. For example: ``` <ng-content selector="[some-attr]"></ng-content> ``` would match ``` <div some-attr="..."></div> ``` If you have an inline-template in your projected nodes. For example: ``` <div *ngIf="..." some-attr="..."></div> ``` This gets pre-parsed and converted to a canonical form. For example: ``` <ng-template [ngIf]="..."> <div some-attr=".."></div> </ng-template> ``` Note that only structural attributes (e.g. `*ngIf`) stay with the `<ng-template>` node. The other attributes move to the contained element inside the template. When this happens in ivy, the ng-template content is removed from the component template function and is compiled into its own template function. But this means that the information about the attributes that were on the content are lost and the projection selection mechanism is unable to match the original `<div *ngIf="..." some-attr>`. This commit adds support for this in ivy. Attributes are separated into three groups (Bindings, Templates and "other"). For inline-templates the Bindings and "other" types are hoisted back from the contained node to the `template()` instruction, so that they can be used in content projection matching. PR Close #29041
2019-03-07 03:31:31 -05:00
/** <my-comp dir dirSame #dir="dir"></my-comp> {{ dir.value }} */
const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'my-comp', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
text(2);
}
if (rf & RenderFlags.Update) {
const tmp = reference(1) as any;
textBinding(2, bind(tmp.value));
}
}, 3, 1, directives);
const app = renderComponent(MyApp);
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
expect(toHtml(app)).toEqual('<my-comp dir="" dirsame=""></my-comp>ViewRef');
expect((comp !.cdr as ViewRef<MyComp>).context).toBe(comp);
// Each ChangeDetectorRef instance should be unique
expect(dir !.cdr).not.toBe(comp !.cdr);
expect(dir !.cdr).not.toBe(dirSameInstance !.cdr);
});
it('should inject host component ChangeDetectorRef into directives on normal elements',
() => {
class MyApp {
constructor(public cdr: ChangeDetectorRef) {}
static ngComponentDef = defineComponent({
type: MyApp,
selectors: [['my-app']],
consts: 3,
vars: 1,
factory: () => new MyApp(directiveInject(ChangeDetectorRef as any)),
fix(ivy): match attribute selectors for content projection with inline-templates (#29041) The content projection mechanism is static, in that it only looks at the static template nodes before directives are matched and change detection is run. When you have a selector-based content projection the selection is based on nodes that are available in the template. For example: ``` <ng-content selector="[some-attr]"></ng-content> ``` would match ``` <div some-attr="..."></div> ``` If you have an inline-template in your projected nodes. For example: ``` <div *ngIf="..." some-attr="..."></div> ``` This gets pre-parsed and converted to a canonical form. For example: ``` <ng-template [ngIf]="..."> <div some-attr=".."></div> </ng-template> ``` Note that only structural attributes (e.g. `*ngIf`) stay with the `<ng-template>` node. The other attributes move to the contained element inside the template. When this happens in ivy, the ng-template content is removed from the component template function and is compiled into its own template function. But this means that the information about the attributes that were on the content are lost and the projection selection mechanism is unable to match the original `<div *ngIf="..." some-attr>`. This commit adds support for this in ivy. Attributes are separated into three groups (Bindings, Templates and "other"). For inline-templates the Bindings and "other" types are hoisted back from the contained node to the `template()` instruction, so that they can be used in content projection matching. PR Close #29041
2019-03-07 03:31:31 -05:00
/** <div dir dirSame #dir="dir"> {{ dir.value }} </div> */
template: function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
{ text(2); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const tmp = reference(1) as any;
textBinding(2, bind(tmp.value));
}
},
directives: directives
});
}
const app = renderComponent(MyApp);
expect(toHtml(app)).toEqual('<div dir="" dirsame="">ViewRef</div>');
expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);
// Each ChangeDetectorRef instance should be unique
expect(dir !.cdr).not.toBe(app.cdr);
expect(dir !.cdr).not.toBe(dirSameInstance !.cdr);
});
it('should inject host component ChangeDetectorRef into directives in a component\'s ContentChildren',
() => {
class MyApp {
constructor(public cdr: ChangeDetectorRef) {}
static ngComponentDef = defineComponent({
type: MyApp,
selectors: [['my-app']],
consts: 4,
vars: 1,
factory: () => new MyApp(directiveInject(ChangeDetectorRef as any)),
/**
* <my-comp>
fix(ivy): match attribute selectors for content projection with inline-templates (#29041) The content projection mechanism is static, in that it only looks at the static template nodes before directives are matched and change detection is run. When you have a selector-based content projection the selection is based on nodes that are available in the template. For example: ``` <ng-content selector="[some-attr]"></ng-content> ``` would match ``` <div some-attr="..."></div> ``` If you have an inline-template in your projected nodes. For example: ``` <div *ngIf="..." some-attr="..."></div> ``` This gets pre-parsed and converted to a canonical form. For example: ``` <ng-template [ngIf]="..."> <div some-attr=".."></div> </ng-template> ``` Note that only structural attributes (e.g. `*ngIf`) stay with the `<ng-template>` node. The other attributes move to the contained element inside the template. When this happens in ivy, the ng-template content is removed from the component template function and is compiled into its own template function. But this means that the information about the attributes that were on the content are lost and the projection selection mechanism is unable to match the original `<div *ngIf="..." some-attr>`. This commit adds support for this in ivy. Attributes are separated into three groups (Bindings, Templates and "other"). For inline-templates the Bindings and "other" types are hoisted back from the contained node to the `template()` instruction, so that they can be used in content projection matching. PR Close #29041
2019-03-07 03:31:31 -05:00
* <div dir dirSame #dir="dir"></div>
* </my-comp>
* {{ dir.value }}
*/
template: function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'my-comp');
{ element(1, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']); }
elementEnd();
text(3);
}
if (rf & RenderFlags.Update) {
const tmp = reference(2) as any;
textBinding(3, bind(tmp.value));
}
},
directives: directives
});
}
const app = renderComponent(MyApp);
expect(toHtml(app)).toEqual('<my-comp><div dir="" dirsame=""></div></my-comp>ViewRef');
expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);
// Each ChangeDetectorRef instance should be unique
expect(dir !.cdr).not.toBe(app !.cdr);
expect(dir !.cdr).not.toBe(dirSameInstance !.cdr);
});
it('should inject host component ChangeDetectorRef into directives in embedded views', () => {
class MyApp {
showing = true;
constructor(public cdr: ChangeDetectorRef) {}
static ngComponentDef = defineComponent({
type: MyApp,
selectors: [['my-app']],
factory: () => new MyApp(directiveInject(ChangeDetectorRef as any)),
consts: 1,
vars: 0,
/**
* % if (showing) {
fix(ivy): match attribute selectors for content projection with inline-templates (#29041) The content projection mechanism is static, in that it only looks at the static template nodes before directives are matched and change detection is run. When you have a selector-based content projection the selection is based on nodes that are available in the template. For example: ``` <ng-content selector="[some-attr]"></ng-content> ``` would match ``` <div some-attr="..."></div> ``` If you have an inline-template in your projected nodes. For example: ``` <div *ngIf="..." some-attr="..."></div> ``` This gets pre-parsed and converted to a canonical form. For example: ``` <ng-template [ngIf]="..."> <div some-attr=".."></div> </ng-template> ``` Note that only structural attributes (e.g. `*ngIf`) stay with the `<ng-template>` node. The other attributes move to the contained element inside the template. When this happens in ivy, the ng-template content is removed from the component template function and is compiled into its own template function. But this means that the information about the attributes that were on the content are lost and the projection selection mechanism is unable to match the original `<div *ngIf="..." some-attr>`. This commit adds support for this in ivy. Attributes are separated into three groups (Bindings, Templates and "other"). For inline-templates the Bindings and "other" types are hoisted back from the contained node to the `template()` instruction, so that they can be used in content projection matching. PR Close #29041
2019-03-07 03:31:31 -05:00
* <div dir dirSame #dir="dir"> {{ dir.value }} </div>
* % }
*/
template: function(rf: RenderFlags, ctx: MyApp) {
if (rf & RenderFlags.Create) {
container(0);
}
if (rf & RenderFlags.Update) {
containerRefreshStart(0);
{
if (ctx.showing) {
let rf1 = embeddedViewStart(0, 3, 1);
if (rf1 & RenderFlags.Create) {
elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
{ text(2); }
elementEnd();
}
if (rf1 & RenderFlags.Update) {
const tmp = reference(1) as any;
textBinding(2, bind(tmp.value));
}
}
embeddedViewEnd();
}
containerRefreshEnd();
}
},
directives: directives
});
}
const app = renderComponent(MyApp);
expect(toHtml(app)).toEqual('<div dir="" dirsame="">ViewRef</div>');
expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);
// Each ChangeDetectorRef instance should be unique
expect(dir !.cdr).not.toBe(app.cdr);
expect(dir !.cdr).not.toBe(dirSameInstance !.cdr);
});
it('should inject host component ChangeDetectorRef into directives on containers', () => {
function C1(rf1: RenderFlags, ctx1: any) {
if (rf1 & RenderFlags.Create) {
elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
{ text(2); }
elementEnd();
}
if (rf1 & RenderFlags.Update) {
const tmp = reference(1) as any;
textBinding(2, bind(tmp.value));
}
}
class MyApp {
showing = true;
constructor(public cdr: ChangeDetectorRef) {}
static ngComponentDef = defineComponent({
type: MyApp,
selectors: [['my-app']],
factory: () => new MyApp(directiveInject(ChangeDetectorRef as any)),
consts: 1,
vars: 0,
fix(ivy): match attribute selectors for content projection with inline-templates (#29041) The content projection mechanism is static, in that it only looks at the static template nodes before directives are matched and change detection is run. When you have a selector-based content projection the selection is based on nodes that are available in the template. For example: ``` <ng-content selector="[some-attr]"></ng-content> ``` would match ``` <div some-attr="..."></div> ``` If you have an inline-template in your projected nodes. For example: ``` <div *ngIf="..." some-attr="..."></div> ``` This gets pre-parsed and converted to a canonical form. For example: ``` <ng-template [ngIf]="..."> <div some-attr=".."></div> </ng-template> ``` Note that only structural attributes (e.g. `*ngIf`) stay with the `<ng-template>` node. The other attributes move to the contained element inside the template. When this happens in ivy, the ng-template content is removed from the component template function and is compiled into its own template function. But this means that the information about the attributes that were on the content are lost and the projection selection mechanism is unable to match the original `<div *ngIf="..." some-attr>`. This commit adds support for this in ivy. Attributes are separated into three groups (Bindings, Templates and "other"). For inline-templates the Bindings and "other" types are hoisted back from the contained node to the `template()` instruction, so that they can be used in content projection matching. PR Close #29041
2019-03-07 03:31:31 -05:00
/** <div *ngIf="showing" dir dirSame #dir="dir"> {{ dir.value }} </div> */
template: function(rf: RenderFlags, ctx: MyApp) {
if (rf & RenderFlags.Create) {
fix(ivy): match attribute selectors for content projection with inline-templates (#29041) The content projection mechanism is static, in that it only looks at the static template nodes before directives are matched and change detection is run. When you have a selector-based content projection the selection is based on nodes that are available in the template. For example: ``` <ng-content selector="[some-attr]"></ng-content> ``` would match ``` <div some-attr="..."></div> ``` If you have an inline-template in your projected nodes. For example: ``` <div *ngIf="..." some-attr="..."></div> ``` This gets pre-parsed and converted to a canonical form. For example: ``` <ng-template [ngIf]="..."> <div some-attr=".."></div> </ng-template> ``` Note that only structural attributes (e.g. `*ngIf`) stay with the `<ng-template>` node. The other attributes move to the contained element inside the template. When this happens in ivy, the ng-template content is removed from the component template function and is compiled into its own template function. But this means that the information about the attributes that were on the content are lost and the projection selection mechanism is unable to match the original `<div *ngIf="..." some-attr>`. This commit adds support for this in ivy. Attributes are separated into three groups (Bindings, Templates and "other"). For inline-templates the Bindings and "other" types are hoisted back from the contained node to the `template()` instruction, so that they can be used in content projection matching. PR Close #29041
2019-03-07 03:31:31 -05:00
template(
0, C1, 3, 1, 'div',
['dir', '', 'dirSame', '', AttributeMarker.Template, 'ngIf']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngIf', bind(ctx.showing));
}
},
directives: directives
});
}
const app = renderComponent(MyApp);
expect(toHtml(app)).toEqual('<div dir="" dirsame="">ViewRef</div>');
expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);
// Each ChangeDetectorRef instance should be unique
expect(dir !.cdr).not.toBe(app.cdr);
expect(dir !.cdr).not.toBe(dirSameInstance !.cdr);
});
});
});
describe('string tokens', () => {
it('should be able to provide a string token', () => {
let injectorDir !: InjectorDir;
let divElement !: HTMLElement;
class InjectorDir {
constructor(public value: string) {}
static ngDirectiveDef = defineDirective({
type: InjectorDir,
selectors: [['', 'injectorDir', '']],
factory: () => injectorDir = new InjectorDir(directiveInject('test' as any)),
features: [ProvidersFeature([{provide: 'test', useValue: 'provided'}])],
});
}
/** <div injectorDir otherInjectorDir></div> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'div', ['injectorDir', '']);
}
// testing only
divElement = load(0);
}, 1, 0, [InjectorDir]);
const fixture = new ComponentFixture(App);
expect(injectorDir.value).toBe('provided');
});
});
describe('Renderer2', () => {
class MyComp {
constructor(public renderer: Renderer2) {}
static ngComponentDef = defineComponent({
type: MyComp,
selectors: [['my-comp']],
factory: () => new MyComp(directiveInject(Renderer2 as any)),
consts: 1,
vars: 0,
template: function(rf: RenderFlags, ctx: MyComp) {
if (rf & RenderFlags.Create) {
text(0, 'Foo');
}
}
});
}
it('should inject the Renderer2 used by the application', () => {
const rendererFactory = getRendererFactory2(document);
const fixture = new ComponentFixture(MyComp, {rendererFactory: rendererFactory});
expect(isProceduralRenderer(fixture.component.renderer)).toBeTruthy();
});
it('should throw when injecting Renderer2 but the application is using Renderer3',
() => { expect(() => new ComponentFixture(MyComp)).toThrow(); });
});
describe('@Attribute', () => {
let myDirectiveInstance !: MyDirective | null;
class MyDirective {
exists = 'wrong' as string | null;
myDirective = 'wrong' as string | null;
constructor(
@Attribute('exist') existAttrValue: string|null,
@Attribute('myDirective') myDirectiveAttrValue: string|null) {
this.exists = existAttrValue;
this.myDirective = myDirectiveAttrValue;
}
static ngDirectiveDef = defineDirective({
type: MyDirective,
selectors: [['', 'myDirective', '']],
factory: () => myDirectiveInstance =
new MyDirective(injectAttribute('exist'), injectAttribute('myDirective'))
});
}
beforeEach(() => myDirectiveInstance = null);
it('should inject attribute', () => {
let exist = 'wrong' as string | null;
let nonExist = 'wrong' as string | null;
const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['exist', 'existValue', 'other', 'ignore']);
exist = injectAttribute('exist');
nonExist = injectAttribute('nonExist');
}
}, 1);
new ComponentFixture(MyApp);
expect(exist).toEqual('existValue');
expect(nonExist).toBeNull();
});
// https://stackblitz.com/edit/angular-scawyi?file=src%2Fapp%2Fapp.component.ts
it('should inject attributes on <ng-template>', () => {
let myDirectiveInstance: MyDirective;
/* <ng-template myDirective="initial" exist="existValue" other="ignore"></ng-template>*/
const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
template(
0, null, 0, 0, 'ng-template',
['myDirective', 'initial', 'exist', 'existValue', 'other', 'ignore']);
}
if (rf & RenderFlags.Update) {
myDirectiveInstance = getDirectiveOnNode(0);
}
}, 1, 0, [MyDirective]);
new ComponentFixture(MyApp);
expect(myDirectiveInstance !.exists).toEqual('existValue');
expect(myDirectiveInstance !.myDirective).toEqual('initial');
});
// https://stackblitz.com/edit/angular-scawyi?file=src%2Fapp%2Fapp.component.ts
it('should inject attributes on <ng-container>', () => {
let myDirectiveInstance: MyDirective;
/* <ng-container myDirective="initial" exist="existValue" other="ignore"></ng-container>*/
const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementContainerStart(
0, ['myDirective', 'initial', 'exist', 'existValue', 'other', 'ignore']);
elementContainerEnd();
}
if (rf & RenderFlags.Update) {
myDirectiveInstance = getDirectiveOnNode(0);
}
}, 1, 0, [MyDirective]);
new ComponentFixture(MyApp);
expect(myDirectiveInstance !.exists).toEqual('existValue');
expect(myDirectiveInstance !.myDirective).toEqual('initial');
});
// https://stackblitz.com/edit/angular-8ytqkp?file=src%2Fapp%2Fapp.component.ts
it('should not inject attributes representing bindings and outputs', () => {
let exist = 'wrong' as string | null;
let nonExist = 'wrong' as string | null;
const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['exist', 'existValue', AttributeMarker.Bindings, 'nonExist']);
exist = injectAttribute('exist');
nonExist = injectAttribute('nonExist');
}
}, 1);
new ComponentFixture(MyApp);
expect(exist).toEqual('existValue');
expect(nonExist).toBeNull();
});
it('should not accidentally inject attributes representing bindings and outputs', () => {
let exist = 'wrong' as string | null;
let nonExist = 'wrong' as string | null;
const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', [
'exist', 'existValue', AttributeMarker.Bindings, 'binding1', 'nonExist', 'binding2'
]);
exist = injectAttribute('exist');
nonExist = injectAttribute('nonExist');
}
}, 1);
new ComponentFixture(MyApp);
expect(exist).toEqual('existValue');
expect(nonExist).toBeNull();
});
});
describe('inject', () => {
describe('bloom filter', () => {
let mockTView: any;
beforeEach(() => {
mockTView = {data: [0, 0, 0, 0, 0, 0, 0, 0, null], firstTemplatePass: true};
});
function bloomState() { return mockTView.data.slice(0, TNODE).reverse(); }
class Dir0 {
/** @internal */ static __NG_ELEMENT_ID__ = 0;
}
class Dir1 {
/** @internal */ static __NG_ELEMENT_ID__ = 1;
}
class Dir33 {
/** @internal */ static __NG_ELEMENT_ID__ = 33;
}
class Dir66 {
/** @internal */ static __NG_ELEMENT_ID__ = 66;
}
class Dir99 {
/** @internal */ static __NG_ELEMENT_ID__ = 99;
}
class Dir132 {
/** @internal */ static __NG_ELEMENT_ID__ = 132;
}
class Dir165 {
/** @internal */ static __NG_ELEMENT_ID__ = 165;
}
class Dir198 {
/** @internal */ static __NG_ELEMENT_ID__ = 198;
}
class Dir231 {
/** @internal */ static __NG_ELEMENT_ID__ = 231;
}
it('should add values', () => {
bloomAdd(0, mockTView, Dir0);
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 0, 1]);
bloomAdd(0, mockTView, Dir33);
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 2, 1]);
bloomAdd(0, mockTView, Dir66);
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 4, 2, 1]);
bloomAdd(0, mockTView, Dir99);
expect(bloomState()).toEqual([0, 0, 0, 0, 8, 4, 2, 1]);
bloomAdd(0, mockTView, Dir132);
expect(bloomState()).toEqual([0, 0, 0, 16, 8, 4, 2, 1]);
bloomAdd(0, mockTView, Dir165);
expect(bloomState()).toEqual([0, 0, 32, 16, 8, 4, 2, 1]);
bloomAdd(0, mockTView, Dir198);
expect(bloomState()).toEqual([0, 64, 32, 16, 8, 4, 2, 1]);
bloomAdd(0, mockTView, Dir231);
expect(bloomState()).toEqual([128, 64, 32, 16, 8, 4, 2, 1]);
});
it('should query values', () => {
bloomAdd(0, mockTView, Dir0);
bloomAdd(0, mockTView, Dir33);
bloomAdd(0, mockTView, Dir66);
bloomAdd(0, mockTView, Dir99);
bloomAdd(0, mockTView, Dir132);
bloomAdd(0, mockTView, Dir165);
bloomAdd(0, mockTView, Dir198);
bloomAdd(0, mockTView, Dir231);
expect(bloomHasToken(bloomHash(Dir0) as number, 0, mockTView.data)).toEqual(true);
expect(bloomHasToken(bloomHash(Dir1) as number, 0, mockTView.data)).toEqual(false);
expect(bloomHasToken(bloomHash(Dir33) as number, 0, mockTView.data)).toEqual(true);
expect(bloomHasToken(bloomHash(Dir66) as number, 0, mockTView.data)).toEqual(true);
expect(bloomHasToken(bloomHash(Dir99) as number, 0, mockTView.data)).toEqual(true);
expect(bloomHasToken(bloomHash(Dir132) as number, 0, mockTView.data)).toEqual(true);
expect(bloomHasToken(bloomHash(Dir165) as number, 0, mockTView.data)).toEqual(true);
expect(bloomHasToken(bloomHash(Dir198) as number, 0, mockTView.data)).toEqual(true);
expect(bloomHasToken(bloomHash(Dir231) as number, 0, mockTView.data)).toEqual(true);
});
});
it('should inject from parent view', () => {
const ParentDirective = createDirective('parentDir');
class ChildDirective {
value: string;
constructor(public parent: any) { this.value = (parent.constructor as any).name; }
static ngDirectiveDef = defineDirective({
type: ChildDirective,
selectors: [['', 'childDir', '']],
factory: () => new ChildDirective(directiveInject(ParentDirective)),
exportAs: ['childDir']
});
}
class Child2Directive {
value: boolean;
constructor(parent: any, child: ChildDirective) { this.value = parent === child.parent; }
static ngDirectiveDef = defineDirective({
selectors: [['', 'child2Dir', '']],
type: Child2Directive,
factory: () => new Child2Directive(
directiveInject(ParentDirective), directiveInject(ChildDirective)),
exportAs: ['child2Dir']
});
}
/**
* <div parentDir>
* % if (...) {
* <span childDir child2Dir #child1="childDir" #child2="child2Dir">
* {{ child1.value }} - {{ child2.value }}
* </span>
* % }
* </div>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['parentDir', '']);
{ container(1); }
elementEnd();
}
if (rf & RenderFlags.Update) {
containerRefreshStart(1);
{
let rf1 = embeddedViewStart(0, 4, 2);
if (rf1 & RenderFlags.Create) {
elementStart(
0, 'span', ['childDir', '', 'child2Dir', ''],
['child1', 'childDir', 'child2', 'child2Dir']);
{ text(3); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const tmp1 = reference(1) as any;
const tmp2 = reference(2) as any;
textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, ''));
}
embeddedViewEnd();
}
containerRefreshEnd();
}
}, 2, 0, [ChildDirective, Child2Directive, ParentDirective]);
const fixture = new ComponentFixture(App);
expect(fixture.html)
.toEqual('<div parentdir=""><span child2dir="" childdir="">Directive-true</span></div>');
});
it('should inject from module Injector', () => {
});
});
describe('getOrCreateNodeInjector', () => {
it('should handle initial undefined state', () => {
const contentView = createLView(
null, createTView(-1, null, 1, 0, null, null, null, null), null, LViewFlags.CheckAlways,
null, null, {} as any, {} as any);
const oldView = enterView(contentView, null);
try {
const parentTNode = createNodeAtIndex(0, TNodeType.Element, null, null, null);
// Simulate the situation where the previous parent is not initialized.
// This happens on first bootstrap because we don't init existing values
// so that we have smaller HelloWorld.
(parentTNode as{parent: any}).parent = undefined;
const injector = getOrCreateNodeInjectorForNode(parentTNode, contentView);
expect(injector).not.toEqual(-1);
} finally {
leaveView(oldView);
}
});
});
});