feat(DirectiveParser): throw errors when expected directives are not present
closes #527 Closes #570
This commit is contained in:
parent
715ee14ced
commit
94e203b9df
|
@ -17,6 +17,7 @@ import {Template} from '../annotations/template';
|
||||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||||
import {CompileStep} from './pipeline/compile_step';
|
import {CompileStep} from './pipeline/compile_step';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache that stores the ProtoView of the template of a component.
|
* Cache that stores the ProtoView of the template of a component.
|
||||||
* Used to prevent duplicate work and resolve cyclic dependencies.
|
* Used to prevent duplicate work and resolve cyclic dependencies.
|
||||||
|
@ -134,7 +135,15 @@ export class Compiler {
|
||||||
// TODO(vicb): union type return ProtoView or Promise<ProtoView>
|
// TODO(vicb): union type return ProtoView or Promise<ProtoView>
|
||||||
_compileTemplate(template: Template, tplElement: Element, component: Type) {
|
_compileTemplate(template: Template, tplElement: Element, component: Type) {
|
||||||
var pipeline = new CompilePipeline(this.createSteps(component, template));
|
var pipeline = new CompilePipeline(this.createSteps(component, template));
|
||||||
var compileElements = pipeline.process(tplElement);
|
var compilationCtxtDescription = stringify(this._reader.read(component).type);
|
||||||
|
var compileElements;
|
||||||
|
|
||||||
|
try {
|
||||||
|
compileElements = pipeline.process(tplElement, compilationCtxtDescription);
|
||||||
|
} catch(ex) {
|
||||||
|
return PromiseWrapper.reject(ex);
|
||||||
|
}
|
||||||
|
|
||||||
var protoView = compileElements[0].inheritedProtoView;
|
var protoView = compileElements[0].inheritedProtoView;
|
||||||
|
|
||||||
// Populate the cache before compiling the nested components,
|
// Populate the cache before compiling the nested components,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {Element, DOM} from 'angular2/src/facade/dom';
|
import {Element, DOM} from 'angular2/src/facade/dom';
|
||||||
import {int, isBlank, isPresent, Type} from 'angular2/src/facade/lang';
|
import {int, isBlank, isPresent, Type, StringJoiner, assertionsEnabled} from 'angular2/src/facade/lang';
|
||||||
import {DirectiveMetadata} from '../directive_metadata';
|
import {DirectiveMetadata} from '../directive_metadata';
|
||||||
import {Decorator, Component, Viewport} from '../../annotations/annotations';
|
import {Decorator, Component, Viewport} from '../../annotations/annotations';
|
||||||
import {ElementBinder} from '../element_binder';
|
import {ElementBinder} from '../element_binder';
|
||||||
|
@ -38,8 +38,9 @@ export class CompileElement {
|
||||||
distanceToParentInjector:number;
|
distanceToParentInjector:number;
|
||||||
compileChildren: boolean;
|
compileChildren: boolean;
|
||||||
ignoreBindings: boolean;
|
ignoreBindings: boolean;
|
||||||
|
elementDescription: string; // e.g. '<div [class]="foo">' : used to provide context in case of error
|
||||||
|
|
||||||
constructor(element:Element) {
|
constructor(element:Element, compilationUnit = '') {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this._attrs = null;
|
this._attrs = null;
|
||||||
this._classList = null;
|
this._classList = null;
|
||||||
|
@ -66,6 +67,14 @@ export class CompileElement {
|
||||||
this.compileChildren = true;
|
this.compileChildren = true;
|
||||||
// set to true to ignore all the bindings on the element
|
// set to true to ignore all the bindings on the element
|
||||||
this.ignoreBindings = false;
|
this.ignoreBindings = false;
|
||||||
|
// description is calculated here as compilation steps may change the element
|
||||||
|
var tplDesc = assertionsEnabled()? getElementDescription(element) : null;
|
||||||
|
if (compilationUnit !== '') {
|
||||||
|
this.elementDescription = compilationUnit;
|
||||||
|
if (isPresent(tplDesc)) this.elementDescription += ": " + tplDesc;
|
||||||
|
} else {
|
||||||
|
this.elementDescription = tplDesc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshAttrs() {
|
refreshAttrs() {
|
||||||
|
@ -165,3 +174,36 @@ export class CompileElement {
|
||||||
return this._allDirectives;
|
return this._allDirectives;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return an HTML representation of an element start tag - without its content
|
||||||
|
// this is used to give contextual information in case of errors
|
||||||
|
function getElementDescription(domElement:Element):string {
|
||||||
|
var buf = new StringJoiner();
|
||||||
|
var atts = DOM.attributeMap(domElement);
|
||||||
|
|
||||||
|
buf.add("<");
|
||||||
|
buf.add(DOM.tagName(domElement).toLowerCase());
|
||||||
|
|
||||||
|
// show id and class first to ease element identification
|
||||||
|
addDescriptionAttribute(buf, "id", MapWrapper.get(atts, "id"));
|
||||||
|
addDescriptionAttribute(buf, "class", MapWrapper.get(atts, "class"));
|
||||||
|
MapWrapper.forEach(atts, (attValue, attName) => {
|
||||||
|
if (attName !== "id" && attName !== "class") {
|
||||||
|
addDescriptionAttribute(buf, attName, attValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buf.add(">");
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function addDescriptionAttribute(buffer:StringJoiner, attName:string, attValue) {
|
||||||
|
if (isPresent(attValue)) {
|
||||||
|
if (attValue.length === 0) {
|
||||||
|
buffer.add(' ' + attName);
|
||||||
|
} else {
|
||||||
|
buffer.add(' ' + attName + '="' + attValue + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,13 +15,13 @@ export class CompilePipeline {
|
||||||
this._control = new CompileControl(steps);
|
this._control = new CompileControl(steps);
|
||||||
}
|
}
|
||||||
|
|
||||||
process(rootElement:Element):List {
|
process(rootElement:Element, compilationCtxtDescription:string = ''):List {
|
||||||
var results = ListWrapper.create();
|
var results = ListWrapper.create();
|
||||||
this._process(results, null, new CompileElement(rootElement));
|
this._process(results, null, new CompileElement(rootElement, compilationCtxtDescription), compilationCtxtDescription);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
_process(results, parent:CompileElement, current:CompileElement) {
|
_process(results, parent:CompileElement, current:CompileElement, compilationCtxtDescription:string = '') {
|
||||||
var additionalChildren = this._control.internalProcess(results, 0, parent, current);
|
var additionalChildren = this._control.internalProcess(results, 0, parent, current);
|
||||||
|
|
||||||
if (current.compileChildren) {
|
if (current.compileChildren) {
|
||||||
|
@ -31,7 +31,7 @@ export class CompilePipeline {
|
||||||
// next sibling before recursing.
|
// next sibling before recursing.
|
||||||
var nextNode = DOM.nextSibling(node);
|
var nextNode = DOM.nextSibling(node);
|
||||||
if (DOM.isElementNode(node)) {
|
if (DOM.isElementNode(node)) {
|
||||||
this._process(results, current, new CompileElement(node));
|
this._process(results, current, new CompileElement(node, compilationCtxtDescription));
|
||||||
}
|
}
|
||||||
node = nextNode;
|
node = nextNode;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {ShimShadowCss} from './shim_shadow_css';
|
||||||
import {ShimShadowDom} from './shim_shadow_dom';
|
import {ShimShadowDom} from './shim_shadow_dom';
|
||||||
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||||
import {ShadowDomStrategy, EmulatedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
import {ShadowDomStrategy, EmulatedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
import {stringify} from 'angular2/src/facade/lang';
|
|
||||||
import {DOM} from 'angular2/src/facade/dom';
|
import {DOM} from 'angular2/src/facade/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,9 +27,7 @@ export function createDefaultSteps(
|
||||||
directives: List<DirectiveMetadata>,
|
directives: List<DirectiveMetadata>,
|
||||||
shadowDomStrategy: ShadowDomStrategy) {
|
shadowDomStrategy: ShadowDomStrategy) {
|
||||||
|
|
||||||
var compilationUnit = stringify(compiledComponent.type);
|
var steps = [new ViewSplitter(parser)];
|
||||||
|
|
||||||
var steps = [new ViewSplitter(parser, compilationUnit)];
|
|
||||||
|
|
||||||
if (shadowDomStrategy instanceof EmulatedShadowDomStrategy) {
|
if (shadowDomStrategy instanceof EmulatedShadowDomStrategy) {
|
||||||
var step = new ShimShadowCss(compiledComponent, shadowDomStrategy, DOM.defaultDoc().head);
|
var step = new ShimShadowCss(compiledComponent, shadowDomStrategy, DOM.defaultDoc().head);
|
||||||
|
@ -38,13 +35,13 @@ export function createDefaultSteps(
|
||||||
}
|
}
|
||||||
|
|
||||||
steps = ListWrapper.concat(steps,[
|
steps = ListWrapper.concat(steps,[
|
||||||
new PropertyBindingParser(parser, compilationUnit),
|
new PropertyBindingParser(parser),
|
||||||
new DirectiveParser(directives),
|
new DirectiveParser(directives),
|
||||||
new TextInterpolationParser(parser, compilationUnit),
|
new TextInterpolationParser(parser),
|
||||||
new ElementBindingMarker(),
|
new ElementBindingMarker(),
|
||||||
new ProtoViewBuilder(changeDetection, shadowDomStrategy),
|
new ProtoViewBuilder(changeDetection, shadowDomStrategy),
|
||||||
new ProtoElementInjectorBuilder(),
|
new ProtoElementInjectorBuilder(),
|
||||||
new ElementBinderBuilder(parser, compilationUnit)
|
new ElementBinderBuilder(parser)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (shadowDomStrategy instanceof EmulatedShadowDomStrategy) {
|
if (shadowDomStrategy instanceof EmulatedShadowDomStrategy) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, BaseException, assertionsEnabled, RegExpWrapper} from 'angular2/src/facade/lang';
|
||||||
import {List, MapWrapper} from 'angular2/src/facade/collection';
|
import {List, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {DOM} from 'angular2/src/facade/dom';
|
import {DOM} from 'angular2/src/facade/dom';
|
||||||
import {SelectorMatcher} from '../selector';
|
import {SelectorMatcher} from '../selector';
|
||||||
import {CssSelector} from '../selector';
|
import {CssSelector} from '../selector';
|
||||||
|
@ -10,6 +10,10 @@ import {CompileStep} from './compile_step';
|
||||||
import {CompileElement} from './compile_element';
|
import {CompileElement} from './compile_element';
|
||||||
import {CompileControl} from './compile_control';
|
import {CompileControl} from './compile_control';
|
||||||
|
|
||||||
|
import {isSpecialProperty} from './element_binder_builder';;
|
||||||
|
|
||||||
|
var PROPERTY_BINDING_REGEXP = RegExpWrapper.create('^ *([^\\s\\|]+)');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the directives on a single element. Assumes ViewSplitter has already created
|
* Parses the directives on a single element. Assumes ViewSplitter has already created
|
||||||
* <template> elements for template directives.
|
* <template> elements for template directives.
|
||||||
|
@ -29,13 +33,13 @@ export class DirectiveParser extends CompileStep {
|
||||||
_selectorMatcher:SelectorMatcher;
|
_selectorMatcher:SelectorMatcher;
|
||||||
constructor(directives:List<DirectiveMetadata>) {
|
constructor(directives:List<DirectiveMetadata>) {
|
||||||
super();
|
super();
|
||||||
|
var selector;
|
||||||
|
|
||||||
this._selectorMatcher = new SelectorMatcher();
|
this._selectorMatcher = new SelectorMatcher();
|
||||||
for (var i=0; i<directives.length; i++) {
|
for (var i=0; i<directives.length; i++) {
|
||||||
var directiveMetadata = directives[i];
|
var directiveMetadata = directives[i];
|
||||||
this._selectorMatcher.addSelectable(
|
selector=CssSelector.parse(directiveMetadata.annotation.selector);
|
||||||
CssSelector.parse(directiveMetadata.annotation.selector),
|
this._selectorMatcher.addSelectable(selector, directiveMetadata);
|
||||||
directiveMetadata
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,19 +71,85 @@ export class DirectiveParser extends CompileStep {
|
||||||
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
|
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
|
||||||
// only be present on <template> elements any more!
|
// only be present on <template> elements any more!
|
||||||
var isTemplateElement = DOM.isTemplateElement(current.element);
|
var isTemplateElement = DOM.isTemplateElement(current.element);
|
||||||
this._selectorMatcher.match(cssSelector, (directive) => {
|
var matchedProperties; // StringMap - used in dev mode to store all properties that have been matched
|
||||||
if (directive.annotation instanceof Viewport) {
|
|
||||||
if (!isTemplateElement) {
|
this._selectorMatcher.match(cssSelector, (selector, directive) => {
|
||||||
throw new BaseException('Viewport directives need to be placed on <template> elements or elements with template attribute!');
|
matchedProperties = updateMatchedProperties(matchedProperties, selector, directive);
|
||||||
} else if (isPresent(current.viewportDirective)) {
|
checkDirectiveValidity(directive, current, isTemplateElement);
|
||||||
throw new BaseException('Only one template directive per element is allowed!');
|
|
||||||
}
|
|
||||||
} else if (isTemplateElement) {
|
|
||||||
throw new BaseException('Only template directives are allowed on <template> elements!');
|
|
||||||
} else if ((directive.annotation instanceof Component) && isPresent(current.componentDirective)) {
|
|
||||||
throw new BaseException('Only one component directive per element is allowed!');
|
|
||||||
}
|
|
||||||
current.addDirective(directive);
|
current.addDirective(directive);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// raise error if some directives are missing
|
||||||
|
checkMissingDirectives(current, matchedProperties, isTemplateElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate all the properties that are used or interpreted by all directives
|
||||||
|
// those properties correspond to the directive selectors and the directive bindings
|
||||||
|
function updateMatchedProperties(matchedProperties, selector, directive) {
|
||||||
|
if (assertionsEnabled()) {
|
||||||
|
var attrs = selector.attrs;
|
||||||
|
if (!isPresent(matchedProperties)) {
|
||||||
|
matchedProperties = StringMapWrapper.create();
|
||||||
|
}
|
||||||
|
if (isPresent(attrs)) {
|
||||||
|
for (var idx = 0; idx<attrs.length; idx+=2) {
|
||||||
|
// attribute name is stored on even indexes
|
||||||
|
StringMapWrapper.set(matchedProperties, attrs[idx], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// some properties can be used by the directive, so we need to register them
|
||||||
|
if (isPresent(directive.annotation) && isPresent(directive.annotation.bind)) {
|
||||||
|
var bindMap = directive.annotation.bind;
|
||||||
|
StringMapWrapper.forEach(bindMap, (value, key) => {
|
||||||
|
// value is the name of the property that is intepreted
|
||||||
|
// e.g. 'myprop' or 'myprop | double' when a pipe is used to transform the property
|
||||||
|
|
||||||
|
// keep the property name and remove the pipe
|
||||||
|
var bindProp = RegExpWrapper.firstMatch(PROPERTY_BINDING_REGEXP, value);
|
||||||
|
if (isPresent(bindProp) && isPresent(bindProp[1])) {
|
||||||
|
StringMapWrapper.set(matchedProperties, bindProp[1], true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchedProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the directive is compatible with the current element
|
||||||
|
function checkDirectiveValidity(directive, current, isTemplateElement) {
|
||||||
|
if (directive.annotation instanceof Viewport) {
|
||||||
|
if (!isTemplateElement) {
|
||||||
|
throw new BaseException(`Viewport directives need to be placed on <template> elements or elements ` +
|
||||||
|
`with template attribute - check ${current.elementDescription}`);
|
||||||
|
} else if (isPresent(current.viewportDirective)) {
|
||||||
|
throw new BaseException(`Only one viewport directive can be used per element - check ${current.elementDescription}`);
|
||||||
|
}
|
||||||
|
} else if (isTemplateElement) {
|
||||||
|
throw new BaseException(`Only template directives are allowed on template elements - check ${current.elementDescription}`);
|
||||||
|
} else if ((directive.annotation instanceof Component) && isPresent(current.componentDirective)) {
|
||||||
|
throw new BaseException(`Multiple component directives not allowed on the same element - check ${current.elementDescription}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates that there is no missing directive - dev mode only
|
||||||
|
function checkMissingDirectives(current, matchedProperties, isTemplateElement) {
|
||||||
|
if (assertionsEnabled()) {
|
||||||
|
var ppBindings=current.propertyBindings;
|
||||||
|
if (isPresent(ppBindings)) {
|
||||||
|
// check that each property corresponds to a real property or has been matched by a directive
|
||||||
|
MapWrapper.forEach(ppBindings, (expression, prop) => {
|
||||||
|
if (!DOM.hasProperty(current.element, prop) && !isSpecialProperty(prop)) {
|
||||||
|
if (!isPresent(matchedProperties) || !isPresent(StringMapWrapper.get(matchedProperties, prop))) {
|
||||||
|
throw new BaseException(`Missing directive to handle '${prop}' in ${current.elementDescription}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// template only store directives as attribute when they are not bound to expressions
|
||||||
|
// so we have to validate the expression case too (e.g. !if="condition")
|
||||||
|
if (isTemplateElement && !current.isViewRoot && !isPresent(current.viewportDirective)) {
|
||||||
|
throw new BaseException(`Missing directive to handle: ${current.elementDescription}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ function ariaSetterFactory(attrName:string) {
|
||||||
return setterFn;
|
return setterFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CLASS_ATTR = 'class';
|
||||||
const CLASS_PREFIX = 'class.';
|
const CLASS_PREFIX = 'class.';
|
||||||
var classSettersCache = StringMapWrapper.create();
|
var classSettersCache = StringMapWrapper.create();
|
||||||
|
|
||||||
|
@ -54,6 +55,7 @@ function classSetterFactory(className:string) {
|
||||||
return setterFn;
|
return setterFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const STYLE_ATTR = 'style';
|
||||||
const STYLE_PREFIX = 'style.';
|
const STYLE_PREFIX = 'style.';
|
||||||
var styleSettersCache = StringMapWrapper.create();
|
var styleSettersCache = StringMapWrapper.create();
|
||||||
|
|
||||||
|
@ -89,6 +91,13 @@ function roleSetter(element:Element, value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tells if an attribute is handled by the ElementBinderBuilder step
|
||||||
|
export function isSpecialProperty(propName:string) {
|
||||||
|
return StringWrapper.startsWith(propName, ARIA_PREFIX)
|
||||||
|
|| StringWrapper.startsWith(propName, CLASS_PREFIX)
|
||||||
|
|| StringWrapper.startsWith(propName, STYLE_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the ElementBinders and adds watches to the
|
* Creates the ElementBinders and adds watches to the
|
||||||
* ProtoChangeDetector.
|
* ProtoChangeDetector.
|
||||||
|
@ -115,11 +124,9 @@ function roleSetter(element:Element, value) {
|
||||||
*/
|
*/
|
||||||
export class ElementBinderBuilder extends CompileStep {
|
export class ElementBinderBuilder extends CompileStep {
|
||||||
_parser:Parser;
|
_parser:Parser;
|
||||||
_compilationUnit:any;
|
constructor(parser:Parser) {
|
||||||
constructor(parser:Parser, compilationUnit:any) {
|
|
||||||
super();
|
super();
|
||||||
this._parser = parser;
|
this._parser = parser;
|
||||||
this._compilationUnit = compilationUnit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||||
|
@ -207,7 +214,7 @@ export class ElementBinderBuilder extends CompileStep {
|
||||||
if (isBlank(bindingAst)) {
|
if (isBlank(bindingAst)) {
|
||||||
var attributeValue = MapWrapper.get(compileElement.attrs(), elProp);
|
var attributeValue = MapWrapper.get(compileElement.attrs(), elProp);
|
||||||
if (isPresent(attributeValue)) {
|
if (isPresent(attributeValue)) {
|
||||||
bindingAst = this._parser.wrapLiteralPrimitive(attributeValue, this._compilationUnit);
|
bindingAst = this._parser.wrapLiteralPrimitive(attributeValue, compileElement.elementDescription);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,4 +236,4 @@ export class ElementBinderBuilder extends CompileStep {
|
||||||
var parts = StringWrapper.split(bindConfig, RegExpWrapper.create("\\|"));
|
var parts = StringWrapper.split(bindConfig, RegExpWrapper.create("\\|"));
|
||||||
return ListWrapper.map(parts, (s) => s.trim());
|
return ListWrapper.map(parts, (s) => s.trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,11 +29,9 @@ var BIND_NAME_REGEXP = RegExpWrapper.create(
|
||||||
*/
|
*/
|
||||||
export class PropertyBindingParser extends CompileStep {
|
export class PropertyBindingParser extends CompileStep {
|
||||||
_parser:Parser;
|
_parser:Parser;
|
||||||
_compilationUnit:any;
|
constructor(parser:Parser) {
|
||||||
constructor(parser:Parser, compilationUnit:any) {
|
|
||||||
super();
|
super();
|
||||||
this._parser = parser;
|
this._parser = parser;
|
||||||
this._compilationUnit = compilationUnit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||||
|
@ -42,12 +40,13 @@ export class PropertyBindingParser extends CompileStep {
|
||||||
}
|
}
|
||||||
|
|
||||||
var attrs = current.attrs();
|
var attrs = current.attrs();
|
||||||
|
var desc = current.elementDescription;
|
||||||
MapWrapper.forEach(attrs, (attrValue, attrName) => {
|
MapWrapper.forEach(attrs, (attrValue, attrName) => {
|
||||||
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
|
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
|
||||||
if (isPresent(bindParts)) {
|
if (isPresent(bindParts)) {
|
||||||
if (isPresent(bindParts[1])) {
|
if (isPresent(bindParts[1])) {
|
||||||
// match: bind-prop
|
// match: bind-prop
|
||||||
current.addPropertyBinding(bindParts[4], this._parseBinding(attrValue));
|
current.addPropertyBinding(bindParts[4], this._parseBinding(attrValue, desc));
|
||||||
} else if (isPresent(bindParts[2]) || isPresent(bindParts[7])) {
|
} else if (isPresent(bindParts[2]) || isPresent(bindParts[7])) {
|
||||||
// match: var-name / var-name="iden" / #name / #name="iden"
|
// match: var-name / var-name="iden" / #name / #name="iden"
|
||||||
var identifier = (isPresent(bindParts[4]) && bindParts[4] !== '') ?
|
var identifier = (isPresent(bindParts[4]) && bindParts[4] !== '') ?
|
||||||
|
@ -56,16 +55,16 @@ export class PropertyBindingParser extends CompileStep {
|
||||||
current.addVariableBinding(identifier, value);
|
current.addVariableBinding(identifier, value);
|
||||||
} else if (isPresent(bindParts[3])) {
|
} else if (isPresent(bindParts[3])) {
|
||||||
// match: on-prop
|
// match: on-prop
|
||||||
current.addEventBinding(bindParts[4], this._parseAction(attrValue));
|
current.addEventBinding(bindParts[4], this._parseAction(attrValue, desc));
|
||||||
} else if (isPresent(bindParts[5])) {
|
} else if (isPresent(bindParts[5])) {
|
||||||
// match: [prop]
|
// match: [prop]
|
||||||
current.addPropertyBinding(bindParts[5], this._parseBinding(attrValue));
|
current.addPropertyBinding(bindParts[5], this._parseBinding(attrValue, desc));
|
||||||
} else if (isPresent(bindParts[6])) {
|
} else if (isPresent(bindParts[6])) {
|
||||||
// match: (prop)
|
// match: (prop)
|
||||||
current.addEventBinding(bindParts[6], this._parseBinding(attrValue));
|
current.addEventBinding(bindParts[6], this._parseBinding(attrValue, desc));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var ast = this._parseInterpolation(attrValue);
|
var ast = this._parseInterpolation(attrValue, desc);
|
||||||
if (isPresent(ast)) {
|
if (isPresent(ast)) {
|
||||||
current.addPropertyBinding(attrName, ast);
|
current.addPropertyBinding(attrName, ast);
|
||||||
}
|
}
|
||||||
|
@ -73,15 +72,15 @@ export class PropertyBindingParser extends CompileStep {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_parseInterpolation(input:string):AST {
|
_parseInterpolation(input:string, location:string):AST {
|
||||||
return this._parser.parseInterpolation(input, this._compilationUnit);
|
return this._parser.parseInterpolation(input, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
_parseBinding(input:string):AST {
|
_parseBinding(input:string, location:string):AST {
|
||||||
return this._parser.parseBinding(input, this._compilationUnit);
|
return this._parser.parseBinding(input, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
_parseAction(input:string):AST {
|
_parseAction(input:string, location:string):AST {
|
||||||
return this._parser.parseAction(input, this._compilationUnit);
|
return this._parser.parseAction(input, location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,9 @@ import {CompileControl} from './compile_control';
|
||||||
*/
|
*/
|
||||||
export class TextInterpolationParser extends CompileStep {
|
export class TextInterpolationParser extends CompileStep {
|
||||||
_parser:Parser;
|
_parser:Parser;
|
||||||
_compilationUnit:any;
|
constructor(parser:Parser) {
|
||||||
constructor(parser:Parser, compilationUnit:any) {
|
|
||||||
super();
|
super();
|
||||||
this._parser = parser;
|
this._parser = parser;
|
||||||
this._compilationUnit = compilationUnit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||||
|
@ -37,7 +35,7 @@ export class TextInterpolationParser extends CompileStep {
|
||||||
}
|
}
|
||||||
|
|
||||||
_parseTextNode(pipelineElement, node, nodeIndex) {
|
_parseTextNode(pipelineElement, node, nodeIndex) {
|
||||||
var ast = this._parser.parseInterpolation(DOM.nodeValue(node), this._compilationUnit);
|
var ast = this._parser.parseInterpolation(DOM.nodeValue(node), pipelineElement.elementDescription);
|
||||||
if (isPresent(ast)) {
|
if (isPresent(ast)) {
|
||||||
DOM.setText(node, ' ');
|
DOM.setText(node, ' ');
|
||||||
pipelineElement.addTextNodeBinding(nodeIndex, ast);
|
pipelineElement.addTextNodeBinding(nodeIndex, ast);
|
||||||
|
|
|
@ -9,6 +9,8 @@ import {CompileElement} from './compile_element';
|
||||||
import {CompileControl} from './compile_control';
|
import {CompileControl} from './compile_control';
|
||||||
import {StringWrapper} from 'angular2/src/facade/lang';
|
import {StringWrapper} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
import {$BANG} from 'angular2/src/change_detection/parser/lexer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splits views at `<template>` elements or elements with `template` attribute:
|
* Splits views at `<template>` elements or elements with `template` attribute:
|
||||||
* For `<template>` elements:
|
* For `<template>` elements:
|
||||||
|
@ -32,14 +34,35 @@ import {StringWrapper} from 'angular2/src/facade/lang';
|
||||||
*/
|
*/
|
||||||
export class ViewSplitter extends CompileStep {
|
export class ViewSplitter extends CompileStep {
|
||||||
_parser:Parser;
|
_parser:Parser;
|
||||||
_compilationUnit:any;
|
constructor(parser:Parser) {
|
||||||
constructor(parser:Parser, compilationUnit:any) {
|
|
||||||
super();
|
super();
|
||||||
this._parser = parser;
|
this._parser = parser;
|
||||||
this._compilationUnit = compilationUnit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||||
|
var attrs = current.attrs();
|
||||||
|
var templateBindings = MapWrapper.get(attrs, 'template');
|
||||||
|
var hasTemplateBinding = isPresent(templateBindings);
|
||||||
|
|
||||||
|
// look for template shortcuts such as !if="condition" and treat them as template="if condition"
|
||||||
|
MapWrapper.forEach(attrs, (attrValue, attrName) => {
|
||||||
|
if (StringWrapper.charCodeAt(attrName, 0) == $BANG) {
|
||||||
|
var key = StringWrapper.substring(attrName, 1); // remove the bang
|
||||||
|
if (hasTemplateBinding) {
|
||||||
|
// 2nd template binding detected
|
||||||
|
throw new BaseException(`Only one template directive per element is allowed: ` +
|
||||||
|
`${templateBindings} and ${key} cannot be used simultaneously ` +
|
||||||
|
`in ${current.elementDescription}`);
|
||||||
|
} else {
|
||||||
|
if (isBlank(parent)) {
|
||||||
|
throw new BaseException(`Template directives cannot be used on root components in ${current.elementDescription}`);
|
||||||
|
}
|
||||||
|
templateBindings = (attrValue.length == 0) ? key : key + ' ' + attrValue;
|
||||||
|
hasTemplateBinding = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (isBlank(parent)) {
|
if (isBlank(parent)) {
|
||||||
current.isViewRoot = true;
|
current.isViewRoot = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -49,6 +72,9 @@ export class ViewSplitter extends CompileStep {
|
||||||
var currentElement:TemplateElement = current.element;
|
var currentElement:TemplateElement = current.element;
|
||||||
var viewRootElement:TemplateElement = viewRoot.element;
|
var viewRootElement:TemplateElement = viewRoot.element;
|
||||||
this._moveChildNodes(DOM.content(currentElement), DOM.content(viewRootElement));
|
this._moveChildNodes(DOM.content(currentElement), DOM.content(viewRootElement));
|
||||||
|
// viewRoot is a doesn't appear in the original template, so we associate
|
||||||
|
// the current element description to get a more meaninful message in case of error
|
||||||
|
viewRoot.elementDescription = current.elementDescription;
|
||||||
viewRoot.isViewRoot = true;
|
viewRoot.isViewRoot = true;
|
||||||
control.addChild(viewRoot);
|
control.addChild(viewRoot);
|
||||||
}
|
}
|
||||||
|
@ -64,7 +90,8 @@ export class ViewSplitter extends CompileStep {
|
||||||
if (hasTemplateBinding) {
|
if (hasTemplateBinding) {
|
||||||
// 2nd template binding detected
|
// 2nd template binding detected
|
||||||
throw new BaseException(`Only one template directive per element is allowed: ` +
|
throw new BaseException(`Only one template directive per element is allowed: ` +
|
||||||
`${templateBindings} and ${key} cannot be used simultaneously!`);
|
`${templateBindings} and ${key} cannot be used simultaneously ` +
|
||||||
|
`in ${current.elementDescription}`);
|
||||||
} else {
|
} else {
|
||||||
templateBindings = (attrValue.length == 0) ? key : key + ' ' + attrValue;
|
templateBindings = (attrValue.length == 0) ? key : key + ' ' + attrValue;
|
||||||
hasTemplateBinding = true;
|
hasTemplateBinding = true;
|
||||||
|
@ -74,6 +101,9 @@ export class ViewSplitter extends CompileStep {
|
||||||
|
|
||||||
if (hasTemplateBinding) {
|
if (hasTemplateBinding) {
|
||||||
var newParent = new CompileElement(DOM.createTemplate(''));
|
var newParent = new CompileElement(DOM.createTemplate(''));
|
||||||
|
// newParent doesn't appear in the original template, so we associate
|
||||||
|
// the current element description to get a more meaninful message in case of error
|
||||||
|
newParent.elementDescription = current.elementDescription;
|
||||||
current.isViewRoot = true;
|
current.isViewRoot = true;
|
||||||
this._parseTemplateBindings(templateBindings, newParent);
|
this._parseTemplateBindings(templateBindings, newParent);
|
||||||
this._addParentElement(current.element, newParent.element);
|
this._addParentElement(current.element, newParent.element);
|
||||||
|
@ -99,7 +129,7 @@ export class ViewSplitter extends CompileStep {
|
||||||
}
|
}
|
||||||
|
|
||||||
_parseTemplateBindings(templateBindings:string, compileElement:CompileElement) {
|
_parseTemplateBindings(templateBindings:string, compileElement:CompileElement) {
|
||||||
var bindings = this._parser.parseTemplateBindings(templateBindings, this._compilationUnit);
|
var bindings = this._parser.parseTemplateBindings(templateBindings, compileElement.elementDescription);
|
||||||
for (var i=0; i<bindings.length; i++) {
|
for (var i=0; i<bindings.length; i++) {
|
||||||
var binding = bindings[i];
|
var binding = bindings[i];
|
||||||
if (binding.keyIsVar) {
|
if (binding.keyIsVar) {
|
||||||
|
|
|
@ -114,13 +114,15 @@ export class SelectorMatcher {
|
||||||
/**
|
/**
|
||||||
* Add an object that can be found later on by calling `match`.
|
* Add an object that can be found later on by calling `match`.
|
||||||
* @param cssSelector A css selector
|
* @param cssSelector A css selector
|
||||||
* @param selectable An opaque object that will be given to the callback of the `match` function
|
* @param callbackCtxt An opaque object that will be given to the callback of the `match` function
|
||||||
*/
|
*/
|
||||||
addSelectable(cssSelector:CssSelector, selectable) {
|
addSelectable(cssSelector:CssSelector, callbackCtxt) {
|
||||||
var matcher = this;
|
var matcher = this;
|
||||||
var element = cssSelector.element;
|
var element = cssSelector.element;
|
||||||
var classNames = cssSelector.classNames;
|
var classNames = cssSelector.classNames;
|
||||||
var attrs = cssSelector.attrs;
|
var attrs = cssSelector.attrs;
|
||||||
|
var selectable = new SelectorContext(cssSelector, callbackCtxt);
|
||||||
|
|
||||||
|
|
||||||
if (isPresent(element)) {
|
if (isPresent(element)) {
|
||||||
var isTerminal = attrs.length === 0 && classNames.length === 0;
|
var isTerminal = attrs.length === 0 && classNames.length === 0;
|
||||||
|
@ -228,8 +230,10 @@ export class SelectorMatcher {
|
||||||
if (isBlank(selectables)) {
|
if (isBlank(selectables)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var selectable;
|
||||||
for (var index=0; index<selectables.length; index++) {
|
for (var index=0; index<selectables.length; index++) {
|
||||||
matchedCallback(selectables[index]);
|
selectable = selectables[index];
|
||||||
|
matchedCallback(selectable.selector, selectable.cbContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,3 +251,15 @@ export class SelectorMatcher {
|
||||||
nestedSelector.match(cssSelector, matchedCallback);
|
nestedSelector.match(cssSelector, matchedCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Store context to pass back selector and context when a selector is matched
|
||||||
|
class SelectorContext {
|
||||||
|
selector:CssSelector;
|
||||||
|
cbContext; // callback context
|
||||||
|
|
||||||
|
constructor(selector:CssSelector, cbContext) {
|
||||||
|
this.selector = selector;
|
||||||
|
this.cbContext = cbContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'angular2/test_lib';
|
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'angular2/test_lib';
|
||||||
|
|
||||||
import {DOM} from 'angular2/src/facade/dom';
|
import {DOM} from 'angular2/src/facade/dom';
|
||||||
|
import {Type, isPresent, BaseException} from 'angular2/src/facade/lang';
|
||||||
|
import {assertionsEnabled, isJsObject} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
import {Injector} from 'angular2/di';
|
import {Injector} from 'angular2/di';
|
||||||
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection,
|
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection,
|
||||||
|
@ -50,7 +52,6 @@ export function main() {
|
||||||
|
|
||||||
it('should consume text node changes', (done) => {
|
it('should consume text node changes', (done) => {
|
||||||
tplResolver.setTemplate(MyComp, new Template({inline: '<div>{{ctxProp}}</div>'}));
|
tplResolver.setTemplate(MyComp, new Template({inline: '<div>{{ctxProp}}</div>'}));
|
||||||
|
|
||||||
compiler.compile(MyComp).then((pv) => {
|
compiler.compile(MyComp).then((pv) => {
|
||||||
createView(pv);
|
createView(pv);
|
||||||
ctx.ctxProp = 'Hello World!';
|
ctx.ctxProp = 'Hello World!';
|
||||||
|
@ -365,6 +366,60 @@ export function main() {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO support these tests with DART e.g. with Promise.catch (JS) transpiled to Future.catchError (DART)
|
||||||
|
if (assertionsEnabled() && isJsObject({})) {
|
||||||
|
|
||||||
|
function expectCompileError(inlineTpl, errMessage, done) {
|
||||||
|
tplResolver.setTemplate(MyComp, new Template({inline: inlineTpl}));
|
||||||
|
compiler.compile(MyComp).then(() => {
|
||||||
|
throw new BaseException("Test failure: should not have come here as an exception was expected");
|
||||||
|
},(err) => {
|
||||||
|
expect(err.message).toBe(errMessage);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should raise an error if no directive is registered for an unsupported DOM property', (done) => {
|
||||||
|
expectCompileError(
|
||||||
|
'<div [some-prop]="foo"></div>',
|
||||||
|
'Missing directive to handle \'some-prop\' in MyComp: <div [some-prop]="foo">',
|
||||||
|
done
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should raise an error if no directive is registered for a template with template bindings', (done) => {
|
||||||
|
expectCompileError(
|
||||||
|
'<div><div template="if: foo"></div></div>',
|
||||||
|
'Missing directive to handle \'if\' in <div template="if: foo">',
|
||||||
|
done
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should raise an error for missing template directive (1)', (done) => {
|
||||||
|
expectCompileError(
|
||||||
|
'<div><template foo></template></div>',
|
||||||
|
'Missing directive to handle: <template foo>',
|
||||||
|
done
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should raise an error for missing template directive (2)', (done) => {
|
||||||
|
expectCompileError(
|
||||||
|
'<div><template *if="condition"></template></div>',
|
||||||
|
'Missing directive to handle: <template *if="condition">',
|
||||||
|
done
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should raise an error for missing template directive (3)', (done) => {
|
||||||
|
expectCompileError(
|
||||||
|
'<div *if="condition"></div>',
|
||||||
|
'Missing directive to handle \'if\' in MyComp: <div *if="condition">',
|
||||||
|
done
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,6 +528,19 @@ class CompWithAncestor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: '[child-cmp2]',
|
||||||
|
componentServices: [MyService]
|
||||||
|
})
|
||||||
|
class ChildComp2 {
|
||||||
|
ctxProp:string;
|
||||||
|
dirProp:string;
|
||||||
|
constructor(service: MyService) {
|
||||||
|
this.ctxProp = service.greeting;
|
||||||
|
this.dirProp = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Viewport({
|
@Viewport({
|
||||||
selector: '[some-viewport]'
|
selector: '[some-viewport]'
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'angular2/test_lib';
|
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'angular2/test_lib';
|
||||||
import {isPresent} from 'angular2/src/facade/lang';
|
import {isPresent, assertionsEnabled} from 'angular2/src/facade/lang';
|
||||||
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {DirectiveParser} from 'angular2/src/core/compiler/pipeline/directive_parser';
|
import {DirectiveParser} from 'angular2/src/core/compiler/pipeline/directive_parser';
|
||||||
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
|
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
|
||||||
|
@ -85,20 +85,20 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow multiple component directives on the same element', () => {
|
it('should not allow multiple component directives on the same element', () => {
|
||||||
expect( () => {
|
expect( () => {
|
||||||
createPipeline().process(
|
createPipeline().process(
|
||||||
el('<div some-comp some-comp2></div>')
|
el('<div some-comp some-comp2></div>')
|
||||||
);
|
);
|
||||||
}).toThrowError('Only one component directive per element is allowed!');
|
}).toThrowError('Multiple component directives not allowed on the same element - check <div some-comp some-comp2>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow component directives on <template> elements', () => {
|
it('should not allow component directives on <template> elements', () => {
|
||||||
expect( () => {
|
expect( () => {
|
||||||
createPipeline().process(
|
createPipeline().process(
|
||||||
el('<template some-comp></template>')
|
el('<template some-comp></template>')
|
||||||
);
|
);
|
||||||
}).toThrowError('Only template directives are allowed on <template> elements!');
|
}).toThrowError('Only template directives are allowed on template elements - check <template some-comp>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('viewport directives', () => {
|
describe('viewport directives', () => {
|
||||||
|
@ -128,7 +128,7 @@ export function main() {
|
||||||
createPipeline().process(
|
createPipeline().process(
|
||||||
el('<template some-templ some-templ2></template>')
|
el('<template some-templ some-templ2></template>')
|
||||||
);
|
);
|
||||||
}).toThrowError('Only one template directive per element is allowed!');
|
}).toThrowError('Only one viewport directive can be used per element - check <template some-templ some-templ2>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow viewport directives on non <template> elements', () => {
|
it('should not allow viewport directives on non <template> elements', () => {
|
||||||
|
@ -136,7 +136,8 @@ export function main() {
|
||||||
createPipeline().process(
|
createPipeline().process(
|
||||||
el('<div some-templ></div>')
|
el('<div some-templ></div>')
|
||||||
);
|
);
|
||||||
}).toThrowError('Viewport directives need to be placed on <template> elements or elements with template attribute!');
|
|
||||||
|
}).toThrowError('Viewport directives need to be placed on <template> elements or elements with template attribute - check <div some-templ>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -172,14 +173,6 @@ export function main() {
|
||||||
expect(results[0].decoratorDirectives).toEqual([reader.read(SomeDecorator)]);
|
expect(results[0].decoratorDirectives).toEqual([reader.read(SomeDecorator)]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow decorator directives on <template> elements', () => {
|
|
||||||
expect( () => {
|
|
||||||
createPipeline().process(
|
|
||||||
el('<template some-decor></template>')
|
|
||||||
);
|
|
||||||
}).toThrowError('Only template directives are allowed on <template> elements!');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not instantiate decorator directive twice', () => {
|
it('should not instantiate decorator directive twice', () => {
|
||||||
var pipeline = createPipeline({propertyBindings: {
|
var pipeline = createPipeline({propertyBindings: {
|
||||||
'some-decor-with-binding': 'someExpr'
|
'some-decor-with-binding': 'someExpr'
|
||||||
|
|
|
@ -76,7 +76,7 @@ export function main() {
|
||||||
} else if (isPresent(parent)) {
|
} else if (isPresent(parent)) {
|
||||||
current.inheritedProtoView = parent.inheritedProtoView;
|
current.inheritedProtoView = parent.inheritedProtoView;
|
||||||
}
|
}
|
||||||
}), new ElementBinderBuilder(parser, null)
|
}), new ElementBinderBuilder(parser)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ export function main() {
|
||||||
function createPipeline(ignoreBindings = false) {
|
function createPipeline(ignoreBindings = false) {
|
||||||
return new CompilePipeline([
|
return new CompilePipeline([
|
||||||
new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }),
|
new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }),
|
||||||
new PropertyBindingParser(new Parser(new Lexer()), null)]);
|
new PropertyBindingParser(new Parser(new Lexer()))]);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should not parse bindings when ignoreBindings is true', () => {
|
it('should not parse bindings when ignoreBindings is true', () => {
|
||||||
|
|
|
@ -14,7 +14,7 @@ export function main() {
|
||||||
return new CompilePipeline([
|
return new CompilePipeline([
|
||||||
new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }),
|
new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }),
|
||||||
new IgnoreChildrenStep(),
|
new IgnoreChildrenStep(),
|
||||||
new TextInterpolationParser(new Parser(new Lexer()), null)
|
new TextInterpolationParser(new Parser(new Lexer()))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ export function main() {
|
||||||
describe('ViewSplitter', () => {
|
describe('ViewSplitter', () => {
|
||||||
|
|
||||||
function createPipeline() {
|
function createPipeline() {
|
||||||
return new CompilePipeline([new ViewSplitter(new Parser(new Lexer()), null)]);
|
return new CompilePipeline([new ViewSplitter(new Parser(new Lexer()))]);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should mark root elements as viewRoot', () => {
|
it('should mark root elements as viewRoot', () => {
|
||||||
|
@ -160,14 +160,14 @@ export function main() {
|
||||||
expect( () => {
|
expect( () => {
|
||||||
var rootElement = el('<div><div *foo *bar="blah"></div></div>');
|
var rootElement = el('<div><div *foo *bar="blah"></div></div>');
|
||||||
createPipeline().process(rootElement);
|
createPipeline().process(rootElement);
|
||||||
}).toThrowError('Only one template directive per element is allowed: foo and bar cannot be used simultaneously!');
|
}).toThrowError('Only one template directive per element is allowed: foo and bar cannot be used simultaneously in <div *foo *bar="blah">');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow template and bang directives on the same element', () => {
|
it('should not allow template and star directives on the same element', () => {
|
||||||
expect( () => {
|
expect( () => {
|
||||||
var rootElement = el('<div><div *foo template="blah"></div></div>');
|
var rootElement = el('<div><div *foo template="bar"></div></div>');
|
||||||
createPipeline().process(rootElement);
|
createPipeline().process(rootElement);
|
||||||
}).toThrowError('Only one template directive per element is allowed: blah and foo cannot be used simultaneously!');
|
}).toThrowError('Only one template directive per element is allowed: bar and foo cannot be used simultaneously in <div *foo template="bar">');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('SelectorMatcher', () => {
|
describe('SelectorMatcher', () => {
|
||||||
var matcher, matched, selectableCollector;
|
var matcher, matched, selectableCollector, s1, s2, s3, s4;
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
matched = ListWrapper.create();
|
matched = ListWrapper.create();
|
||||||
|
@ -13,79 +13,81 @@ export function main() {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
reset();
|
reset();
|
||||||
selectableCollector = (selectable) => {
|
s1 = s2 = s3 = s4 = null;
|
||||||
ListWrapper.push(matched, selectable);
|
selectableCollector = (selector, context) => {
|
||||||
|
ListWrapper.push(matched, selector);
|
||||||
|
ListWrapper.push(matched, context);
|
||||||
}
|
}
|
||||||
matcher = new SelectorMatcher();
|
matcher = new SelectorMatcher();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select by element name case insensitive', () => {
|
it('should select by element name case insensitive', () => {
|
||||||
matcher.addSelectable(CssSelector.parse('someTag'), 1);
|
matcher.addSelectable(s1 = CssSelector.parse('someTag'), 1);
|
||||||
|
|
||||||
matcher.match(CssSelector.parse('SOMEOTHERTAG'), selectableCollector);
|
matcher.match(CssSelector.parse('SOMEOTHERTAG'), selectableCollector);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
matcher.match(CssSelector.parse('SOMETAG'), selectableCollector);
|
matcher.match(CssSelector.parse('SOMETAG'), selectableCollector);
|
||||||
expect(matched).toEqual([1]);
|
expect(matched).toEqual([s1,1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select by class name case insensitive', () => {
|
it('should select by class name case insensitive', () => {
|
||||||
matcher.addSelectable(CssSelector.parse('.someClass'), 1);
|
matcher.addSelectable(s1 = CssSelector.parse('.someClass'), 1);
|
||||||
matcher.addSelectable(CssSelector.parse('.someClass.class2'), 2);
|
matcher.addSelectable(s2 = CssSelector.parse('.someClass.class2'), 2);
|
||||||
|
|
||||||
matcher.match(CssSelector.parse('.SOMEOTHERCLASS'), selectableCollector);
|
matcher.match(CssSelector.parse('.SOMEOTHERCLASS'), selectableCollector);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
matcher.match(CssSelector.parse('.SOMECLASS'), selectableCollector);
|
matcher.match(CssSelector.parse('.SOMECLASS'), selectableCollector);
|
||||||
expect(matched).toEqual([1]);
|
expect(matched).toEqual([s1,1]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
matcher.match(CssSelector.parse('.someClass.class2'), selectableCollector);
|
matcher.match(CssSelector.parse('.someClass.class2'), selectableCollector);
|
||||||
expect(matched).toEqual([1,2]);
|
expect(matched).toEqual([s1,1,s2,2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select by attr name case insensitive independent of the value', () => {
|
it('should select by attr name case insensitive independent of the value', () => {
|
||||||
matcher.addSelectable(CssSelector.parse('[someAttr]'), 1);
|
matcher.addSelectable(s1 = CssSelector.parse('[someAttr]'), 1);
|
||||||
matcher.addSelectable(CssSelector.parse('[someAttr][someAttr2]'), 2);
|
matcher.addSelectable(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
|
||||||
|
|
||||||
matcher.match(CssSelector.parse('[SOMEOTHERATTR]'), selectableCollector);
|
matcher.match(CssSelector.parse('[SOMEOTHERATTR]'), selectableCollector);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
matcher.match(CssSelector.parse('[SOMEATTR]'), selectableCollector);
|
matcher.match(CssSelector.parse('[SOMEATTR]'), selectableCollector);
|
||||||
expect(matched).toEqual([1]);
|
expect(matched).toEqual([s1,1]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
matcher.match(CssSelector.parse('[SOMEATTR=someValue]'), selectableCollector);
|
matcher.match(CssSelector.parse('[SOMEATTR=someValue]'), selectableCollector);
|
||||||
expect(matched).toEqual([1]);
|
expect(matched).toEqual([s1,1]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
matcher.match(CssSelector.parse('[someAttr][someAttr2]'), selectableCollector);
|
matcher.match(CssSelector.parse('[someAttr][someAttr2]'), selectableCollector);
|
||||||
expect(matched).toEqual([1,2]);
|
expect(matched).toEqual([s1,1,s2,2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select by attr name only once if the value is from the DOM', () => {
|
it('should select by attr name only once if the value is from the DOM', () => {
|
||||||
matcher.addSelectable(CssSelector.parse('[some-decor]'), 1);
|
matcher.addSelectable(s1 = CssSelector.parse('[some-decor]'), 1);
|
||||||
|
|
||||||
var elementSelector = new CssSelector();
|
var elementSelector = new CssSelector();
|
||||||
var element = el('<div attr></div>');
|
var element = el('<div attr></div>');
|
||||||
var empty = element.getAttribute('attr');
|
var empty = element.getAttribute('attr');
|
||||||
elementSelector.addAttribute('some-decor', empty);
|
elementSelector.addAttribute('some-decor', empty);
|
||||||
matcher.match(elementSelector, selectableCollector);
|
matcher.match(elementSelector, selectableCollector);
|
||||||
expect(matched).toEqual([1]);
|
expect(matched).toEqual([s1,1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select by attr name and value case insensitive', () => {
|
it('should select by attr name and value case insensitive', () => {
|
||||||
matcher.addSelectable(CssSelector.parse('[someAttr=someValue]'), 1);
|
matcher.addSelectable(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
|
||||||
|
|
||||||
matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]'), selectableCollector);
|
matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]'), selectableCollector);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]'), selectableCollector);
|
matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]'), selectableCollector);
|
||||||
expect(matched).toEqual([1]);
|
expect(matched).toEqual([s1,1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select by element name, class name and attribute name with value', () => {
|
it('should select by element name, class name and attribute name with value', () => {
|
||||||
matcher.addSelectable(CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
|
matcher.addSelectable(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
|
||||||
|
|
||||||
matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]'), selectableCollector);
|
matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]'), selectableCollector);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
@ -100,29 +102,29 @@ export function main() {
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]'), selectableCollector);
|
matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]'), selectableCollector);
|
||||||
expect(matched).toEqual([1]);
|
expect(matched).toEqual([s1,1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select independent of the order in the css selector', () => {
|
it('should select independent of the order in the css selector', () => {
|
||||||
matcher.addSelectable(CssSelector.parse('[someAttr].someClass'), 1);
|
matcher.addSelectable(s1 = CssSelector.parse('[someAttr].someClass'), 1);
|
||||||
matcher.addSelectable(CssSelector.parse('.someClass[someAttr]'), 2);
|
matcher.addSelectable(s2 = CssSelector.parse('.someClass[someAttr]'), 2);
|
||||||
matcher.addSelectable(CssSelector.parse('.class1.class2'), 3);
|
matcher.addSelectable(s3 = CssSelector.parse('.class1.class2'), 3);
|
||||||
matcher.addSelectable(CssSelector.parse('.class2.class1'), 4);
|
matcher.addSelectable(s4 = CssSelector.parse('.class2.class1'), 4);
|
||||||
|
|
||||||
matcher.match(CssSelector.parse('[someAttr].someClass'), selectableCollector);
|
matcher.match(CssSelector.parse('[someAttr].someClass'), selectableCollector);
|
||||||
expect(matched).toEqual([1,2]);
|
expect(matched).toEqual([s1,1,s2,2]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
matcher.match(CssSelector.parse('.someClass[someAttr]'), selectableCollector);
|
matcher.match(CssSelector.parse('.someClass[someAttr]'), selectableCollector);
|
||||||
expect(matched).toEqual([1,2]);
|
expect(matched).toEqual([s1,1,s2,2]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
matcher.match(CssSelector.parse('.class1.class2'), selectableCollector);
|
matcher.match(CssSelector.parse('.class1.class2'), selectableCollector);
|
||||||
expect(matched).toEqual([3,4]);
|
expect(matched).toEqual([s3,3,s4,4]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
matcher.match(CssSelector.parse('.class2.class1'), selectableCollector);
|
matcher.match(CssSelector.parse('.class2.class1'), selectableCollector);
|
||||||
expect(matched).toEqual([4,3]);
|
expect(matched).toEqual([s4,4,s3,3]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ export function main() {
|
||||||
function match() {
|
function match() {
|
||||||
var matchCount = 0;
|
var matchCount = 0;
|
||||||
for (var i=0; i<count; i++) {
|
for (var i=0; i<count; i++) {
|
||||||
fixedMatcher.match(fixedSelectors[i], (selected) => {
|
fixedMatcher.match(fixedSelectors[i], (selector, selected) => {
|
||||||
matchCount += selected;
|
matchCount += selected;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue