feat(compiler): allow to create ChangeDetectors from parsed templates
Part of #3605 Closes #3950
This commit is contained in:
parent
5c9613e084
commit
2fea0c2602
|
@ -20,18 +20,41 @@ export class ChangeDetectionMetadata {
|
|||
events: string[];
|
||||
hostListeners: StringMap<string, string>;
|
||||
hostProperties: StringMap<string, string>;
|
||||
constructor({changeDetection, properties, events, hostListeners, hostProperties}: {
|
||||
callAfterContentInit: boolean;
|
||||
callAfterContentChecked: boolean;
|
||||
callAfterViewInit: boolean;
|
||||
callAfterViewChecked: boolean;
|
||||
callOnChanges: boolean;
|
||||
callDoCheck: boolean;
|
||||
callOnInit: boolean;
|
||||
constructor({changeDetection, properties, events, hostListeners, hostProperties,
|
||||
callAfterContentInit, callAfterContentChecked, callAfterViewInit,
|
||||
callAfterViewChecked, callOnChanges, callDoCheck, callOnInit}: {
|
||||
changeDetection?: ChangeDetectionStrategy,
|
||||
properties?: string[],
|
||||
events?: string[],
|
||||
hostListeners?: StringMap<string, string>,
|
||||
hostProperties?: StringMap<string, string>
|
||||
hostProperties?: StringMap<string, string>,
|
||||
callAfterContentInit?: boolean,
|
||||
callAfterContentChecked?: boolean,
|
||||
callAfterViewInit?: boolean,
|
||||
callAfterViewChecked?: boolean,
|
||||
callOnChanges?: boolean,
|
||||
callDoCheck?: boolean,
|
||||
callOnInit?: boolean
|
||||
}) {
|
||||
this.changeDetection = changeDetection;
|
||||
this.properties = properties;
|
||||
this.events = events;
|
||||
this.hostListeners = hostListeners;
|
||||
this.hostProperties = hostProperties;
|
||||
this.callAfterContentInit = callAfterContentInit;
|
||||
this.callAfterContentChecked = callAfterContentChecked;
|
||||
this.callAfterViewInit = callAfterViewInit;
|
||||
this.callAfterViewChecked = callAfterViewChecked;
|
||||
this.callOnChanges = callOnChanges;
|
||||
this.callDoCheck = callDoCheck;
|
||||
this.callOnInit = callOnInit;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
import {ListWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {isPresent, isBlank} from 'angular2/src/core/facade/lang';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
|
||||
import {
|
||||
ChangeDetection,
|
||||
DirectiveIndex,
|
||||
BindingRecord,
|
||||
DirectiveRecord,
|
||||
ProtoChangeDetector,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorDefinition,
|
||||
ChangeDetectorGenConfig,
|
||||
ASTWithSource
|
||||
} from 'angular2/src/core/change_detection/change_detection';
|
||||
|
||||
import {DirectiveMetadata, TypeMetadata} from './api';
|
||||
import {
|
||||
TemplateAst,
|
||||
ElementAst,
|
||||
BoundTextAst,
|
||||
PropertyBindingType,
|
||||
DirectiveAst,
|
||||
TemplateAstVisitor,
|
||||
templateVisitAll,
|
||||
NgContentAst,
|
||||
EmbeddedTemplateAst,
|
||||
VariableAst,
|
||||
BoundElementPropertyAst,
|
||||
BoundEventAst,
|
||||
BoundDirectivePropertyAst,
|
||||
AttrAst,
|
||||
TextAst
|
||||
} from './template_ast';
|
||||
|
||||
export function createChangeDetectorDefinitions(
|
||||
componentType: TypeMetadata, componentStrategy: ChangeDetectionStrategy,
|
||||
genConfig: ChangeDetectorGenConfig, parsedTemplate: TemplateAst[]): ChangeDetectorDefinition[] {
|
||||
var visitor = new ProtoViewVisitor(componentStrategy);
|
||||
templateVisitAll(visitor, parsedTemplate);
|
||||
return createChangeDefinitions(visitor.allProtoViews, componentType, genConfig);
|
||||
}
|
||||
|
||||
class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
viewCount: number = 0;
|
||||
protoViewStack: ProtoViewVisitorData[] = [];
|
||||
allProtoViews: ProtoViewVisitorData[] = [];
|
||||
|
||||
constructor(componentStrategy: ChangeDetectionStrategy) {
|
||||
this._beginProtoView(new ProtoViewVisitorData(null, componentStrategy, this.viewCount++));
|
||||
}
|
||||
|
||||
private _beginProtoView(data: ProtoViewVisitorData) {
|
||||
this.protoViewStack.push(data);
|
||||
this.allProtoViews.push(data);
|
||||
}
|
||||
|
||||
get currentProtoView(): ProtoViewVisitorData {
|
||||
return this.protoViewStack[this.protoViewStack.length - 1];
|
||||
}
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
this.currentProtoView.boundElementCount++;
|
||||
templateVisitAll(this, ast.directives);
|
||||
|
||||
this.viewCount++;
|
||||
this._beginProtoView(new ProtoViewVisitorData(
|
||||
this.currentProtoView, ChangeDetectionStrategy.Default, this.viewCount - 1));
|
||||
// Attention: variables present on an embedded template count towards
|
||||
// the embedded template and not the template anchor!
|
||||
templateVisitAll(this, ast.vars);
|
||||
templateVisitAll(this, ast.children);
|
||||
this.protoViewStack.pop();
|
||||
return null;
|
||||
}
|
||||
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
if (ast.isBound()) {
|
||||
this.currentProtoView.boundElementCount++;
|
||||
}
|
||||
templateVisitAll(this, ast.properties, null);
|
||||
templateVisitAll(this, ast.events);
|
||||
templateVisitAll(this, ast.vars);
|
||||
for (var i = 0; i < ast.directives.length; i++) {
|
||||
ast.directives[i].visit(this, i);
|
||||
}
|
||||
templateVisitAll(this, ast.children);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitNgContent(ast: NgContentAst, context: any): any { return null; }
|
||||
|
||||
visitVariable(ast: VariableAst, context: any): any {
|
||||
this.currentProtoView.variableNames.push(ast.name);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitEvent(ast: BoundEventAst, directiveRecord: DirectiveRecord): any {
|
||||
var bindingRecord =
|
||||
isPresent(directiveRecord) ?
|
||||
BindingRecord.createForHostEvent(ast.handler, ast.name, directiveRecord) :
|
||||
BindingRecord.createForEvent(ast.handler, ast.name,
|
||||
this.currentProtoView.boundElementCount - 1);
|
||||
this.currentProtoView.eventRecords.push(bindingRecord);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitElementProperty(ast: BoundElementPropertyAst, directiveRecord: DirectiveRecord): any {
|
||||
var boundElementIndex = this.currentProtoView.boundElementCount - 1;
|
||||
var dirIndex = isPresent(directiveRecord) ? directiveRecord.directiveIndex : null;
|
||||
var bindingRecord;
|
||||
if (ast.type === PropertyBindingType.Property) {
|
||||
bindingRecord =
|
||||
isPresent(dirIndex) ?
|
||||
BindingRecord.createForHostProperty(dirIndex, ast.value, ast.name) :
|
||||
BindingRecord.createForElementProperty(ast.value, boundElementIndex, ast.name);
|
||||
} else if (ast.type === PropertyBindingType.Attribute) {
|
||||
bindingRecord =
|
||||
isPresent(dirIndex) ?
|
||||
BindingRecord.createForHostAttribute(dirIndex, ast.value, ast.name) :
|
||||
BindingRecord.createForElementAttribute(ast.value, boundElementIndex, ast.name);
|
||||
} else if (ast.type === PropertyBindingType.Class) {
|
||||
bindingRecord =
|
||||
isPresent(dirIndex) ?
|
||||
BindingRecord.createForHostClass(dirIndex, ast.value, ast.name) :
|
||||
BindingRecord.createForElementClass(ast.value, boundElementIndex, ast.name);
|
||||
} else if (ast.type === PropertyBindingType.Style) {
|
||||
bindingRecord =
|
||||
isPresent(dirIndex) ?
|
||||
BindingRecord.createForHostStyle(dirIndex, ast.value, ast.name, ast.unit) :
|
||||
BindingRecord.createForElementStyle(ast.value, boundElementIndex, ast.name, ast.unit);
|
||||
}
|
||||
this.currentProtoView.bindingRecords.push(bindingRecord);
|
||||
return null;
|
||||
}
|
||||
visitAttr(ast: AttrAst, context: any): any { return null; }
|
||||
visitBoundText(ast: BoundTextAst, context: any): any {
|
||||
var boundTextIndex = this.currentProtoView.boundTextCount++;
|
||||
this.currentProtoView.bindingRecords.push(
|
||||
BindingRecord.createForTextNode(ast.value, boundTextIndex));
|
||||
return null;
|
||||
}
|
||||
visitText(ast: TextAst, context: any): any { return null; }
|
||||
visitDirective(ast: DirectiveAst, directiveIndexAsNumber: number): any {
|
||||
var directiveIndex =
|
||||
new DirectiveIndex(this.currentProtoView.boundElementCount - 1, directiveIndexAsNumber);
|
||||
var directiveMetadata = ast.directive;
|
||||
var changeDetectionMeta = directiveMetadata.changeDetection;
|
||||
var directiveRecord = new DirectiveRecord({
|
||||
directiveIndex: directiveIndex,
|
||||
callAfterContentInit: changeDetectionMeta.callAfterContentInit,
|
||||
callAfterContentChecked: changeDetectionMeta.callAfterContentChecked,
|
||||
callAfterViewInit: changeDetectionMeta.callAfterViewInit,
|
||||
callAfterViewChecked: changeDetectionMeta.callAfterViewChecked,
|
||||
callOnChanges: changeDetectionMeta.callOnChanges,
|
||||
callDoCheck: changeDetectionMeta.callDoCheck,
|
||||
callOnInit: changeDetectionMeta.callOnInit,
|
||||
changeDetection: changeDetectionMeta.changeDetection
|
||||
});
|
||||
this.currentProtoView.directiveRecords.push(directiveRecord);
|
||||
|
||||
templateVisitAll(this, ast.properties, directiveRecord);
|
||||
var bindingRecords = this.currentProtoView.bindingRecords;
|
||||
if (directiveRecord.callOnChanges) {
|
||||
bindingRecords.push(BindingRecord.createDirectiveOnChanges(directiveRecord));
|
||||
}
|
||||
if (directiveRecord.callOnInit) {
|
||||
bindingRecords.push(BindingRecord.createDirectiveOnInit(directiveRecord));
|
||||
}
|
||||
if (directiveRecord.callDoCheck) {
|
||||
bindingRecords.push(BindingRecord.createDirectiveDoCheck(directiveRecord));
|
||||
}
|
||||
templateVisitAll(this, ast.hostProperties, directiveRecord);
|
||||
templateVisitAll(this, ast.hostEvents, directiveRecord);
|
||||
return null;
|
||||
}
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, directiveRecord: DirectiveRecord): any {
|
||||
// TODO: these setters should eventually be created by change detection, to make
|
||||
// it monomorphic!
|
||||
var setter = reflector.setter(ast.directiveName);
|
||||
this.currentProtoView.bindingRecords.push(
|
||||
BindingRecord.createForDirective(ast.value, ast.directiveName, setter, directiveRecord));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class ProtoViewVisitorData {
|
||||
boundTextCount: number = 0;
|
||||
boundElementCount: number = 0;
|
||||
variableNames: string[] = [];
|
||||
bindingRecords: BindingRecord[] = [];
|
||||
eventRecords: BindingRecord[] = [];
|
||||
directiveRecords: DirectiveRecord[] = [];
|
||||
constructor(public parent: ProtoViewVisitorData, public strategy: ChangeDetectionStrategy,
|
||||
public viewIndex: number) {}
|
||||
}
|
||||
|
||||
function createChangeDefinitions(pvDatas: ProtoViewVisitorData[], componentType: TypeMetadata,
|
||||
genConfig: ChangeDetectorGenConfig): ChangeDetectorDefinition[] {
|
||||
var pvVariableNames = _collectNestedProtoViewsVariableNames(pvDatas);
|
||||
return pvDatas.map(pvData => {
|
||||
var viewType = pvData.viewIndex === 0 ? 'component' : 'embedded';
|
||||
var id = _protoViewId(componentType, pvData.viewIndex, viewType);
|
||||
return new ChangeDetectorDefinition(id, pvData.strategy, pvVariableNames[pvData.viewIndex],
|
||||
pvData.bindingRecords, pvData.eventRecords,
|
||||
pvData.directiveRecords, genConfig);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function _collectNestedProtoViewsVariableNames(pvs: ProtoViewVisitorData[]): string[][] {
|
||||
var nestedPvVariableNames: string[][] = ListWrapper.createFixedSize(pvs.length);
|
||||
pvs.forEach((pv) => {
|
||||
var parentVariableNames: string[] =
|
||||
isPresent(pv.parent) ? nestedPvVariableNames[pv.parent.viewIndex] : [];
|
||||
nestedPvVariableNames[pv.viewIndex] = parentVariableNames.concat(pv.variableNames);
|
||||
});
|
||||
return nestedPvVariableNames;
|
||||
}
|
||||
|
||||
|
||||
function _protoViewId(hostComponentType: TypeMetadata, pvIndex: number, viewType: string): string {
|
||||
return `${hostComponentType.typeName}_${viewType}_${pvIndex}`;
|
||||
}
|
|
@ -4,39 +4,47 @@ import {DirectiveMetadata} from './api';
|
|||
|
||||
export interface TemplateAst {
|
||||
sourceInfo: string;
|
||||
visit(visitor: TemplateAstVisitor): any;
|
||||
visit(visitor: TemplateAstVisitor, context: any): any;
|
||||
}
|
||||
|
||||
export class TextAst implements TemplateAst {
|
||||
constructor(public value: string, public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor): any { return visitor.visitText(this); }
|
||||
visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitText(this, context); }
|
||||
}
|
||||
|
||||
export class BoundTextAst implements TemplateAst {
|
||||
constructor(public value: AST, public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor): any { return visitor.visitBoundText(this); }
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitBoundText(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class AttrAst implements TemplateAst {
|
||||
constructor(public name: string, public value: string, public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor): any { return visitor.visitAttr(this); }
|
||||
visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitAttr(this, context); }
|
||||
}
|
||||
|
||||
export class BoundElementPropertyAst implements TemplateAst {
|
||||
constructor(public name: string, public type: PropertyBindingType, public value: AST,
|
||||
public unit: string, public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor): any { return visitor.visitElementProperty(this); }
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitElementProperty(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class BoundEventAst implements TemplateAst {
|
||||
constructor(public name: string, public target: string, public handler: AST,
|
||||
public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor): any { return visitor.visitEvent(this); }
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitEvent(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class VariableAst implements TemplateAst {
|
||||
constructor(public name: string, public value: string, public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor): any { return visitor.visitVariable(this); }
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitVariable(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class ElementAst implements TemplateAst {
|
||||
|
@ -44,32 +52,47 @@ export class ElementAst implements TemplateAst {
|
|||
public events: BoundEventAst[], public vars: VariableAst[],
|
||||
public directives: DirectiveAst[], public children: TemplateAst[],
|
||||
public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor): any { return visitor.visitElement(this); }
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitElement(this, context);
|
||||
}
|
||||
|
||||
isBound(): boolean {
|
||||
return (this.properties.length > 0 || this.events.length > 0 || this.vars.length > 0 ||
|
||||
this.directives.length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class EmbeddedTemplateAst implements TemplateAst {
|
||||
constructor(public attrs: AttrAst[], public vars: VariableAst[],
|
||||
public directives: DirectiveAst[], public children: TemplateAst[],
|
||||
public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor): any { return visitor.visitEmbeddedTemplate(this); }
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitEmbeddedTemplate(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class BoundDirectivePropertyAst implements TemplateAst {
|
||||
constructor(public directiveName: string, public templateName: string, public value: AST,
|
||||
public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor): any { return visitor.visitDirectiveProperty(this); }
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitDirectiveProperty(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class DirectiveAst implements TemplateAst {
|
||||
constructor(public directive: DirectiveMetadata, public properties: BoundDirectivePropertyAst[],
|
||||
public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[],
|
||||
public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor): any { return visitor.visitDirective(this); }
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitDirective(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class NgContentAst implements TemplateAst {
|
||||
constructor(public select: string, public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor): any { return visitor.visitNgContent(this); }
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitNgContent(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export enum PropertyBindingType {
|
||||
|
@ -80,24 +103,25 @@ export enum PropertyBindingType {
|
|||
}
|
||||
|
||||
export interface TemplateAstVisitor {
|
||||
visitNgContent(ast: NgContentAst): any;
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst): any;
|
||||
visitElement(ast: ElementAst): any;
|
||||
visitVariable(ast: VariableAst): any;
|
||||
visitEvent(ast: BoundEventAst): any;
|
||||
visitElementProperty(ast: BoundElementPropertyAst): any;
|
||||
visitAttr(ast: AttrAst): any;
|
||||
visitBoundText(ast: BoundTextAst): any;
|
||||
visitText(ast: TextAst): any;
|
||||
visitDirective(ast: DirectiveAst): any;
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst): any;
|
||||
visitNgContent(ast: NgContentAst, context: any): any;
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any;
|
||||
visitElement(ast: ElementAst, context: any): any;
|
||||
visitVariable(ast: VariableAst, context: any): any;
|
||||
visitEvent(ast: BoundEventAst, context: any): any;
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any;
|
||||
visitAttr(ast: AttrAst, context: any): any;
|
||||
visitBoundText(ast: BoundTextAst, context: any): any;
|
||||
visitText(ast: TextAst, context: any): any;
|
||||
visitDirective(ast: DirectiveAst, context: any): any;
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any;
|
||||
}
|
||||
|
||||
|
||||
export function templateVisitAll(visitor: TemplateAstVisitor, asts: TemplateAst[]): any[] {
|
||||
export function templateVisitAll(visitor: TemplateAstVisitor, asts: TemplateAst[],
|
||||
context: any = null): any[] {
|
||||
var result = [];
|
||||
asts.forEach(ast => {
|
||||
var astResult = ast.visit(visitor);
|
||||
var astResult = ast.visit(visitor, context);
|
||||
if (isPresent(astResult)) {
|
||||
result.push(astResult);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
el,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
TestComponentBuilder,
|
||||
asNativeElements,
|
||||
By
|
||||
} from 'angular2/test_lib';
|
||||
|
||||
import {MapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {isBlank} from 'angular2/src/core/facade/lang';
|
||||
|
||||
import {HtmlParser} from 'angular2/src/compiler/html_parser';
|
||||
import {DirectiveMetadata, TypeMetadata, ChangeDetectionMetadata} from 'angular2/src/compiler/api';
|
||||
|
||||
import {MockSchemaRegistry} from './template_parser_spec';
|
||||
|
||||
import {TemplateParser} from 'angular2/src/compiler/template_parser';
|
||||
|
||||
import {
|
||||
Parser,
|
||||
Lexer,
|
||||
ChangeDetectorDefinition,
|
||||
ChangeDetectorGenConfig,
|
||||
DynamicProtoChangeDetector,
|
||||
ProtoChangeDetector,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDispatcher,
|
||||
DirectiveIndex,
|
||||
Locals,
|
||||
BindingTarget,
|
||||
ChangeDetector
|
||||
} from 'angular2/src/core/change_detection/change_detection';
|
||||
|
||||
import {Pipes} from 'angular2/src/core/change_detection/pipes';
|
||||
|
||||
|
||||
import {createChangeDetectorDefinitions} from 'angular2/src/compiler/change_definition_factory';
|
||||
|
||||
export function main() {
|
||||
describe('ChangeDefinitionFactory', () => {
|
||||
var domParser: HtmlParser;
|
||||
var parser: TemplateParser;
|
||||
var dispatcher: TestDispatcher;
|
||||
var context: TestContext;
|
||||
var directive: TestDirective;
|
||||
var locals: Locals;
|
||||
var pipes: Pipes;
|
||||
var eventLocals: Locals;
|
||||
|
||||
beforeEach(() => {
|
||||
domParser = new HtmlParser();
|
||||
parser = new TemplateParser(
|
||||
new Parser(new Lexer()),
|
||||
new MockSchemaRegistry({'invalidProp': false}, {'mappedAttr': 'mappedProp'}));
|
||||
context = new TestContext();
|
||||
directive = new TestDirective();
|
||||
dispatcher = new TestDispatcher([directive], []);
|
||||
locals = new Locals(null, MapWrapper.createFromStringMap({'someVar': null}));
|
||||
eventLocals = new Locals(null, MapWrapper.createFromStringMap({'$event': null}));
|
||||
pipes = new TestPipes();
|
||||
});
|
||||
|
||||
function createChangeDetector(template: string, directives: DirectiveMetadata[],
|
||||
protoViewIndex: number = 0): ChangeDetector {
|
||||
var protoChangeDetectors =
|
||||
createChangeDetectorDefinitions(
|
||||
new TypeMetadata({typeName: 'SomeComp'}), ChangeDetectionStrategy.CheckAlways,
|
||||
new ChangeDetectorGenConfig(true, true, false),
|
||||
parser.parse(domParser.parse(template, 'TestComp'), directives))
|
||||
.map(definition => new DynamicProtoChangeDetector(definition));
|
||||
var changeDetector = protoChangeDetectors[protoViewIndex].instantiate(dispatcher);
|
||||
changeDetector.hydrate(context, locals, dispatcher, pipes);
|
||||
return changeDetector;
|
||||
}
|
||||
|
||||
it('should watch element properties', () => {
|
||||
var changeDetector = createChangeDetector('<div [el-prop]="someProp">', [], 0);
|
||||
|
||||
context.someProp = 'someValue';
|
||||
changeDetector.detectChanges();
|
||||
expect(dispatcher.log).toEqual(['elementProperty(elProp)=someValue']);
|
||||
});
|
||||
|
||||
it('should watch text nodes', () => {
|
||||
var changeDetector = createChangeDetector('{{someProp}}', [], 0);
|
||||
|
||||
context.someProp = 'someValue';
|
||||
changeDetector.detectChanges();
|
||||
expect(dispatcher.log).toEqual(['textNode(null)=someValue']);
|
||||
});
|
||||
|
||||
it('should handle events', () => {
|
||||
var changeDetector = createChangeDetector('<div on-click="onEvent($event)">', [], 0);
|
||||
|
||||
eventLocals.set('$event', 'click');
|
||||
changeDetector.handleEvent('click', 0, eventLocals);
|
||||
expect(context.eventLog).toEqual(['click']);
|
||||
});
|
||||
|
||||
it('should watch variables', () => {
|
||||
var changeDetector = createChangeDetector('<div #some-var [el-prop]="someVar">', [], 0);
|
||||
|
||||
locals.set('someVar', 'someValue');
|
||||
changeDetector.detectChanges();
|
||||
expect(dispatcher.log).toEqual(['elementProperty(elProp)=someValue']);
|
||||
});
|
||||
|
||||
it('should write directive properties', () => {
|
||||
var dirMeta = new DirectiveMetadata({
|
||||
type: new TypeMetadata({typeName: 'SomeDir'}),
|
||||
selector: 'div',
|
||||
changeDetection: new ChangeDetectionMetadata({properties: ['dirProp']})
|
||||
});
|
||||
|
||||
var changeDetector = createChangeDetector('<div [dir-prop]="someProp">', [dirMeta], 0);
|
||||
|
||||
context.someProp = 'someValue';
|
||||
changeDetector.detectChanges();
|
||||
expect(directive.dirProp).toEqual('someValue');
|
||||
});
|
||||
|
||||
it('should watch directive host properties', () => {
|
||||
var dirMeta = new DirectiveMetadata({
|
||||
type: new TypeMetadata({typeName: 'SomeDir'}),
|
||||
selector: 'div',
|
||||
changeDetection: new ChangeDetectionMetadata({hostProperties: {'elProp': 'dirProp'}})
|
||||
});
|
||||
|
||||
var changeDetector = createChangeDetector('<div>', [dirMeta], 0);
|
||||
|
||||
directive.dirProp = 'someValue';
|
||||
changeDetector.detectChanges();
|
||||
expect(dispatcher.log).toEqual(['elementProperty(elProp)=someValue']);
|
||||
});
|
||||
|
||||
it('should handle directive events', () => {
|
||||
var dirMeta = new DirectiveMetadata({
|
||||
type: new TypeMetadata({typeName: 'SomeDir'}),
|
||||
selector: 'div',
|
||||
changeDetection:
|
||||
new ChangeDetectionMetadata({hostListeners: {'click': 'onEvent($event)'}})
|
||||
});
|
||||
|
||||
var changeDetector = createChangeDetector('<div>', [dirMeta], 0);
|
||||
|
||||
eventLocals.set('$event', 'click');
|
||||
changeDetector.handleEvent('click', 0, eventLocals);
|
||||
expect(directive.eventLog).toEqual(['click']);
|
||||
});
|
||||
|
||||
it('should create change detectors for embedded templates', () => {
|
||||
var changeDetector = createChangeDetector('<template>{{someProp}}<template>', [], 1);
|
||||
|
||||
context.someProp = 'someValue';
|
||||
changeDetector.detectChanges();
|
||||
expect(dispatcher.log).toEqual(['textNode(null)=someValue']);
|
||||
});
|
||||
|
||||
it('should watch expressions after embedded templates', () => {
|
||||
var changeDetector =
|
||||
createChangeDetector('<template>{{someProp2}}</template>{{someProp}}', [], 0);
|
||||
|
||||
context.someProp = 'someValue';
|
||||
changeDetector.detectChanges();
|
||||
expect(dispatcher.log).toEqual(['textNode(null)=someValue']);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class TestContext {
|
||||
eventLog: string[] = [];
|
||||
someProp: string;
|
||||
someProp2: string;
|
||||
|
||||
onEvent(value: string) { this.eventLog.push(value); }
|
||||
}
|
||||
|
||||
class TestDirective {
|
||||
eventLog: string[] = [];
|
||||
dirProp: string;
|
||||
|
||||
onEvent(value: string) { this.eventLog.push(value); }
|
||||
}
|
||||
|
||||
class TestDispatcher implements ChangeDispatcher {
|
||||
log: string[];
|
||||
|
||||
constructor(public directives: any[], public detectors: ProtoChangeDetector[]) { this.clear(); }
|
||||
|
||||
getDirectiveFor(di: DirectiveIndex) { return this.directives[di.directiveIndex]; }
|
||||
|
||||
getDetectorFor(di: DirectiveIndex) { return this.detectors[di.directiveIndex]; }
|
||||
|
||||
clear() { this.log = []; }
|
||||
|
||||
notifyOnBinding(target: BindingTarget, value) {
|
||||
this.log.push(`${target.mode}(${target.name})=${this._asString(value)}`);
|
||||
}
|
||||
|
||||
logBindingUpdate(target, value) {}
|
||||
|
||||
notifyAfterContentChecked() {}
|
||||
notifyAfterViewChecked() {}
|
||||
|
||||
getDebugContext(a, b) { return null; }
|
||||
|
||||
_asString(value) { return (isBlank(value) ? 'null' : value.toString()); }
|
||||
}
|
||||
|
||||
class TestPipes implements Pipes {
|
||||
get(type: string) { return null; }
|
||||
}
|
|
@ -667,11 +667,11 @@ export function humanizeTemplateAsts(templateAsts: TemplateAst[]): any[] {
|
|||
|
||||
class TemplateHumanizer implements TemplateAstVisitor {
|
||||
result: any[] = [];
|
||||
visitNgContent(ast: NgContentAst): any {
|
||||
visitNgContent(ast: NgContentAst, context: any): any {
|
||||
this.result.push([NgContentAst, ast.select, ast.sourceInfo]);
|
||||
return null;
|
||||
}
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst): any {
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
this.result.push([EmbeddedTemplateAst, ast.sourceInfo]);
|
||||
templateVisitAll(this, ast.attrs);
|
||||
templateVisitAll(this, ast.vars);
|
||||
|
@ -679,7 +679,7 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
|||
templateVisitAll(this, ast.children);
|
||||
return null;
|
||||
}
|
||||
visitElement(ast: ElementAst): any {
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
this.result.push([ElementAst, ast.sourceInfo]);
|
||||
templateVisitAll(this, ast.attrs);
|
||||
templateVisitAll(this, ast.properties);
|
||||
|
@ -689,11 +689,11 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
|||
templateVisitAll(this, ast.children);
|
||||
return null;
|
||||
}
|
||||
visitVariable(ast: VariableAst): any {
|
||||
visitVariable(ast: VariableAst, context: any): any {
|
||||
this.result.push([VariableAst, ast.name, ast.value, ast.sourceInfo]);
|
||||
return null;
|
||||
}
|
||||
visitEvent(ast: BoundEventAst): any {
|
||||
visitEvent(ast: BoundEventAst, context: any): any {
|
||||
this.result.push([
|
||||
BoundEventAst,
|
||||
ast.name,
|
||||
|
@ -703,7 +703,7 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
|||
]);
|
||||
return null;
|
||||
}
|
||||
visitElementProperty(ast: BoundElementPropertyAst): any {
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {
|
||||
this.result.push([
|
||||
BoundElementPropertyAst,
|
||||
ast.type,
|
||||
|
@ -714,26 +714,26 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
|||
]);
|
||||
return null;
|
||||
}
|
||||
visitAttr(ast: AttrAst): any {
|
||||
visitAttr(ast: AttrAst, context: any): any {
|
||||
this.result.push([AttrAst, ast.name, ast.value, ast.sourceInfo]);
|
||||
return null;
|
||||
}
|
||||
visitBoundText(ast: BoundTextAst): any {
|
||||
visitBoundText(ast: BoundTextAst, context: any): any {
|
||||
this.result.push([BoundTextAst, expressionUnparser.unparse(ast.value), ast.sourceInfo]);
|
||||
return null;
|
||||
}
|
||||
visitText(ast: TextAst): any {
|
||||
visitText(ast: TextAst, context: any): any {
|
||||
this.result.push([TextAst, ast.value, ast.sourceInfo]);
|
||||
return null;
|
||||
}
|
||||
visitDirective(ast: DirectiveAst): any {
|
||||
visitDirective(ast: DirectiveAst, context: any): any {
|
||||
this.result.push([DirectiveAst, ast.directive, ast.sourceInfo]);
|
||||
templateVisitAll(this, ast.properties);
|
||||
templateVisitAll(this, ast.hostProperties);
|
||||
templateVisitAll(this, ast.hostEvents);
|
||||
return null;
|
||||
}
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst): any {
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {
|
||||
this.result.push([
|
||||
BoundDirectivePropertyAst,
|
||||
ast.directiveName,
|
||||
|
@ -744,7 +744,7 @@ class TemplateHumanizer implements TemplateAstVisitor {
|
|||
}
|
||||
}
|
||||
|
||||
class MockSchemaRegistry implements ElementSchemaRegistry {
|
||||
export class MockSchemaRegistry implements ElementSchemaRegistry {
|
||||
constructor(public existingProperties: StringMap<string, boolean>,
|
||||
public attrPropMapping: StringMap<string, string>) {}
|
||||
hasProperty(tagName: string, property: string): boolean {
|
||||
|
|
Loading…
Reference in New Issue