refactor: move angular source to /packages rather than modules/@angular
This commit is contained in:
62
packages/core/test/view/anchor_spec.ts
Normal file
62
packages/core/test/view/anchor_spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
312
packages/core/test/view/component_view_spec.ts
Normal file
312
packages/core/test/view/component_view_spec.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
302
packages/core/test/view/element_spec.ts
Normal file
302
packages/core/test/view/element_spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
189
packages/core/test/view/embedded_view_spec.ts
Normal file
189
packages/core/test/view/embedded_view_spec.ts
Normal 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']);
|
||||
});
|
||||
});
|
||||
}
|
41
packages/core/test/view/helper.ts
Normal file
41
packages/core/test/view/helper.ts
Normal 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)); });
|
139
packages/core/test/view/ng_content_spec.ts
Normal file
139
packages/core/test/view/ng_content_spec.ts
Normal 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]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
554
packages/core/test/view/provider_spec.ts
Normal file
554
packages/core/test/view/provider_spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
149
packages/core/test/view/pure_expression_spec.ts
Normal file
149
packages/core/test/view/pure_expression_spec.ts
Normal 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]);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
431
packages/core/test/view/query_spec.ts
Normal file
431
packages/core/test/view/query_spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
90
packages/core/test/view/services_spec.ts
Normal file
90
packages/core/test/view/services_spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
84
packages/core/test/view/text_spec.ts
Normal file
84
packages/core/test/view/text_spec.ts
Normal 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');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
219
packages/core/test/view/view_def_spec.ts
Normal file
219
packages/core/test/view/view_def_spec.ts
Normal 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 {}
|
Reference in New Issue
Block a user