perf(ivy): add performance counters in ngDevMode (#23385)
PR Close #23385
This commit is contained in:
parent
fb41b7dc30
commit
b76f5a6a7d
|
@ -566,6 +566,7 @@ export function elementStart(
|
||||||
assertEqual(
|
assertEqual(
|
||||||
currentView.bindingStartIndex, -1, 'elements should be created before any bindings');
|
currentView.bindingStartIndex, -1, 'elements should be created before any bindings');
|
||||||
|
|
||||||
|
ngDevMode && ngDevMode.rendererCreateElement++;
|
||||||
const native: RElement = renderer.createElement(name);
|
const native: RElement = renderer.createElement(name);
|
||||||
const node: LElementNode = createLNode(index, LNodeType.Element, native !, null);
|
const node: LElementNode = createLNode(index, LNodeType.Element, native !, null);
|
||||||
|
|
||||||
|
@ -580,6 +581,7 @@ function createDirectivesAndLocals(
|
||||||
localRefs: string[] | null | undefined, containerData: TView[] | null) {
|
localRefs: string[] | null | undefined, containerData: TView[] | null) {
|
||||||
const node = previousOrParentNode;
|
const node = previousOrParentNode;
|
||||||
if (firstTemplatePass) {
|
if (firstTemplatePass) {
|
||||||
|
ngDevMode && ngDevMode.firstTemplatePass++;
|
||||||
ngDevMode && assertDataInRange(index - 1);
|
ngDevMode && assertDataInRange(index - 1);
|
||||||
node.tNode = tData[index] = createTNode(name, attrs || null, containerData);
|
node.tNode = tData[index] = createTNode(name, attrs || null, containerData);
|
||||||
cacheMatchingDirectivesForNode(node.tNode, currentView.tView, localRefs || null);
|
cacheMatchingDirectivesForNode(node.tNode, currentView.tView, localRefs || null);
|
||||||
|
@ -756,6 +758,7 @@ function getOrCreateTView(
|
||||||
/** Creates a TView instance */
|
/** Creates a TView instance */
|
||||||
export function createTView(
|
export function createTView(
|
||||||
defs: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null): TView {
|
defs: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null): TView {
|
||||||
|
ngDevMode && ngDevMode.tView++;
|
||||||
return {
|
return {
|
||||||
data: [],
|
data: [],
|
||||||
directives: null,
|
directives: null,
|
||||||
|
@ -784,6 +787,7 @@ function setUpAttributes(native: RElement, attrs: string[]): void {
|
||||||
const attrName = attrs[i];
|
const attrName = attrs[i];
|
||||||
if (attrName !== NG_PROJECT_AS_ATTR_NAME) {
|
if (attrName !== NG_PROJECT_AS_ATTR_NAME) {
|
||||||
const attrVal = attrs[i + 1];
|
const attrVal = attrs[i + 1];
|
||||||
|
ngDevMode && ngDevMode.rendererSetAttribute++;
|
||||||
isProc ? (renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal) :
|
isProc ? (renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal) :
|
||||||
native.setAttribute(attrName, attrVal);
|
native.setAttribute(attrName, attrVal);
|
||||||
}
|
}
|
||||||
|
@ -867,6 +871,7 @@ export function listener(
|
||||||
// In order to match current behavior, native DOM event listeners must be added for all
|
// In order to match current behavior, native DOM event listeners must be added for all
|
||||||
// events (including outputs).
|
// events (including outputs).
|
||||||
const cleanupFns = cleanup || (cleanup = currentView.cleanup = []);
|
const cleanupFns = cleanup || (cleanup = currentView.cleanup = []);
|
||||||
|
ngDevMode && ngDevMode.rendererAddEventListener++;
|
||||||
if (isProceduralRenderer(renderer)) {
|
if (isProceduralRenderer(renderer)) {
|
||||||
const wrappedListener = wrapListenerWithDirtyLogic(currentView, listenerFn);
|
const wrappedListener = wrapListenerWithDirtyLogic(currentView, listenerFn);
|
||||||
const cleanupFn = renderer.listen(native, eventName, wrappedListener);
|
const cleanupFn = renderer.listen(native, eventName, wrappedListener);
|
||||||
|
@ -931,9 +936,11 @@ export function elementAttribute(
|
||||||
if (value !== NO_CHANGE) {
|
if (value !== NO_CHANGE) {
|
||||||
const element: LElementNode = data[index];
|
const element: LElementNode = data[index];
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
ngDevMode && ngDevMode.rendererRemoveAttribute++;
|
||||||
isProceduralRenderer(renderer) ? renderer.removeAttribute(element.native, name) :
|
isProceduralRenderer(renderer) ? renderer.removeAttribute(element.native, name) :
|
||||||
element.native.removeAttribute(name);
|
element.native.removeAttribute(name);
|
||||||
} else {
|
} else {
|
||||||
|
ngDevMode && ngDevMode.rendererSetAttribute++;
|
||||||
const strValue = sanitizer == null ? stringify(value) : sanitizer(value);
|
const strValue = sanitizer == null ? stringify(value) : sanitizer(value);
|
||||||
isProceduralRenderer(renderer) ? renderer.setAttribute(element.native, name, strValue) :
|
isProceduralRenderer(renderer) ? renderer.setAttribute(element.native, name, strValue) :
|
||||||
element.native.setAttribute(name, strValue);
|
element.native.setAttribute(name, strValue);
|
||||||
|
@ -977,6 +984,7 @@ export function elementProperty<T>(
|
||||||
// is risky, so sanitization can be done without further checks.
|
// is risky, so sanitization can be done without further checks.
|
||||||
value = sanitizer != null ? (sanitizer(value) as any) : value;
|
value = sanitizer != null ? (sanitizer(value) as any) : value;
|
||||||
const native = node.native;
|
const native = node.native;
|
||||||
|
ngDevMode && ngDevMode.rendererSetProperty++;
|
||||||
isProceduralRenderer(renderer) ? renderer.setProperty(native, propName, value) :
|
isProceduralRenderer(renderer) ? renderer.setProperty(native, propName, value) :
|
||||||
(native.setProperty ? native.setProperty(propName, value) :
|
(native.setProperty ? native.setProperty(propName, value) :
|
||||||
(native as any)[propName] = value);
|
(native as any)[propName] = value);
|
||||||
|
@ -994,6 +1002,7 @@ export function elementProperty<T>(
|
||||||
*/
|
*/
|
||||||
function createTNode(
|
function createTNode(
|
||||||
tagName: string | null, attrs: string[] | null, data: TContainer | null): TNode {
|
tagName: string | null, attrs: string[] | null, data: TContainer | null): TNode {
|
||||||
|
ngDevMode && ngDevMode.tNode++;
|
||||||
return {
|
return {
|
||||||
flags: 0,
|
flags: 0,
|
||||||
tagName: tagName,
|
tagName: tagName,
|
||||||
|
@ -1067,10 +1076,12 @@ export function elementClassNamed<T>(index: number, className: string, value: T
|
||||||
if (value !== NO_CHANGE) {
|
if (value !== NO_CHANGE) {
|
||||||
const lElement = data[index] as LElementNode;
|
const lElement = data[index] as LElementNode;
|
||||||
if (value) {
|
if (value) {
|
||||||
|
ngDevMode && ngDevMode.rendererAddClass++;
|
||||||
isProceduralRenderer(renderer) ? renderer.addClass(lElement.native, className) :
|
isProceduralRenderer(renderer) ? renderer.addClass(lElement.native, className) :
|
||||||
lElement.native.classList.add(className);
|
lElement.native.classList.add(className);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
ngDevMode && ngDevMode.rendererRemoveClass++;
|
||||||
isProceduralRenderer(renderer) ? renderer.removeClass(lElement.native, className) :
|
isProceduralRenderer(renderer) ? renderer.removeClass(lElement.native, className) :
|
||||||
lElement.native.classList.remove(className);
|
lElement.native.classList.remove(className);
|
||||||
}
|
}
|
||||||
|
@ -1095,6 +1106,7 @@ export function elementClass<T>(index: number, value: T | NO_CHANGE): void {
|
||||||
// future
|
// future
|
||||||
// we will add logic here which would work with the animation code.
|
// we will add logic here which would work with the animation code.
|
||||||
const lElement: LElementNode = data[index];
|
const lElement: LElementNode = data[index];
|
||||||
|
ngDevMode && ngDevMode.rendererSetClassName++;
|
||||||
isProceduralRenderer(renderer) ? renderer.setProperty(lElement.native, 'className', value) :
|
isProceduralRenderer(renderer) ? renderer.setProperty(lElement.native, 'className', value) :
|
||||||
lElement.native['className'] = stringify(value);
|
lElement.native['className'] = stringify(value);
|
||||||
}
|
}
|
||||||
|
@ -1121,6 +1133,7 @@ export function elementStyleNamed<T>(
|
||||||
if (value !== NO_CHANGE) {
|
if (value !== NO_CHANGE) {
|
||||||
const lElement: LElementNode = data[index];
|
const lElement: LElementNode = data[index];
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
ngDevMode && ngDevMode.rendererRemoveStyle++;
|
||||||
isProceduralRenderer(renderer) ?
|
isProceduralRenderer(renderer) ?
|
||||||
renderer.removeStyle(lElement.native, styleName, RendererStyleFlags3.DashCase) :
|
renderer.removeStyle(lElement.native, styleName, RendererStyleFlags3.DashCase) :
|
||||||
lElement.native['style'].removeProperty(styleName);
|
lElement.native['style'].removeProperty(styleName);
|
||||||
|
@ -1128,6 +1141,7 @@ export function elementStyleNamed<T>(
|
||||||
let strValue =
|
let strValue =
|
||||||
typeof suffixOrSanitizer == 'function' ? suffixOrSanitizer(value) : stringify(value);
|
typeof suffixOrSanitizer == 'function' ? suffixOrSanitizer(value) : stringify(value);
|
||||||
if (typeof suffixOrSanitizer == 'string') strValue = strValue + suffixOrSanitizer;
|
if (typeof suffixOrSanitizer == 'string') strValue = strValue + suffixOrSanitizer;
|
||||||
|
ngDevMode && ngDevMode.rendererSetStyle++;
|
||||||
isProceduralRenderer(renderer) ?
|
isProceduralRenderer(renderer) ?
|
||||||
renderer.setStyle(lElement.native, styleName, strValue, RendererStyleFlags3.DashCase) :
|
renderer.setStyle(lElement.native, styleName, strValue, RendererStyleFlags3.DashCase) :
|
||||||
lElement.native['style'].setProperty(styleName, strValue);
|
lElement.native['style'].setProperty(styleName, strValue);
|
||||||
|
@ -1155,14 +1169,20 @@ export function elementStyle<T>(
|
||||||
// we will add logic here which would work with the animation code.
|
// we will add logic here which would work with the animation code.
|
||||||
const lElement = data[index] as LElementNode;
|
const lElement = data[index] as LElementNode;
|
||||||
if (isProceduralRenderer(renderer)) {
|
if (isProceduralRenderer(renderer)) {
|
||||||
|
ngDevMode && ngDevMode.rendererSetStyle++;
|
||||||
renderer.setProperty(lElement.native, 'style', value);
|
renderer.setProperty(lElement.native, 'style', value);
|
||||||
} else {
|
} else {
|
||||||
const style = lElement.native['style'];
|
const style = lElement.native['style'];
|
||||||
for (let i = 0, keys = Object.keys(value); i < keys.length; i++) {
|
for (let i = 0, keys = Object.keys(value); i < keys.length; i++) {
|
||||||
const styleName: string = keys[i];
|
const styleName: string = keys[i];
|
||||||
const styleValue: any = (value as any)[styleName];
|
const styleValue: any = (value as any)[styleName];
|
||||||
styleValue == null ? style.removeProperty(styleName) :
|
if (styleValue == null) {
|
||||||
style.setProperty(styleName, styleValue);
|
ngDevMode && ngDevMode.rendererRemoveStyle++;
|
||||||
|
style.removeProperty(styleName);
|
||||||
|
} else {
|
||||||
|
ngDevMode && ngDevMode.rendererSetStyle++;
|
||||||
|
style.setProperty(styleName, styleValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1184,6 +1204,7 @@ export function text(index: number, value?: any): void {
|
||||||
ngDevMode &&
|
ngDevMode &&
|
||||||
assertEqual(
|
assertEqual(
|
||||||
currentView.bindingStartIndex, -1, 'text nodes should be created before bindings');
|
currentView.bindingStartIndex, -1, 'text nodes should be created before bindings');
|
||||||
|
ngDevMode && ngDevMode.rendererCreateTextNode++;
|
||||||
const textNode = createTextNode(value, renderer);
|
const textNode = createTextNode(value, renderer);
|
||||||
const node = createLNode(index, LNodeType.Element, textNode);
|
const node = createLNode(index, LNodeType.Element, textNode);
|
||||||
// Text nodes are self closing.
|
// Text nodes are self closing.
|
||||||
|
@ -1203,9 +1224,18 @@ export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
|
||||||
let existingNode = data[index] as LTextNode;
|
let existingNode = data[index] as LTextNode;
|
||||||
ngDevMode && assertNotNull(existingNode, 'LNode should exist');
|
ngDevMode && assertNotNull(existingNode, 'LNode should exist');
|
||||||
ngDevMode && assertNotNull(existingNode.native, 'native element should exist');
|
ngDevMode && assertNotNull(existingNode.native, 'native element should exist');
|
||||||
value !== NO_CHANGE &&
|
if (existingNode.native) {
|
||||||
(isProceduralRenderer(renderer) ? renderer.setValue(existingNode.native, stringify(value)) :
|
// If DOM node exists and value changed, update textContent
|
||||||
existingNode.native.textContent = stringify(value));
|
ngDevMode && ngDevMode.rendererSetText++;
|
||||||
|
value !== NO_CHANGE &&
|
||||||
|
(isProceduralRenderer(renderer) ? renderer.setValue(existingNode.native, stringify(value)) :
|
||||||
|
existingNode.native.textContent = stringify(value));
|
||||||
|
} else {
|
||||||
|
// Node was created but DOM node creation was delayed. Create and append now.
|
||||||
|
ngDevMode && ngDevMode.rendererCreateTextNode++;
|
||||||
|
existingNode.native = createTextNode(value, renderer);
|
||||||
|
insertChild(existingNode, currentView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
@ -1407,7 +1437,7 @@ export function createLContainer(
|
||||||
* @param localRefs A set of local reference bindings on the element.
|
* @param localRefs A set of local reference bindings on the element.
|
||||||
*/
|
*/
|
||||||
export function container(
|
export function container(
|
||||||
index: number, template?: ComponentTemplate<any>, tagName?: string, attrs?: string[],
|
index: number, template?: ComponentTemplate<any>, tagName?: string | null, attrs?: string[],
|
||||||
localRefs?: string[] | null): void {
|
localRefs?: string[] | null): void {
|
||||||
ngDevMode && assertEqual(
|
ngDevMode && assertEqual(
|
||||||
currentView.bindingStartIndex, -1,
|
currentView.bindingStartIndex, -1,
|
||||||
|
|
|
@ -8,15 +8,51 @@
|
||||||
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
const ngDevMode: boolean;
|
const ngDevMode: null|NgDevModePerfCounters;
|
||||||
|
interface NgDevModePerfCounters {
|
||||||
|
firstTemplatePass: number;
|
||||||
|
tNode: number;
|
||||||
|
tView: number;
|
||||||
|
rendererCreateTextNode: number;
|
||||||
|
rendererSetText: number;
|
||||||
|
rendererCreateElement: number;
|
||||||
|
rendererAddEventListener: number;
|
||||||
|
rendererSetAttribute: number;
|
||||||
|
rendererRemoveAttribute: number;
|
||||||
|
rendererSetProperty: number;
|
||||||
|
rendererSetClassName: number;
|
||||||
|
rendererAddClass: number;
|
||||||
|
rendererRemoveClass: number;
|
||||||
|
rendererSetStyle: number;
|
||||||
|
rendererRemoveStyle: number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
declare let global: any;
|
declare let global: any;
|
||||||
|
export const ngDevModeResetPerfCounters: () => void =
|
||||||
if (typeof ngDevMode == 'undefined') {
|
(typeof ngDevMode == 'undefined' && (function(global: {ngDevMode: NgDevModePerfCounters}) {
|
||||||
if (typeof window != 'undefined') (window as any).ngDevMode = true;
|
function ngDevModeResetPerfCounters() {
|
||||||
if (typeof self != 'undefined') (self as any).ngDevMode = true;
|
global['ngDevMode'] = {
|
||||||
if (typeof global != 'undefined') (global as any).ngDevMode = true;
|
firstTemplatePass: 0,
|
||||||
}
|
tNode: 0,
|
||||||
|
tView: 0,
|
||||||
export const _ngDevMode = true;
|
rendererCreateTextNode: 0,
|
||||||
|
rendererSetText: 0,
|
||||||
|
rendererCreateElement: 0,
|
||||||
|
rendererAddEventListener: 0,
|
||||||
|
rendererSetAttribute: 0,
|
||||||
|
rendererRemoveAttribute: 0,
|
||||||
|
rendererSetProperty: 0,
|
||||||
|
rendererSetClassName: 0,
|
||||||
|
rendererAddClass: 0,
|
||||||
|
rendererRemoveClass: 0,
|
||||||
|
rendererSetStyle: 0,
|
||||||
|
rendererRemoveStyle: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ngDevModeResetPerfCounters();
|
||||||
|
return ngDevModeResetPerfCounters;
|
||||||
|
})(typeof window != 'undefined' && window || typeof self != 'undefined' && self ||
|
||||||
|
typeof global != 'undefined' && global)) as() => void;
|
||||||
|
|
|
@ -6,12 +6,17 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleNamed, renderTemplate} from '../../src/render3/instructions';
|
import {NgForOfContext} from '@angular/common';
|
||||||
|
|
||||||
|
import {RenderFlags, directiveInject} from '../../src/render3';
|
||||||
|
import {defineComponent} from '../../src/render3/definition';
|
||||||
|
import {bind, container, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleNamed, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions';
|
||||||
import {LElementNode, LNode} from '../../src/render3/interfaces/node';
|
import {LElementNode, LNode} from '../../src/render3/interfaces/node';
|
||||||
import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
||||||
import {bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
|
import {bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
|
||||||
|
|
||||||
import {TemplateFixture} from './render_util';
|
import {NgForOf} from './common_with_def';
|
||||||
|
import {ComponentFixture, TemplateFixture} from './render_util';
|
||||||
|
|
||||||
describe('instructions', () => {
|
describe('instructions', () => {
|
||||||
function createDiv() {
|
function createDiv() {
|
||||||
|
@ -30,6 +35,13 @@ describe('instructions', () => {
|
||||||
() => elementAttribute(
|
() => elementAttribute(
|
||||||
0, 'title', bypassSanitizationTrustUrl('javascript:true'), sanitizeUrl));
|
0, 'title', bypassSanitizationTrustUrl('javascript:true'), sanitizeUrl));
|
||||||
expect(t.html).toEqual('<div title="javascript:true"></div>');
|
expect(t.html).toEqual('<div title="javascript:true"></div>');
|
||||||
|
expect(ngDevMode).toHaveProperties({
|
||||||
|
firstTemplatePass: 1,
|
||||||
|
tNode: 1,
|
||||||
|
tView: 1,
|
||||||
|
rendererCreateElement: 1,
|
||||||
|
rendererSetAttribute: 2
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -44,6 +56,12 @@ describe('instructions', () => {
|
||||||
() => elementProperty(
|
() => elementProperty(
|
||||||
0, 'title', bypassSanitizationTrustUrl('javascript:false'), sanitizeUrl));
|
0, 'title', bypassSanitizationTrustUrl('javascript:false'), sanitizeUrl));
|
||||||
expect(t.html).toEqual('<div title="javascript:false"></div>');
|
expect(t.html).toEqual('<div title="javascript:false"></div>');
|
||||||
|
expect(ngDevMode).toHaveProperties({
|
||||||
|
firstTemplatePass: 1,
|
||||||
|
tNode: 1,
|
||||||
|
tView: 1,
|
||||||
|
rendererCreateElement: 1,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not stringify non string values', () => {
|
it('should not stringify non string values', () => {
|
||||||
|
@ -52,6 +70,13 @@ describe('instructions', () => {
|
||||||
t.update(() => elementProperty(0, 'hidden', false));
|
t.update(() => elementProperty(0, 'hidden', false));
|
||||||
// The hidden property would be true if `false` was stringified into `"false"`.
|
// The hidden property would be true if `false` was stringified into `"false"`.
|
||||||
expect((t.hostNode.native as HTMLElement).querySelector('div') !.hidden).toEqual(false);
|
expect((t.hostNode.native as HTMLElement).querySelector('div') !.hidden).toEqual(false);
|
||||||
|
expect(ngDevMode).toHaveProperties({
|
||||||
|
firstTemplatePass: 1,
|
||||||
|
tNode: 1,
|
||||||
|
tView: 1,
|
||||||
|
rendererCreateElement: 1,
|
||||||
|
rendererSetProperty: 1
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -93,4 +118,63 @@ describe('instructions', () => {
|
||||||
expect(fixture.html).toEqual('<div class="multiple classes"></div>');
|
expect(fixture.html).toEqual('<div class="multiple classes"></div>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('performance counters', () => {
|
||||||
|
it('should create tViews only once for each nested level', () => {
|
||||||
|
const _c0 = ['ngFor', '', 'ngForOf', ''];
|
||||||
|
/**
|
||||||
|
* <ul *ngFor="let row of rows">
|
||||||
|
* <li *ngFor="let col of row.cols">{{col}}</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
class NestedLoops {
|
||||||
|
rows = [['a', 'b'], ['A', 'B'], ['a', 'b'], ['A', 'B']];
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: NestedLoops,
|
||||||
|
selectors: [['todo-app']],
|
||||||
|
factory: function ToDoAppComponent_Factory() { return new NestedLoops(); },
|
||||||
|
template: function ToDoAppComponent_Template(rf: RenderFlags, ctx: NestedLoops) {
|
||||||
|
if (rf & 1) {
|
||||||
|
container(0, ToDoAppComponent_NgForOf_Template_0, null, _c0);
|
||||||
|
}
|
||||||
|
if (rf & 2) {
|
||||||
|
elementProperty(0, 'ngForOf', bind(ctx.rows));
|
||||||
|
}
|
||||||
|
function ToDoAppComponent_NgForOf_Template_0(
|
||||||
|
rf: RenderFlags, ctx0: NgForOfContext<any>) {
|
||||||
|
if (rf & 1) {
|
||||||
|
elementStart(0, 'ul');
|
||||||
|
container(1, ToDoAppComponent_NgForOf_NgForOf_Template_1, null, _c0);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
if (rf & 2) {
|
||||||
|
const row_r2 = ctx0.$implicit;
|
||||||
|
elementProperty(1, 'ngForOf', bind(row_r2));
|
||||||
|
}
|
||||||
|
function ToDoAppComponent_NgForOf_NgForOf_Template_1(
|
||||||
|
rf: RenderFlags, ctx1: NgForOfContext<any>) {
|
||||||
|
if (rf & 1) {
|
||||||
|
elementStart(0, 'li');
|
||||||
|
text(1);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
if (rf & 2) {
|
||||||
|
const col_r3 = ctx1.$implicit;
|
||||||
|
textBinding(1, interpolation1('', col_r3, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directives: [NgForOf]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const fixture = new ComponentFixture(NestedLoops);
|
||||||
|
expect(ngDevMode).toHaveProperties({
|
||||||
|
// Expect: host view + component + *ngForRow + *ngForCol
|
||||||
|
tView: 7, // should be: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,6 +28,12 @@ describe('render3 integration test', () => {
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
expect(ngDevMode).toHaveProperties({
|
||||||
|
firstTemplatePass: 1,
|
||||||
|
tNode: 1,
|
||||||
|
tView: 1,
|
||||||
|
rendererCreateElement: 1,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render and update basic "Hello, World" template', () => {
|
it('should render and update basic "Hello, World" template', () => {
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* @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 {ngDevModeResetPerfCounters} from '../../src/render3/ng_dev_mode';
|
||||||
|
|
||||||
|
beforeEach(ngDevModeResetPerfCounters);
|
||||||
|
beforeEach(() => {
|
||||||
|
jasmine.addMatchers({
|
||||||
|
toHaveProperties: function(util, customEqualityTesters) {
|
||||||
|
return {compare: toHavePropertiesCompare};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
function toHavePropertiesCompare(actual: any, expected: any) {
|
||||||
|
let pass = true;
|
||||||
|
let errors = [];
|
||||||
|
for (let key of Object.keys(actual)) {
|
||||||
|
if (expected.hasOwnProperty(key)) {
|
||||||
|
if (actual[key] !== expected[key]) {
|
||||||
|
pass = false;
|
||||||
|
errors.push(`Expected '${key}' to be '${expected[key]}' but was '${actual[key]}'.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {pass: pass, message: errors.join('\n')};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('toHaveProperties', () => {
|
||||||
|
it('should pass', () => {
|
||||||
|
expect({tNode: 1}).toHaveProperties({});
|
||||||
|
expect({tNode: 2}).toHaveProperties({tNode: 2});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail', () => {
|
||||||
|
expect(toHavePropertiesCompare({tNode: 2, tView: 4}, {tNode: 3, tView: 5})).toEqual({
|
||||||
|
pass: false,
|
||||||
|
message:
|
||||||
|
'Expected \'tNode\' to be \'3\' but was \'2\'.\nExpected \'tView\' to be \'5\' but was \'4\'.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -19,3 +19,9 @@
|
||||||
|
|
||||||
declare let isNode: boolean;
|
declare let isNode: boolean;
|
||||||
declare let isBrowser: boolean;
|
declare let isBrowser: boolean;
|
||||||
|
|
||||||
|
declare namespace jasmine {
|
||||||
|
interface Matchers {
|
||||||
|
toHaveProperties(obj: any): boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue