2017-12-01 17:23:03 -05:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2018-02-26 19:58:15 -05:00
|
|
|
import {ChangeDetectorRef, ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
|
2017-12-01 17:23:03 -05:00
|
|
|
|
2018-01-17 13:09:05 -05:00
|
|
|
import {defineComponent} from '../../src/render3/definition';
|
2018-03-01 01:18:34 -05:00
|
|
|
import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di';
|
2018-03-04 23:21:23 -05:00
|
|
|
import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
|
2018-03-27 14:01:52 -04:00
|
|
|
import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
2018-01-09 21:38:17 -05:00
|
|
|
import {LInjector} from '../../src/render3/interfaces/injector';
|
2018-03-20 22:06:49 -04:00
|
|
|
import {LNodeType} from '../../src/render3/interfaces/node';
|
2018-02-23 16:17:20 -05:00
|
|
|
import {LViewFlags} from '../../src/render3/interfaces/view';
|
2018-02-26 19:58:15 -05:00
|
|
|
import {ViewRef} from '../../src/render3/view_ref';
|
2017-12-01 17:23:03 -05:00
|
|
|
|
2018-03-26 00:32:39 -04:00
|
|
|
import {createComponent, createDirective, renderComponent, renderToHtml, toHtml} from './render_util';
|
2017-12-01 17:23:03 -05:00
|
|
|
|
|
|
|
describe('di', () => {
|
|
|
|
describe('no dependencies', () => {
|
|
|
|
it('should create directive with no deps', () => {
|
|
|
|
class Directive {
|
|
|
|
value: string = 'Created';
|
2018-03-27 14:01:52 -04:00
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: Directive,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dir', '']],
|
2018-03-27 14:01:52 -04:00
|
|
|
factory: () => new Directive,
|
|
|
|
exportAs: 'dir'
|
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-27 14:01:52 -04:00
|
|
|
/** <div dir #dir="dir"> {{ dir.value }} </div> */
|
2017-12-01 17:23:03 -05:00
|
|
|
function Template(ctx: any, cm: boolean) {
|
|
|
|
if (cm) {
|
2018-03-27 14:01:52 -04:00
|
|
|
elementStart(0, 'div', ['dir', ''], ['dir', 'dir']);
|
|
|
|
{ text(2); }
|
2018-02-06 19:11:20 -05:00
|
|
|
elementEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2018-03-27 14:01:52 -04:00
|
|
|
const tmp = load(1) as any;
|
|
|
|
textBinding(2, bind(tmp.value));
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-29 15:58:41 -04:00
|
|
|
expect(renderToHtml(Template, {}, [Directive])).toEqual('<div dir="">Created</div>');
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('view dependencies', () => {
|
|
|
|
it('should create directive with inter view dependencies', () => {
|
|
|
|
class DirectiveA {
|
|
|
|
value: string = 'A';
|
2018-03-26 00:32:39 -04:00
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: DirectiveA,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dirA', '']],
|
2018-03-26 00:32:39 -04:00
|
|
|
factory: () => new DirectiveA,
|
|
|
|
features: [PublicFeature]
|
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
class DirectiveB {
|
|
|
|
value: string = 'B';
|
2018-03-26 00:32:39 -04:00
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: DirectiveB,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dirB', '']],
|
2018-03-26 00:32:39 -04:00
|
|
|
factory: () => new DirectiveB,
|
|
|
|
features: [PublicFeature]
|
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
class DirectiveC {
|
|
|
|
value: string;
|
|
|
|
constructor(a: DirectiveA, b: DirectiveB) { this.value = a.value + b.value; }
|
2018-01-22 18:27:21 -05:00
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: DirectiveC,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dirC', '']],
|
2018-03-27 14:01:52 -04:00
|
|
|
factory: () => new DirectiveC(directiveInject(DirectiveA), directiveInject(DirectiveB)),
|
|
|
|
exportAs: 'dirC'
|
2018-01-22 18:27:21 -05:00
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-27 14:01:52 -04:00
|
|
|
/**
|
|
|
|
* <div dirA>
|
|
|
|
* <span dirB dirC #dir="dirC"> {{ dir.value }} </span>
|
|
|
|
* </div>
|
|
|
|
*/
|
2017-12-01 17:23:03 -05:00
|
|
|
function Template(ctx: any, cm: boolean) {
|
|
|
|
if (cm) {
|
2018-03-26 00:32:39 -04:00
|
|
|
elementStart(0, 'div', ['dirA', '']);
|
2017-12-01 17:23:03 -05:00
|
|
|
{
|
2018-03-27 14:01:52 -04:00
|
|
|
elementStart(1, 'span', ['dirB', '', 'dirC', ''], ['dir', 'dirC']);
|
|
|
|
{ text(3); }
|
2018-02-06 19:11:20 -05:00
|
|
|
elementEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2018-02-06 19:11:20 -05:00
|
|
|
elementEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2018-03-27 14:01:52 -04:00
|
|
|
const tmp = load(2) as any;
|
|
|
|
textBinding(3, bind(tmp.value));
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-29 15:58:41 -04:00
|
|
|
const defs = [DirectiveA, DirectiveB, DirectiveC];
|
2018-03-26 00:32:39 -04:00
|
|
|
expect(renderToHtml(Template, {}, defs))
|
|
|
|
.toEqual('<div dira=""><span dirb="" dirc="">AB</span></div>');
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('ElementRef', () => {
|
|
|
|
it('should create directive with ElementRef dependencies', () => {
|
|
|
|
class Directive {
|
|
|
|
value: string;
|
|
|
|
constructor(public elementRef: ElementRef) {
|
|
|
|
this.value = (elementRef.constructor as any).name;
|
|
|
|
}
|
2018-01-22 18:27:21 -05:00
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: Directive,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dir', '']],
|
2018-01-22 18:27:21 -05:00
|
|
|
factory: () => new Directive(injectElementRef()),
|
2018-03-27 14:01:52 -04:00
|
|
|
features: [PublicFeature],
|
|
|
|
exportAs: 'dir'
|
2018-01-22 18:27:21 -05:00
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
class DirectiveSameInstance {
|
|
|
|
value: boolean;
|
|
|
|
constructor(elementRef: ElementRef, directive: Directive) {
|
|
|
|
this.value = elementRef === directive.elementRef;
|
|
|
|
}
|
2018-01-22 18:27:21 -05:00
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: DirectiveSameInstance,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dirSame', '']],
|
2018-03-27 14:01:52 -04:00
|
|
|
factory: () => new DirectiveSameInstance(injectElementRef(), directiveInject(Directive)),
|
|
|
|
exportAs: 'dirSame'
|
2018-01-22 18:27:21 -05:00
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-27 14:01:52 -04:00
|
|
|
/**
|
|
|
|
* <div dir dirSame #dirSame="dirSame" #dir="dir">
|
|
|
|
* {{ dir.value }} - {{ dirSame.value }}
|
|
|
|
* </div>
|
|
|
|
*/
|
2017-12-01 17:23:03 -05:00
|
|
|
function Template(ctx: any, cm: boolean) {
|
|
|
|
if (cm) {
|
2018-03-27 14:01:52 -04:00
|
|
|
elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dirSame', 'dirSame', 'dir', 'dir']);
|
|
|
|
{ text(3); }
|
2018-02-06 19:11:20 -05:00
|
|
|
elementEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2018-03-27 14:01:52 -04:00
|
|
|
|
|
|
|
const tmp1 = load(1) as any;
|
|
|
|
const tmp2 = load(2) as any;
|
|
|
|
textBinding(3, interpolation2('', tmp2.value, '-', tmp1.value, ''));
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-29 15:58:41 -04:00
|
|
|
const defs = [Directive, DirectiveSameInstance];
|
2018-03-26 00:32:39 -04:00
|
|
|
expect(renderToHtml(Template, {}, defs))
|
|
|
|
.toEqual('<div dir="" dirsame="">ElementRef-true</div>');
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('TemplateRef', () => {
|
|
|
|
it('should create directive with TemplateRef dependencies', () => {
|
|
|
|
class Directive {
|
|
|
|
value: string;
|
|
|
|
constructor(public templateRef: TemplateRef<any>) {
|
|
|
|
this.value = (templateRef.constructor as any).name;
|
|
|
|
}
|
2018-01-22 18:27:21 -05:00
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: Directive,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dir', '']],
|
2018-01-22 18:27:21 -05:00
|
|
|
factory: () => new Directive(injectTemplateRef()),
|
2018-03-27 14:01:52 -04:00
|
|
|
features: [PublicFeature],
|
|
|
|
exportAs: 'dir'
|
2018-01-22 18:27:21 -05:00
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
class DirectiveSameInstance {
|
|
|
|
value: boolean;
|
|
|
|
constructor(templateRef: TemplateRef<any>, directive: Directive) {
|
|
|
|
this.value = templateRef === directive.templateRef;
|
|
|
|
}
|
2018-01-22 18:27:21 -05:00
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: DirectiveSameInstance,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dirSame', '']],
|
2018-03-27 14:01:52 -04:00
|
|
|
factory: () => new DirectiveSameInstance(injectTemplateRef(), directiveInject(Directive)),
|
|
|
|
exportAs: 'dirSame'
|
2018-01-22 18:27:21 -05:00
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-27 14:01:52 -04:00
|
|
|
/**
|
|
|
|
* <ng-template dir dirSame #dir="dir" #dirSame="dirSame">
|
|
|
|
* {{ dir.value }} - {{ dirSame.value }}
|
|
|
|
* </ng-template>
|
|
|
|
*/
|
2017-12-01 17:23:03 -05:00
|
|
|
function Template(ctx: any, cm: any) {
|
|
|
|
if (cm) {
|
2018-03-27 14:01:52 -04:00
|
|
|
container(0, function() {
|
|
|
|
}, undefined, ['dir', '', 'dirSame', ''], ['dir', 'dir', 'dirSame', 'dirSame']);
|
|
|
|
text(3);
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2018-03-27 14:01:52 -04:00
|
|
|
const tmp1 = load(1) as any;
|
|
|
|
const tmp2 = load(2) as any;
|
|
|
|
textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, ''));
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-29 15:58:41 -04:00
|
|
|
const defs = [Directive, DirectiveSameInstance];
|
2018-03-26 00:32:39 -04:00
|
|
|
expect(renderToHtml(Template, {}, defs)).toEqual('TemplateRef-true');
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('ViewContainerRef', () => {
|
|
|
|
it('should create directive with ViewContainerRef dependencies', () => {
|
|
|
|
class Directive {
|
|
|
|
value: string;
|
|
|
|
constructor(public viewContainerRef: ViewContainerRef) {
|
|
|
|
this.value = (viewContainerRef.constructor as any).name;
|
|
|
|
}
|
2018-01-22 18:27:21 -05:00
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: Directive,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dir', '']],
|
2018-01-22 18:27:21 -05:00
|
|
|
factory: () => new Directive(injectViewContainerRef()),
|
2018-03-27 14:01:52 -04:00
|
|
|
features: [PublicFeature],
|
|
|
|
exportAs: 'dir'
|
2018-01-22 18:27:21 -05:00
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
class DirectiveSameInstance {
|
|
|
|
value: boolean;
|
|
|
|
constructor(viewContainerRef: ViewContainerRef, directive: Directive) {
|
|
|
|
this.value = viewContainerRef === directive.viewContainerRef;
|
|
|
|
}
|
2018-01-09 00:57:50 -05:00
|
|
|
static ngDirectiveDef = defineDirective({
|
2018-01-22 18:27:21 -05:00
|
|
|
type: DirectiveSameInstance,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dirSame', '']],
|
2018-03-04 23:21:23 -05:00
|
|
|
factory:
|
2018-03-27 14:01:52 -04:00
|
|
|
() => new DirectiveSameInstance(injectViewContainerRef(), directiveInject(Directive)),
|
|
|
|
exportAs: 'dirSame'
|
2018-01-09 00:57:50 -05:00
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-27 14:01:52 -04:00
|
|
|
/**
|
|
|
|
* <div dir dirSame #dir="dir" #dirSame="dirSame">
|
|
|
|
* {{ dir.value }} - {{ dirSame.value }}
|
|
|
|
* </div>
|
|
|
|
*/
|
2017-12-01 17:23:03 -05:00
|
|
|
function Template(ctx: any, cm: boolean) {
|
|
|
|
if (cm) {
|
2018-03-27 14:01:52 -04:00
|
|
|
elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir', 'dirSame', 'dirSame']);
|
|
|
|
{ text(3); }
|
2018-02-06 19:11:20 -05:00
|
|
|
elementEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2018-03-27 14:01:52 -04:00
|
|
|
|
|
|
|
const tmp1 = load(1) as any;
|
|
|
|
const tmp2 = load(2) as any;
|
|
|
|
textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, ''));
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-29 15:58:41 -04:00
|
|
|
const defs = [Directive, DirectiveSameInstance];
|
2018-03-26 00:32:39 -04:00
|
|
|
expect(renderToHtml(Template, {}, defs))
|
|
|
|
.toEqual('<div dir="" dirsame="">ViewContainerRef-true</div>');
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-02-26 19:58:15 -05:00
|
|
|
describe('ChangeDetectorRef', () => {
|
|
|
|
let dir: Directive;
|
|
|
|
let dirSameInstance: DirectiveSameInstance;
|
|
|
|
let comp: MyComp;
|
|
|
|
|
|
|
|
class MyComp {
|
|
|
|
constructor(public cdr: ChangeDetectorRef) {}
|
|
|
|
|
|
|
|
static ngComponentDef = defineComponent({
|
|
|
|
type: MyComp,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['my-comp']],
|
2018-02-26 19:58:15 -05:00
|
|
|
factory: () => comp = new MyComp(injectChangeDetectorRef()),
|
|
|
|
template: function(ctx: MyComp, cm: boolean) {
|
|
|
|
if (cm) {
|
|
|
|
projectionDef(0);
|
|
|
|
projection(1, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
class Directive {
|
|
|
|
value: string;
|
|
|
|
constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; }
|
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: Directive,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dir', '']],
|
2018-02-26 19:58:15 -05:00
|
|
|
factory: () => dir = new Directive(injectChangeDetectorRef()),
|
|
|
|
features: [PublicFeature],
|
|
|
|
exportAs: 'dir'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
class DirectiveSameInstance {
|
|
|
|
constructor(public cdr: ChangeDetectorRef) {}
|
|
|
|
|
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: DirectiveSameInstance,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dirSame', '']],
|
2018-02-26 19:58:15 -05:00
|
|
|
factory: () => dirSameInstance = new DirectiveSameInstance(injectChangeDetectorRef())
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-03-26 00:32:39 -04:00
|
|
|
class IfDirective {
|
|
|
|
/* @Input */
|
|
|
|
myIf = true;
|
2018-02-26 19:58:15 -05:00
|
|
|
|
2018-03-26 00:32:39 -04:00
|
|
|
constructor(public template: TemplateRef<any>, public vcr: ViewContainerRef) {}
|
|
|
|
|
|
|
|
ngOnChanges() {
|
|
|
|
if (this.myIf) {
|
|
|
|
this.vcr.createEmbeddedView(this.template);
|
|
|
|
}
|
2018-02-26 19:58:15 -05:00
|
|
|
}
|
|
|
|
|
2018-03-26 00:32:39 -04:00
|
|
|
static ngDirectiveDef = defineDirective({
|
|
|
|
type: IfDirective,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'myIf', '']],
|
2018-03-26 00:32:39 -04:00
|
|
|
factory: () => new IfDirective(injectTemplateRef(), injectViewContainerRef()),
|
|
|
|
inputs: {myIf: 'myIf'},
|
|
|
|
features: [PublicFeature, NgOnChangesFeature()]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-29 15:58:41 -04:00
|
|
|
const directives = [MyComp, Directive, DirectiveSameInstance, IfDirective];
|
2018-03-26 00:32:39 -04:00
|
|
|
|
|
|
|
it('should inject current component ChangeDetectorRef into directives on components', () => {
|
|
|
|
/** <my-comp dir dirSameInstance #dir="dir"></my-comp> {{ dir.value }} */
|
|
|
|
const MyApp = createComponent('my-app', function(ctx: any, cm: boolean) {
|
|
|
|
if (cm) {
|
|
|
|
elementStart(0, 'my-comp', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
|
|
|
|
elementEnd();
|
2018-03-27 14:01:52 -04:00
|
|
|
text(2);
|
2018-03-26 00:32:39 -04:00
|
|
|
}
|
2018-03-27 14:01:52 -04:00
|
|
|
const tmp = load(1) as any;
|
|
|
|
textBinding(2, bind(tmp.value));
|
2018-03-29 15:58:41 -04:00
|
|
|
}, directives);
|
2018-03-26 00:32:39 -04:00
|
|
|
|
2018-02-26 19:58:15 -05:00
|
|
|
const app = renderComponent(MyApp);
|
|
|
|
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
|
2018-03-26 00:32:39 -04:00
|
|
|
expect(toHtml(app)).toEqual('<my-comp dir="" dirsame=""></my-comp>ViewRef');
|
2018-02-26 19:58:15 -05:00
|
|
|
expect((comp !.cdr as ViewRef<MyComp>).context).toBe(comp);
|
|
|
|
|
|
|
|
expect(dir !.cdr).toBe(comp !.cdr);
|
|
|
|
expect(dir !.cdr).toBe(dirSameInstance !.cdr);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should inject host component ChangeDetectorRef into directives on elements', () => {
|
|
|
|
|
|
|
|
class MyApp {
|
|
|
|
constructor(public cdr: ChangeDetectorRef) {}
|
|
|
|
|
|
|
|
static ngComponentDef = defineComponent({
|
|
|
|
type: MyApp,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['my-app']],
|
2018-02-26 19:58:15 -05:00
|
|
|
factory: () => new MyApp(injectChangeDetectorRef()),
|
|
|
|
/** <div dir dirSameInstance #dir="dir"> {{ dir.value }} </div> */
|
|
|
|
template: function(ctx: any, cm: boolean) {
|
|
|
|
if (cm) {
|
2018-03-26 00:32:39 -04:00
|
|
|
elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
|
2018-03-27 14:01:52 -04:00
|
|
|
{ text(2); }
|
2018-02-26 19:58:15 -05:00
|
|
|
elementEnd();
|
|
|
|
}
|
2018-03-27 14:01:52 -04:00
|
|
|
const tmp = load(1) as any;
|
|
|
|
textBinding(2, bind(tmp.value));
|
2018-03-26 00:32:39 -04:00
|
|
|
},
|
2018-03-29 15:58:41 -04:00
|
|
|
directives: directives
|
2018-02-26 19:58:15 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const app = renderComponent(MyApp);
|
2018-03-26 00:32:39 -04:00
|
|
|
expect(toHtml(app)).toEqual('<div dir="" dirsame="">ViewRef</div>');
|
2018-02-26 19:58:15 -05:00
|
|
|
expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);
|
|
|
|
|
|
|
|
expect(dir !.cdr).toBe(app.cdr);
|
|
|
|
expect(dir !.cdr).toBe(dirSameInstance !.cdr);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should inject host component ChangeDetectorRef into directives in ContentChildren', () => {
|
|
|
|
class MyApp {
|
|
|
|
constructor(public cdr: ChangeDetectorRef) {}
|
|
|
|
|
|
|
|
static ngComponentDef = defineComponent({
|
|
|
|
type: MyApp,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['my-app']],
|
2018-02-26 19:58:15 -05:00
|
|
|
factory: () => new MyApp(injectChangeDetectorRef()),
|
|
|
|
/**
|
|
|
|
* <my-comp>
|
|
|
|
* <div dir dirSameInstance #dir="dir"></div>
|
|
|
|
* </my-comp>
|
|
|
|
* {{ dir.value }}
|
|
|
|
*/
|
|
|
|
template: function(ctx: any, cm: boolean) {
|
|
|
|
if (cm) {
|
2018-03-26 00:32:39 -04:00
|
|
|
elementStart(0, 'my-comp');
|
2018-02-26 19:58:15 -05:00
|
|
|
{
|
2018-03-26 00:32:39 -04:00
|
|
|
elementStart(1, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
|
2018-02-26 19:58:15 -05:00
|
|
|
elementEnd();
|
|
|
|
}
|
|
|
|
elementEnd();
|
2018-03-27 14:01:52 -04:00
|
|
|
text(3);
|
2018-02-26 19:58:15 -05:00
|
|
|
}
|
2018-03-27 14:01:52 -04:00
|
|
|
const tmp = load(2) as any;
|
|
|
|
textBinding(3, bind(tmp.value));
|
2018-03-26 00:32:39 -04:00
|
|
|
},
|
2018-03-29 15:58:41 -04:00
|
|
|
directives: directives
|
2018-02-26 19:58:15 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const app = renderComponent(MyApp);
|
2018-03-26 00:32:39 -04:00
|
|
|
expect(toHtml(app)).toEqual('<my-comp><div dir="" dirsame=""></div></my-comp>ViewRef');
|
2018-02-26 19:58:15 -05:00
|
|
|
expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);
|
|
|
|
|
|
|
|
expect(dir !.cdr).toBe(app !.cdr);
|
|
|
|
expect(dir !.cdr).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,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['my-app']],
|
2018-02-26 19:58:15 -05:00
|
|
|
factory: () => new MyApp(injectChangeDetectorRef()),
|
|
|
|
/**
|
|
|
|
* % if (showing) {
|
|
|
|
* <div dir dirSameInstance #dir="dir"> {{ dir.value }} </div>
|
|
|
|
* % }
|
|
|
|
*/
|
|
|
|
template: function(ctx: MyApp, cm: boolean) {
|
|
|
|
if (cm) {
|
|
|
|
container(0);
|
|
|
|
}
|
|
|
|
containerRefreshStart(0);
|
|
|
|
{
|
|
|
|
if (ctx.showing) {
|
|
|
|
if (embeddedViewStart(0)) {
|
2018-03-26 00:32:39 -04:00
|
|
|
elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
|
2018-03-27 14:01:52 -04:00
|
|
|
{ text(2); }
|
2018-02-26 19:58:15 -05:00
|
|
|
elementEnd();
|
|
|
|
}
|
2018-03-27 14:01:52 -04:00
|
|
|
const tmp = load(1) as any;
|
|
|
|
textBinding(2, bind(tmp.value));
|
2018-02-26 19:58:15 -05:00
|
|
|
}
|
|
|
|
embeddedViewEnd();
|
|
|
|
}
|
|
|
|
containerRefreshEnd();
|
2018-03-26 00:32:39 -04:00
|
|
|
},
|
2018-03-29 15:58:41 -04:00
|
|
|
directives: directives
|
2018-02-26 19:58:15 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const app = renderComponent(MyApp);
|
2018-03-26 00:32:39 -04:00
|
|
|
expect(toHtml(app)).toEqual('<div dir="" dirsame="">ViewRef</div>');
|
2018-02-26 19:58:15 -05:00
|
|
|
expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);
|
|
|
|
|
|
|
|
expect(dir !.cdr).toBe(app.cdr);
|
|
|
|
expect(dir !.cdr).toBe(dirSameInstance !.cdr);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should inject host component ChangeDetectorRef into directives on containers', () => {
|
|
|
|
class MyApp {
|
|
|
|
showing = true;
|
|
|
|
|
|
|
|
constructor(public cdr: ChangeDetectorRef) {}
|
|
|
|
|
|
|
|
static ngComponentDef = defineComponent({
|
|
|
|
type: MyApp,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['my-app']],
|
2018-02-26 19:58:15 -05:00
|
|
|
factory: () => new MyApp(injectChangeDetectorRef()),
|
|
|
|
/** <div *myIf="showing" dir dirSameInstance #dir="dir"> {{ dir.value }} </div> */
|
|
|
|
template: function(ctx: MyApp, cm: boolean) {
|
|
|
|
if (cm) {
|
2018-03-26 00:32:39 -04:00
|
|
|
container(0, C1, undefined, ['myIf', 'showing']);
|
2018-02-26 19:58:15 -05:00
|
|
|
}
|
|
|
|
containerRefreshStart(0);
|
|
|
|
containerRefreshEnd();
|
|
|
|
|
|
|
|
function C1(ctx1: any, cm1: boolean) {
|
|
|
|
if (cm1) {
|
2018-03-26 00:32:39 -04:00
|
|
|
elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
|
2018-03-27 14:01:52 -04:00
|
|
|
{ text(2); }
|
2018-02-26 19:58:15 -05:00
|
|
|
elementEnd();
|
|
|
|
}
|
2018-03-27 14:01:52 -04:00
|
|
|
const tmp = load(1) as any;
|
|
|
|
textBinding(2, bind(tmp.value));
|
2018-02-26 19:58:15 -05:00
|
|
|
}
|
2018-03-26 00:32:39 -04:00
|
|
|
},
|
2018-03-29 15:58:41 -04:00
|
|
|
directives: directives
|
2018-02-26 19:58:15 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const app = renderComponent(MyApp);
|
2018-03-26 00:32:39 -04:00
|
|
|
expect(toHtml(app)).toEqual('<div dir="" dirsame="">ViewRef</div>');
|
2018-02-26 19:58:15 -05:00
|
|
|
expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);
|
|
|
|
|
|
|
|
expect(dir !.cdr).toBe(app.cdr);
|
|
|
|
expect(dir !.cdr).toBe(dirSameInstance !.cdr);
|
|
|
|
});
|
|
|
|
|
2018-03-01 01:18:34 -05:00
|
|
|
it('should injectAttribute', () => {
|
|
|
|
let exist: string|undefined = 'wrong';
|
|
|
|
let nonExist: string|undefined = 'wrong';
|
2018-03-26 00:32:39 -04:00
|
|
|
|
|
|
|
const MyApp = createComponent('my-app', function(ctx: any, cm: boolean) {
|
|
|
|
if (cm) {
|
|
|
|
elementStart(0, 'div', ['exist', 'existValue', 'other', 'ignore']);
|
|
|
|
exist = injectAttribute('exist');
|
|
|
|
nonExist = injectAttribute('nonExist');
|
|
|
|
}
|
|
|
|
});
|
2018-03-01 01:18:34 -05:00
|
|
|
|
|
|
|
const app = renderComponent(MyApp);
|
|
|
|
expect(exist).toEqual('existValue');
|
|
|
|
expect(nonExist).toEqual(undefined);
|
|
|
|
});
|
|
|
|
|
2018-02-26 19:58:15 -05:00
|
|
|
});
|
|
|
|
|
2017-12-01 17:23:03 -05:00
|
|
|
describe('inject', () => {
|
|
|
|
describe('bloom filter', () => {
|
2018-01-08 23:17:13 -05:00
|
|
|
let di: LInjector;
|
2017-12-01 17:23:03 -05:00
|
|
|
beforeEach(() => {
|
|
|
|
di = {} as any;
|
|
|
|
di.bf0 = 0;
|
|
|
|
di.bf1 = 0;
|
|
|
|
di.bf2 = 0;
|
|
|
|
di.bf3 = 0;
|
2018-03-14 16:29:48 -04:00
|
|
|
di.bf4 = 0;
|
|
|
|
di.bf5 = 0;
|
|
|
|
di.bf6 = 0;
|
|
|
|
di.bf7 = 0;
|
|
|
|
di.bf3 = 0;
|
2017-12-01 17:23:03 -05:00
|
|
|
di.cbf0 = 0;
|
|
|
|
di.cbf1 = 0;
|
|
|
|
di.cbf2 = 0;
|
|
|
|
di.cbf3 = 0;
|
2018-03-14 16:29:48 -04:00
|
|
|
di.cbf4 = 0;
|
|
|
|
di.cbf5 = 0;
|
|
|
|
di.cbf6 = 0;
|
|
|
|
di.cbf7 = 0;
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
|
2018-03-14 16:29:48 -04:00
|
|
|
function bloomState() {
|
|
|
|
return [di.bf7, di.bf6, di.bf5, di.bf4, di.bf3, di.bf2, di.bf1, di.bf0];
|
|
|
|
}
|
2017-12-01 17:23:03 -05:00
|
|
|
|
|
|
|
it('should add values', () => {
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 0 } as any);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 0, 1]);
|
2017-12-01 17:23:03 -05:00
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 32 + 1 } as any);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 2, 1]);
|
2017-12-01 17:23:03 -05:00
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 64 + 2 } as any);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 4, 2, 1]);
|
2017-12-01 17:23:03 -05:00
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 96 + 3 } as any);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([0, 0, 0, 0, 8, 4, 2, 1]);
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 128 + 4 } as any);
|
|
|
|
expect(bloomState()).toEqual([0, 0, 0, 16, 8, 4, 2, 1]);
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 160 + 5 } as any);
|
|
|
|
expect(bloomState()).toEqual([0, 0, 32, 16, 8, 4, 2, 1]);
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 192 + 6 } as any);
|
|
|
|
expect(bloomState()).toEqual([0, 64, 32, 16, 8, 4, 2, 1]);
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 224 + 7 } as any);
|
|
|
|
expect(bloomState()).toEqual([128, 64, 32, 16, 8, 4, 2, 1]);
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should query values', () => {
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 0 } as any);
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 32 } as any);
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 64 } as any);
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 96 } as any);
|
2018-03-14 16:29:48 -04:00
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 127 } as any);
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 161 } as any);
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 188 } as any);
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 223 } as any);
|
|
|
|
bloomAdd(di, { __NG_ELEMENT_ID__: 255 } as any);
|
2017-12-01 17:23:03 -05:00
|
|
|
|
|
|
|
expect(bloomFindPossibleInjector(di, 0)).toEqual(di);
|
|
|
|
expect(bloomFindPossibleInjector(di, 1)).toEqual(null);
|
|
|
|
expect(bloomFindPossibleInjector(di, 32)).toEqual(di);
|
|
|
|
expect(bloomFindPossibleInjector(di, 64)).toEqual(di);
|
|
|
|
expect(bloomFindPossibleInjector(di, 96)).toEqual(di);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomFindPossibleInjector(di, 127)).toEqual(di);
|
|
|
|
expect(bloomFindPossibleInjector(di, 161)).toEqual(di);
|
|
|
|
expect(bloomFindPossibleInjector(di, 188)).toEqual(di);
|
|
|
|
expect(bloomFindPossibleInjector(di, 223)).toEqual(di);
|
|
|
|
expect(bloomFindPossibleInjector(di, 255)).toEqual(di);
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-01-17 13:09:05 -05:00
|
|
|
describe('flags', () => {
|
|
|
|
it('should return defaultValue not found', () => {
|
|
|
|
class MyApp {
|
|
|
|
constructor(public value: string) {}
|
|
|
|
|
|
|
|
static ngComponentDef = defineComponent({
|
2018-01-23 13:57:48 -05:00
|
|
|
type: MyApp,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['my-app']],
|
2018-03-04 23:21:23 -05:00
|
|
|
factory: () => new MyApp(
|
|
|
|
directiveInject(String as any, InjectFlags.Default, 'DefaultValue')),
|
2018-01-17 13:09:05 -05:00
|
|
|
template: () => null
|
|
|
|
});
|
|
|
|
}
|
|
|
|
const myApp = renderComponent(MyApp);
|
|
|
|
expect(myApp.value).toEqual('DefaultValue');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2017-12-01 17:23:03 -05:00
|
|
|
it('should inject from parent view', () => {
|
2018-03-26 00:32:39 -04:00
|
|
|
const ParentDirective = createDirective('parentDir');
|
2017-12-01 17:23:03 -05:00
|
|
|
|
|
|
|
class ChildDirective {
|
|
|
|
value: string;
|
2018-03-26 00:32:39 -04:00
|
|
|
constructor(public parent: any) { this.value = (parent.constructor as any).name; }
|
2018-01-09 00:57:50 -05:00
|
|
|
static ngDirectiveDef = defineDirective({
|
2018-01-22 18:27:21 -05:00
|
|
|
type: ChildDirective,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'childDir', '']],
|
2018-03-04 23:21:23 -05:00
|
|
|
factory: () => new ChildDirective(directiveInject(ParentDirective)),
|
2018-03-27 14:01:52 -04:00
|
|
|
features: [PublicFeature],
|
|
|
|
exportAs: 'childDir'
|
2018-01-09 00:57:50 -05:00
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
class Child2Directive {
|
|
|
|
value: boolean;
|
2018-03-26 00:32:39 -04:00
|
|
|
constructor(parent: any, child: ChildDirective) { this.value = parent === child.parent; }
|
2018-01-22 18:27:21 -05:00
|
|
|
static ngDirectiveDef = defineDirective({
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'child2Dir', '']],
|
2018-01-22 18:27:21 -05:00
|
|
|
type: Child2Directive,
|
2018-03-04 23:21:23 -05:00
|
|
|
factory: () => new Child2Directive(
|
2018-03-27 14:01:52 -04:00
|
|
|
directiveInject(ParentDirective), directiveInject(ChildDirective)),
|
|
|
|
exportAs: 'child2Dir'
|
2018-01-22 18:27:21 -05:00
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-27 14:01:52 -04:00
|
|
|
/**
|
|
|
|
* <div parentDir>
|
|
|
|
* <span childDir child2Dir #child1="childDir" #child2="child2Dir">
|
|
|
|
* {{ child1.value }} - {{ child2.value }}
|
|
|
|
* </span>
|
|
|
|
* </div>
|
|
|
|
*/
|
2017-12-01 17:23:03 -05:00
|
|
|
function Template(ctx: any, cm: boolean) {
|
|
|
|
if (cm) {
|
2018-03-26 00:32:39 -04:00
|
|
|
elementStart(0, 'div', ['parentDir', '']);
|
2018-03-21 18:10:34 -04:00
|
|
|
{ container(1); }
|
2018-02-06 19:11:20 -05:00
|
|
|
elementEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2018-03-21 18:10:34 -04:00
|
|
|
containerRefreshStart(1);
|
2017-12-01 17:23:03 -05:00
|
|
|
{
|
2018-02-06 20:27:16 -05:00
|
|
|
if (embeddedViewStart(0)) {
|
2018-03-27 14:01:52 -04:00
|
|
|
elementStart(
|
|
|
|
0, 'span', ['childDir', '', 'child2Dir', ''],
|
|
|
|
['child1', 'childDir', 'child2', 'child2Dir']);
|
|
|
|
{ text(3); }
|
2018-02-06 19:11:20 -05:00
|
|
|
elementEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2018-03-27 14:01:52 -04:00
|
|
|
const tmp1 = load(1) as any;
|
|
|
|
const tmp2 = load(2) as any;
|
|
|
|
textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, ''));
|
2018-02-06 20:27:16 -05:00
|
|
|
embeddedViewEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2018-02-06 19:11:20 -05:00
|
|
|
containerRefreshEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2018-03-29 15:58:41 -04:00
|
|
|
const defs = [ChildDirective, Child2Directive, ParentDirective];
|
2018-03-26 00:32:39 -04:00
|
|
|
expect(renderToHtml(Template, {}, defs))
|
|
|
|
.toEqual('<div parentdir=""><span child2dir="" childdir="">Directive-true</span></div>');
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should inject from module Injector', () => {
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('getOrCreateNodeInjector', () => {
|
|
|
|
it('should handle initial undefined state', () => {
|
2018-02-23 16:17:20 -05:00
|
|
|
const contentView =
|
2018-03-27 18:53:48 -04:00
|
|
|
createLView(-1, null !, createTView(null, null), null, null, LViewFlags.CheckAlways);
|
2017-12-01 17:23:03 -05:00
|
|
|
const oldView = enterView(contentView, null !);
|
|
|
|
try {
|
2018-03-20 22:06:49 -04:00
|
|
|
const parent = createLNode(0, LNodeType.Element, null, null);
|
2017-12-01 17:23:03 -05:00
|
|
|
|
|
|
|
// 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.
|
|
|
|
(parent as{parent: any}).parent = undefined;
|
|
|
|
|
|
|
|
const injector = getOrCreateNodeInjector();
|
|
|
|
expect(injector).not.toBe(null);
|
|
|
|
} finally {
|
|
|
|
leaveView(oldView);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|