refactor: move angular source to /packages rather than modules/@angular

This commit is contained in:
Jason Aden
2017-03-02 10:48:42 -08:00
parent 5ad5301a3e
commit 3e51a19983
1051 changed files with 18 additions and 18 deletions

View File

@ -0,0 +1,62 @@
/**
* @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 {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, getDebugNode} from '@angular/core';
import {DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView, isBrowser} from './helper';
export function main() {
describe(`View Anchor`, () => {
function compViewDef(
nodes: NodeDef[], updateDirectives?: ViewUpdateFn,
updateRenderer?: ViewUpdateFn): ViewDefinition {
return viewDef(ViewFlags.None, nodes, updateDirectives, updateRenderer);
}
function createAndGetRootNodes(
viewDef: ViewDefinition, ctx?: any): {rootNodes: any[], view: ViewData} {
const view = createRootView(viewDef, ctx);
const rootNodes = rootRenderNodes(view);
return {rootNodes, view};
}
describe('create', () => {
it('should create anchor nodes without parents', () => {
const rootNodes = createAndGetRootNodes(compViewDef([
anchorDef(NodeFlags.None, null, null, 0)
])).rootNodes;
expect(rootNodes.length).toBe(1);
});
it('should create views with multiple root anchor nodes', () => {
const rootNodes =
createAndGetRootNodes(compViewDef([
anchorDef(NodeFlags.None, null, null, 0), anchorDef(NodeFlags.None, null, null, 0)
])).rootNodes;
expect(rootNodes.length).toBe(2);
});
it('should create anchor nodes with parents', () => {
const rootNodes = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
anchorDef(NodeFlags.None, null, null, 0),
])).rootNodes;
expect(getDOM().childNodes(rootNodes[0]).length).toBe(1);
});
it('should add debug information to the renderer', () => {
const someContext = new Object();
const {view, rootNodes} = createAndGetRootNodes(
compViewDef([anchorDef(NodeFlags.None, null, null, 0)]), someContext);
expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asElementData(view, 0).renderElement);
});
});
});
}

View File

@ -0,0 +1,312 @@
/**
* @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 {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
import {ArgumentType, BindingType, NodeCheckFn, NodeDef, NodeFlags, OutputType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, anchorDef, asElementData, asProviderData, directiveDef, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView, isBrowser, removeNodes} from './helper';
export function main() {
describe(
`Component Views`, () => {
function compViewDef(
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer);
}
function createAndGetRootNodes(viewDef: ViewDefinition):
{rootNodes: any[], view: ViewData} {
const view = createRootView(viewDef);
const rootNodes = rootRenderNodes(view);
return {rootNodes, view};
}
it('should create and attach component views', () => {
let instance: AComp;
class AComp {
constructor() { instance = this; }
}
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(
NodeFlags.None, null, null, 1, 'div', null, null, null, null,
() => compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span'),
])),
directiveDef(NodeFlags.Component, null, 0, AComp, []),
]));
const compView = asElementData(view, 0).componentView;
expect(compView.context).toBe(instance);
expect(compView.component).toBe(instance);
const compRootEl = getDOM().childNodes(rootNodes[0])[0];
expect(getDOM().nodeName(compRootEl).toLowerCase()).toBe('span');
});
if (isBrowser()) {
describe('root views', () => {
let rootNode: HTMLElement;
beforeEach(() => {
rootNode = document.createElement('root');
document.body.appendChild(rootNode);
removeNodes.push(rootNode);
});
it('should select root elements based on a selector', () => {
const view = createRootView(
compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'div'),
]),
{}, [], 'root');
const rootNodes = rootRenderNodes(view);
expect(rootNodes).toEqual([rootNode]);
});
it('should select root elements based on a node', () => {
const view = createRootView(
compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'div'),
]),
{}, [], rootNode);
const rootNodes = rootRenderNodes(view);
expect(rootNodes).toEqual([rootNode]);
});
it('should set attributes on the root node', () => {
const view = createRootView(
compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'div', [['a', 'b']]),
]),
{}, [], rootNode);
expect(rootNode.getAttribute('a')).toBe('b');
});
it('should clear the content of the root node', () => {
rootNode.appendChild(document.createElement('div'));
const view = createRootView(
compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'div', [['a', 'b']]),
]),
{}, [], rootNode);
expect(rootNode.childNodes.length).toBe(0);
});
});
}
describe(
'data binding', () => {
it('should dirty check component views',
() => {
let value: any;
class AComp {
a: any;
}
const update = jasmine.createSpy('updater').and.callFake(
(check: NodeCheckFn, view: ViewData) => {
check(view, 0, ArgumentType.Inline, value);
});
const {view, rootNodes} = createAndGetRootNodes(
compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef(
[
elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
], null, update
)),
directiveDef(NodeFlags.Component, null, 0, AComp, []),
]));
const compView = asElementData(view, 0).componentView;
value = 'v1';
Services.checkAndUpdateView(view);
expect(update.calls.mostRecent().args[1]).toBe(compView);
update.calls.reset();
Services.checkNoChangesView(view);
expect(update.calls.mostRecent().args[1]).toBe(compView);
value = 'v2';
expect(() => Services.checkNoChangesView(view))
.toThrowError(
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`);
});
it('should support detaching and attaching component views for dirty checking',
() => {
class AComp {
a: any;
}
const update = jasmine.createSpy('updater');
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(
NodeFlags.None, null, null, 1, 'div', null, null, null, null,
() => compViewDef(
[
elementDef(NodeFlags.None, null, null, 0, 'span'),
],
update)),
directiveDef(NodeFlags.Component, null, 0, AComp, [], null, null),
]));
const compView = asElementData(view, 0).componentView;
Services.checkAndUpdateView(view);
update.calls.reset();
compView.state &= ~ViewState.ChecksEnabled;
Services.checkAndUpdateView(view);
expect(update).not.toHaveBeenCalled();
compView.state |= ViewState.ChecksEnabled;
Services.checkAndUpdateView(view);
expect(update).toHaveBeenCalled();
});
if (isBrowser()) {
it('should support OnPush components', () => {
let compInputValue: any;
class AComp {
a: any;
}
const update = jasmine.createSpy('updater');
const addListenerSpy =
spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough();
const {view} = createAndGetRootNodes(compViewDef(
[
elementDef(
NodeFlags.None, null, null, 1, 'div', null, null, null, null,
() => {
return compViewDef(
[
elementDef(
NodeFlags.None, null, null, 0, 'span', null, null,
[[null, 'click']]),
],
update, null, ViewFlags.OnPush);
}),
directiveDef(NodeFlags.Component, null, 0, AComp, [], {a: [0, 'a']}),
],
(check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); }));
Services.checkAndUpdateView(view);
// auto detach
update.calls.reset();
Services.checkAndUpdateView(view);
expect(update).not.toHaveBeenCalled();
// auto attach on input changes
update.calls.reset();
compInputValue = 'v1';
Services.checkAndUpdateView(view);
expect(update).toHaveBeenCalled();
// auto detach
update.calls.reset();
Services.checkAndUpdateView(view);
expect(update).not.toHaveBeenCalled();
// auto attach on events
addListenerSpy.calls.mostRecent().args[1]('SomeEvent');
update.calls.reset();
Services.checkAndUpdateView(view);
expect(update).toHaveBeenCalled();
// auto detach
update.calls.reset();
Services.checkAndUpdateView(view);
expect(update).not.toHaveBeenCalled();
});
}
it('should stop dirty checking views that threw errors in change detection',
() => {
class AComp {
a: any;
}
const update = jasmine.createSpy('updater');
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef(
[
elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
],
null, update)),
directiveDef(
NodeFlags.Component, null, 0, AComp, [], null, null,
),
]));
const compView = asElementData(view, 0).componentView;
update.and.callFake(
(check: NodeCheckFn, view: ViewData) => { throw new Error('Test'); });
expect(() => Services.checkAndUpdateView(view)).toThrowError('Test');
expect(update).toHaveBeenCalled();
update.calls.reset();
Services.checkAndUpdateView(view);
expect(update).not.toHaveBeenCalled();
});
});
describe('destroy', () => {
it('should destroy component views', () => {
const log: string[] = [];
class AComp {}
class ChildProvider {
ngOnDestroy() { log.push('ngOnDestroy'); };
}
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(
NodeFlags.None, null, null, 1, 'div', null, null, null, null,
() => compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.OnDestroy, null, 0, ChildProvider, [])
])),
directiveDef(NodeFlags.Component, null, 0, AComp, [], null, null, ),
]));
Services.destroyView(view);
expect(log).toEqual(['ngOnDestroy']);
});
it('should throw on dirty checking destroyed views', () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(NodeFlags.None, null, null, 0, 'div'),
],
(view) => {}));
Services.destroyView(view);
expect(() => Services.checkAndUpdateView(view))
.toThrowError('ViewDestroyedError: Attempt to use a destroyed view: detectChanges');
});
});
});
}

View File

@ -0,0 +1,302 @@
/**
* @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 {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
import {getDebugContext} from '@angular/core/src/errors';
import {ArgumentType, BindingType, DebugContext, NodeDef, NodeFlags, OutputType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser, removeNodes} from './helper';
export function main() {
describe(`View Elements`, () => {
function compViewDef(
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer);
}
function createAndGetRootNodes(
viewDef: ViewDefinition, context?: any): {rootNodes: any[], view: ViewData} {
const view = createRootView(viewDef, context);
const rootNodes = rootRenderNodes(view);
return {rootNodes, view};
}
describe('create', () => {
it('should create elements without parents', () => {
const rootNodes = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span')
])).rootNodes;
expect(rootNodes.length).toBe(1);
expect(getDOM().nodeName(rootNodes[0]).toLowerCase()).toBe('span');
});
it('should create views with multiple root elements', () => {
const rootNodes = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span'),
elementDef(NodeFlags.None, null, null, 0, 'span')
])).rootNodes;
expect(rootNodes.length).toBe(2);
});
it('should create elements with parents', () => {
const rootNodes = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
elementDef(NodeFlags.None, null, null, 0, 'span'),
])).rootNodes;
expect(rootNodes.length).toBe(1);
const spanEl = getDOM().childNodes(rootNodes[0])[0];
expect(getDOM().nodeName(spanEl).toLowerCase()).toBe('span');
});
it('should set fixed attributes', () => {
const rootNodes = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'div', [['title', 'a']]),
])).rootNodes;
expect(rootNodes.length).toBe(1);
expect(getDOM().getAttribute(rootNodes[0], 'title')).toBe('a');
});
it('should add debug information to the renderer', () => {
const someContext = new Object();
const {view, rootNodes} = createAndGetRootNodes(
compViewDef([elementDef(NodeFlags.None, null, null, 0, 'div')]), someContext);
expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asElementData(view, 0).renderElement);
});
});
describe('change properties', () => {
ARG_TYPE_VALUES.forEach((inlineDynamic) => {
it(`should update via strategy ${inlineDynamic}`, () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(
NodeFlags.None, null, null, 0, 'input', null,
[
[BindingType.ElementProperty, 'title', SecurityContext.NONE],
[BindingType.ElementProperty, 'value', SecurityContext.NONE]
]),
],
null, (check, view) => {
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']);
}));
Services.checkAndUpdateView(view);
const el = rootNodes[0];
expect(getDOM().getProperty(el, 'title')).toBe('v1');
expect(getDOM().getProperty(el, 'value')).toBe('v2');
});
});
});
describe('change attributes', () => {
ARG_TYPE_VALUES.forEach((inlineDynamic) => {
it(`should update via strategy ${inlineDynamic}`, () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(
NodeFlags.None, null, null, 0, 'div', null,
[
[BindingType.ElementAttribute, 'a1', SecurityContext.NONE],
[BindingType.ElementAttribute, 'a2', SecurityContext.NONE]
]),
],
null, (check, view) => {
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']);
}));
Services.checkAndUpdateView(view);
const el = rootNodes[0];
expect(getDOM().getAttribute(el, 'a1')).toBe('v1');
expect(getDOM().getAttribute(el, 'a2')).toBe('v2');
});
});
});
describe('change classes', () => {
ARG_TYPE_VALUES.forEach((inlineDynamic) => {
it(`should update via strategy ${inlineDynamic}`, () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(
NodeFlags.None, null, null, 0, 'div', null,
[[BindingType.ElementClass, 'c1'], [BindingType.ElementClass, 'c2']]),
],
(check, view) => {
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [true, true]);
}));
Services.checkAndUpdateView(view);
const el = rootNodes[0];
expect(getDOM().hasClass(el, 'c1')).toBeTruthy();
expect(getDOM().hasClass(el, 'c2')).toBeTruthy();
});
});
});
describe('change styles', () => {
ARG_TYPE_VALUES.forEach((inlineDynamic) => {
it(`should update via strategy ${inlineDynamic}`, () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(
NodeFlags.None, null, null, 0, 'div', null,
[
[BindingType.ElementStyle, 'width', 'px'],
[BindingType.ElementStyle, 'color', null]
]),
],
null, (check, view) => {
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [10, 'red']);
}));
Services.checkAndUpdateView(view);
const el = rootNodes[0];
expect(getDOM().getStyle(el, 'width')).toBe('10px');
expect(getDOM().getStyle(el, 'color')).toBe('red');
});
});
});
if (isBrowser()) {
describe('listen to DOM events', () => {
function createAndAttachAndGetRootNodes(viewDef: ViewDefinition):
{rootNodes: any[], view: ViewData} {
const result = createAndGetRootNodes(viewDef);
// Note: We need to append the node to the document.body, otherwise `click` events
// won't work in IE.
result.rootNodes.forEach((node) => {
document.body.appendChild(node);
removeNodes.push(node);
});
return result;
}
it('should listen to DOM events', () => {
const handleEventSpy = jasmine.createSpy('handleEvent');
const removeListenerSpy =
spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough();
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef(
NodeFlags.None, null, null, 0, 'button', null, null, [[null, 'click']],
handleEventSpy)]));
rootNodes[0].click();
expect(handleEventSpy).toHaveBeenCalled();
let handleEventArgs = handleEventSpy.calls.mostRecent().args;
expect(handleEventArgs[0]).toBe(view);
expect(handleEventArgs[1]).toBe('click');
expect(handleEventArgs[2]).toBeTruthy();
Services.destroyView(view);
expect(removeListenerSpy).toHaveBeenCalled();
});
it('should listen to window events', () => {
const handleEventSpy = jasmine.createSpy('handleEvent');
const addListenerSpy = spyOn(window, 'addEventListener');
const removeListenerSpy = spyOn(window, 'removeEventListener');
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef(
NodeFlags.None, null, null, 0, 'button', null, null, [['window', 'windowClick']],
handleEventSpy)]));
expect(addListenerSpy).toHaveBeenCalled();
expect(addListenerSpy.calls.mostRecent().args[0]).toBe('windowClick');
addListenerSpy.calls.mostRecent().args[1]({name: 'windowClick'});
expect(handleEventSpy).toHaveBeenCalled();
const handleEventArgs = handleEventSpy.calls.mostRecent().args;
expect(handleEventArgs[0]).toBe(view);
expect(handleEventArgs[1]).toBe('window:windowClick');
expect(handleEventArgs[2]).toBeTruthy();
Services.destroyView(view);
expect(removeListenerSpy).toHaveBeenCalled();
});
it('should listen to document events', () => {
const handleEventSpy = jasmine.createSpy('handleEvent');
const addListenerSpy = spyOn(document, 'addEventListener');
const removeListenerSpy = spyOn(document, 'removeEventListener');
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef(
NodeFlags.None, null, null, 0, 'button', null, null, [['document', 'documentClick']],
handleEventSpy)]));
expect(addListenerSpy).toHaveBeenCalled();
expect(addListenerSpy.calls.mostRecent().args[0]).toBe('documentClick');
addListenerSpy.calls.mostRecent().args[1]({name: 'documentClick'});
expect(handleEventSpy).toHaveBeenCalled();
const handleEventArgs = handleEventSpy.calls.mostRecent().args;
expect(handleEventArgs[0]).toBe(view);
expect(handleEventArgs[1]).toBe('document:documentClick');
expect(handleEventArgs[2]).toBeTruthy();
Services.destroyView(view);
expect(removeListenerSpy).toHaveBeenCalled();
});
it('should preventDefault only if the handler returns false', () => {
let eventHandlerResult: any;
let preventDefaultSpy: jasmine.Spy;
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef(
NodeFlags.None, null, null, 0, 'button', null, null, [[null, 'click']],
(view, eventName, event) => {
preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough();
return eventHandlerResult;
})]));
eventHandlerResult = undefined;
rootNodes[0].click();
expect(preventDefaultSpy).not.toHaveBeenCalled();
eventHandlerResult = true;
rootNodes[0].click();
expect(preventDefaultSpy).not.toHaveBeenCalled();
eventHandlerResult = 'someString';
rootNodes[0].click();
expect(preventDefaultSpy).not.toHaveBeenCalled();
eventHandlerResult = false;
rootNodes[0].click();
expect(preventDefaultSpy).toHaveBeenCalled();
});
it('should report debug info on event errors', () => {
const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough();
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef(
NodeFlags.None, null, null, 0, 'button', null, null, [[null, 'click']],
() => { throw new Error('Test'); })]));
let err: any;
try {
addListenerSpy.calls.mostRecent().args[1]('SomeEvent');
} catch (e) {
err = e;
}
expect(err).toBeTruthy();
expect(err.message).toBe('Test');
const debugCtx = getDebugContext(err);
expect(debugCtx.view).toBe(view);
expect(debugCtx.nodeIndex).toBe(0);
});
});
}
});
}

View File

@ -0,0 +1,189 @@
/**
* @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 {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
import {ArgumentType, BindingType, NodeCheckFn, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, moveEmbeddedView, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {inject} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView, isBrowser} from './helper';
export function main() {
describe(`Embedded Views`, () => {
function compViewDef(
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer);
}
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
return () => viewDef(ViewFlags.None, nodes, update);
}
function createAndGetRootNodes(
viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} {
const view = createRootView(viewDef, context);
const rootNodes = rootRenderNodes(view);
return {rootNodes, view};
}
it('should create embedded views with the right context', () => {
const parentContext = new Object();
const childContext = new Object();
const {view: parentView} = createAndGetRootNodes(
compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
anchorDef(
NodeFlags.EmbeddedViews, null, null, 0, null,
embeddedViewDef([elementDef(NodeFlags.None, null, null, 0, 'span')])),
]),
parentContext);
const childView =
Services.createEmbeddedView(parentView, parentView.def.nodes[1], childContext);
expect(childView.component).toBe(parentContext);
expect(childView.context).toBe(childContext);
});
it('should attach and detach embedded views', () => {
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'div'),
anchorDef(NodeFlags.EmbeddedViews, null, null, 0, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child0']])
])),
anchorDef(NodeFlags.None, null, null, 0, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child1']])
]))
]));
const viewContainerData = asElementData(parentView, 1);
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]);
const childView1 = Services.createEmbeddedView(parentView, parentView.def.nodes[2]);
attachEmbeddedView(parentView, viewContainerData, 0, childView0);
attachEmbeddedView(parentView, viewContainerData, 1, childView1);
// 2 anchors + 2 elements
const rootChildren = getDOM().childNodes(rootNodes[0]);
expect(rootChildren.length).toBe(4);
expect(getDOM().getAttribute(rootChildren[1], 'name')).toBe('child0');
expect(getDOM().getAttribute(rootChildren[2], 'name')).toBe('child1');
detachEmbeddedView(viewContainerData, 1);
detachEmbeddedView(viewContainerData, 0);
expect(getDOM().childNodes(rootNodes[0]).length).toBe(2);
});
it('should move embedded views', () => {
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'div'),
anchorDef(NodeFlags.EmbeddedViews, null, null, 0, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child0']])
])),
anchorDef(NodeFlags.None, null, null, 0, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child1']])
]))
]));
const viewContainerData = asElementData(parentView, 1);
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]);
const childView1 = Services.createEmbeddedView(parentView, parentView.def.nodes[2]);
attachEmbeddedView(parentView, viewContainerData, 0, childView0);
attachEmbeddedView(parentView, viewContainerData, 1, childView1);
moveEmbeddedView(viewContainerData, 0, 1);
expect(viewContainerData.embeddedViews).toEqual([childView1, childView0]);
// 2 anchors + 2 elements
const rootChildren = getDOM().childNodes(rootNodes[0]);
expect(rootChildren.length).toBe(4);
expect(getDOM().getAttribute(rootChildren[1], 'name')).toBe('child1');
expect(getDOM().getAttribute(rootChildren[2], 'name')).toBe('child0');
});
it('should include embedded views in root nodes', () => {
const {view: parentView} = createAndGetRootNodes(compViewDef([
anchorDef(NodeFlags.EmbeddedViews, null, null, 0, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child0']])
])),
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'after']])
]));
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[0]);
attachEmbeddedView(parentView, asElementData(parentView, 0), 0, childView0);
const rootNodes = rootRenderNodes(parentView);
expect(rootNodes.length).toBe(3);
expect(getDOM().getAttribute(rootNodes[1], 'name')).toBe('child0');
expect(getDOM().getAttribute(rootNodes[2], 'name')).toBe('after');
});
it('should dirty check embedded views', () => {
let childValue = 'v1';
const update =
jasmine.createSpy('updater').and.callFake((check: NodeCheckFn, view: ViewData) => {
check(view, 0, ArgumentType.Inline, childValue);
});
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
anchorDef(
NodeFlags.EmbeddedViews, null, null, 0, null,
embeddedViewDef(
[elementDef(
NodeFlags.None, null, null, 0, 'span', null,
[[BindingType.ElementAttribute, 'name', SecurityContext.NONE]])],
update))
]));
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]);
attachEmbeddedView(parentView, asElementData(parentView, 1), 0, childView0);
Services.checkAndUpdateView(parentView);
expect(update.calls.mostRecent().args[1]).toBe(childView0);
update.calls.reset();
Services.checkNoChangesView(parentView);
expect(update.calls.mostRecent().args[1]).toBe(childView0);
childValue = 'v2';
expect(() => Services.checkNoChangesView(parentView))
.toThrowError(
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`);
});
it('should destroy embedded views', () => {
const log: string[] = [];
class ChildProvider {
ngOnDestroy() { log.push('ngOnDestroy'); };
}
const {view: parentView} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
anchorDef(NodeFlags.EmbeddedViews, null, null, 0, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.OnDestroy, null, 0, ChildProvider, [])
]))
]));
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[1]);
attachEmbeddedView(parentView, asElementData(parentView, 1), 0, childView0);
Services.destroyView(parentView);
expect(log).toEqual(['ngOnDestroy']);
});
});
}

View File

@ -0,0 +1,41 @@
/**
* @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 {Injector, RootRenderer, Sanitizer} from '@angular/core';
import {ArgumentType, NodeCheckFn, RootData, Services, ViewData, ViewDefinition, initServicesIfNeeded} from '@angular/core/src/view/index';
import {TestBed} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
export function isBrowser() {
return getDOM().supportsDOMEvents();
}
export const ARG_TYPE_VALUES = [ArgumentType.Inline, ArgumentType.Dynamic];
export function checkNodeInlineOrDynamic(
check: NodeCheckFn, view: ViewData, nodeIndex: number, argType: ArgumentType,
values: any[]): any {
switch (argType) {
case ArgumentType.Inline:
return (<any>check)(view, nodeIndex, argType, ...values);
case ArgumentType.Dynamic:
return check(view, nodeIndex, argType, values);
}
}
export function createRootView(
def: ViewDefinition, context?: any, projectableNodes?: any[][],
rootSelectorOrNode?: any): ViewData {
initServicesIfNeeded();
return Services.createRootView(
TestBed.get(Injector), projectableNodes || [], rootSelectorOrNode, def, context);
}
export let removeNodes: Node[];
beforeEach(() => { removeNodes = []; });
afterEach(() => { removeNodes.forEach((node) => getDOM().remove(node)); });

View File

@ -0,0 +1,139 @@
/**
* @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 {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core';
import {DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, asTextData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, ngContentDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView, isBrowser} from './helper';
export function main() {
describe(`View NgContent`, () => {
function compViewDef(
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer);
}
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
return () => viewDef(ViewFlags.None, nodes, update);
}
function hostElDef(contentNodes: NodeDef[], viewNodes: NodeDef[]): NodeDef[] {
class AComp {}
const aCompViewDef = compViewDef(viewNodes);
return [
elementDef(
NodeFlags.None, null, null, 1 + contentNodes.length, 'acomp', null, null, null, null,
() => aCompViewDef),
directiveDef(NodeFlags.Component, null, 0, AComp, []), ...contentNodes
];
}
function createAndGetRootNodes(
viewDef: ViewDefinition, ctx?: any): {rootNodes: any[], view: ViewData} {
const view = createRootView(viewDef, ctx || {});
const rootNodes = rootRenderNodes(view);
return {rootNodes, view};
}
it('should create ng-content nodes without parents', () => {
const {view, rootNodes} = createAndGetRootNodes(
compViewDef(hostElDef([textDef(0, ['a'])], [ngContentDef(null, 0)])));
expect(getDOM().firstChild(rootNodes[0])).toBe(asTextData(view, 2).renderText);
});
it('should create views with multiple root ng-content nodes', () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef(hostElDef(
[textDef(0, ['a']), textDef(1, ['b'])], [ngContentDef(null, 0), ngContentDef(null, 1)])));
expect(getDOM().childNodes(rootNodes[0])[0]).toBe(asTextData(view, 2).renderText);
expect(getDOM().childNodes(rootNodes[0])[1]).toBe(asTextData(view, 3).renderText);
});
it('should create ng-content nodes with parents', () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef(hostElDef(
[textDef(0, ['a'])],
[elementDef(NodeFlags.None, null, null, 1, 'div'), ngContentDef(null, 0)])));
expect(getDOM().firstChild(getDOM().firstChild(rootNodes[0])))
.toBe(asTextData(view, 2).renderText);
});
it('should reproject ng-content nodes', () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
hostElDef([textDef(0, ['a'])], hostElDef([ngContentDef(0, 0)], [
elementDef(NodeFlags.None, null, null, 1, 'span'), ngContentDef(null, 0)
]))));
expect(getDOM().firstChild(getDOM().firstChild(getDOM().firstChild(rootNodes[0]))))
.toBe(asTextData(view, 2).renderText);
});
it('should project already attached embedded views', () => {
class CreateViewService {
constructor(templateRef: TemplateRef<any>, viewContainerRef: ViewContainerRef) {
viewContainerRef.createEmbeddedView(templateRef);
}
}
const {view, rootNodes} = createAndGetRootNodes(compViewDef(hostElDef(
[
anchorDef(
NodeFlags.EmbeddedViews, null, 0, 1, null, embeddedViewDef([textDef(null, ['a'])])),
directiveDef(
NodeFlags.None, null, 0, CreateViewService, [TemplateRef, ViewContainerRef])
],
[elementDef(NodeFlags.None, null, null, 1, 'div'), ngContentDef(null, 0)])));
const anchor = asElementData(view, 2);
expect((getDOM().childNodes(getDOM().firstChild(rootNodes[0]))[0]))
.toBe(anchor.renderElement);
const embeddedView = anchor.embeddedViews[0];
expect((getDOM().childNodes(getDOM().firstChild(rootNodes[0]))[1]))
.toBe(asTextData(embeddedView, 0).renderText);
});
it('should include projected nodes when attaching / detaching embedded views', () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef(hostElDef([textDef(0, ['a'])], [
elementDef(NodeFlags.None, null, null, 1, 'div'),
anchorDef(NodeFlags.EmbeddedViews, null, 0, 0, null, embeddedViewDef([
ngContentDef(null, 0),
// The anchor would be added by the compiler after the ngContent
anchorDef(NodeFlags.None, null, null, 0),
])),
])));
const componentView = asElementData(view, 0).componentView;
const view0 = Services.createEmbeddedView(componentView, componentView.def.nodes[1]);
attachEmbeddedView(view, asElementData(componentView, 1), 0, view0);
expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0])).length).toBe(3);
expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0]))[1])
.toBe(asTextData(view, 2).renderText);
detachEmbeddedView(asElementData(componentView, 1), 0);
expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0])).length).toBe(1);
});
if (isBrowser()) {
it('should use root projectable nodes', () => {
const projectableNodes = [[document.createTextNode('a')], [document.createTextNode('b')]];
const view = createRootView(
compViewDef(hostElDef([], [ngContentDef(null, 0), ngContentDef(null, 1)])), {},
projectableNodes);
const rootNodes = rootRenderNodes(view);
expect(getDOM().childNodes(rootNodes[0])[0]).toBe(projectableNodes[0][0]);
expect(getDOM().childNodes(rootNodes[0])[1]).toBe(projectableNodes[1][0]);
});
}
});
}

View File

@ -0,0 +1,554 @@
/**
* @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 {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectorRef, DoCheck, ElementRef, EventEmitter, Injector, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, Renderer2, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
import {getDebugContext} from '@angular/core/src/errors';
import {ArgumentType, BindingType, DebugContext, DepFlags, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, directiveDef, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {TestBed, inject, withModule} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser} from './helper';
export function main() {
describe(`View Providers`, () => {
function compViewDef(
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer);
}
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
return () => viewDef(ViewFlags.None, nodes, update);
}
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
const view = createRootView(viewDef, {});
const rootNodes = rootRenderNodes(view);
return {rootNodes, view};
}
describe('create', () => {
let instance: SomeService;
class SomeService {
constructor(public dep: any) { instance = this; }
}
beforeEach(() => { instance = null; });
it('should create providers eagerly', () => {
createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, [])
]));
expect(instance instanceof SomeService).toBe(true);
});
it('should create providers lazily', () => {
let lazy: LazyService;
class LazyService {
constructor() { lazy = this; }
}
createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'span'),
providerDef(
NodeFlags.TypeClassProvider | NodeFlags.LazyProvider, null, LazyService, LazyService,
[]),
directiveDef(NodeFlags.None, null, 0, SomeService, [Injector])
]));
expect(lazy).toBeUndefined();
instance.dep.get(LazyService);
expect(lazy instanceof LazyService).toBe(true);
});
it('should create value providers', () => {
createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'span'),
providerDef(NodeFlags.TypeValueProvider, null, 'someToken', 'someValue', []),
directiveDef(NodeFlags.None, null, 0, SomeService, ['someToken']),
]));
expect(instance.dep).toBe('someValue');
});
it('should create factory providers', () => {
function someFactory() { return 'someValue'; }
createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'span'),
providerDef(NodeFlags.TypeFactoryProvider, null, 'someToken', someFactory, []),
directiveDef(NodeFlags.None, null, 0, SomeService, ['someToken']),
]));
expect(instance.dep).toBe('someValue');
});
it('should create useExisting providers', () => {
createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 3, 'span'),
providerDef(NodeFlags.TypeValueProvider, null, 'someExistingToken', 'someValue', []),
providerDef(
NodeFlags.TypeUseExistingProvider, null, 'someToken', null, ['someExistingToken']),
directiveDef(NodeFlags.None, null, 0, SomeService, ['someToken']),
]));
expect(instance.dep).toBe('someValue');
});
it('should add a DebugContext to errors in provider factories', () => {
class SomeService {
constructor() { throw new Error('Test'); }
}
let err: any;
try {
createRootView(
compViewDef([
elementDef(
NodeFlags.None, null, null, 1, 'div', null, null, null, null,
() => compViewDef([textDef(null, ['a'])])),
directiveDef(NodeFlags.Component, null, 0, SomeService, [])
]),
TestBed.get(Injector), [], getDOM().createElement('div'));
} catch (e) {
err = e;
}
expect(err).toBeTruthy();
expect(err.message).toBe('Test');
const debugCtx = getDebugContext(err);
expect(debugCtx.view).toBeTruthy();
expect(debugCtx.nodeIndex).toBe(1);
});
describe('deps', () => {
class Dep {}
it('should inject deps from the same element', () => {
createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'span'),
directiveDef(NodeFlags.None, null, 0, Dep, []),
directiveDef(NodeFlags.None, null, 0, SomeService, [Dep])
]));
expect(instance.dep instanceof Dep).toBeTruthy();
});
it('should inject deps from a parent element', () => {
createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 3, 'span'),
directiveDef(NodeFlags.None, null, 0, Dep, []),
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, [Dep])
]));
expect(instance.dep instanceof Dep).toBeTruthy();
});
it('should not inject deps from sibling root elements', () => {
const nodes = [
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, Dep, []),
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, [Dep])
];
// root elements
expect(() => createAndGetRootNodes(compViewDef(nodes)))
.toThrowError('No provider for Dep!');
// non root elements
expect(
() => createAndGetRootNodes(
compViewDef([elementDef(NodeFlags.None, null, null, 4, 'span')].concat(nodes))))
.toThrowError('No provider for Dep!');
});
it('should inject from a parent elment in a parent view', () => {
createAndGetRootNodes(compViewDef([
elementDef(
NodeFlags.None, null, null, 1, 'div', null, null, null, null,
() => compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, [Dep])
])),
directiveDef(NodeFlags.Component, null, 0, Dep, []),
]));
expect(instance.dep instanceof Dep).toBeTruthy();
});
it('should throw for missing dependencies', () => {
expect(() => createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, ['nonExistingDep'])
])))
.toThrowError('No provider for nonExistingDep!');
});
it('should use null for optional missing dependencies', () => {
createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(
NodeFlags.None, null, 0, SomeService, [[DepFlags.Optional, 'nonExistingDep']])
]));
expect(instance.dep).toBe(null);
});
it('should skip the current element when using SkipSelf', () => {
createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 4, 'span'),
providerDef(NodeFlags.TypeValueProvider, null, 'someToken', 'someParentValue', []),
elementDef(NodeFlags.None, null, null, 2, 'span'),
providerDef(NodeFlags.TypeValueProvider, null, 'someToken', 'someValue', []),
directiveDef(
NodeFlags.None, null, 0, SomeService, [[DepFlags.SkipSelf, 'someToken']])
]));
expect(instance.dep).toBe('someParentValue');
});
it('should ask the root injector',
withModule({providers: [{provide: 'rootDep', useValue: 'rootValue'}]}, () => {
createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, ['rootDep'])
]));
expect(instance.dep).toBe('rootValue');
}));
describe('builtin tokens', () => {
it('should inject ViewContainerRef', () => {
createAndGetRootNodes(compViewDef([
anchorDef(NodeFlags.EmbeddedViews, null, null, 1),
directiveDef(NodeFlags.None, null, 0, SomeService, [ViewContainerRef])
]));
expect(instance.dep.createEmbeddedView).toBeTruthy();
});
it('should inject TemplateRef', () => {
createAndGetRootNodes(compViewDef([
anchorDef(NodeFlags.None, null, null, 1, null, embeddedViewDef([anchorDef(
NodeFlags.None, null, null, 0)])),
directiveDef(NodeFlags.None, null, 0, SomeService, [TemplateRef])
]));
expect(instance.dep.createEmbeddedView).toBeTruthy();
});
it('should inject ElementRef', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, [ElementRef])
]));
expect(instance.dep.nativeElement).toBe(asElementData(view, 0).renderElement);
});
it('should inject Injector', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, [Injector])
]));
expect(instance.dep.get(SomeService)).toBe(instance);
});
it('should inject ChangeDetectorRef for non component providers', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, [ChangeDetectorRef])
]));
expect(instance.dep._view).toBe(view);
});
it('should inject ChangeDetectorRef for component providers', () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(
NodeFlags.None, null, null, 1, 'div', null, null, null, null,
() => compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span'),
])),
directiveDef(NodeFlags.Component, null, 0, SomeService, [ChangeDetectorRef]),
]));
const compView = asElementData(view, 0).componentView;
expect(instance.dep._view).toBe(compView);
});
it('should inject RendererV1', () => {
createAndGetRootNodes(compViewDef([
elementDef(
NodeFlags.None, null, null, 1, 'span', null, null, null, null,
() => compViewDef([anchorDef(NodeFlags.None, null, null, 0)])),
directiveDef(NodeFlags.Component, null, 0, SomeService, [Renderer])
]));
expect(instance.dep.createElement).toBeTruthy();
});
it('should inject Renderer2', () => {
createAndGetRootNodes(compViewDef([
elementDef(
NodeFlags.None, null, null, 1, 'span', null, null, null, null,
() => compViewDef([anchorDef(NodeFlags.None, null, null, 0)])),
directiveDef(NodeFlags.Component, null, 0, SomeService, [Renderer2])
]));
expect(instance.dep.createElement).toBeTruthy();
});
});
});
});
describe('data binding', () => {
ARG_TYPE_VALUES.forEach((inlineDynamic) => {
it(`should update via strategy ${inlineDynamic}`, () => {
let instance: SomeService;
class SomeService {
a: any;
b: any;
constructor() { instance = this; }
}
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, [], {a: [0, 'a'], b: [1, 'b']})
],
(check, view) => {
checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, ['v1', 'v2']);
}));
Services.checkAndUpdateView(view);
expect(instance.a).toBe('v1');
expect(instance.b).toBe('v2');
const el = rootNodes[0];
expect(getDOM().getAttribute(el, 'ng-reflect-a')).toBe('v1');
});
});
});
describe('outputs', () => {
it('should listen to provider events', () => {
let emitter = new EventEmitter<any>();
let unsubscribeSpy: any;
class SomeService {
emitter = {
subscribe: (callback: any) => {
const subscription = emitter.subscribe(callback);
unsubscribeSpy = spyOn(subscription, 'unsubscribe').and.callThrough();
return subscription;
}
};
}
const handleEvent = jasmine.createSpy('handleEvent');
const subscribe = spyOn(emitter, 'subscribe').and.callThrough();
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span', null, null, null, handleEvent),
directiveDef(
NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'})
]));
emitter.emit('someEventInstance');
expect(handleEvent).toHaveBeenCalledWith(view, 'someEventName', 'someEventInstance');
Services.destroyView(view);
expect(unsubscribeSpy).toHaveBeenCalled();
});
it('should report debug info on event errors', () => {
let emitter = new EventEmitter<any>();
class SomeService {
emitter = emitter;
}
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(
NodeFlags.None, null, null, 1, 'span', null, null, null,
() => { throw new Error('Test'); }),
directiveDef(
NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'})
]));
let err: any;
try {
emitter.emit('someEventInstance');
} catch (e) {
err = e;
}
expect(err).toBeTruthy();
const debugCtx = getDebugContext(err);
expect(debugCtx.view).toBe(view);
// events are emitted with the index of the element, not the index of the provider.
expect(debugCtx.nodeIndex).toBe(0);
});
});
describe('lifecycle hooks', () => {
it('should call the lifecycle hooks in the right order', () => {
let instanceCount = 0;
let log: string[] = [];
class SomeService implements OnInit, DoCheck, OnChanges, AfterContentInit,
AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy {
id: number;
a: any;
ngOnInit() { log.push(`${this.id}_ngOnInit`); }
ngDoCheck() { log.push(`${this.id}_ngDoCheck`); }
ngOnChanges() { log.push(`${this.id}_ngOnChanges`); }
ngAfterContentInit() { log.push(`${this.id}_ngAfterContentInit`); }
ngAfterContentChecked() { log.push(`${this.id}_ngAfterContentChecked`); }
ngAfterViewInit() { log.push(`${this.id}_ngAfterViewInit`); }
ngAfterViewChecked() { log.push(`${this.id}_ngAfterViewChecked`); }
ngOnDestroy() { log.push(`${this.id}_ngOnDestroy`); }
constructor() { this.id = instanceCount++; }
}
const allFlags = NodeFlags.OnInit | NodeFlags.DoCheck | NodeFlags.OnChanges |
NodeFlags.AfterContentInit | NodeFlags.AfterContentChecked | NodeFlags.AfterViewInit |
NodeFlags.AfterViewChecked | NodeFlags.OnDestroy;
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(NodeFlags.None, null, null, 3, 'span'),
directiveDef(allFlags, null, 0, SomeService, [], {a: [0, 'a']}),
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(allFlags, null, 0, SomeService, [], {a: [0, 'a']})
],
(check, view) => {
check(view, 1, ArgumentType.Inline, 'someValue');
check(view, 3, ArgumentType.Inline, 'someValue');
}));
Services.checkAndUpdateView(view);
// Note: After... hooks are called bottom up.
expect(log).toEqual([
'0_ngOnChanges',
'0_ngOnInit',
'0_ngDoCheck',
'1_ngOnChanges',
'1_ngOnInit',
'1_ngDoCheck',
'1_ngAfterContentInit',
'1_ngAfterContentChecked',
'0_ngAfterContentInit',
'0_ngAfterContentChecked',
'1_ngAfterViewInit',
'1_ngAfterViewChecked',
'0_ngAfterViewInit',
'0_ngAfterViewChecked',
]);
log = [];
Services.checkAndUpdateView(view);
// Note: After... hooks are called bottom up.
expect(log).toEqual([
'0_ngDoCheck', '1_ngDoCheck', '1_ngAfterContentChecked', '0_ngAfterContentChecked',
'1_ngAfterViewChecked', '0_ngAfterViewChecked'
]);
log = [];
Services.destroyView(view);
// Note: ngOnDestroy ist called bottom up.
expect(log).toEqual(['1_ngOnDestroy', '0_ngOnDestroy']);
});
it('should call ngOnChanges with the changed values and the non minified names', () => {
let changesLog: SimpleChange[] = [];
let currValue = 'v1';
class SomeService implements OnChanges {
a: any;
ngOnChanges(changes: {[name: string]: SimpleChange}) {
changesLog.push(changes['nonMinifiedA']);
}
}
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(
NodeFlags.OnChanges, null, 0, SomeService, [], {a: [0, 'nonMinifiedA']})
],
(check, view) => { check(view, 1, ArgumentType.Inline, currValue); }));
Services.checkAndUpdateView(view);
expect(changesLog).toEqual([new SimpleChange(undefined, 'v1', true)]);
currValue = 'v2';
changesLog = [];
Services.checkAndUpdateView(view);
expect(changesLog).toEqual([new SimpleChange('v1', 'v2', false)]);
});
it('should add a DebugContext to errors in provider afterXXX lifecycles', () => {
class SomeService implements AfterContentChecked {
ngAfterContentChecked() { throw new Error('Test'); }
}
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.AfterContentChecked, null, 0, SomeService, [], {a: [0, 'a']}),
]));
let err: any;
try {
Services.checkAndUpdateView(view);
} catch (e) {
err = e;
}
expect(err).toBeTruthy();
expect(err.message).toBe('Test');
const debugCtx = getDebugContext(err);
expect(debugCtx.view).toBe(view);
expect(debugCtx.nodeIndex).toBe(1);
});
it('should add a DebugContext to errors inServices.destroyView', () => {
class SomeService implements OnDestroy {
ngOnDestroy() { throw new Error('Test'); }
}
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.OnDestroy, null, 0, SomeService, [], {a: [0, 'a']}),
]));
let err: any;
try {
Services.destroyView(view);
} catch (e) {
err = e;
}
expect(err).toBeTruthy();
expect(err.message).toBe('Test');
const debugCtx = getDebugContext(err);
expect(debugCtx.view).toBe(view);
expect(debugCtx.nodeIndex).toBe(1);
});
});
});
}

View File

@ -0,0 +1,149 @@
/**
* @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 {Injector, PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue} from '@angular/core';
import {ArgumentType, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, asPureExpressionData, directiveDef, elementDef, nodeValue, pipeDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {inject} from '@angular/core/testing';
import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView} from './helper';
export function main() {
describe(`View Pure Expressions`, () => {
function compViewDef(
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer);
}
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
const view = createRootView(viewDef);
const rootNodes = rootRenderNodes(view);
return {rootNodes, view};
}
class Service {
data: any;
}
describe('pure arrays', () => {
ARG_TYPE_VALUES.forEach((inlineDynamic) => {
it(`should update via strategy ${inlineDynamic}`, () => {
let values: any[];
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(NodeFlags.None, null, null, 2, 'span'), pureArrayDef(2),
directiveDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
],
(check, view) => {
const pureValue = checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, values);
checkNodeInlineOrDynamic(check, view, 2, inlineDynamic, [pureValue]);
}));
const service = asProviderData(view, 2).instance;
values = [1, 2];
Services.checkAndUpdateView(view);
const arr0 = service.data;
expect(arr0).toEqual([1, 2]);
// instance should not change
// if the values don't change
Services.checkAndUpdateView(view);
expect(service.data).toBe(arr0);
values = [3, 2];
Services.checkAndUpdateView(view);
const arr1 = service.data;
expect(arr1).not.toBe(arr0);
expect(arr1).toEqual([3, 2]);
});
});
});
describe('pure objects', () => {
ARG_TYPE_VALUES.forEach((inlineDynamic) => {
it(`should update via strategy ${inlineDynamic}`, () => {
let values: any[];
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(NodeFlags.None, null, null, 2, 'span'), pureObjectDef(['a', 'b']),
directiveDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
],
(check, view) => {
const pureValue = checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, values);
checkNodeInlineOrDynamic(check, view, 2, inlineDynamic, [pureValue]);
}));
const service = asProviderData(view, 2).instance;
values = [1, 2];
Services.checkAndUpdateView(view);
const obj0 = service.data;
expect(obj0).toEqual({a: 1, b: 2});
// instance should not change
// if the values don't change
Services.checkAndUpdateView(view);
expect(service.data).toBe(obj0);
values = [3, 2];
Services.checkAndUpdateView(view);
const obj1 = service.data;
expect(obj1).not.toBe(obj0);
expect(obj1).toEqual({a: 3, b: 2});
});
});
});
describe('pure pipes', () => {
ARG_TYPE_VALUES.forEach((inlineDynamic) => {
it(`should update via strategy ${inlineDynamic}`, () => {
class SomePipe implements PipeTransform {
transform(v1: any, v2: any) { return [v1 + 10, v2 + 20]; }
}
let values: any[];
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(NodeFlags.None, null, null, 3, 'span'),
pipeDef(NodeFlags.None, SomePipe, []), purePipeDef(2),
directiveDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
],
(check, view) => {
const pureValue = checkNodeInlineOrDynamic(
check, view, 2, inlineDynamic, [nodeValue(view, 1)].concat(values));
checkNodeInlineOrDynamic(check, view, 3, inlineDynamic, [pureValue]);
}));
const service = asProviderData(view, 3).instance;
values = [1, 2];
Services.checkAndUpdateView(view);
const obj0 = service.data;
expect(obj0).toEqual([11, 22]);
// instance should not change
// if the values don't change
Services.checkAndUpdateView(view);
expect(service.data).toBe(obj0);
values = [3, 2];
Services.checkAndUpdateView(view);
const obj1 = service.data;
expect(obj1).not.toBe(obj0);
expect(obj1).toEqual([13, 22]);
});
});
});
});
}

View File

@ -0,0 +1,431 @@
/**
* @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 {ElementRef, Injector, QueryList, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core';
import {getDebugContext} from '@angular/core/src/errors';
import {BindingType, DebugContext, NodeDef, NodeFlags, QueryBindingType, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, queryDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {inject} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView} from './helper';
export function main() {
describe(`Query Views`, () => {
function compViewDef(
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer);
}
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
return () => viewDef(ViewFlags.None, nodes, update);
}
function createAndGetRootNodes(
viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} {
const view = createRootView(viewDef, context);
const rootNodes = rootRenderNodes(view);
return {rootNodes, view};
}
const someQueryId = 1;
class AService {}
class QueryService {
a: QueryList<AService>;
}
function contentQueryProviders() {
return [
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(
NodeFlags.TypeContentQuery | NodeFlags.DynamicQuery, someQueryId,
{'a': QueryBindingType.All})
];
}
function compViewQueryProviders(extraChildCount: number, nodes: NodeDef[]) {
return [
elementDef(
NodeFlags.None, null, null, 1 + extraChildCount, 'div', null, null, null, null,
() => compViewDef([
queryDef(
NodeFlags.TypeViewQuery | NodeFlags.DynamicQuery, someQueryId,
{'a': QueryBindingType.All}),
...nodes
])),
directiveDef(NodeFlags.Component, null, 0, QueryService, [], null, null, ),
];
}
function aServiceProvider() {
return directiveDef(
NodeFlags.None, [[someQueryId, QueryValueType.Provider]], 0, AService, []);
}
describe('content queries', () => {
it('should query providers on the same element and child elements', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 5, 'div'),
...contentQueryProviders(),
aServiceProvider(),
elementDef(NodeFlags.None, null, null, 1, 'div'),
aServiceProvider(),
]));
const qs: QueryService = asProviderData(view, 1).instance;
expect(qs.a).toBeUndefined();
Services.checkAndUpdateView(view);
const as = qs.a.toArray();
expect(as.length).toBe(2);
expect(as[0]).toBe(asProviderData(view, 3).instance);
expect(as[1]).toBe(asProviderData(view, 5).instance);
});
it('should not query providers on sibling or parent elements', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 6, 'div'),
aServiceProvider(),
elementDef(NodeFlags.None, null, null, 2, 'div'),
...contentQueryProviders(),
elementDef(NodeFlags.None, null, null, 1, 'div'),
aServiceProvider(),
]));
Services.checkAndUpdateView(view);
const qs: QueryService = asProviderData(view, 3).instance;
expect(qs.a.length).toBe(0);
});
});
describe('view queries', () => {
it('should query providers in the view', () => {
const {view} = createAndGetRootNodes(compViewDef([
...compViewQueryProviders(
0,
[
elementDef(NodeFlags.None, null, null, 1, 'span'),
aServiceProvider(),
]),
]));
Services.checkAndUpdateView(view);
const comp: QueryService = asProviderData(view, 1).instance;
const compView = asElementData(view, 0).componentView;
expect(comp.a.length).toBe(1);
expect(comp.a.first).toBe(asProviderData(compView, 2).instance);
});
it('should not query providers on the host element', () => {
const {view} = createAndGetRootNodes(compViewDef([
...compViewQueryProviders(
1,
[
elementDef(NodeFlags.None, null, null, 0, 'span'),
]),
aServiceProvider(),
]));
Services.checkAndUpdateView(view);
const comp: QueryService = asProviderData(view, 1).instance;
expect(comp.a.length).toBe(0);
});
});
describe('embedded views', () => {
it('should query providers in embedded views', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 5, 'div'),
...contentQueryProviders(),
anchorDef(NodeFlags.EmbeddedViews, null, null, 2, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
aServiceProvider(),
])),
...contentQueryProviders(),
]));
const childView = Services.createEmbeddedView(view, view.def.nodes[3]);
attachEmbeddedView(view, asElementData(view, 3), 0, childView);
Services.checkAndUpdateView(view);
// queries on parent elements of anchors
const qs1: QueryService = asProviderData(view, 1).instance;
expect(qs1.a.length).toBe(1);
expect(qs1.a.first instanceof AService).toBe(true);
// queries on the anchor
const qs2: QueryService = asProviderData(view, 4).instance;
expect(qs2.a.length).toBe(1);
expect(qs2.a.first instanceof AService).toBe(true);
});
it('should query providers in embedded views only at the template declaration', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 3, 'div'),
...contentQueryProviders(),
anchorDef(NodeFlags.EmbeddedViews, null, null, 0, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
aServiceProvider(),
])),
elementDef(NodeFlags.None, null, null, 3, 'div'),
...contentQueryProviders(),
anchorDef(NodeFlags.EmbeddedViews, null, null, 0),
]));
const childView = Services.createEmbeddedView(view, view.def.nodes[3]);
// attach at a different place than the one where the template was defined
attachEmbeddedView(view, asElementData(view, 7), 0, childView);
Services.checkAndUpdateView(view);
// query on the declaration place
const qs1: QueryService = asProviderData(view, 1).instance;
expect(qs1.a.length).toBe(1);
expect(qs1.a.first instanceof AService).toBe(true);
// query on the attach place
const qs2: QueryService = asProviderData(view, 5).instance;
expect(qs2.a.length).toBe(0);
});
it('should update content queries if embedded views are added or removed', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 3, 'div'),
...contentQueryProviders(),
anchorDef(NodeFlags.EmbeddedViews, null, null, 0, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
aServiceProvider(),
])),
]));
Services.checkAndUpdateView(view);
const qs: QueryService = asProviderData(view, 1).instance;
expect(qs.a.length).toBe(0);
const childView = Services.createEmbeddedView(view, view.def.nodes[3]);
attachEmbeddedView(view, asElementData(view, 3), 0, childView);
Services.checkAndUpdateView(view);
expect(qs.a.length).toBe(1);
detachEmbeddedView(asElementData(view, 3), 0);
Services.checkAndUpdateView(view);
expect(qs.a.length).toBe(0);
});
it('should update view queries if embedded views are added or removed', () => {
const {view} = createAndGetRootNodes(compViewDef([
...compViewQueryProviders(
0,
[
anchorDef(NodeFlags.EmbeddedViews, null, null, 0, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
aServiceProvider(),
])),
]),
]));
Services.checkAndUpdateView(view);
const comp: QueryService = asProviderData(view, 1).instance;
expect(comp.a.length).toBe(0);
const compView = asElementData(view, 0).componentView;
const childView = Services.createEmbeddedView(compView, compView.def.nodes[1]);
attachEmbeddedView(view, asElementData(compView, 1), 0, childView);
Services.checkAndUpdateView(view);
expect(comp.a.length).toBe(1);
detachEmbeddedView(asElementData(compView, 1), 0);
Services.checkAndUpdateView(view);
expect(comp.a.length).toBe(0);
});
});
describe('QueryBindingType', () => {
it('should query all matches', () => {
class QueryService {
a: QueryList<AService>;
}
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 4, 'div'),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(
NodeFlags.TypeContentQuery | NodeFlags.DynamicQuery, someQueryId,
{'a': QueryBindingType.All}),
aServiceProvider(),
aServiceProvider(),
]));
Services.checkAndUpdateView(view);
const qs: QueryService = asProviderData(view, 1).instance;
expect(qs.a instanceof QueryList).toBeTruthy();
expect(qs.a.toArray()).toEqual([
asProviderData(view, 3).instance,
asProviderData(view, 4).instance,
]);
});
it('should query the first match', () => {
class QueryService {
a: AService;
}
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 4, 'div'),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(
NodeFlags.TypeContentQuery | NodeFlags.DynamicQuery, someQueryId,
{'a': QueryBindingType.First}),
aServiceProvider(),
aServiceProvider(),
]));
Services.checkAndUpdateView(view);
const qs: QueryService = asProviderData(view, 1).instance;
expect(qs.a).toBe(asProviderData(view, 3).instance);
});
});
describe('query builtins', () => {
it('should query ElementRef', () => {
class QueryService {
a: ElementRef;
}
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, [[someQueryId, QueryValueType.ElementRef]], null, 2, 'div'),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(
NodeFlags.TypeContentQuery | NodeFlags.DynamicQuery, someQueryId,
{'a': QueryBindingType.First}),
]));
Services.checkAndUpdateView(view);
const qs: QueryService = asProviderData(view, 1).instance;
expect(qs.a.nativeElement).toBe(asElementData(view, 0).renderElement);
});
it('should query TemplateRef', () => {
class QueryService {
a: TemplateRef<any>;
}
const {view} = createAndGetRootNodes(compViewDef([
anchorDef(
NodeFlags.None, [[someQueryId, QueryValueType.TemplateRef]], null, 2, null,
embeddedViewDef([anchorDef(NodeFlags.None, null, null, 0)])),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(
NodeFlags.TypeContentQuery | NodeFlags.DynamicQuery, someQueryId,
{'a': QueryBindingType.First}),
]));
Services.checkAndUpdateView(view);
const qs: QueryService = asProviderData(view, 1).instance;
expect(qs.a.createEmbeddedView).toBeTruthy();
});
it('should query ViewContainerRef', () => {
class QueryService {
a: ViewContainerRef;
}
const {view} = createAndGetRootNodes(compViewDef([
anchorDef(NodeFlags.None, [[someQueryId, QueryValueType.ViewContainerRef]], null, 2),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(
NodeFlags.TypeContentQuery | NodeFlags.DynamicQuery, someQueryId,
{'a': QueryBindingType.First}),
]));
Services.checkAndUpdateView(view);
const qs: QueryService = asProviderData(view, 1).instance;
expect(qs.a.createEmbeddedView).toBeTruthy();
});
});
describe('general binding behavior', () => {
it('should checkNoChanges', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 3, 'div'),
...contentQueryProviders(),
anchorDef(NodeFlags.EmbeddedViews, null, null, 0, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
aServiceProvider(),
])),
]));
Services.checkAndUpdateView(view);
Services.checkNoChangesView(view);
const childView = Services.createEmbeddedView(view, view.def.nodes[3]);
attachEmbeddedView(view, asElementData(view, 3), 0, childView);
let err: any;
try {
Services.checkNoChangesView(view);
} catch (e) {
err = e;
}
expect(err).toBeTruthy();
expect(err.message)
.toBe(
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'Query 1 not dirty'. Current value: 'Query 1 dirty'.`);
const debugCtx = getDebugContext(err);
expect(debugCtx.view).toBe(view);
expect(debugCtx.nodeIndex).toBe(2);
});
it('should report debug info on binding errors', () => {
class QueryService {
set a(value: any) { throw new Error('Test'); }
}
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 3, 'div'),
directiveDef(NodeFlags.None, null, 1, QueryService, []),
queryDef(
NodeFlags.TypeContentQuery | NodeFlags.DynamicQuery, someQueryId,
{'a': QueryBindingType.All}),
aServiceProvider(),
]));
let err: any;
try {
Services.checkAndUpdateView(view);
} catch (e) {
err = e;
}
expect(err).toBeTruthy();
expect(err.message).toBe('Test');
const debugCtx = getDebugContext(err);
expect(debugCtx.view).toBe(view);
expect(debugCtx.nodeIndex).toBe(2);
});
});
});
}

View File

@ -0,0 +1,90 @@
/**
* @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 {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, getDebugNode} from '@angular/core';
import {DebugContext, NodeDef, NodeFlags, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, asTextData, directiveDef, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {inject} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView, isBrowser} from './helper';
export function main() {
describe('View Services', () => {
function compViewDef(
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer);
}
function createAndGetRootNodes(
viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} {
const view = createRootView(viewDef, context);
const rootNodes = rootRenderNodes(view);
return {rootNodes, view};
}
describe('DebugContext', () => {
class AComp {}
class AService {}
function createViewWithData() {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(
NodeFlags.None, null, null, 1, 'div', null, null, null, null,
() => compViewDef([
elementDef(NodeFlags.None, [['ref', QueryValueType.ElementRef]], null, 2, 'span'),
directiveDef(NodeFlags.None, null, 0, AService, []), textDef(null, ['a'])
])),
directiveDef(NodeFlags.Component, null, 0, AComp, []),
]));
return view;
}
it('should provide data for elements', () => {
const view = createViewWithData();
const compView = asElementData(view, 0).componentView;
const debugCtx = Services.createDebugContext(compView, 0);
expect(debugCtx.componentRenderElement).toBe(asElementData(view, 0).renderElement);
expect(debugCtx.renderNode).toBe(asElementData(compView, 0).renderElement);
expect(debugCtx.injector.get(AComp)).toBe(compView.component);
expect(debugCtx.component).toBe(compView.component);
expect(debugCtx.context).toBe(compView.context);
expect(debugCtx.providerTokens).toEqual([AService]);
expect(debugCtx.source).toBeTruthy();
expect(debugCtx.references['ref'].nativeElement)
.toBe(asElementData(compView, 0).renderElement);
});
it('should provide data for text nodes', () => {
const view = createViewWithData();
const compView = asElementData(view, 0).componentView;
const debugCtx = Services.createDebugContext(compView, 2);
expect(debugCtx.componentRenderElement).toBe(asElementData(view, 0).renderElement);
expect(debugCtx.renderNode).toBe(asTextData(compView, 2).renderText);
expect(debugCtx.injector.get(AComp)).toBe(compView.component);
expect(debugCtx.component).toBe(compView.component);
expect(debugCtx.context).toBe(compView.context);
expect(debugCtx.source).toBeTruthy();
});
it('should provide data for other nodes based on the nearest element parent', () => {
const view = createViewWithData();
const compView = asElementData(view, 0).componentView;
const debugCtx = Services.createDebugContext(compView, 1);
expect(debugCtx.renderNode).toBe(asElementData(compView, 0).renderElement);
});
});
});
}

View File

@ -0,0 +1,84 @@
/**
* @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 {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
import {ArgumentType, DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asTextData, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {inject} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser} from './helper';
export function main() {
describe(`View Text`, () => {
function compViewDef(
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer);
}
function createAndGetRootNodes(
viewDef: ViewDefinition, context?: any): {rootNodes: any[], view: ViewData} {
const view = createRootView(viewDef, context);
const rootNodes = rootRenderNodes(view);
return {rootNodes, view};
}
describe('create', () => {
it('should create text nodes without parents', () => {
const rootNodes = createAndGetRootNodes(compViewDef([textDef(null, ['a'])])).rootNodes;
expect(rootNodes.length).toBe(1);
expect(getDOM().getText(rootNodes[0])).toBe('a');
});
it('should create views with multiple root text nodes', () => {
const rootNodes = createAndGetRootNodes(compViewDef([
textDef(null, ['a']), textDef(null, ['b'])
])).rootNodes;
expect(rootNodes.length).toBe(2);
});
it('should create text nodes with parents', () => {
const rootNodes = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
textDef(null, ['a']),
])).rootNodes;
expect(rootNodes.length).toBe(1);
const textNode = getDOM().firstChild(rootNodes[0]);
expect(getDOM().getText(textNode)).toBe('a');
});
it('should add debug information to the renderer', () => {
const someContext = new Object();
const {view, rootNodes} =
createAndGetRootNodes(compViewDef([textDef(null, ['a'])]), someContext);
expect(getDebugNode(rootNodes[0]).nativeNode).toBe(asTextData(view, 0).renderText);
});
});
describe('change text', () => {
ARG_TYPE_VALUES.forEach((inlineDynamic) => {
it(`should update via strategy ${inlineDynamic}`, () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
textDef(null, ['0', '1', '2']),
],
null, (check, view) => {
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['a', 'b']);
}));
Services.checkAndUpdateView(view);
const node = rootNodes[0];
expect(getDOM().getText(rootNodes[0])).toBe('0a1b2');
});
});
});
});
}

View File

@ -0,0 +1,219 @@
/**
* @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 {NodeFlags, QueryValueType, ViewData, ViewDefinition, ViewFlags, anchorDef, directiveDef, elementDef, textDef, viewDef} from '@angular/core/src/view/index';
import {filterQueryId} from '@angular/core/src/view/util';
export function main() {
describe('viewDef', () => {
describe('parent', () => {
function parents(viewDef: ViewDefinition): number[] {
return viewDef.nodes.map(node => node.parent ? node.parent.index : null);
}
it('should calculate parents for one level', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 2, 'span'),
textDef(null, ['a']),
textDef(null, ['a']),
]);
expect(parents(vd)).toEqual([null, 0, 0]);
});
it('should calculate parents for one level, multiple roots', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 1, 'span'),
textDef(null, ['a']),
elementDef(NodeFlags.None, null, null, 1, 'span'),
textDef(null, ['a']),
textDef(null, ['a']),
]);
expect(parents(vd)).toEqual([null, 0, null, 2, null]);
});
it('should calculate parents for multiple levels', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 2, 'span'),
elementDef(NodeFlags.None, null, null, 1, 'span'),
textDef(null, ['a']),
elementDef(NodeFlags.None, null, null, 1, 'span'),
textDef(null, ['a']),
textDef(null, ['a']),
]);
expect(parents(vd)).toEqual([null, 0, 1, null, 3, null]);
});
});
describe('childFlags', () => {
function childFlags(viewDef: ViewDefinition): number[] {
return viewDef.nodes.map(node => node.childFlags);
}
function directChildFlags(viewDef: ViewDefinition): number[] {
return viewDef.nodes.map(node => node.directChildFlags);
}
it('should calculate childFlags for one level', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.AfterContentChecked, null, 0, AService, [])
]);
expect(childFlags(vd)).toEqual([
NodeFlags.TypeDirective | NodeFlags.AfterContentChecked, NodeFlags.None
]);
expect(directChildFlags(vd)).toEqual([
NodeFlags.TypeDirective | NodeFlags.AfterContentChecked, NodeFlags.None
]);
});
it('should calculate childFlags for two levels', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 2, 'span'),
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.AfterContentChecked, null, 0, AService, [])
]);
expect(childFlags(vd)).toEqual([
NodeFlags.TypeElement | NodeFlags.TypeDirective | NodeFlags.AfterContentChecked,
NodeFlags.TypeDirective | NodeFlags.AfterContentChecked, NodeFlags.None
]);
expect(directChildFlags(vd)).toEqual([
NodeFlags.TypeElement, NodeFlags.TypeDirective | NodeFlags.AfterContentChecked,
NodeFlags.None
]);
});
it('should calculate childFlags for one level, multiple roots', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.AfterContentChecked, null, 0, AService, []),
elementDef(NodeFlags.None, null, null, 2, 'span'),
directiveDef(NodeFlags.AfterContentInit, null, 0, AService, []),
directiveDef(NodeFlags.AfterViewChecked, null, 0, AService, []),
]);
expect(childFlags(vd)).toEqual([
NodeFlags.TypeDirective | NodeFlags.AfterContentChecked, NodeFlags.None,
NodeFlags.TypeDirective | NodeFlags.AfterContentInit | NodeFlags.AfterViewChecked,
NodeFlags.None, NodeFlags.None
]);
expect(directChildFlags(vd)).toEqual([
NodeFlags.TypeDirective | NodeFlags.AfterContentChecked, NodeFlags.None,
NodeFlags.TypeDirective | NodeFlags.AfterContentInit | NodeFlags.AfterViewChecked,
NodeFlags.None, NodeFlags.None
]);
});
it('should calculate childFlags for multiple levels', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 2, 'span'),
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.AfterContentChecked, null, 0, AService, []),
elementDef(NodeFlags.None, null, null, 2, 'span'),
directiveDef(NodeFlags.AfterContentInit, null, 0, AService, []),
directiveDef(NodeFlags.AfterViewInit, null, 0, AService, []),
]);
expect(childFlags(vd)).toEqual([
NodeFlags.TypeElement | NodeFlags.TypeDirective | NodeFlags.AfterContentChecked,
NodeFlags.TypeDirective | NodeFlags.AfterContentChecked, NodeFlags.None,
NodeFlags.TypeDirective | NodeFlags.AfterContentInit | NodeFlags.AfterViewInit,
NodeFlags.None, NodeFlags.None
]);
expect(directChildFlags(vd)).toEqual([
NodeFlags.TypeElement, NodeFlags.TypeDirective | NodeFlags.AfterContentChecked,
NodeFlags.None,
NodeFlags.TypeDirective | NodeFlags.AfterContentInit | NodeFlags.AfterViewInit,
NodeFlags.None, NodeFlags.None
]);
});
});
describe('childMatchedQueries', () => {
function childMatchedQueries(viewDef: ViewDefinition): number[] {
return viewDef.nodes.map(node => node.childMatchedQueries);
}
it('should calculate childMatchedQueries for one level', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, [])
]);
expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), 0]);
});
it('should calculate childMatchedQueries for two levels', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 2, 'span'),
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, [])
]);
expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), filterQueryId(1), 0]);
});
it('should calculate childMatchedQueries for one level, multiple roots', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, []),
elementDef(NodeFlags.None, null, null, 2, 'span'),
directiveDef(NodeFlags.None, [[2, QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [[3, QueryValueType.Provider]], 0, AService, []),
]);
expect(childMatchedQueries(vd)).toEqual([
filterQueryId(1), 0, filterQueryId(2) | filterQueryId(3), 0, 0
]);
});
it('should calculate childMatchedQueries for multiple levels', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 2, 'span'),
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, [[1, QueryValueType.Provider]], 0, AService, []),
elementDef(NodeFlags.None, null, null, 2, 'span'),
directiveDef(NodeFlags.None, [[2, QueryValueType.Provider]], 0, AService, []),
directiveDef(NodeFlags.None, [[3, QueryValueType.Provider]], 0, AService, []),
]);
expect(childMatchedQueries(vd)).toEqual([
filterQueryId(1), filterQueryId(1), 0, filterQueryId(2) | filterQueryId(3), 0, 0
]);
});
it('should included embedded views into childMatchedQueries', () => {
const vd = viewDef(ViewFlags.None, [
elementDef(NodeFlags.None, null, null, 1, 'span'),
anchorDef(
NodeFlags.None, null, null, 0, null,
() => viewDef(
ViewFlags.None,
[
elementDef(NodeFlags.None, [[1, QueryValueType.Provider]], null, 0, 'span'),
]))
]);
// Note: the template will become a sibling to the anchor once stamped out,
expect(childMatchedQueries(vd)).toEqual([filterQueryId(1), 0]);
});
});
});
}
class AService {}