feat(core): add initial view engine (#14014)
The new view engine allows our codegen to produce less code, as it can interpret view definitions during runtime. The view engine is not feature complete yet, but already allows to implement a tree benchmark based on it. Part of #14013
This commit is contained in:

committed by
Alex Rickabaugh

parent
9d8c467cb0
commit
2f87eb52fe
70
modules/@angular/core/test/view/anchor_spec.ts
Normal file
70
modules/@angular/core/test/view/anchor_spec.ts
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @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 {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
||||
import {DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, 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 {isBrowser, setupAndCheckRenderer} from './helper';
|
||||
|
||||
export function main() {
|
||||
if (isBrowser()) {
|
||||
defineTests({directDom: true, viewFlags: ViewFlags.DirectDom});
|
||||
}
|
||||
defineTests({directDom: false, viewFlags: 0});
|
||||
}
|
||||
|
||||
function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||
describe(`View Anchor, directDom: ${config.directDom}`, () => {
|
||||
setupAndCheckRenderer(config);
|
||||
|
||||
let services: Services;
|
||||
let renderComponentType: RenderComponentType;
|
||||
|
||||
beforeEach(
|
||||
inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => {
|
||||
services = new DefaultServices(rootRenderer, sanitizer);
|
||||
renderComponentType =
|
||||
new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {});
|
||||
}));
|
||||
|
||||
function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition {
|
||||
return viewDef(config.viewFlags, nodes, updater, renderComponentType);
|
||||
}
|
||||
|
||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
||||
const view = createRootView(services, viewDef);
|
||||
const rootNodes = rootRenderNodes(view);
|
||||
return {rootNodes, view};
|
||||
}
|
||||
|
||||
describe('create', () => {
|
||||
it('should create anchor nodes without parents', () => {
|
||||
const rootNodes =
|
||||
createAndGetRootNodes(compViewDef([anchorDef(NodeFlags.None, 0)])).rootNodes;
|
||||
expect(rootNodes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should create views with multiple root anchor nodes', () => {
|
||||
const rootNodes = createAndGetRootNodes(compViewDef([
|
||||
anchorDef(NodeFlags.None, 0), anchorDef(NodeFlags.None, 0)
|
||||
])).rootNodes;
|
||||
expect(rootNodes.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should create anchor nodes with parents', () => {
|
||||
const rootNodes = createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'div'),
|
||||
anchorDef(NodeFlags.None, 0),
|
||||
])).rootNodes;
|
||||
expect(getDOM().childNodes(rootNodes[0]).length).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
128
modules/@angular/core/test/view/component_view_spec.ts
Normal file
128
modules/@angular/core/test/view/component_view_spec.ts
Normal file
@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @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 {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
||||
import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, destroyView, elementDef, providerDef, 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 {isBrowser, setupAndCheckRenderer} from './helper';
|
||||
|
||||
export function main() {
|
||||
if (isBrowser()) {
|
||||
defineTests({directDom: true, viewFlags: ViewFlags.DirectDom});
|
||||
}
|
||||
defineTests({directDom: false, viewFlags: 0});
|
||||
}
|
||||
|
||||
function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||
describe(`Component Views, directDom: ${config.directDom}`, () => {
|
||||
setupAndCheckRenderer(config);
|
||||
|
||||
let services: Services;
|
||||
let renderComponentType: RenderComponentType;
|
||||
|
||||
beforeEach(
|
||||
inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => {
|
||||
services = new DefaultServices(rootRenderer, sanitizer);
|
||||
renderComponentType =
|
||||
new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {});
|
||||
}));
|
||||
|
||||
function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition {
|
||||
return viewDef(config.viewFlags, nodes, updater, renderComponentType);
|
||||
}
|
||||
|
||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
||||
const view = createRootView(services, viewDef);
|
||||
const rootNodes = rootRenderNodes(view);
|
||||
return {rootNodes, view};
|
||||
}
|
||||
|
||||
it('should create and attach component views', () => {
|
||||
class AComp {}
|
||||
|
||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'div'),
|
||||
providerDef(NodeFlags.None, AComp, [], null, () => compViewDef([
|
||||
elementDef(NodeFlags.None, 0, 'span'),
|
||||
])),
|
||||
]));
|
||||
|
||||
const compRootEl = getDOM().childNodes(rootNodes[0])[0];
|
||||
expect(getDOM().nodeName(compRootEl).toLowerCase()).toBe('span');
|
||||
});
|
||||
|
||||
it('should dirty check component views', () => {
|
||||
let value = 'v1';
|
||||
let instance: AComp;
|
||||
class AComp {
|
||||
a: any;
|
||||
constructor() { instance = this; }
|
||||
}
|
||||
|
||||
const updater = jasmine.createSpy('updater').and.callFake(
|
||||
(updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, value));
|
||||
|
||||
const {view, rootNodes} = createAndGetRootNodes(
|
||||
compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'div'),
|
||||
providerDef(NodeFlags.None, AComp, [], null, () => compViewDef(
|
||||
[
|
||||
elementDef(NodeFlags.None, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
|
||||
], updater
|
||||
)),
|
||||
], jasmine.createSpy('parentUpdater')));
|
||||
|
||||
checkAndUpdateView(view);
|
||||
|
||||
expect(updater).toHaveBeenCalled();
|
||||
// component
|
||||
expect(updater.calls.mostRecent().args[2]).toBe(instance);
|
||||
// view context
|
||||
expect(updater.calls.mostRecent().args[3]).toBe(instance);
|
||||
|
||||
updater.calls.reset();
|
||||
checkNoChangesView(view);
|
||||
|
||||
expect(updater).toHaveBeenCalled();
|
||||
// component
|
||||
expect(updater.calls.mostRecent().args[2]).toBe(instance);
|
||||
// view context
|
||||
expect(updater.calls.mostRecent().args[3]).toBe(instance);
|
||||
|
||||
value = 'v2';
|
||||
expect(() => checkNoChangesView(view))
|
||||
.toThrowError(
|
||||
`Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`);
|
||||
});
|
||||
|
||||
it('should destroy component views', () => {
|
||||
const log: string[] = [];
|
||||
|
||||
class AComp {}
|
||||
|
||||
class ChildProvider {
|
||||
ngOnDestroy() { log.push('ngOnDestroy'); };
|
||||
}
|
||||
|
||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'div'),
|
||||
providerDef(
|
||||
NodeFlags.None, AComp, [], null, () => compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(NodeFlags.OnDestroy, ChildProvider, [])
|
||||
])),
|
||||
]));
|
||||
|
||||
destroyView(view);
|
||||
|
||||
expect(log).toEqual(['ngOnDestroy']);
|
||||
});
|
||||
});
|
||||
}
|
223
modules/@angular/core/test/view/element_spec.ts
Normal file
223
modules/@angular/core/test/view/element_spec.ts
Normal file
@ -0,0 +1,223 @@
|
||||
/**
|
||||
* @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 {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
||||
import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, 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 {isBrowser, setupAndCheckRenderer} from './helper';
|
||||
|
||||
export function main() {
|
||||
if (isBrowser()) {
|
||||
defineTests({directDom: true, viewFlags: ViewFlags.DirectDom});
|
||||
}
|
||||
defineTests({directDom: false, viewFlags: 0});
|
||||
}
|
||||
|
||||
function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||
describe(`View Elements, directDom: ${config.directDom}`, () => {
|
||||
setupAndCheckRenderer(config);
|
||||
|
||||
let services: Services;
|
||||
let renderComponentType: RenderComponentType;
|
||||
|
||||
beforeEach(
|
||||
inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => {
|
||||
services = new DefaultServices(rootRenderer, sanitizer);
|
||||
renderComponentType =
|
||||
new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {});
|
||||
}));
|
||||
|
||||
function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition {
|
||||
return viewDef(config.viewFlags, nodes, updater, renderComponentType);
|
||||
}
|
||||
|
||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
||||
const view = createRootView(services, viewDef);
|
||||
const rootNodes = rootRenderNodes(view);
|
||||
return {rootNodes, view};
|
||||
}
|
||||
|
||||
describe('create', () => {
|
||||
it('should create elements without parents', () => {
|
||||
const rootNodes =
|
||||
createAndGetRootNodes(compViewDef([elementDef(NodeFlags.None, 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, 0, 'span'), elementDef(NodeFlags.None, 0, 'span')
|
||||
])).rootNodes;
|
||||
expect(rootNodes.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should create elements with parents', () => {
|
||||
const rootNodes = createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'div'),
|
||||
elementDef(NodeFlags.None, 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, 0, 'div', {'title': 'a'}),
|
||||
])).rootNodes;
|
||||
expect(rootNodes.length).toBe(1);
|
||||
expect(getDOM().getAttribute(rootNodes[0], 'title')).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
it('should checkNoChanges', () => {
|
||||
let attrValue = 'v1';
|
||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||
[
|
||||
elementDef(
|
||||
NodeFlags.None, 0, 'div', null,
|
||||
[[BindingType.ElementAttribute, 'a1', SecurityContext.NONE]]),
|
||||
],
|
||||
(updater, view) => updater.checkInline(view, 0, attrValue)));
|
||||
|
||||
checkAndUpdateView(view);
|
||||
checkNoChangesView(view);
|
||||
|
||||
attrValue = 'v2';
|
||||
expect(() => checkNoChangesView(view))
|
||||
.toThrowError(
|
||||
`Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`);
|
||||
});
|
||||
|
||||
describe('change properties', () => {
|
||||
[{
|
||||
name: 'inline',
|
||||
updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'v1', 'v2')
|
||||
},
|
||||
{
|
||||
name: 'dynamic',
|
||||
updater: (updater: NodeUpdater, view: ViewData) =>
|
||||
updater.checkDynamic(view, 0, ['v1', 'v2'])
|
||||
}].forEach((config) => {
|
||||
it(`should update ${config.name}`, () => {
|
||||
|
||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||
[
|
||||
elementDef(
|
||||
NodeFlags.None, 0, 'input', null,
|
||||
[
|
||||
[BindingType.ElementProperty, 'title', SecurityContext.NONE],
|
||||
[BindingType.ElementProperty, 'value', SecurityContext.NONE]
|
||||
]),
|
||||
],
|
||||
config.updater));
|
||||
|
||||
checkAndUpdateView(view);
|
||||
|
||||
const el = rootNodes[0];
|
||||
expect(getDOM().getProperty(el, 'title')).toBe('v1');
|
||||
expect(getDOM().getProperty(el, 'value')).toBe('v2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('change attributes', () => {
|
||||
[{
|
||||
name: 'inline',
|
||||
updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'v1', 'v2')
|
||||
},
|
||||
{
|
||||
name: 'dynamic',
|
||||
updater: (updater: NodeUpdater, view: ViewData) =>
|
||||
updater.checkDynamic(view, 0, ['v1', 'v2'])
|
||||
}].forEach((config) => {
|
||||
it(`should update ${config.name}`, () => {
|
||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||
[
|
||||
elementDef(
|
||||
NodeFlags.None, 0, 'div', null,
|
||||
[
|
||||
[BindingType.ElementAttribute, 'a1', SecurityContext.NONE],
|
||||
[BindingType.ElementAttribute, 'a2', SecurityContext.NONE]
|
||||
]),
|
||||
],
|
||||
config.updater));
|
||||
|
||||
checkAndUpdateView(view);
|
||||
|
||||
const el = rootNodes[0];
|
||||
expect(getDOM().getAttribute(el, 'a1')).toBe('v1');
|
||||
expect(getDOM().getAttribute(el, 'a2')).toBe('v2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('change classes', () => {
|
||||
[{
|
||||
name: 'inline',
|
||||
updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, true, true)
|
||||
},
|
||||
{
|
||||
name: 'dynamic',
|
||||
updater: (updater: NodeUpdater, view: ViewData) =>
|
||||
updater.checkDynamic(view, 0, [true, true])
|
||||
}].forEach((config) => {
|
||||
it(`should update ${config.name}`, () => {
|
||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||
[
|
||||
elementDef(
|
||||
NodeFlags.None, 0, 'div', null,
|
||||
[[BindingType.ElementClass, 'c1'], [BindingType.ElementClass, 'c2']]),
|
||||
],
|
||||
config.updater));
|
||||
|
||||
checkAndUpdateView(view);
|
||||
|
||||
const el = rootNodes[0];
|
||||
expect(getDOM().hasClass(el, 'c1')).toBeTruthy();
|
||||
expect(getDOM().hasClass(el, 'c2')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('change styles', () => {
|
||||
[{
|
||||
name: 'inline',
|
||||
updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 10, 'red')
|
||||
},
|
||||
{
|
||||
name: 'dynamic',
|
||||
updater: (updater: NodeUpdater, view: ViewData) =>
|
||||
updater.checkDynamic(view, 0, [10, 'red'])
|
||||
}].forEach((config) => {
|
||||
it(`should update ${config.name}`, () => {
|
||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||
[
|
||||
elementDef(
|
||||
NodeFlags.None, 0, 'div', null,
|
||||
[
|
||||
[BindingType.ElementStyle, 'width', 'px'],
|
||||
[BindingType.ElementStyle, 'color', null]
|
||||
]),
|
||||
],
|
||||
config.updater));
|
||||
|
||||
checkAndUpdateView(view);
|
||||
|
||||
const el = rootNodes[0];
|
||||
expect(getDOM().getStyle(el, 'width')).toBe('10px');
|
||||
expect(getDOM().getStyle(el, 'color')).toBe('red');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
169
modules/@angular/core/test/view/embedded_view_spec.ts
Normal file
169
modules/@angular/core/test/view/embedded_view_spec.ts
Normal file
@ -0,0 +1,169 @@
|
||||
/**
|
||||
* @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 {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
||||
import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, attachEmbeddedView, checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView, detachEmbeddedView, elementDef, providerDef, 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 {isBrowser, setupAndCheckRenderer} from './helper';
|
||||
|
||||
export function main() {
|
||||
if (isBrowser()) {
|
||||
defineTests({directDom: true, viewFlags: ViewFlags.DirectDom});
|
||||
}
|
||||
defineTests({directDom: false, viewFlags: 0});
|
||||
}
|
||||
|
||||
function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||
describe(`Embedded Views, directDom: ${config.directDom}`, () => {
|
||||
setupAndCheckRenderer(config);
|
||||
|
||||
let services: Services;
|
||||
let renderComponentType: RenderComponentType;
|
||||
|
||||
beforeEach(
|
||||
inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => {
|
||||
services = new DefaultServices(rootRenderer, sanitizer);
|
||||
renderComponentType =
|
||||
new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {});
|
||||
}));
|
||||
|
||||
function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition {
|
||||
return viewDef(config.viewFlags, nodes, updater, renderComponentType);
|
||||
}
|
||||
|
||||
function embeddedViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition {
|
||||
return viewDef(config.viewFlags, nodes, updater);
|
||||
}
|
||||
|
||||
function createAndGetRootNodes(
|
||||
viewDef: ViewDefinition, context: any = null): {rootNodes: any[], view: ViewData} {
|
||||
const view = createRootView(services, viewDef, context);
|
||||
const rootNodes = rootRenderNodes(view);
|
||||
return {rootNodes, view};
|
||||
}
|
||||
|
||||
it('should attach and detach embedded views', () => {
|
||||
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 2, 'div'),
|
||||
anchorDef(
|
||||
NodeFlags.HasEmbeddedViews, 0,
|
||||
embeddedViewDef([elementDef(NodeFlags.None, 0, 'span', {'name': 'child0'})])),
|
||||
anchorDef(NodeFlags.None, 0, embeddedViewDef([elementDef(
|
||||
NodeFlags.None, 0, 'span', {'name': 'child1'})]))
|
||||
]));
|
||||
|
||||
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
|
||||
|
||||
const childView1 = createEmbeddedView(parentView, parentView.def.nodes[2]);
|
||||
|
||||
const rootChildren = getDOM().childNodes(rootNodes[0]);
|
||||
attachEmbeddedView(parentView.nodes[1], 0, childView0);
|
||||
attachEmbeddedView(parentView.nodes[1], 1, childView1);
|
||||
|
||||
// 2 anchors + 2 elements
|
||||
expect(rootChildren.length).toBe(4);
|
||||
expect(getDOM().getAttribute(rootChildren[1], 'name')).toBe('child0');
|
||||
expect(getDOM().getAttribute(rootChildren[2], 'name')).toBe('child1');
|
||||
|
||||
detachEmbeddedView(parentView.nodes[1], 1);
|
||||
detachEmbeddedView(parentView.nodes[1], 0);
|
||||
|
||||
expect(getDOM().childNodes(rootNodes[0]).length).toBe(2);
|
||||
});
|
||||
|
||||
it('should include embedded views in root nodes', () => {
|
||||
const {view: parentView} = createAndGetRootNodes(compViewDef([
|
||||
anchorDef(
|
||||
NodeFlags.HasEmbeddedViews, 0,
|
||||
embeddedViewDef([elementDef(NodeFlags.None, 0, 'span', {'name': 'child0'})])),
|
||||
elementDef(NodeFlags.None, 0, 'span', {'name': 'after'})
|
||||
]));
|
||||
|
||||
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[0]);
|
||||
attachEmbeddedView(parentView.nodes[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 parentContext = new Object();
|
||||
const childContext = new Object();
|
||||
const updater = jasmine.createSpy('updater').and.callFake(
|
||||
(updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, childValue));
|
||||
|
||||
const {view: parentView, rootNodes} = createAndGetRootNodes(
|
||||
compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'div'),
|
||||
anchorDef(
|
||||
NodeFlags.HasEmbeddedViews, 0,
|
||||
embeddedViewDef(
|
||||
[elementDef(
|
||||
NodeFlags.None, 0, 'span', null,
|
||||
[[BindingType.ElementAttribute, 'name', SecurityContext.NONE]])],
|
||||
updater))
|
||||
]),
|
||||
parentContext);
|
||||
|
||||
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1], childContext);
|
||||
|
||||
const rootEl = rootNodes[0];
|
||||
attachEmbeddedView(parentView.nodes[1], 0, childView0);
|
||||
|
||||
checkAndUpdateView(parentView);
|
||||
|
||||
expect(updater).toHaveBeenCalled();
|
||||
// component
|
||||
expect(updater.calls.mostRecent().args[2]).toBe(parentContext);
|
||||
// view context
|
||||
expect(updater.calls.mostRecent().args[3]).toBe(childContext);
|
||||
|
||||
updater.calls.reset();
|
||||
checkNoChangesView(parentView);
|
||||
|
||||
expect(updater).toHaveBeenCalled();
|
||||
// component
|
||||
expect(updater.calls.mostRecent().args[2]).toBe(parentContext);
|
||||
// view context
|
||||
expect(updater.calls.mostRecent().args[3]).toBe(childContext);
|
||||
|
||||
childValue = 'v2';
|
||||
expect(() => checkNoChangesView(parentView))
|
||||
.toThrowError(
|
||||
`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, rootNodes} = createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'div'),
|
||||
anchorDef(NodeFlags.HasEmbeddedViews, 0, embeddedViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(NodeFlags.OnDestroy, ChildProvider, [])
|
||||
]))
|
||||
]));
|
||||
|
||||
const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]);
|
||||
|
||||
attachEmbeddedView(parentView.nodes[1], 0, childView0);
|
||||
destroyView(parentView);
|
||||
|
||||
expect(log).toEqual(['ngOnDestroy']);
|
||||
});
|
||||
});
|
||||
}
|
36
modules/@angular/core/test/view/helper.ts
Normal file
36
modules/@angular/core/test/view/helper.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @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 {RootRenderer} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
||||
export function isBrowser() {
|
||||
return getDOM().supportsDOMEvents();
|
||||
}
|
||||
|
||||
export function setupAndCheckRenderer(config: {directDom: boolean}) {
|
||||
let rootRenderer: any;
|
||||
if (config.directDom) {
|
||||
beforeEach(() => {
|
||||
rootRenderer = <any>{
|
||||
renderComponent: jasmine.createSpy('renderComponent')
|
||||
.and.throwError('Renderer should not have been called!')
|
||||
};
|
||||
TestBed.configureTestingModule(
|
||||
{providers: [{provide: RootRenderer, useValue: rootRenderer}]});
|
||||
});
|
||||
afterEach(() => { expect(rootRenderer.renderComponent).not.toHaveBeenCalled(); });
|
||||
} else {
|
||||
beforeEach(() => {
|
||||
rootRenderer = TestBed.get(RootRenderer);
|
||||
spyOn(rootRenderer, 'renderComponent').and.callThrough();
|
||||
});
|
||||
afterEach(() => { expect(rootRenderer.renderComponent).toHaveBeenCalled(); });
|
||||
}
|
||||
}
|
328
modules/@angular/core/test/view/provider_spec.ts
Normal file
328
modules/@angular/core/test/view/provider_spec.ts
Normal file
@ -0,0 +1,328 @@
|
||||
/**
|
||||
* @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, DoCheck, ElementRef, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||
import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, destroyView, elementDef, providerDef, 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 {isBrowser, setupAndCheckRenderer} from './helper';
|
||||
|
||||
export function main() {
|
||||
if (isBrowser()) {
|
||||
defineTests({directDom: true, viewFlags: ViewFlags.DirectDom});
|
||||
}
|
||||
defineTests({directDom: false, viewFlags: 0});
|
||||
}
|
||||
|
||||
function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||
describe(`View Providers, directDom: ${config.directDom}`, () => {
|
||||
setupAndCheckRenderer(config);
|
||||
|
||||
let services: Services;
|
||||
let renderComponentType: RenderComponentType;
|
||||
|
||||
beforeEach(
|
||||
inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => {
|
||||
services = new DefaultServices(rootRenderer, sanitizer);
|
||||
renderComponentType =
|
||||
new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {});
|
||||
}));
|
||||
|
||||
function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition {
|
||||
return viewDef(config.viewFlags, nodes, updater, renderComponentType);
|
||||
}
|
||||
|
||||
function embeddedViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition {
|
||||
return viewDef(config.viewFlags, nodes, updater);
|
||||
}
|
||||
|
||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
||||
const view = createRootView(services, viewDef);
|
||||
const rootNodes = rootRenderNodes(view);
|
||||
return {rootNodes, view};
|
||||
}
|
||||
|
||||
describe('create', () => {
|
||||
it('should create providers eagerly', () => {
|
||||
let instances: SomeService[] = [];
|
||||
class SomeService {
|
||||
constructor() { instances.push(this); }
|
||||
}
|
||||
|
||||
createAndGetRootNodes(compViewDef(
|
||||
[elementDef(NodeFlags.None, 1, 'span'), providerDef(NodeFlags.None, SomeService, [])]));
|
||||
|
||||
expect(instances.length).toBe(1);
|
||||
});
|
||||
|
||||
describe('deps', () => {
|
||||
let instance: SomeService;
|
||||
class Dep {}
|
||||
|
||||
class SomeService {
|
||||
constructor(public dep: any) { instance = this; }
|
||||
}
|
||||
|
||||
beforeEach(() => { instance = null; });
|
||||
|
||||
it('should inject deps from the same element', () => {
|
||||
createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 2, 'span'), providerDef(NodeFlags.None, Dep, []),
|
||||
providerDef(NodeFlags.None, SomeService, [Dep])
|
||||
]));
|
||||
|
||||
expect(instance.dep instanceof Dep).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should inject deps from a parent element', () => {
|
||||
createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 3, 'span'), providerDef(NodeFlags.None, Dep, []),
|
||||
elementDef(NodeFlags.None, 1, 'span'), providerDef(NodeFlags.None, SomeService, [Dep])
|
||||
]));
|
||||
|
||||
expect(instance.dep instanceof Dep).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not inject deps from sibling root elements', () => {
|
||||
const nodes = [
|
||||
elementDef(NodeFlags.None, 1, 'span'), providerDef(NodeFlags.None, Dep, []),
|
||||
elementDef(NodeFlags.None, 1, 'span'), providerDef(NodeFlags.None, SomeService, [Dep])
|
||||
];
|
||||
|
||||
// root elements
|
||||
expect(() => createAndGetRootNodes(compViewDef(nodes)))
|
||||
.toThrowError('No provider for Dep!');
|
||||
|
||||
// non root elements
|
||||
expect(
|
||||
() => createAndGetRootNodes(
|
||||
compViewDef([elementDef(NodeFlags.None, 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, 1, 'div'),
|
||||
providerDef(
|
||||
NodeFlags.None, Dep, [], null, () => compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(NodeFlags.None, SomeService, [Dep])
|
||||
])),
|
||||
]));
|
||||
|
||||
expect(instance.dep instanceof Dep).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('builtin tokens', () => {
|
||||
it('should inject ViewContainerRef', () => {
|
||||
createAndGetRootNodes(compViewDef([
|
||||
anchorDef(NodeFlags.HasEmbeddedViews, 1),
|
||||
providerDef(NodeFlags.None, SomeService, [ViewContainerRef])
|
||||
]));
|
||||
|
||||
expect(instance.dep.createEmbeddedView).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should inject TemplateRef', () => {
|
||||
createAndGetRootNodes(compViewDef([
|
||||
anchorDef(NodeFlags.None, 1, embeddedViewDef([anchorDef(NodeFlags.None, 0)])),
|
||||
providerDef(NodeFlags.None, SomeService, [TemplateRef])
|
||||
]));
|
||||
|
||||
expect(instance.dep.createEmbeddedView).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should inject ElementRef', () => {
|
||||
createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(NodeFlags.None, SomeService, [ElementRef])
|
||||
]));
|
||||
|
||||
expect(getDOM().nodeName(instance.dep.nativeElement).toLowerCase()).toBe('span');
|
||||
});
|
||||
|
||||
if (config.directDom) {
|
||||
it('should not inject Renderer when using directDom', () => {
|
||||
expect(() => createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(NodeFlags.None, SomeService, [Renderer])
|
||||
])))
|
||||
.toThrowError('No provider for Renderer!');
|
||||
});
|
||||
} else {
|
||||
it('should inject Renderer when not using directDom', () => {
|
||||
createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(NodeFlags.None, SomeService, [Renderer])
|
||||
]));
|
||||
|
||||
expect(instance.dep.createElement).toBeTruthy();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('data binding', () => {
|
||||
[{
|
||||
name: 'inline',
|
||||
updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 1, 'v1', 'v2')
|
||||
},
|
||||
{
|
||||
name: 'dynamic',
|
||||
updater: (updater: NodeUpdater, view: ViewData) =>
|
||||
updater.checkDynamic(view, 1, ['v1', 'v2'])
|
||||
}].forEach((config) => {
|
||||
it(`should update ${config.name}`, () => {
|
||||
let instance: SomeService;
|
||||
|
||||
class SomeService {
|
||||
a: any;
|
||||
b: any;
|
||||
constructor() { instance = this; }
|
||||
}
|
||||
|
||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||
[
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(NodeFlags.None, SomeService, [], {a: [0, 'a'], b: [1, 'b']})
|
||||
],
|
||||
config.updater));
|
||||
|
||||
checkAndUpdateView(view);
|
||||
|
||||
expect(instance.a).toBe('v1');
|
||||
expect(instance.b).toBe('v2');
|
||||
});
|
||||
});
|
||||
|
||||
it('should checkNoChanges', () => {
|
||||
class SomeService {
|
||||
a: any;
|
||||
}
|
||||
|
||||
let propValue = 'v1';
|
||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||
[
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(NodeFlags.None, SomeService, [], {a: [0, 'a']})
|
||||
],
|
||||
(updater, view) => updater.checkInline(view, 1, propValue)));
|
||||
|
||||
checkAndUpdateView(view);
|
||||
checkNoChangesView(view);
|
||||
|
||||
propValue = 'v2';
|
||||
expect(() => checkNoChangesView(view))
|
||||
.toThrowError(
|
||||
`Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`);
|
||||
});
|
||||
});
|
||||
|
||||
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, 3, 'span'),
|
||||
providerDef(allFlags, SomeService, [], {a: [0, 'a']}),
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(allFlags, SomeService, [], {a: [0, 'a']})
|
||||
],
|
||||
(updater) => {
|
||||
updater.checkInline(view, 1, 'someValue');
|
||||
updater.checkInline(view, 3, 'someValue');
|
||||
}));
|
||||
|
||||
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 = [];
|
||||
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 = [];
|
||||
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, 1, 'span'),
|
||||
providerDef(NodeFlags.OnChanges, SomeService, [], {a: [0, 'nonMinifiedA']})
|
||||
],
|
||||
(updater) => updater.checkInline(view, 1, currValue)));
|
||||
|
||||
checkAndUpdateView(view);
|
||||
expect(changesLog).toEqual([new SimpleChange(undefined, 'v1', true)]);
|
||||
|
||||
currValue = 'v2';
|
||||
changesLog = [];
|
||||
checkAndUpdateView(view);
|
||||
expect(changesLog).toEqual([new SimpleChange('v1', 'v2', false)]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
114
modules/@angular/core/test/view/text_spec.ts
Normal file
114
modules/@angular/core/test/view/text_spec.ts
Normal file
@ -0,0 +1,114 @@
|
||||
/**
|
||||
* @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 {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
||||
import {DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, 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 {isBrowser, setupAndCheckRenderer} from './helper';
|
||||
|
||||
export function main() {
|
||||
if (isBrowser()) {
|
||||
defineTests({directDom: true, viewFlags: ViewFlags.DirectDom});
|
||||
}
|
||||
defineTests({directDom: false, viewFlags: 0});
|
||||
}
|
||||
|
||||
function defineTests(config: {directDom: boolean, viewFlags: number}) {
|
||||
describe(`View Text, directDom: ${config.directDom}`, () => {
|
||||
setupAndCheckRenderer(config);
|
||||
|
||||
let services: Services;
|
||||
let renderComponentType: RenderComponentType;
|
||||
|
||||
beforeEach(
|
||||
inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => {
|
||||
services = new DefaultServices(rootRenderer, sanitizer);
|
||||
renderComponentType =
|
||||
new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {});
|
||||
}));
|
||||
|
||||
function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition {
|
||||
return viewDef(config.viewFlags, nodes, updater, renderComponentType);
|
||||
}
|
||||
|
||||
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
||||
const view = createRootView(services, viewDef);
|
||||
const rootNodes = rootRenderNodes(view);
|
||||
return {rootNodes, view};
|
||||
}
|
||||
|
||||
describe('create', () => {
|
||||
it('should create text nodes without parents', () => {
|
||||
const rootNodes = createAndGetRootNodes(compViewDef([textDef(['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(['a']), textDef(['b'])])).rootNodes;
|
||||
expect(rootNodes.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should create text nodes with parents', () => {
|
||||
const rootNodes = createAndGetRootNodes(compViewDef([
|
||||
elementDef(NodeFlags.None, 1, 'div'),
|
||||
textDef(['a']),
|
||||
])).rootNodes;
|
||||
expect(rootNodes.length).toBe(1);
|
||||
const textNode = getDOM().firstChild(rootNodes[0]);
|
||||
expect(getDOM().getText(textNode)).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
it('should checkNoChanges', () => {
|
||||
let textValue = 'v1';
|
||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||
[
|
||||
textDef(['', '']),
|
||||
],
|
||||
(updater, view) => updater.checkInline(view, 0, textValue)));
|
||||
|
||||
checkAndUpdateView(view);
|
||||
checkNoChangesView(view);
|
||||
|
||||
textValue = 'v2';
|
||||
expect(() => checkNoChangesView(view))
|
||||
.toThrowError(
|
||||
`Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`);
|
||||
});
|
||||
|
||||
describe('change text', () => {
|
||||
[{
|
||||
name: 'inline',
|
||||
updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'a', 'b')
|
||||
},
|
||||
{
|
||||
name: 'dynamic',
|
||||
updater: (updater: NodeUpdater, view: ViewData) =>
|
||||
updater.checkDynamic(view, 0, ['a', 'b'])
|
||||
}].forEach((config) => {
|
||||
it(`should update ${config.name}`, () => {
|
||||
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||
[
|
||||
textDef(['0', '1', '2']),
|
||||
],
|
||||
config.updater));
|
||||
|
||||
checkAndUpdateView(view);
|
||||
|
||||
const node = rootNodes[0];
|
||||
expect(getDOM().getText(rootNodes[0])).toBe('0a1b2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
167
modules/@angular/core/test/view/view_def_spec.ts
Normal file
167
modules/@angular/core/test/view/view_def_spec.ts
Normal file
@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @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, NodeUpdater, ViewData, ViewDefinition, ViewFlags, anchorDef, checkAndUpdateView, checkNoChangesView, elementDef, providerDef, textDef, viewDef} from '@angular/core/src/view/index';
|
||||
|
||||
export function main() {
|
||||
describe('viewDef', () => {
|
||||
describe('reverseChild order', () => {
|
||||
function reverseChildOrder(viewDef: ViewDefinition): number[] {
|
||||
return viewDef.reverseChildNodes.map(node => node.index);
|
||||
}
|
||||
|
||||
it('should reverse child order for root nodes', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
textDef(['a']), // level 0, index 0
|
||||
textDef(['a']), // level 0, index 0
|
||||
]);
|
||||
|
||||
expect(reverseChildOrder(vd)).toEqual([1, 0]);
|
||||
});
|
||||
|
||||
it('should reverse child order for one level, one root', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, 2, 'span'), // level 0, index 0
|
||||
textDef(['a']), // level 1, index 1
|
||||
textDef(['a']), // level 1, index 2
|
||||
]);
|
||||
|
||||
expect(reverseChildOrder(vd)).toEqual([0, 2, 1]);
|
||||
});
|
||||
|
||||
it('should reverse child order for 1 level, 2 roots', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, 2, 'span'), // level 0, index 0
|
||||
textDef(['a']), // level 1, index 1
|
||||
textDef(['a']), // level 1, index 2
|
||||
elementDef(NodeFlags.None, 1, 'span'), // level 0, index 3
|
||||
textDef(['a']), // level 1, index 4
|
||||
]);
|
||||
|
||||
expect(reverseChildOrder(vd)).toEqual([3, 4, 0, 2, 1]);
|
||||
});
|
||||
|
||||
it('should reverse child order for 2 levels', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, 4, 'span'), // level 0, index 0
|
||||
elementDef(NodeFlags.None, 1, 'span'), // level 1, index 1
|
||||
textDef(['a']), // level 2, index 2
|
||||
elementDef(NodeFlags.None, 1, 'span'), // level 1, index 3
|
||||
textDef(['a']), // level 2, index 4
|
||||
]);
|
||||
|
||||
expect(reverseChildOrder(vd)).toEqual([0, 3, 4, 1, 2]);
|
||||
});
|
||||
|
||||
it('should reverse child order for mixed levels', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
textDef(['a']), // level 0, index 0
|
||||
elementDef(NodeFlags.None, 5, 'span'), // level 0, index 1
|
||||
textDef(['a']), // level 1, index 2
|
||||
elementDef(NodeFlags.None, 1, 'span'), // level 1, index 3
|
||||
textDef(['a']), // level 2, index 4
|
||||
elementDef(NodeFlags.None, 1, 'span'), // level 1, index 5
|
||||
textDef(['a']), // level 2, index 6
|
||||
textDef(['a']), // level 0, index 7
|
||||
]);
|
||||
|
||||
expect(reverseChildOrder(vd)).toEqual([7, 1, 5, 6, 3, 4, 2, 0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parent', () => {
|
||||
function parents(viewDef: ViewDefinition): number[] {
|
||||
return viewDef.nodes.map(node => node.parent);
|
||||
}
|
||||
|
||||
it('should calculate parents for one level', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, 2, 'span'),
|
||||
textDef(['a']),
|
||||
textDef(['a']),
|
||||
]);
|
||||
|
||||
expect(parents(vd)).toEqual([undefined, 0, 0]);
|
||||
});
|
||||
|
||||
it('should calculate parents for one level, multiple roots', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
textDef(['a']),
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
textDef(['a']),
|
||||
textDef(['a']),
|
||||
]);
|
||||
|
||||
expect(parents(vd)).toEqual([undefined, 0, undefined, 2, undefined]);
|
||||
});
|
||||
|
||||
it('should calculate parents for multiple levels', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, 2, 'span'),
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
textDef(['a']),
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
textDef(['a']),
|
||||
textDef(['a']),
|
||||
]);
|
||||
|
||||
expect(parents(vd)).toEqual([undefined, 0, 1, undefined, 3, undefined]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('childFlags', () => {
|
||||
|
||||
function childFlags(viewDef: ViewDefinition): number[] {
|
||||
return viewDef.nodes.map(node => node.childFlags);
|
||||
}
|
||||
|
||||
it('should calculate childFlags for one level', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(NodeFlags.AfterContentChecked, AService, [])
|
||||
]);
|
||||
|
||||
expect(childFlags(vd)).toEqual([NodeFlags.AfterContentChecked, NodeFlags.None]);
|
||||
});
|
||||
|
||||
it('should calculate childFlags for one level, multiple roots', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(NodeFlags.AfterContentChecked, AService, []),
|
||||
elementDef(NodeFlags.None, 2, 'span'),
|
||||
providerDef(NodeFlags.AfterContentInit, AService, []),
|
||||
providerDef(NodeFlags.AfterViewChecked, AService, []),
|
||||
]);
|
||||
|
||||
expect(childFlags(vd)).toEqual([
|
||||
NodeFlags.AfterContentChecked, NodeFlags.None,
|
||||
NodeFlags.AfterContentInit | NodeFlags.AfterViewChecked, NodeFlags.None, NodeFlags.None
|
||||
]);
|
||||
});
|
||||
|
||||
it('should calculate childFlags for multiple levels', () => {
|
||||
const vd = viewDef(ViewFlags.None, [
|
||||
elementDef(NodeFlags.None, 2, 'span'),
|
||||
elementDef(NodeFlags.None, 1, 'span'),
|
||||
providerDef(NodeFlags.AfterContentChecked, AService, []),
|
||||
elementDef(NodeFlags.None, 2, 'span'),
|
||||
providerDef(NodeFlags.AfterContentInit, AService, []),
|
||||
providerDef(NodeFlags.AfterViewInit, AService, []),
|
||||
]);
|
||||
|
||||
expect(childFlags(vd)).toEqual([
|
||||
NodeFlags.AfterContentChecked, NodeFlags.AfterContentChecked, NodeFlags.None,
|
||||
NodeFlags.AfterContentInit | NodeFlags.AfterViewInit, NodeFlags.None, NodeFlags.None
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class AService {}
|
Reference in New Issue
Block a user