2015-03-23 17:10:55 -04:00
|
|
|
import {describe, beforeEach, it, xit, expect, iit, ddescribe, el} from 'angular2/test_lib';
|
2015-05-18 14:57:20 -04:00
|
|
|
import {isPresent, isBlank, assertionsEnabled, IMPLEMENTS} from 'angular2/src/facade/lang';
|
2015-03-23 17:10:55 -04:00
|
|
|
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
2015-05-01 07:41:56 -04:00
|
|
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
2015-03-23 17:10:55 -04:00
|
|
|
import {DirectiveParser} from 'angular2/src/render/dom/compiler/directive_parser';
|
|
|
|
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
|
|
|
|
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step';
|
|
|
|
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
|
|
|
|
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
|
2015-04-09 15:20:11 -04:00
|
|
|
import {ViewDefinition, DirectiveMetadata} from 'angular2/src/render/api';
|
2015-07-11 11:26:48 -04:00
|
|
|
import {Lexer, Parser} from 'angular2/src/change_detection/change_detection';
|
2015-05-26 12:25:39 -04:00
|
|
|
import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
|
2015-03-23 17:10:55 -04:00
|
|
|
|
|
|
|
export function main() {
|
|
|
|
describe('DirectiveParser', () => {
|
|
|
|
var parser, annotatedDirectives;
|
|
|
|
|
2015-05-26 12:25:39 -04:00
|
|
|
beforeEach(() => {
|
2015-03-23 17:10:55 -04:00
|
|
|
annotatedDirectives = [
|
|
|
|
someComponent,
|
|
|
|
someComponent2,
|
2015-04-30 16:38:40 -04:00
|
|
|
someDirective,
|
|
|
|
someDirectiveIgnoringChildren,
|
2015-04-27 18:14:30 -04:00
|
|
|
decoratorWithMultipleAttrs,
|
2015-04-30 16:38:40 -04:00
|
|
|
someDirectiveWithProps,
|
|
|
|
someDirectiveWithHostProperties,
|
2015-06-22 11:21:03 -04:00
|
|
|
someDirectiveWithInvalidHostProperties,
|
2015-05-01 07:41:56 -04:00
|
|
|
someDirectiveWithHostAttributes,
|
2015-04-30 16:38:40 -04:00
|
|
|
someDirectiveWithEvents,
|
2015-06-18 18:44:44 -04:00
|
|
|
someDirectiveWithGlobalEvents
|
2015-03-23 17:10:55 -04:00
|
|
|
];
|
|
|
|
parser = new Parser(new Lexer());
|
|
|
|
});
|
|
|
|
|
2015-04-27 18:14:30 -04:00
|
|
|
function createPipeline(propertyBindings = null, directives = null) {
|
|
|
|
if (isBlank(directives)) directives = annotatedDirectives;
|
|
|
|
|
2015-03-23 17:10:55 -04:00
|
|
|
return new CompilePipeline([
|
2015-06-03 16:42:57 -04:00
|
|
|
new MockStep((parent, current, control) => {
|
|
|
|
if (isPresent(propertyBindings)) {
|
|
|
|
StringMapWrapper.forEach(propertyBindings, (ast, name) => {
|
|
|
|
current.bindElement().bindProperty(name, ast);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}),
|
2015-04-27 18:14:30 -04:00
|
|
|
new DirectiveParser(parser, directives)
|
2015-03-23 17:10:55 -04:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2015-05-26 12:25:39 -04:00
|
|
|
function process(el, propertyBindings = null, directives = null): List<ElementBinderBuilder> {
|
2015-04-27 18:14:30 -04:00
|
|
|
var pipeline = createPipeline(propertyBindings, directives);
|
2015-05-26 12:25:39 -04:00
|
|
|
return ListWrapper.map(pipeline.process(el), (ce) => ce.inheritedElementBinder);
|
2015-03-23 17:10:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
it('should not add directives if they are not used', () => {
|
|
|
|
var results = process(el('<div></div>'));
|
|
|
|
expect(results[0]).toBe(null);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should detect directives in attributes', () => {
|
|
|
|
var results = process(el('<div some-decor></div>'));
|
2015-05-26 12:25:39 -04:00
|
|
|
expect(results[0].directives[0].directiveIndex)
|
|
|
|
.toBe(annotatedDirectives.indexOf(someDirective));
|
2015-03-23 17:10:55 -04:00
|
|
|
});
|
|
|
|
|
2015-03-19 18:38:48 -04:00
|
|
|
it('should detect directives with multiple attributes', () => {
|
|
|
|
var results = process(el('<input type=text control=one></input>'));
|
2015-05-26 12:25:39 -04:00
|
|
|
expect(results[0].directives[0].directiveIndex)
|
|
|
|
.toBe(annotatedDirectives.indexOf(decoratorWithMultipleAttrs));
|
2015-03-19 18:38:48 -04:00
|
|
|
});
|
|
|
|
|
2015-03-23 17:10:55 -04:00
|
|
|
it('should compile children by default', () => {
|
|
|
|
var results = createPipeline().process(el('<div some-decor></div>'));
|
|
|
|
expect(results[0].compileChildren).toEqual(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should stop compiling children when specified in the directive config', () => {
|
|
|
|
var results = createPipeline().process(el('<div some-decor-ignoring-children></div>'));
|
|
|
|
expect(results[0].compileChildren).toEqual(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bind directive properties from bound properties', () => {
|
2015-05-26 12:25:39 -04:00
|
|
|
var results = process(el('<div some-decor-props></div>'),
|
|
|
|
{'elProp': parser.parseBinding('someExpr', '')});
|
2015-03-23 17:10:55 -04:00
|
|
|
var directiveBinding = results[0].directives[0];
|
2015-06-17 19:21:40 -04:00
|
|
|
expect(directiveBinding.propertyBindings.get('dirProp').source).toEqual('someExpr');
|
2015-03-23 17:10:55 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should bind directive properties from attribute values', () => {
|
2015-05-26 12:25:39 -04:00
|
|
|
var results = process(el('<div some-decor-props el-prop="someValue"></div>'));
|
2015-03-23 17:10:55 -04:00
|
|
|
var directiveBinding = results[0].directives[0];
|
2015-06-17 19:21:40 -04:00
|
|
|
var simpleProp = directiveBinding.propertyBindings.get('dirProp');
|
2015-03-23 17:10:55 -04:00
|
|
|
expect(simpleProp.source).toEqual('someValue');
|
|
|
|
});
|
|
|
|
|
2015-04-21 14:47:53 -04:00
|
|
|
it('should bind host directive properties', () => {
|
|
|
|
var element = el('<input some-decor-with-host-props>');
|
2015-03-23 17:10:55 -04:00
|
|
|
var results = process(element);
|
2015-04-21 14:47:53 -04:00
|
|
|
|
|
|
|
var directiveBinding = results[0].directives[0];
|
|
|
|
|
2015-06-17 19:21:40 -04:00
|
|
|
var ast = directiveBinding.hostPropertyBindings.get('hostProp');
|
2015-04-21 14:47:53 -04:00
|
|
|
expect(ast.source).toEqual('dirProp');
|
2015-03-23 17:10:55 -04:00
|
|
|
});
|
|
|
|
|
2015-06-22 11:21:03 -04:00
|
|
|
it('should throw when parsing invalid host properties', () => {
|
|
|
|
expect(() => process(el('<input some-decor-with-invalid-host-props>')))
|
|
|
|
.toThrowError(
|
|
|
|
new RegExp('Simple binding expression can only contain field access and constants'));
|
|
|
|
});
|
|
|
|
|
2015-05-01 07:41:56 -04:00
|
|
|
it('should set host element attributes', () => {
|
|
|
|
var element = el('<input some-decor-with-host-attrs>');
|
|
|
|
var results = process(element);
|
|
|
|
|
|
|
|
expect(DOM.getAttribute(results[0].element, 'attr_name')).toEqual('attr_val');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not set host element attribute if an attribute already exists', () => {
|
|
|
|
var element = el('<input attr_name="initial" some-decor-with-host-attrs>');
|
|
|
|
var results = process(element);
|
|
|
|
|
|
|
|
expect(DOM.getAttribute(results[0].element, 'attr_name')).toEqual('initial');
|
|
|
|
|
|
|
|
DOM.removeAttribute(element, 'attr_name');
|
|
|
|
results = process(element);
|
|
|
|
expect(DOM.getAttribute(results[0].element, 'attr_name')).toEqual('attr_val');
|
|
|
|
});
|
|
|
|
|
2015-05-12 10:46:40 -04:00
|
|
|
it('should add CSS classes if "class" specified in host element attributes', () => {
|
|
|
|
var element = el('<input class="foo baz" some-decor-with-host-attrs>');
|
|
|
|
var results = process(element);
|
|
|
|
|
|
|
|
expect(DOM.hasClass(results[0].element, 'foo')).toBeTruthy();
|
|
|
|
expect(DOM.hasClass(results[0].element, 'bar')).toBeTruthy();
|
|
|
|
expect(DOM.hasClass(results[0].element, 'baz')).toBeTruthy();
|
|
|
|
});
|
|
|
|
|
2015-04-02 17:40:49 -04:00
|
|
|
it('should read attribute values', () => {
|
|
|
|
var element = el('<input some-decor-props some-attr="someValue">');
|
|
|
|
var results = process(element);
|
2015-06-17 19:21:40 -04:00
|
|
|
expect(results[0].readAttributes.get('some-attr')).toEqual('someValue');
|
2015-04-02 17:40:49 -04:00
|
|
|
});
|
|
|
|
|
2015-03-23 17:10:55 -04:00
|
|
|
it('should bind directive events', () => {
|
2015-05-26 12:25:39 -04:00
|
|
|
var results = process(el('<div some-decor-events></div>'));
|
2015-03-23 17:10:55 -04:00
|
|
|
var directiveBinding = results[0].directives[0];
|
2015-04-02 09:56:58 -04:00
|
|
|
expect(directiveBinding.eventBindings.length).toEqual(1);
|
|
|
|
var eventBinding = directiveBinding.eventBindings[0];
|
|
|
|
expect(eventBinding.fullName).toEqual('click');
|
|
|
|
expect(eventBinding.source.source).toEqual('doIt()');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bind directive global events', () => {
|
2015-05-26 12:25:39 -04:00
|
|
|
var results = process(el('<div some-decor-globalevents></div>'));
|
2015-04-02 09:56:58 -04:00
|
|
|
var directiveBinding = results[0].directives[0];
|
|
|
|
expect(directiveBinding.eventBindings.length).toEqual(1);
|
|
|
|
var eventBinding = directiveBinding.eventBindings[0];
|
|
|
|
expect(eventBinding.fullName).toEqual('window:resize');
|
|
|
|
expect(eventBinding.source.source).toEqual('doItGlobal()');
|
2015-03-23 17:10:55 -04:00
|
|
|
});
|
|
|
|
|
2015-05-26 12:25:39 -04:00
|
|
|
// TODO: assertions should be enabled when running tests:
|
|
|
|
// https://github.com/angular/angular/issues/1340
|
2015-03-23 17:10:55 -04:00
|
|
|
describe('component directives', () => {
|
|
|
|
it('should save the component id', () => {
|
2015-05-26 12:25:39 -04:00
|
|
|
var results = process(el('<some-comp></some-comp>'));
|
2015-03-23 17:10:55 -04:00
|
|
|
expect(results[0].componentId).toEqual('someComponent');
|
|
|
|
});
|
|
|
|
|
2015-04-27 18:14:30 -04:00
|
|
|
it('should throw when the provided selector is not an element selector', () => {
|
2015-05-26 12:25:39 -04:00
|
|
|
expect(() => { createPipeline(null, [componentWithNonElementSelector]); })
|
|
|
|
.toThrowError(
|
|
|
|
`Component 'componentWithNonElementSelector' can only have an element selector, but had '[attr]'`);
|
2015-03-23 17:10:55 -04:00
|
|
|
});
|
|
|
|
|
2015-04-27 18:14:30 -04:00
|
|
|
it('should not allow multiple component directives on the same element', () => {
|
2015-05-26 12:25:39 -04:00
|
|
|
expect(() => {
|
|
|
|
process(el('<some-comp></some-comp>'), null, [someComponent, someComponentDup]);
|
|
|
|
}).toThrowError(new RegExp('Only one component directive is allowed per element'));
|
2015-04-27 18:14:30 -04:00
|
|
|
});
|
2015-05-11 20:59:39 -04:00
|
|
|
|
|
|
|
it('should sort the directives and store the component as the first directive', () => {
|
2015-05-26 12:25:39 -04:00
|
|
|
var results = process(el('<some-comp some-decor></some-comp>'));
|
|
|
|
expect(annotatedDirectives[results[0].directives[0].directiveIndex].id)
|
|
|
|
.toEqual('someComponent');
|
|
|
|
expect(annotatedDirectives[results[0].directives[1].directiveIndex].id)
|
|
|
|
.toEqual('someDirective');
|
2015-05-11 20:59:39 -04:00
|
|
|
});
|
2015-03-23 17:10:55 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-05-26 12:25:39 -04:00
|
|
|
class MockStep implements CompileStep {
|
|
|
|
processClosure: Function;
|
|
|
|
constructor(process) { this.processClosure = process; }
|
|
|
|
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
|
2015-03-23 17:10:55 -04:00
|
|
|
this.processClosure(parent, current, control);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-09 06:33:40 -04:00
|
|
|
var someComponent = DirectiveMetadata.create(
|
2015-05-26 12:25:39 -04:00
|
|
|
{selector: 'some-comp', id: 'someComponent', type: DirectiveMetadata.COMPONENT_TYPE});
|
2015-03-23 17:10:55 -04:00
|
|
|
|
2015-06-09 06:33:40 -04:00
|
|
|
var someComponentDup = DirectiveMetadata.create(
|
2015-05-26 12:25:39 -04:00
|
|
|
{selector: 'some-comp', id: 'someComponentDup', type: DirectiveMetadata.COMPONENT_TYPE});
|
2015-03-23 17:10:55 -04:00
|
|
|
|
2015-06-09 06:33:40 -04:00
|
|
|
var someComponent2 = DirectiveMetadata.create(
|
2015-05-26 12:25:39 -04:00
|
|
|
{selector: 'some-comp2', id: 'someComponent2', type: DirectiveMetadata.COMPONENT_TYPE});
|
2015-03-19 18:38:48 -04:00
|
|
|
|
2015-06-09 06:33:40 -04:00
|
|
|
var someDirective = DirectiveMetadata.create(
|
2015-05-26 12:25:39 -04:00
|
|
|
{selector: '[some-decor]', id: 'someDirective', type: DirectiveMetadata.DIRECTIVE_TYPE});
|
2015-03-23 17:10:55 -04:00
|
|
|
|
2015-06-09 06:33:40 -04:00
|
|
|
var someDirectiveIgnoringChildren = DirectiveMetadata.create({
|
2015-03-23 17:10:55 -04:00
|
|
|
selector: '[some-decor-ignoring-children]',
|
2015-04-27 18:14:30 -04:00
|
|
|
compileChildren: false,
|
2015-04-30 16:38:40 -04:00
|
|
|
type: DirectiveMetadata.DIRECTIVE_TYPE
|
2015-04-27 18:14:30 -04:00
|
|
|
|
|
|
|
});
|
|
|
|
|
2015-06-09 06:33:40 -04:00
|
|
|
var decoratorWithMultipleAttrs = DirectiveMetadata.create({
|
2015-04-27 18:14:30 -04:00
|
|
|
selector: 'input[type=text][control]',
|
|
|
|
id: 'decoratorWithMultipleAttrs',
|
2015-04-30 16:38:40 -04:00
|
|
|
type: DirectiveMetadata.DIRECTIVE_TYPE
|
2015-03-23 17:10:55 -04:00
|
|
|
});
|
|
|
|
|
2015-06-09 06:33:40 -04:00
|
|
|
var someDirectiveWithProps = DirectiveMetadata.create({
|
2015-03-23 17:10:55 -04:00
|
|
|
selector: '[some-decor-props]',
|
2015-06-18 18:40:12 -04:00
|
|
|
properties: ['dirProp: elProp'],
|
2015-04-02 17:40:49 -04:00
|
|
|
readAttributes: ['some-attr']
|
2015-03-23 17:10:55 -04:00
|
|
|
});
|
|
|
|
|
2015-06-09 06:33:40 -04:00
|
|
|
var someDirectiveWithHostProperties = DirectiveMetadata.create({
|
2015-04-21 14:47:53 -04:00
|
|
|
selector: '[some-decor-with-host-props]',
|
2015-07-07 23:03:00 -04:00
|
|
|
host: MapWrapper.createFromStringMap<string>({'[hostProp]': 'dirProp'})
|
2015-04-21 14:47:53 -04:00
|
|
|
});
|
|
|
|
|
2015-06-22 11:21:03 -04:00
|
|
|
var someDirectiveWithInvalidHostProperties = DirectiveMetadata.create({
|
|
|
|
selector: '[some-decor-with-invalid-host-props]',
|
2015-07-07 23:03:00 -04:00
|
|
|
host: MapWrapper.createFromStringMap<string>({'[hostProp]': 'dirProp + dirProp2'})
|
2015-06-22 11:21:03 -04:00
|
|
|
});
|
|
|
|
|
2015-06-09 06:33:40 -04:00
|
|
|
var someDirectiveWithHostAttributes = DirectiveMetadata.create({
|
2015-05-01 07:41:56 -04:00
|
|
|
selector: '[some-decor-with-host-attrs]',
|
2015-07-07 23:03:00 -04:00
|
|
|
host: MapWrapper.createFromStringMap<string>({'attr_name': 'attr_val', 'class': 'foo bar'})
|
2015-05-01 07:41:56 -04:00
|
|
|
});
|
|
|
|
|
2015-07-07 23:03:00 -04:00
|
|
|
var someDirectiveWithEvents = DirectiveMetadata.create({
|
|
|
|
selector: '[some-decor-events]',
|
|
|
|
host: MapWrapper.createFromStringMap<string>({'(click)': 'doIt()'})
|
|
|
|
});
|
2015-04-02 09:56:58 -04:00
|
|
|
|
2015-06-09 06:33:40 -04:00
|
|
|
var someDirectiveWithGlobalEvents = DirectiveMetadata.create({
|
2015-04-02 09:56:58 -04:00
|
|
|
selector: '[some-decor-globalevents]',
|
2015-07-07 23:03:00 -04:00
|
|
|
host: MapWrapper.createFromStringMap<string>({'(window:resize)': 'doItGlobal()'})
|
2015-04-02 09:56:58 -04:00
|
|
|
});
|
2015-04-27 18:14:30 -04:00
|
|
|
|
2015-06-09 06:33:40 -04:00
|
|
|
var componentWithNonElementSelector = DirectiveMetadata.create({
|
2015-04-27 18:14:30 -04:00
|
|
|
id: 'componentWithNonElementSelector',
|
|
|
|
selector: '[attr]',
|
|
|
|
type: DirectiveMetadata.COMPONENT_TYPE
|
2015-05-11 20:59:39 -04:00
|
|
|
});
|