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
|
|
|
|
*/
|
|
|
|
|
2019-03-30 06:32:48 -04:00
|
|
|
import {ChangeDetectorRef, Host, InjectFlags, Injector, Optional, Renderer2, Self, ViewContainerRef} from '@angular/core';
|
2019-04-04 14:41:52 -04:00
|
|
|
import {createLView, createNodeAtIndex, createTView} from '@angular/core/src/render3/instructions/shared';
|
2019-03-30 06:32:48 -04:00
|
|
|
import {RenderFlags} from '@angular/core/src/render3/interfaces/definition';
|
2017-12-01 17:23:03 -05:00
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
import {ΔdefineComponent} from '../../src/render3/definition';
|
2018-10-18 03:23:18 -04:00
|
|
|
import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
|
2019-05-09 14:47:25 -04:00
|
|
|
import {Δbind, Δcontainer, ΔcontainerRefreshEnd, ΔcontainerRefreshStart, ΔdefineDirective, ΔdirectiveInject, Δelement, ΔelementEnd, ΔelementStart, ΔembeddedViewEnd, ΔembeddedViewStart, Δinterpolation2, Δprojection, ΔprojectionDef, Δreference, Δtext, ΔtextBinding} from '../../src/render3/index';
|
2019-04-01 18:36:43 -04:00
|
|
|
import {TNODE} from '../../src/render3/interfaces/injector';
|
2019-03-30 06:32:48 -04:00
|
|
|
import {TNodeType} from '../../src/render3/interfaces/node';
|
2019-03-19 14:41:10 -04:00
|
|
|
import {isProceduralRenderer} from '../../src/render3/interfaces/renderer';
|
2018-10-03 00:12:26 -04:00
|
|
|
import {LViewFlags} from '../../src/render3/interfaces/view';
|
2019-03-19 14:41:10 -04:00
|
|
|
import {enterView, leaveView} from '../../src/render3/state';
|
2018-02-26 19:58:15 -05:00
|
|
|
import {ViewRef} from '../../src/render3/view_ref';
|
2017-12-01 17:23:03 -05:00
|
|
|
|
2018-08-16 11:43:29 -04:00
|
|
|
import {getRendererFactory2} from './imported_renderer2';
|
2018-10-06 00:23:41 -04:00
|
|
|
import {ComponentFixture, createComponent, createDirective, getDirectiveOnNode, renderComponent, toHtml} from './render_util';
|
2017-12-01 17:23:03 -05:00
|
|
|
|
|
|
|
describe('di', () => {
|
2018-04-05 00:21:12 -04:00
|
|
|
describe('directive injection', () => {
|
|
|
|
let log: string[] = [];
|
|
|
|
|
|
|
|
class DirB {
|
|
|
|
value = 'DirB';
|
|
|
|
constructor() { log.push(this.value); }
|
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngDirectiveDef = ΔdefineDirective({
|
2018-04-05 00:21:12 -04:00
|
|
|
selectors: [['', 'dirB', '']],
|
|
|
|
type: DirB,
|
|
|
|
factory: () => new DirB(),
|
2018-09-29 00:26:45 -04:00
|
|
|
inputs: {value: 'value'}
|
2018-04-05 00:21:12 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
beforeEach(() => log = []);
|
2017-12-01 17:23:03 -05:00
|
|
|
|
2019-03-29 07:30:52 -04:00
|
|
|
/**
|
|
|
|
* This test needs to be moved to acceptance/di_spec.ts
|
|
|
|
* when Ivy compiler supports inline views.
|
|
|
|
*/
|
2018-04-05 00:21:12 -04:00
|
|
|
it('should inject directives in the correct order in a for loop', () => {
|
|
|
|
class DirA {
|
|
|
|
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
|
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngDirectiveDef = ΔdefineDirective({
|
2018-04-05 00:21:12 -04:00
|
|
|
selectors: [['', 'dirA', '']],
|
|
|
|
type: DirA,
|
2019-05-09 14:47:25 -04:00
|
|
|
factory: () => new DirA(ΔdirectiveInject(DirB))
|
2018-04-05 00:21:12 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* % for(let i = 0; i < 3; i++) {
|
|
|
|
* <div dirA dirB></div>
|
|
|
|
* % }
|
|
|
|
*/
|
2018-04-23 21:24:40 -04:00
|
|
|
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
|
|
if (rf & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
Δcontainer(0);
|
2018-04-05 00:21:12 -04:00
|
|
|
}
|
2018-08-18 14:14:50 -04:00
|
|
|
if (rf & RenderFlags.Update) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshStart(0);
|
2018-08-18 14:14:50 -04:00
|
|
|
{
|
|
|
|
for (let i = 0; i < 3; i++) {
|
2019-05-09 14:47:25 -04:00
|
|
|
if (ΔembeddedViewStart(0, 1, 0)) {
|
|
|
|
Δelement(0, 'div', ['dirA', '', 'dirB', '']);
|
2018-08-18 14:14:50 -04:00
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔembeddedViewEnd();
|
2018-04-05 00:21:12 -04:00
|
|
|
}
|
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshEnd();
|
2018-04-05 00:21:12 -04:00
|
|
|
}
|
2018-08-18 14:14:50 -04:00
|
|
|
}, 1, 0, [DirA, DirB]);
|
2018-04-05 00:21:12 -04:00
|
|
|
|
2018-07-27 12:56:35 -04:00
|
|
|
new ComponentFixture(App);
|
2018-04-05 00:21:12 -04:00
|
|
|
expect(log).toEqual(
|
|
|
|
['DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)']);
|
|
|
|
});
|
|
|
|
|
2018-09-29 00:26:45 -04:00
|
|
|
describe('dependencies in parent views', () => {
|
|
|
|
|
|
|
|
class DirA {
|
|
|
|
injector: Injector;
|
|
|
|
constructor(public dirB: DirB, public vcr: ViewContainerRef) {
|
|
|
|
this.injector = vcr.injector;
|
|
|
|
}
|
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngDirectiveDef = ΔdefineDirective({
|
2018-09-29 00:26:45 -04:00
|
|
|
type: DirA,
|
|
|
|
selectors: [['', 'dirA', '']],
|
2019-05-09 14:47:25 -04:00
|
|
|
factory: () =>
|
|
|
|
new DirA(ΔdirectiveInject(DirB), ΔdirectiveInject(ViewContainerRef as any)),
|
2019-01-10 16:24:32 -05:00
|
|
|
exportAs: ['dirA']
|
2018-09-29 00:26:45 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-03-29 07:30:52 -04:00
|
|
|
* This test needs to be moved to acceptance/di_spec.ts
|
|
|
|
* when Ivy compiler supports inline views.
|
2018-09-29 00:26:45 -04:00
|
|
|
*/
|
|
|
|
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) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔelementStart(0, 'div', ['dirB', '']);
|
|
|
|
{ Δcontainer(1); }
|
|
|
|
ΔelementEnd();
|
2018-09-29 00:26:45 -04:00
|
|
|
}
|
|
|
|
if (rf & RenderFlags.Update) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshStart(1);
|
2018-09-29 00:26:45 -04:00
|
|
|
{
|
|
|
|
if (!ctx.skipContent) {
|
2019-05-09 14:47:25 -04:00
|
|
|
let rf1 = ΔembeddedViewStart(0, 1, 0);
|
2018-09-29 00:26:45 -04:00
|
|
|
{
|
|
|
|
if (rf1 & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
Δcontainer(0);
|
2018-09-29 00:26:45 -04:00
|
|
|
}
|
|
|
|
if (rf1 & RenderFlags.Update) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshStart(0);
|
2018-09-29 00:26:45 -04:00
|
|
|
{
|
|
|
|
if (!ctx.skipContent2) {
|
2019-05-09 14:47:25 -04:00
|
|
|
let rf2 = ΔembeddedViewStart(0, 3, 1);
|
2018-09-29 00:26:45 -04:00
|
|
|
{
|
|
|
|
if (rf2 & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔelementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']);
|
|
|
|
{ Δtext(2); }
|
|
|
|
ΔelementEnd();
|
2018-09-29 00:26:45 -04:00
|
|
|
}
|
|
|
|
if (rf2 & RenderFlags.Update) {
|
2019-05-09 14:47:25 -04:00
|
|
|
const dir = Δreference(1) as DirA;
|
|
|
|
ΔtextBinding(2, Δbind(dir.dirB.value));
|
2018-09-29 00:26:45 -04:00
|
|
|
}
|
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔembeddedViewEnd();
|
2018-09-29 00:26:45 -04:00
|
|
|
}
|
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshEnd();
|
2018-09-29 00:26:45 -04:00
|
|
|
}
|
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔembeddedViewEnd();
|
2018-09-29 00:26:45 -04:00
|
|
|
}
|
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshEnd();
|
2018-09-29 00:26:45 -04:00
|
|
|
}
|
|
|
|
}, 2, 0, [DirA, DirB]);
|
|
|
|
|
|
|
|
const fixture = new ComponentFixture(App);
|
|
|
|
expect(fixture.hostElement.textContent).toEqual(`DirB`);
|
|
|
|
});
|
2018-04-05 00:21:12 -04:00
|
|
|
});
|
|
|
|
|
2018-04-23 21:24:40 -04:00
|
|
|
describe('flags', () => {
|
|
|
|
|
|
|
|
class DirB {
|
2018-06-18 19:38:33 -04:00
|
|
|
// TODO(issue/24571): remove '!'.
|
|
|
|
value !: string;
|
2018-04-23 21:24:40 -04:00
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngDirectiveDef = ΔdefineDirective({
|
2018-04-23 21:24:40 -04:00
|
|
|
type: DirB,
|
|
|
|
selectors: [['', 'dirB', '']],
|
|
|
|
factory: () => new DirB(),
|
2018-10-18 03:23:18 -04:00
|
|
|
inputs: {value: 'dirB'}
|
2018-04-23 21:24:40 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-10-25 14:35:51 -04:00
|
|
|
describe('Optional', () => {
|
|
|
|
let dirA: DirA|null = null;
|
2018-04-23 21:24:40 -04:00
|
|
|
|
|
|
|
class DirA {
|
|
|
|
constructor(@Optional() public dirB: DirB|null) {}
|
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngDirectiveDef = ΔdefineDirective({
|
2018-04-23 21:24:40 -04:00
|
|
|
type: DirA,
|
|
|
|
selectors: [['', 'dirA', '']],
|
2019-05-09 14:47:25 -04:00
|
|
|
factory: () => dirA = new DirA(ΔdirectiveInject(DirB, InjectFlags.Optional))
|
2018-04-23 21:24:40 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-10-25 14:35:51 -04:00
|
|
|
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) {
|
2019-05-09 14:47:25 -04:00
|
|
|
Δelement(0, 'div', ['dirA', '']);
|
2018-10-25 14:35:51 -04:00
|
|
|
}
|
|
|
|
}, 1, 0, [DirA, DirB]);
|
|
|
|
|
|
|
|
expect(() => { new ComponentFixture(App); }).not.toThrow();
|
|
|
|
expect(dirA !.dirB).toEqual(null);
|
|
|
|
});
|
2018-04-23 21:24:40 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should check only the current node with @Self even with false positive', () => {
|
|
|
|
let dirA: DirA;
|
|
|
|
|
|
|
|
class DirA {
|
|
|
|
constructor(@Self() public dirB: DirB) {}
|
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngDirectiveDef = ΔdefineDirective({
|
2018-04-23 21:24:40 -04:00
|
|
|
type: DirA,
|
|
|
|
selectors: [['', 'dirA', '']],
|
2019-05-09 14:47:25 -04:00
|
|
|
factory: () => dirA = new DirA(ΔdirectiveInject(DirB, InjectFlags.Self))
|
2018-04-23 21:24:40 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const DirC = createDirective('dirC');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* <div dirB>
|
|
|
|
* <div dirA dirC></div>
|
|
|
|
* </div>
|
|
|
|
*/
|
|
|
|
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
|
|
if (rf & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔelementStart(0, 'div', ['dirB', '']);
|
|
|
|
Δelement(1, 'div', ['dirA', '', 'dirC', '']);
|
|
|
|
ΔelementEnd();
|
2018-04-23 21:24:40 -04:00
|
|
|
}
|
2018-08-18 14:14:50 -04:00
|
|
|
}, 2, 0, [DirA, DirB, DirC]);
|
2018-04-23 21:24:40 -04:00
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
(DirA as any)['__NG_ELEMENT_ID__'] = 1;
|
|
|
|
(DirC as any)['__NG_ELEMENT_ID__'] = 257;
|
2018-07-27 12:56:35 -04:00
|
|
|
new ComponentFixture(App);
|
2018-10-18 03:23:18 -04:00
|
|
|
}).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/);
|
2018-04-23 21:24:40 -04:00
|
|
|
});
|
|
|
|
|
2018-10-26 22:12:24 -04:00
|
|
|
describe('@Host', () => {
|
|
|
|
let dirA: DirA|null = null;
|
|
|
|
|
2019-03-30 06:32:48 -04:00
|
|
|
beforeEach(() => { dirA = null; });
|
2018-04-23 21:24:40 -04:00
|
|
|
|
|
|
|
class DirA {
|
|
|
|
constructor(@Host() public dirB: DirB) {}
|
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngDirectiveDef = ΔdefineDirective({
|
2018-04-23 21:24:40 -04:00
|
|
|
type: DirA,
|
|
|
|
selectors: [['', 'dirA', '']],
|
2019-05-09 14:47:25 -04:00
|
|
|
factory: () => dirA = new DirA(ΔdirectiveInject(DirB, InjectFlags.Host))
|
2018-04-23 21:24:40 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-03-30 06:32:48 -04:00
|
|
|
/**
|
|
|
|
* This test needs to be moved to acceptance/di_spec.ts
|
|
|
|
* when Ivy compiler supports inline views.
|
|
|
|
*/
|
2018-12-13 05:14:33 -05:00
|
|
|
it('should not find providers on the host itself if in inline view', () => {
|
2018-10-26 22:12:24 -04:00
|
|
|
let comp !: any;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* % if (showing) {
|
|
|
|
* <div dirA></div>
|
|
|
|
* % }
|
|
|
|
*/
|
|
|
|
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
|
|
|
|
if (rf & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
Δcontainer(0);
|
2018-10-26 22:12:24 -04:00
|
|
|
}
|
|
|
|
if (rf & RenderFlags.Update) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshStart(0);
|
2018-10-26 22:12:24 -04:00
|
|
|
{
|
|
|
|
if (ctx.showing) {
|
2019-05-09 14:47:25 -04:00
|
|
|
let rf1 = ΔembeddedViewStart(0, 1, 0);
|
2018-10-26 22:12:24 -04:00
|
|
|
if (rf1 & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
Δelement(0, 'div', ['dirA', '']);
|
2018-10-26 22:12:24 -04:00
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔembeddedViewEnd();
|
2018-10-26 22:12:24 -04:00
|
|
|
}
|
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshEnd();
|
2018-10-26 22:12:24 -04:00
|
|
|
}
|
|
|
|
}, 1, 0, [DirA, DirB]);
|
|
|
|
|
|
|
|
/* <comp dirB></comp> */
|
|
|
|
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
|
|
|
if (rf & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
Δelement(0, 'comp', ['dirB', '']);
|
2018-10-26 22:12:24 -04:00
|
|
|
}
|
|
|
|
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\]/);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
|
2018-09-21 21:38:13 -04:00
|
|
|
describe('Special tokens', () => {
|
2018-09-20 14:48:06 -04:00
|
|
|
|
2018-09-21 21:38:13 -04:00
|
|
|
describe('ChangeDetectorRef', () => {
|
|
|
|
let dir: Directive;
|
|
|
|
let dirSameInstance: DirectiveSameInstance;
|
|
|
|
let comp: MyComp;
|
|
|
|
|
|
|
|
class MyComp {
|
|
|
|
constructor(public cdr: ChangeDetectorRef) {}
|
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngComponentDef = ΔdefineComponent({
|
2018-09-21 21:38:13 -04:00
|
|
|
type: MyComp,
|
|
|
|
selectors: [['my-comp']],
|
2019-05-09 14:47:25 -04:00
|
|
|
factory: () => comp = new MyComp(ΔdirectiveInject(ChangeDetectorRef as any)),
|
2018-09-21 21:38:13 -04:00
|
|
|
consts: 1,
|
|
|
|
vars: 0,
|
|
|
|
template: function(rf: RenderFlags, ctx: MyComp) {
|
|
|
|
if (rf & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔprojectionDef();
|
|
|
|
Δprojection(0);
|
2018-09-21 21:38:13 -04:00
|
|
|
}
|
2018-10-18 03:23:18 -04:00
|
|
|
}
|
2018-09-21 21:38:13 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-12-01 17:23:03 -05:00
|
|
|
class Directive {
|
|
|
|
value: string;
|
2018-09-21 21:38:13 -04:00
|
|
|
|
|
|
|
constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; }
|
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngDirectiveDef = ΔdefineDirective({
|
2018-01-22 18:27:21 -05:00
|
|
|
type: Directive,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'dir', '']],
|
2019-05-09 14:47:25 -04:00
|
|
|
factory: () => dir = new Directive(ΔdirectiveInject(ChangeDetectorRef as any)),
|
2019-01-10 16:24:32 -05:00
|
|
|
exportAs: ['dir']
|
2018-01-22 18:27:21 -05:00
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
class DirectiveSameInstance {
|
2018-09-21 21:38:13 -04:00
|
|
|
constructor(public cdr: ChangeDetectorRef) {}
|
|
|
|
|
2019-05-09 14:47:25 -04: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-09-21 21:38:13 -04:00
|
|
|
factory: () => dirSameInstance =
|
2019-05-09 14:47:25 -04:00
|
|
|
new DirectiveSameInstance(ΔdirectiveInject(ChangeDetectorRef as any))
|
2018-01-09 00:57:50 -05:00
|
|
|
});
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
|
|
|
|
2019-03-30 06:32:48 -04:00
|
|
|
const directives = [MyComp, Directive, DirectiveSameInstance];
|
2018-09-21 21:38:13 -04:00
|
|
|
|
2019-03-19 14:41:10 -04:00
|
|
|
/**
|
|
|
|
* This test needs to be moved to acceptance/di_spec.ts
|
|
|
|
* when Ivy compiler supports inline views.
|
|
|
|
*/
|
2018-09-21 21:38:13 -04:00
|
|
|
it('should inject host component ChangeDetectorRef into directives in embedded views', () => {
|
2018-02-26 19:58:15 -05:00
|
|
|
|
2018-09-21 21:38:13 -04:00
|
|
|
class MyApp {
|
|
|
|
showing = true;
|
2018-02-26 19:58:15 -05:00
|
|
|
|
2018-09-21 21:38:13 -04:00
|
|
|
constructor(public cdr: ChangeDetectorRef) {}
|
2018-02-26 19:58:15 -05:00
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngComponentDef = ΔdefineComponent({
|
2018-09-21 21:38:13 -04:00
|
|
|
type: MyApp,
|
|
|
|
selectors: [['my-app']],
|
2019-05-09 14:47:25 -04:00
|
|
|
factory: () => new MyApp(ΔdirectiveInject(ChangeDetectorRef as any)),
|
2018-09-21 21:38:13 -04:00
|
|
|
consts: 1,
|
|
|
|
vars: 0,
|
|
|
|
/**
|
|
|
|
* % if (showing) {
|
2019-03-19 14:41:10 -04:00
|
|
|
* <div dir dirSame #dir="dir"> {{ dir.value }} </div>
|
|
|
|
* % }
|
2018-09-21 21:38:13 -04:00
|
|
|
*/
|
|
|
|
template: function(rf: RenderFlags, ctx: MyApp) {
|
|
|
|
if (rf & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
Δcontainer(0);
|
2018-09-21 21:38:13 -04:00
|
|
|
}
|
|
|
|
if (rf & RenderFlags.Update) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshStart(0);
|
2018-09-21 21:38:13 -04:00
|
|
|
{
|
|
|
|
if (ctx.showing) {
|
2019-05-09 14:47:25 -04:00
|
|
|
let rf1 = ΔembeddedViewStart(0, 3, 1);
|
2018-09-21 21:38:13 -04:00
|
|
|
if (rf1 & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔelementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
|
|
|
|
{ Δtext(2); }
|
|
|
|
ΔelementEnd();
|
2018-09-21 21:38:13 -04:00
|
|
|
}
|
|
|
|
if (rf1 & RenderFlags.Update) {
|
2019-05-09 14:47:25 -04:00
|
|
|
const tmp = Δreference(1) as any;
|
|
|
|
ΔtextBinding(2, Δbind(tmp.value));
|
2018-09-21 21:38:13 -04:00
|
|
|
}
|
2018-04-10 23:57:09 -04:00
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔembeddedViewEnd();
|
2018-02-26 19:58:15 -05:00
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshEnd();
|
2018-02-26 19:58:15 -05:00
|
|
|
}
|
2018-09-21 21:38:13 -04:00
|
|
|
},
|
|
|
|
directives: directives
|
|
|
|
});
|
|
|
|
}
|
2018-02-26 19:58:15 -05:00
|
|
|
|
2018-09-21 21:38:13 -04:00
|
|
|
const app = renderComponent(MyApp);
|
|
|
|
expect(toHtml(app)).toEqual('<div dir="" dirsame="">ViewRef</div>');
|
|
|
|
expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);
|
2018-02-26 19:58:15 -05:00
|
|
|
|
2018-09-21 21:38:13 -04:00
|
|
|
// Each ChangeDetectorRef instance should be unique
|
|
|
|
expect(dir !.cdr).not.toBe(app.cdr);
|
|
|
|
expect(dir !.cdr).not.toBe(dirSameInstance !.cdr);
|
|
|
|
});
|
2018-11-28 15:51:00 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-08-16 11:43:29 -04:00
|
|
|
describe('Renderer2', () => {
|
|
|
|
class MyComp {
|
|
|
|
constructor(public renderer: Renderer2) {}
|
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngComponentDef = ΔdefineComponent({
|
2018-08-16 11:43:29 -04:00
|
|
|
type: MyComp,
|
|
|
|
selectors: [['my-comp']],
|
2019-05-09 14:47:25 -04:00
|
|
|
factory: () => new MyComp(ΔdirectiveInject(Renderer2 as any)),
|
2018-08-16 11:43:29 -04:00
|
|
|
consts: 1,
|
|
|
|
vars: 0,
|
|
|
|
template: function(rf: RenderFlags, ctx: MyComp) {
|
|
|
|
if (rf & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
Δtext(0, 'Foo');
|
2018-08-16 11:43:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
it('should inject the Renderer2 used by the application', () => {
|
|
|
|
const rendererFactory = getRendererFactory2(document);
|
2018-10-10 09:53:14 -04:00
|
|
|
const fixture = new ComponentFixture(MyComp, {rendererFactory: rendererFactory});
|
|
|
|
expect(isProceduralRenderer(fixture.component.renderer)).toBeTruthy();
|
2018-08-16 11:43:29 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should throw when injecting Renderer2 but the application is using Renderer3',
|
|
|
|
() => { expect(() => new ComponentFixture(MyComp)).toThrow(); });
|
|
|
|
});
|
|
|
|
|
2019-05-09 14:47:25 -04:00
|
|
|
describe('Δinject', () => {
|
2017-12-01 17:23:03 -05:00
|
|
|
describe('bloom filter', () => {
|
2018-10-03 00:12:26 -04:00
|
|
|
let mockTView: any;
|
2017-12-01 17:23:03 -05:00
|
|
|
beforeEach(() => {
|
2018-10-03 00:12:26 -04:00
|
|
|
mockTView = {data: [0, 0, 0, 0, 0, 0, 0, 0, null], firstTemplatePass: true};
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
|
2018-10-03 00:12:26 -04:00
|
|
|
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;
|
2018-03-14 16:29:48 -04:00
|
|
|
}
|
2017-12-01 17:23:03 -05:00
|
|
|
|
|
|
|
it('should add values', () => {
|
2018-10-03 00:12:26 -04:00
|
|
|
bloomAdd(0, mockTView, Dir0);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 0, 1]);
|
2018-10-03 00:12:26 -04:00
|
|
|
bloomAdd(0, mockTView, Dir33);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 0, 2, 1]);
|
2018-10-03 00:12:26 -04:00
|
|
|
bloomAdd(0, mockTView, Dir66);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([0, 0, 0, 0, 0, 4, 2, 1]);
|
2018-10-03 00:12:26 -04:00
|
|
|
bloomAdd(0, mockTView, Dir99);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([0, 0, 0, 0, 8, 4, 2, 1]);
|
2018-10-03 00:12:26 -04:00
|
|
|
bloomAdd(0, mockTView, Dir132);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([0, 0, 0, 16, 8, 4, 2, 1]);
|
2018-10-03 00:12:26 -04:00
|
|
|
bloomAdd(0, mockTView, Dir165);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([0, 0, 32, 16, 8, 4, 2, 1]);
|
2018-10-03 00:12:26 -04:00
|
|
|
bloomAdd(0, mockTView, Dir198);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([0, 64, 32, 16, 8, 4, 2, 1]);
|
2018-10-03 00:12:26 -04:00
|
|
|
bloomAdd(0, mockTView, Dir231);
|
2018-03-14 16:29:48 -04:00
|
|
|
expect(bloomState()).toEqual([128, 64, 32, 16, 8, 4, 2, 1]);
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should query values', () => {
|
2018-10-03 00:12:26 -04:00
|
|
|
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);
|
|
|
|
|
2018-10-18 03:23:18 -04:00
|
|
|
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);
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-03-30 12:35:12 -04:00
|
|
|
/**
|
|
|
|
* This test needs to be moved to acceptance/di_spec.ts when Ivy compiler supports inline views.
|
|
|
|
*/
|
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; }
|
2019-05-09 14:47:25 -04:00
|
|
|
static ngDirectiveDef = ΔdefineDirective({
|
2018-01-22 18:27:21 -05:00
|
|
|
type: ChildDirective,
|
2018-03-29 19:41:45 -04:00
|
|
|
selectors: [['', 'childDir', '']],
|
2019-05-09 14:47:25 -04:00
|
|
|
factory: () => new ChildDirective(ΔdirectiveInject(ParentDirective)),
|
2019-01-10 16:24:32 -05:00
|
|
|
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; }
|
2019-05-09 14:47:25 -04: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(
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔdirectiveInject(ParentDirective), ΔdirectiveInject(ChildDirective)),
|
2019-01-10 16:24:32 -05:00
|
|
|
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>
|
2018-10-03 00:12:26 -04:00
|
|
|
* % if (...) {
|
2018-03-27 14:01:52 -04:00
|
|
|
* <span childDir child2Dir #child1="childDir" #child2="child2Dir">
|
|
|
|
* {{ child1.value }} - {{ child2.value }}
|
|
|
|
* </span>
|
2018-10-03 00:12:26 -04:00
|
|
|
* % }
|
2018-03-27 14:01:52 -04:00
|
|
|
* </div>
|
|
|
|
*/
|
2018-08-16 21:53:21 -04:00
|
|
|
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
2018-04-10 23:57:09 -04:00
|
|
|
if (rf & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔelementStart(0, 'div', ['parentDir', '']);
|
|
|
|
{ Δcontainer(1); }
|
|
|
|
ΔelementEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2018-04-10 23:57:09 -04:00
|
|
|
if (rf & RenderFlags.Update) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshStart(1);
|
2018-04-10 23:57:09 -04:00
|
|
|
{
|
2019-05-09 14:47:25 -04:00
|
|
|
let rf1 = ΔembeddedViewStart(0, 4, 2);
|
2018-04-10 23:57:09 -04:00
|
|
|
if (rf1 & RenderFlags.Create) {
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔelementStart(
|
2018-04-10 23:57:09 -04:00
|
|
|
0, 'span', ['childDir', '', 'child2Dir', ''],
|
|
|
|
['child1', 'childDir', 'child2', 'child2Dir']);
|
2019-05-09 14:47:25 -04:00
|
|
|
{ Δtext(3); }
|
|
|
|
ΔelementEnd();
|
2018-04-10 23:57:09 -04:00
|
|
|
}
|
|
|
|
if (rf & RenderFlags.Update) {
|
2019-05-09 14:47:25 -04:00
|
|
|
const tmp1 = Δreference(1) as any;
|
|
|
|
const tmp2 = Δreference(2) as any;
|
|
|
|
ΔtextBinding(3, Δinterpolation2('', tmp1.value, '-', tmp2.value, ''));
|
2018-04-10 23:57:09 -04:00
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔembeddedViewEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2019-05-09 14:47:25 -04:00
|
|
|
ΔcontainerRefreshEnd();
|
2017-12-01 17:23:03 -05:00
|
|
|
}
|
2018-08-18 14:14:50 -04:00
|
|
|
}, 2, 0, [ChildDirective, Child2Directive, ParentDirective]);
|
2017-12-01 17:23:03 -05:00
|
|
|
|
2018-08-16 21:53:21 -04:00
|
|
|
const fixture = new ComponentFixture(App);
|
|
|
|
expect(fixture.html)
|
2018-03-26 00:32:39 -04:00
|
|
|
.toEqual('<div parentdir=""><span child2dir="" childdir="">Directive-true</span></div>');
|
2017-12-01 17:23:03 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('getOrCreateNodeInjector', () => {
|
|
|
|
it('should handle initial undefined state', () => {
|
2018-11-22 00:14:06 -05:00
|
|
|
const contentView = createLView(
|
2019-02-11 18:03:04 -05:00
|
|
|
null, createTView(-1, null, 1, 0, null, null, null, null), null, LViewFlags.CheckAlways,
|
|
|
|
null, null, {} as any, {} as any);
|
2018-09-05 19:15:37 -04:00
|
|
|
const oldView = enterView(contentView, null);
|
2017-12-01 17:23:03 -05:00
|
|
|
try {
|
2018-10-12 21:49:00 -04:00
|
|
|
const parentTNode = createNodeAtIndex(0, TNodeType.Element, null, 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.
|
2018-09-13 19:07:23 -04:00
|
|
|
(parentTNode as{parent: any}).parent = undefined;
|
2017-12-01 17:23:03 -05:00
|
|
|
|
2018-10-18 03:23:18 -04:00
|
|
|
const injector = getOrCreateNodeInjectorForNode(parentTNode, contentView);
|
2018-10-08 19:04:46 -04:00
|
|
|
expect(injector).not.toEqual(-1);
|
2017-12-01 17:23:03 -05:00
|
|
|
} finally {
|
|
|
|
leaveView(oldView);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|