diff --git a/lib/core/filter.dart b/lib/core/filter.dart index b3b33d7ea..2c27715f4 100644 --- a/lib/core/filter.dart +++ b/lib/core/filter.dart @@ -40,9 +40,9 @@ class NgFilter { @NgInjectableService() class FilterMap extends AnnotationMap { Injector _injector; - FilterMap(Injector injector, MetadataExtractor extractMetadata) : - this._injector = injector, - super(injector, extractMetadata); + FilterMap(Injector injector, MetadataExtractor extractMetadata) + : this._injector = injector, + super(injector, extractMetadata); call(String name) { var filter = new NgFilter(name: name); diff --git a/lib/core/interpolate.dart b/lib/core/interpolate.dart index 5071787e5..34a1ba51c 100644 --- a/lib/core/interpolate.dart +++ b/lib/core/interpolate.dart @@ -1,34 +1,12 @@ part of angular.core; -class Interpolation implements Function { - final String template; - final List separators; - final List expressions; - Function setter = (_) => _; - - Interpolation(this.template, this.separators, this.expressions); - - String call(List parts, [_]) { - if (parts == null) return separators.join(''); - var sb = new StringBuffer(); - for (var i = 0; i < parts.length; i++) { - sb.write(separators[i]); - var value = parts[i]; - sb.write(value == null ? '' : '$value'); - } - sb.write(separators.last); - return setter(sb.toString()); - } -} - /** - * Compiles a string with markup into an interpolation function. This service - * is used by the HTML [Compiler] service for data binding. - * + * Compiles a string with markup into an expression. This service is used by the + * HTML [Compiler] service for data binding. * * var $interpolate = ...; // injected * var exp = $interpolate('Hello {{name}}!'); - * expect(exp({name:'Angular'}).toEqual('Hello Angular!'); + * expect(exp).toEqual('"Hello "+(name)+"!"'); */ @NgInjectableService() class Interpolate implements Function { @@ -37,49 +15,49 @@ class Interpolate implements Function { Interpolate(this._parse); /** - * Compiles markup text into interpolation function. + * Compiles markup text into expression. * - * - `text`: The markup text to interpolate in form `foo {{expr}} bar`. + * - `template`: The markup text to interpolate in form `foo {{expr}} bar`. * - `mustHaveExpression`: if set to true then the interpolation string must - * have embedded expression in order to return an interpolation function. - * Strings with no embedded expression will return null for the - * interpolation function. + * have embedded expression in order to return an expression. Strings with + * no embedded expression will return null. * - `startSymbol`: The symbol to start interpolation. '{{' by default. * - `endSymbol`: The symbol to end interpolation. '}}' by default. */ - Interpolation call(String template, [bool mustHaveExpression = false, + + String call(String template, [bool mustHaveExpression = false, String startSymbol = '{{', String endSymbol = '}}']) { - int startSymbolLength = startSymbol.length; - int endSymbolLength = endSymbol.length; - int startIndex; - int endIndex; - int index = 0; + + int startLen = startSymbol.length; + int endLen = endSymbol.length; int length = template.length; + + int startIdx; + int endIdx; + int index = 0; + bool hasInterpolation = false; - bool shouldAddSeparator = true; + String exp; - final separators = []; - final expressions = []; + final expParts = []; while (index < length) { - if (((startIndex = template.indexOf(startSymbol, index)) != -1) && - ((endIndex = template.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { - separators.add(template.substring(index, startIndex)); - exp = template.substring(startIndex + startSymbolLength, endIndex); - expressions.add(exp); - index = endIndex + endSymbolLength; + startIdx = template.indexOf(startSymbol, index); + endIdx = template.indexOf(endSymbol, startIdx + startLen); + if (startIdx != -1 && endIdx != -1) { + if (index < startIdx) { + expParts.add('"${template.substring(index, startIdx)}"'); + } + expParts.add('(${template.substring(startIdx + startLen, endIdx)})'); + index = endIdx + endLen; hasInterpolation = true; } else { - // we did not find anything, so we have to add the remainder to the - // chunks array - separators.add(template.substring(index)); - shouldAddSeparator = false; + // we did not find any interpolation, so add the remainder + expParts.add('"${template.substring(index)}"'); break; } } - if (shouldAddSeparator) separators.add(''); - return (!mustHaveExpression || hasInterpolation) - ? new Interpolation(template, separators, expressions) - : null; + + return !mustHaveExpression || hasInterpolation ? expParts.join('+') : null; } -} +} \ No newline at end of file diff --git a/lib/core/module.dart b/lib/core/module.dart index 4558a3d5f..bda8ac5c9 100644 --- a/lib/core/module.dart +++ b/lib/core/module.dart @@ -45,7 +45,6 @@ class NgCoreModule extends Module { value(ScopeStats, new ScopeStats()); value(GetterCache, new GetterCache({})); value(Object, {}); // RootScope context - type(AstParser); type(NgZone); type(Parser, implementedBy: DynamicParser); diff --git a/lib/core/scope.dart b/lib/core/scope.dart index 4f34a794d..8c6a1d968 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -196,32 +196,30 @@ class Scope { * On the opposite, [readOnly] should be set to [:false:] if the [reactionFn] * could change the model so that the watch is observed in the [digest] cycle. */ - Watch watch(expression, ReactionFn reactionFn, - {context, FilterMap filters, bool readOnly: false}) { + Watch watch(String expression, ReactionFn reactionFn, {Object context, + FilterMap filters, bool readOnly: false, bool collection: false}) { assert(isAttached); - assert(expression != null); - AST ast; + assert(expression is String && expression != null); + Watch watch; ReactionFn fn = reactionFn; - if (expression is AST) { - ast = expression; - } else if (expression is String) { - if (expression.startsWith('::')) { - expression = expression.substring(2); - fn = (value, last) { - if (value != null) { - watch.remove(); - return reactionFn(value, last); - } - }; - } else if (expression.startsWith(':')) { - expression = expression.substring(1); - fn = (value, last) => value == null ? null : reactionFn(value, last); - } - ast = rootScope._astParser(expression, context: context, filters: filters); - } else { - throw 'expressions must be String or AST got $expression.'; + + if (expression.startsWith('::')) { + expression = expression.substring(2); + fn = (value, last) { + if (value != null) { + watch.remove(); + return reactionFn(value, last); + } + }; + } else if (expression.startsWith(':')) { + expression = expression.substring(1); + fn = (value, last) => value == null ? null : reactionFn(value, last); } + + AST ast = rootScope._astParser(expression, context: context, + filters: filters, collection: collection); + WatchGroup group = readOnly ? _readOnlyGroup : _readWriteGroup; return watch = group.watch(ast, fn); } @@ -253,10 +251,9 @@ class Scope { } catch (e, s) { rootScope._exceptionHandler(e, s); } finally { - rootScope - .._transitionState(RootScope.STATE_APPLY, null) - ..digest() - ..flush(); + rootScope.._transitionState(RootScope.STATE_APPLY, null) + ..digest() + ..flush(); } } @@ -409,7 +406,7 @@ class RootScope extends Scope { static final STATE_FLUSH = 'flush'; final ExceptionHandler _exceptionHandler; - final AstParser _astParser; + final _AstParser _astParser; final Parser _parser; final ScopeDigestTTL _ttl; final NgZone _zone; @@ -422,11 +419,12 @@ class RootScope extends Scope { String _state; - RootScope(Object context, this._astParser, this._parser, - GetterCache cacheGetter, FilterMap filterMap, - this._exceptionHandler, this._ttl, this._zone, + RootScope(Object context, Parser parser, GetterCache cacheGetter, + FilterMap filterMap, this._exceptionHandler, this._ttl, this._zone, this._scopeStats) - : super(context, null, null, + : _astParser = new _AstParser(parser), + _parser = parser, + super(context, null, null, new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context), new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context), '') @@ -827,12 +825,12 @@ class _FunctionChain { } } -class AstParser { +class _AstParser { final Parser _parser; int _id = 0; ExpressionVisitor _visitor = new ExpressionVisitor(); - AstParser(this._parser); + _AstParser(this._parser); AST call(String exp, { FilterMap filters, bool collection: false, diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index 7a30053eb..849b61ba1 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -136,7 +136,7 @@ class ElementBinder { nodeModule.factory(NgTextMustacheDirective, (Injector injector) { return new NgTextMustacheDirective( node, ref.value, injector.get(Interpolate), injector.get(Scope), - injector.get(AstParser), injector.get(FilterMap)); + injector.get(FilterMap)); }); } else if (ref.type == NgAttrMustacheDirective) { if (nodesAttrsDirectives == null) { @@ -146,7 +146,7 @@ class ElementBinder { var interpolate = injector.get(Interpolate); for (var ref in nodesAttrsDirectives) { new NgAttrMustacheDirective(nodeAttrs, ref.value, interpolate, - scope, injector.get(AstParser), injector.get(FilterMap)); + scope, injector.get(FilterMap)); } }); } diff --git a/lib/core_dom/ng_mustache.dart b/lib/core_dom/ng_mustache.dart index c6c604b6f..dfe3da763 100644 --- a/lib/core_dom/ng_mustache.dart +++ b/lib/core_dom/ng_mustache.dart @@ -3,20 +3,20 @@ part of angular.core.dom; // This Directive is special and does not go through injection. @NgDirective(selector: r':contains(/{{.*}}/)') class NgTextMustacheDirective { - NgTextMustacheDirective(dom.Node element, - String markup, + final dom.Node _element; + + NgTextMustacheDirective(this._element, + String template, Interpolate interpolate, Scope scope, - AstParser parser, FilterMap filters) { - Interpolation interpolation = interpolate(markup); - interpolation.setter = (text) => element.text = text; + String expression = interpolate(template); + + scope.watch(expression, _updateMarkup, readOnly: true, filters: filters); + } - List items = interpolation.expressions - .map((exp) => parser(exp, filters: filters)) - .toList(); - AST ast = new PureFunctionAST('[[$markup]]', new ArrayFn(), items); - scope.watch(ast, interpolation.call, readOnly: true); + void _updateMarkup(text, previousText) { + if (text != previousText) _element.text = text.toString(); } } @@ -25,40 +25,30 @@ class NgTextMustacheDirective { class NgAttrMustacheDirective { bool _hasObservers; Watch _watch; + NodeAttrs _attrs; + String _attrName; - // This Directive is special and does not go through injection. - NgAttrMustacheDirective(NodeAttrs attrs, - String markup, + NgAttrMustacheDirective(this._attrs, + String template, Interpolate interpolate, Scope scope, - AstParser parser, FilterMap filters) { + var eqPos = template.indexOf('='); + _attrName = template.substring(0, eqPos); + String expression = interpolate(template.substring(eqPos + 1)); - var eqPos = markup.indexOf('='); - var attrName = markup.substring(0, eqPos); - var attrValue = markup.substring(eqPos + 1); - var lastValue = markup; - Interpolation interpolation = interpolate(attrValue)..setter = (text) { - if (lastValue != text) lastValue = attrs[attrName] = text; - }; - - // TODO(misko): figure out how to remove call to setter. It slows down - // View instantiation - interpolation.setter(''); - - List items = interpolation.expressions - .map((exp) => parser(exp, filters: filters)) - .toList(); - - AST ast = new PureFunctionAST('[[$markup]]', new ArrayFn(), items); - - attrs.listenObserverChanges(attrName, (hasObservers) { + _attrs.listenObserverChanges(_attrName, (hasObservers) { if (_hasObservers != hasObservers) { - hasObservers = hasObservers; + _hasObservers = hasObservers; if (_watch != null) _watch.remove(); - _watch = scope.watch(ast, interpolation.call, readOnly: !hasObservers); + _watch = scope.watch(expression, _updateMarkup, filters: filters, + readOnly: !_hasObservers); } }); } + + void _updateMarkup(text, previousText) { + if (previousText != text) _attrs[_attrName] = text.toString(); + } } diff --git a/lib/directive/ng_class.dart b/lib/directive/ng_class.dart index 7c30282a2..af1dd535f 100644 --- a/lib/directive/ng_class.dart +++ b/lib/directive/ng_class.dart @@ -68,8 +68,8 @@ part of angular.directive; exportExpressionAttrs: const ['ng-class']) class NgClassDirective extends _NgClassBase { NgClassDirective(dom.Element element, Scope scope, NodeAttrs attrs, - AstParser parser, NgAnimate animate) - : super(element, scope, null, attrs, parser, animate); + NgAnimate animate) + : super(element, scope, null, attrs, animate); } /** @@ -104,8 +104,8 @@ class NgClassDirective extends _NgClassBase { exportExpressionAttrs: const ['ng-class-odd']) class NgClassOddDirective extends _NgClassBase { NgClassOddDirective(dom.Element element, Scope scope, NodeAttrs attrs, - AstParser parser, NgAnimate animate) - : super(element, scope, 0, attrs, parser, animate); + NgAnimate animate) + : super(element, scope, 0, attrs, animate); } /** @@ -140,8 +140,8 @@ class NgClassOddDirective extends _NgClassBase { exportExpressionAttrs: const ['ng-class-even']) class NgClassEvenDirective extends _NgClassBase { NgClassEvenDirective(dom.Element element, Scope scope, NodeAttrs attrs, - AstParser parser, NgAnimate animate) - : super(element, scope, 1, attrs, parser, animate); + NgAnimate animate) + : super(element, scope, 1, attrs, animate); } abstract class _NgClassBase { @@ -149,14 +149,13 @@ abstract class _NgClassBase { final Scope scope; final int mode; final NodeAttrs nodeAttrs; - final AstParser _parser; final NgAnimate _animate; var previousSet = []; var currentSet = []; Watch _watch; _NgClassBase(this.element, this.scope, this.mode, this.nodeAttrs, - this._parser, this._animate) + this._animate) { var prevClass; @@ -170,12 +169,16 @@ abstract class _NgClassBase { set valueExpression(currentExpression) { if (_watch != null) _watch.remove(); - _watch = scope.watch(_parser(currentExpression, collection: true), (current, _) { - currentSet = _flatten(current); - _handleChange(scope.context[r'$index']); - }, readOnly: true); + + _watch = scope.watch(currentExpression, (current, _) { + currentSet = _flatten(current); + _handleChange(scope.context[r'$index']); + }, + readOnly: true, + collection: true); + if (mode != null) { - scope.watch(_parser(r'$index'), (index, oldIndex) { + scope.watch(r'$index', (index, oldIndex) { var mod = index % 2; if (oldIndex == null || mod != oldIndex % 2) { if (mod == mode) { diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index b3a894e08..0c0e71a7e 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -22,7 +22,6 @@ class _NoopModelConverter extends NgModelConverter { */ @NgDirective(selector: '[ng-model]') class NgModel extends NgControl implements NgAttachAware { - final AstParser _parser; final Scope _scope; BoundSetter setter = (_, [__]) => null; @@ -37,8 +36,8 @@ class NgModel extends NgControl implements NgAttachAware { bool _watchCollection; Function render = (value) => null; - NgModel(this._scope, NgElement element, Injector injector, - this._parser, NodeAttrs attrs, NgAnimate animate) + NgModel(this._scope, NgElement element, Injector injector, NodeAttrs attrs, + NgAnimate animate) : super(element, injector, animate) { _exp = attrs["ng-model"]; @@ -117,13 +116,12 @@ class NgModel extends NgControl implements NgAttachAware { _watchCollection = value; if (_removeWatch!=null) _removeWatch.remove(); if (_watchCollection) { - _removeWatch = _scope.watch( - _parser(_exp, collection: true), - (changeRecord, _) { - onChange(changeRecord is CollectionChangeRecord - ? changeRecord.iterable - : changeRecord); - }); + _removeWatch = _scope.watch(_exp, (changeRecord, _) { + onChange(changeRecord is CollectionChangeRecord + ? changeRecord.iterable + : changeRecord); + }, + collection: true); } else if (_exp != null) { _removeWatch = _scope.watch(_exp, onChange); } diff --git a/lib/directive/ng_pluralize.dart b/lib/directive/ng_pluralize.dart index 7b2903a7d..45b9b14d9 100644 --- a/lib/directive/ng_pluralize.dart +++ b/lib/directive/ng_pluralize.dart @@ -92,10 +92,11 @@ class NgPluralizeDirective { final dom.Element element; final Scope scope; final Interpolate interpolate; - final AstParser parser; int offset; - var discreteRules = {}; - var categoryRules = {}; + final discreteRules = {}; + final categoryRules = {}; + final expressionCache = {}; + Watch _watch; static final RegExp IS_WHEN = new RegExp(r'^when-(minus-)?.'); static const Map SYMBOLS = const { @@ -108,33 +109,35 @@ class NgPluralizeDirective { }; NgPluralizeDirective(this.scope, this.element, this.interpolate, - NodeAttrs attributes, this.parser) { - Map whens = attributes['when'] == null - ? {} + NodeAttrs attributes) { + final whens = attributes['when'] == null + ? {} : scope.eval(attributes['when']); offset = attributes['offset'] == null ? 0 : int.parse(attributes['offset']); element.attributes.keys.where((k) => IS_WHEN.hasMatch(k)).forEach((k) { - var ruleName = k.replaceFirst('when-', '').replaceFirst('minus-', '-'); + var ruleName = k + .replace(new RegExp('^when-'), '') + .replace(new RegExp('^minus-'), '-'); whens[ruleName] = element.attributes[k]; }); if (whens['other'] == null) { throw "ngPluralize error! The 'other' plural category must always be " - "specified"; + "specified"; } whens.forEach((k, v) { Symbol symbol = SYMBOLS[k]; if (symbol != null) { - this.categoryRules[symbol] = v; + categoryRules[symbol] = v; } else { - this.discreteRules[k] = v; + discreteRules[k] = v; } }); } - set count(value) { + void set count(value) { if (value is! num) { try { value = int.parse(value); @@ -163,12 +166,14 @@ class NgPluralizeDirective { } } - _setAndWatch(expression) { - var interpolation = interpolate(expression, false, '\${', '}'); - interpolation.setter = (text) => element.text = text; - interpolation.setter(expression); - var items = interpolation.expressions.map((exp) => parser(exp)).toList(); - AST ast = new PureFunctionAST(expression, new ArrayFn(), items); - scope.watch(ast, interpolation.call); + void _setAndWatch(template) { + if (_watch != null) _watch.remove(); + var expression = expressionCache.putIfAbsent(template, () => + interpolate(template, false, r'${', '}')); + _watch = scope.watch(expression, _updateMarkup); + } + + void _updateMarkup(text, previousText) { + if (text != previousText) element.text = text.toString(); } } diff --git a/lib/directive/ng_repeat.dart b/lib/directive/ng_repeat.dart index 087900be9..ef834dca2 100644 --- a/lib/directive/ng_repeat.dart +++ b/lib/directive/ng_repeat.dart @@ -88,7 +88,6 @@ class NgRepeatDirective { final BoundViewFactory _boundViewFactory; final Scope _scope; final Parser _parser; - final AstParser _astParser; final FilterMap filters; String _expression; @@ -100,9 +99,8 @@ class NgRepeatDirective { Watch _watch = null; Iterable _lastCollection; - NgRepeatDirective(this._viewPort, this._boundViewFactory, - this._scope, this._parser, this._astParser, - this.filters); + NgRepeatDirective(this._viewPort, this._boundViewFactory, this._scope, + this._parser, this.filters); set expression(value) { assert(value != null); @@ -138,12 +136,12 @@ class NgRepeatDirective { if (_valueIdentifier == null) _valueIdentifier = match.group(1); _keyIdentifier = match.group(2); - _watch = _scope.watch( - _astParser(_listExpr, collection: true, filters: filters), - (CollectionChangeRecord collection, _) { + _watch = _scope.watch(_listExpr, (CollectionChangeRecord collection, _) { //TODO(misko): we should take advantage of the CollectionChangeRecord! _onCollectionChange(collection == null ? [] : collection.iterable); - } + }, + collection: true, + filters: filters ); } diff --git a/lib/directive/ng_style.dart b/lib/directive/ng_style.dart index 142081bcd..bef4ec4a9 100644 --- a/lib/directive/ng_style.dart +++ b/lib/directive/ng_style.dart @@ -13,12 +13,11 @@ part of angular.directive; class NgStyleDirective { final dom.Element _element; final Scope _scope; - final AstParser _parser; String _styleExpression; Watch _watch; - NgStyleDirective(this._element, this._scope, this._parser); + NgStyleDirective(this._element, this._scope); /** * ng-style attribute takes an expression which evaluates to an @@ -28,8 +27,8 @@ class NgStyleDirective { set styleExpression(String value) { _styleExpression = value; if (_watch != null) _watch.remove(); - _watch = _scope.watch(_parser(_styleExpression, collection: true), - _onStyleChange, readOnly: true); + _watch = _scope.watch(_styleExpression, _onStyleChange, collection: true, + readOnly: true); } _onStyleChange(MapChangeRecord mapChangeRecord, _) { diff --git a/test/core/interpolate_spec.dart b/test/core/interpolate_spec.dart index 6e8dd92de..8b482ede5 100644 --- a/test/core/interpolate_spec.dart +++ b/test/core/interpolate_spec.dart @@ -14,100 +14,20 @@ main() { expect($interpolate('some text', true)).toBe(null); }); - it('should suppress falsy objects', (Interpolate $interpolate) { - expect($interpolate('{{undefined}}')([null])).toEqual(''); - expect($interpolate('{{undefined+undefined}}')([null])).toEqual(''); - expect($interpolate('{{null}}')([null])).toEqual(''); - expect($interpolate('{{a.b}}')([null])).toEqual(''); + it('should return an expression', (Interpolate $interpolate) { + expect($interpolate('Hello {{name}}!')).toEqual('"Hello "+(name)+"!"'); + expect($interpolate('a{{b}}C')).toEqual('"a"+(b)+"C"'); + expect($interpolate('a{{b}}')).toEqual('"a"+(b)'); + expect($interpolate('{{a}}b')).toEqual('(a)+"b"'); + expect($interpolate('{{b}}')).toEqual('(b)'); + expect($interpolate('{{b}}+{{c}}')).toEqual('(b)+"+"+(c)'); + expect($interpolate('{{b}}x{{c}}')).toEqual('(b)+"x"+(c)'); }); - it('should jsonify objects', (Interpolate $interpolate) { - expect($interpolate('{{ {} }}')([{}])).toEqual('{}'); - expect($interpolate('{{ true }}')([true])).toEqual('true'); - expect($interpolate('{{ false }}')([false])).toEqual('false'); + it('should Parse Multiline', (Interpolate $interpolate) { + expect($interpolate("X\nY{{A\n+B}}C\nD")) + .toEqual('"X\nY"+(A\n+B)+"C\nD"'); }); - - it('should return interpolation function', (Interpolate $interpolate, Scope rootScope) { - rootScope.context['name'] = 'Misko'; - var fn = $interpolate('Hello {{name}}!'); - expect(fn(['Misko'])).toEqual('Hello Misko!'); - }); - - - it('should ignore undefined model', (Interpolate $interpolate) { - expect($interpolate("Hello {{'World' + foo}}")(['World'])).toEqual('Hello World'); - }); - - - it('should use toString to conver objects to string', (Interpolate $interpolate, Scope rootScope) { - expect($interpolate("Hello, {{obj}}!")([new ToStringableObject()])).toEqual('Hello, World!'); - }); - - - describe('parseBindings', () { - it('should Parse Text With No Bindings', (Interpolate $interpolate) { - var parts = $interpolate("a").separators; - expect(parts.length).toEqual(1); - expect(parts[0]).toEqual("a"); - }); - - it('should Parse Empty Text', (Interpolate $interpolate) { - var parts = $interpolate("").separators; - expect(parts.length).toEqual(1); - expect(parts[0]).toEqual(""); - }); - - it('should Parse Inner Binding', (Interpolate $interpolate) { - var parts = $interpolate("a{{b}}C").separators; - expect(parts.length).toEqual(2); - expect(parts[0]).toEqual("a"); - expect(parts[1]).toEqual("C"); - }); - - it('should Parse Ending Binding', (Interpolate $interpolate) { - var parts = $interpolate("a{{b}}").separators; - expect(parts.length).toEqual(2); - expect(parts[0]).toEqual("a"); - expect(parts[1]).toEqual(""); - }); - - it('should Parse Begging Binding', (Interpolate $interpolate) { - var parts = $interpolate("{{b}}c").separators; - expect(parts.length).toEqual(2); - expect(parts[0]).toEqual(""); - expect(parts[1]).toEqual("c"); - }); - - it('should Parse Loan Binding', (Interpolate $interpolate) { - var parts = $interpolate("{{b}}").separators; - expect(parts.length).toEqual(2); - expect(parts[0]).toEqual(""); - expect(parts[1]).toEqual(""); - }); - - it('should Parse Two Bindings', (Interpolate $interpolate) { - var parts = $interpolate("{{b}}{{c}}").separators; - expect(parts.length).toEqual(3); - expect(parts[0]).toEqual(""); - expect(parts[1]).toEqual(""); - expect(parts[2]).toEqual(""); - }); - - it('should Parse Two Bindings With Text In Middle', (Interpolate $interpolate) { - var parts = $interpolate("{{b}}x{{c}}").separators; - expect(parts.length).toEqual(3); - expect(parts[0]).toEqual(""); - expect(parts[1]).toEqual("x"); - expect(parts[2]).toEqual(""); - }); - - it('should Parse Multiline', (Interpolate $interpolate) { - var parts = $interpolate('"X\nY{{A\n+B}}C\nD"').separators; - expect(parts.length).toEqual(2); - expect(parts[0]).toEqual('"X\nY'); - expect(parts[1]).toEqual('C\nD"'); - }); - }); }); } diff --git a/test/core/scope_spec.dart b/test/core/scope_spec.dart index cc700f9d9..7b516a824 100644 --- a/test/core/scope_spec.dart +++ b/test/core/scope_spec.dart @@ -163,13 +163,11 @@ void main() { }); it('should support filters', (Logger logger, Map context, - RootScope rootScope, AstParser parser, - FilterMap filters) { + RootScope rootScope, FilterMap filters) { context['a'] = 123; context['b'] = 2; - rootScope.watch( - parser('a | multiply:b', filters: filters), - (value, previous) => logger(value)); + rootScope.watch('a | multiply:b', (value, previous) => logger(value), + filters: filters); rootScope.digest(); expect(logger).toEqual([246]); logger.clear(); @@ -180,12 +178,10 @@ void main() { it('should support arrays in filters', (Logger logger, Map context, RootScope rootScope, - AstParser parser, FilterMap filters) { context['a'] = [1]; - rootScope.watch( - parser('a | sort | listHead:"A" | listTail:"B"', filters: filters), - (value, previous) => logger(value)); + rootScope.watch('a | sort | listHead:"A" | listTail:"B"', + (value, previous) => logger(value), filters: filters); rootScope.digest(); expect(logger).toEqual(['sort', 'listHead', 'listTail', ['A', 1, 'B']]); logger.clear(); @@ -209,12 +205,10 @@ void main() { it('should support maps in filters', (Logger logger, Map context, RootScope rootScope, - AstParser parser, FilterMap filters) { context['a'] = {'foo': 'bar'}; - rootScope.watch( - parser('a | identity | keys', filters: filters), - (value, previous) => logger(value)); + rootScope.watch('a | identity | keys', + (value, previous) => logger(value), filters: filters); rootScope.digest(); expect(logger).toEqual(['identity', 'keys', ['foo']]); logger.clear(); diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart index 3a2b68c42..0af36b035 100644 --- a/test/directive/ng_model_spec.dart +++ b/test/directive/ng_model_spec.dart @@ -96,7 +96,7 @@ void main() { }); it('should write to input only if the value is different', - (Injector i, AstParser parser, NgAnimate animate) { + (Injector i, NgAnimate animate) { var scope = _.rootScope; var element = new dom.InputElement(); @@ -104,7 +104,8 @@ void main() { NodeAttrs nodeAttrs = new NodeAttrs(new DivElement()); nodeAttrs['ng-model'] = 'model'; - var model = new NgModel(scope, ngElement, i.createChild([new Module()]), parser, nodeAttrs, new NgAnimate()); + var model = new NgModel(scope, ngElement, i.createChild([new Module()]), + nodeAttrs, new NgAnimate()); dom.querySelector('body').append(element); var input = new InputTextLikeDirective(element, model, scope); @@ -142,7 +143,7 @@ void main() { scope.context['model'] = 'xyz'; expect(inputElement.value).not.toEqual('xyz'); - + scope.apply(); expect(inputElement.value).toEqual('xyz'); @@ -161,7 +162,8 @@ void main() { describe('type="number" like', () { - it('should leave input unchanged when text does not represent a valid number', (Injector i) { + it('should leave input unchanged when text does not represent a valid number', + (Injector i) { var modelFieldName = 'modelForNumFromInvalid1'; var element = _.compile(''); dom.querySelector('body').append(element); @@ -189,7 +191,8 @@ void main() { expect(_.rootScope.context[modelFieldName]).toEqual(10); }); - it('should not reformat user input to equivalent numeric representation', (Injector i) { + it('should not reformat user input to equivalent numeric representation', + (Injector i) { var modelFieldName = 'modelForNumFromInvalid2'; var element = _.compile(''); dom.querySelector('body').append(element); @@ -258,7 +261,8 @@ void main() { expect(_.rootScope.context['model']).toEqual(43); }); - it('should update model to a native default value from a blank range input value', () { + it('should update model to a native default value from a blank range input value', + () { _.compile(''); Probe probe = _.rootScope.context['p']; var ngModel = probe.directive(NgModel); @@ -276,8 +280,9 @@ void main() { _.rootScope.apply('model = null'); expect((_.rootElement as dom.InputElement).value).toEqual(''); }); - - it('should only render the input value upon the next digest', (Scope scope) { + + it('should only render the input value upon the next digest', + (Scope scope) { _.compile(''); Probe probe = _.rootScope.context['p']; var ngModel = probe.directive(NgModel); @@ -287,7 +292,7 @@ void main() { scope.context['model'] = 123; expect(inputElement.value).not.toEqual('123'); - + scope.apply(); expect(inputElement.value).toEqual('123'); @@ -334,7 +339,7 @@ void main() { }); it('should write to input only if value is different', - (Injector i, AstParser parser, NgAnimate animate) { + (Injector i, NgAnimate animate) { var scope = _.rootScope; var element = new dom.InputElement(); @@ -342,7 +347,8 @@ void main() { NodeAttrs nodeAttrs = new NodeAttrs(new DivElement()); nodeAttrs['ng-model'] = 'model'; - var model = new NgModel(scope, ngElement, i.createChild([new Module()]), parser, nodeAttrs, new NgAnimate()); + var model = new NgModel(scope, ngElement, i.createChild([new Module()]), + nodeAttrs, new NgAnimate()); dom.querySelector('body').append(element); var input = new InputTextLikeDirective(element, model, scope); @@ -368,7 +374,8 @@ void main() { expect(element.selectionEnd).toEqual(3); }); - it('should only render the input value upon the next digest', (Scope scope) { + it('should only render the input value upon the next digest', + (Scope scope) { _.compile(''); Probe probe = _.rootScope.context['p']; var ngModel = probe.directive(NgModel); @@ -378,7 +385,7 @@ void main() { scope.context['model'] = 'xyz'; expect(inputElement.value).not.toEqual('xyz'); - + scope.apply(); expect(inputElement.value).toEqual('xyz'); @@ -423,7 +430,7 @@ void main() { }); it('should write to input only if value is different', - (Injector i, AstParser parser, NgAnimate animate) { + (Injector i, NgAnimate animate) { var scope = _.rootScope; var element = new dom.InputElement(); @@ -431,7 +438,8 @@ void main() { NodeAttrs nodeAttrs = new NodeAttrs(new DivElement()); nodeAttrs['ng-model'] = 'model'; - var model = new NgModel(scope, ngElement, i.createChild([new Module()]), parser, nodeAttrs, new NgAnimate()); + var model = new NgModel(scope, ngElement, i.createChild([new Module()]), + nodeAttrs, new NgAnimate()); dom.querySelector('body').append(element); var input = new InputTextLikeDirective(element, model, scope); @@ -459,7 +467,8 @@ void main() { expect(element.selectionEnd).toEqual(3); }); - it('should only render the input value upon the next digest', (Scope scope) { + it('should only render the input value upon the next digest', + (Scope scope) { _.compile(''); Probe probe = _.rootScope.context['p']; var ngModel = probe.directive(NgModel); @@ -469,7 +478,7 @@ void main() { scope.context['model'] = 'xyz'; expect(inputElement.value).not.toEqual('xyz'); - + scope.apply(); expect(inputElement.value).toEqual('xyz'); @@ -480,7 +489,8 @@ void main() { it('should be set "text" as default value for "type" attribute', () { _.compile(''); _.rootScope.apply(); - expect((_.rootElement as dom.InputElement).attributes['type']).toEqual('text'); + expect((_.rootElement as dom.InputElement).attributes['type']) + .toEqual('text'); }); it('should update input value from model', () { @@ -520,7 +530,7 @@ void main() { }); it('should write to input only if value is different', - (Injector i, AstParser parser, NgAnimate animate) { + (Injector i, NgAnimate animate) { var scope = _.rootScope; var element = new dom.InputElement(); @@ -528,7 +538,8 @@ void main() { NodeAttrs nodeAttrs = new NodeAttrs(new DivElement()); nodeAttrs['ng-model'] = 'model'; - var model = new NgModel(scope, ngElement, i.createChild([new Module()]), parser, nodeAttrs, new NgAnimate()); + var model = new NgModel(scope, ngElement, i.createChild([new Module()]), + nodeAttrs, new NgAnimate()); dom.querySelector('body').append(element); var input = new InputTextLikeDirective(element, model, scope); @@ -554,7 +565,8 @@ void main() { expect(element.selectionEnd).toEqual(3); }); - it('should only render the input value upon the next digest', (Scope scope) { + it('should only render the input value upon the next digest', + (Scope scope) { _.compile(''); Probe probe = _.rootScope.context['p']; var ngModel = probe.directive(NgModel); @@ -564,7 +576,7 @@ void main() { scope.context['model'] = 'xyz'; expect(inputElement.value).not.toEqual('xyz'); - + scope.apply(); expect(inputElement.value).toEqual('xyz'); @@ -587,7 +599,8 @@ void main() { }); it('should render as dirty when checked', (Scope scope) { - var element = _.compile(''); + var element = + _.compile(''); Probe probe = _.rootScope.context['i']; var model = probe.directive(NgModel); @@ -601,8 +614,10 @@ void main() { }); - it('should update input value from model using ng-true-value/false', (Scope scope) { - var element = _.compile(''); + it('should update input value from model using ng-true-value/false', + (Scope scope) { + var element = _.compile(''); scope.apply(() { scope.context['model'] = 1; @@ -656,7 +671,8 @@ void main() { expect(scope.context['model']).toBe(false); }); - it('should only render the input value upon the next digest', (Scope scope) { + it('should only render the input value upon the next digest', + (Scope scope) { _.compile(''); Probe probe = _.rootScope.context['p']; var ngModel = probe.directive(NgModel); @@ -666,7 +682,7 @@ void main() { scope.context['model'] = true; expect(inputElement.checked).toBe(false); - + scope.apply(); expect(inputElement.checked).toBe(true); @@ -714,7 +730,7 @@ void main() { // NOTE(deboer): This test passes on Dartium, but fails in the content_shell. // The Dart team is looking into this bug. xit('should write to input only if value is different', - (Injector i, AstParser parser, NgAnimate animate) { + (Injector i, NgAnimate animate) { var scope = _.rootScope; var element = new dom.TextAreaElement(); @@ -722,14 +738,15 @@ void main() { NodeAttrs nodeAttrs = new NodeAttrs(new DivElement()); nodeAttrs['ng-model'] = 'model'; - var model = new NgModel(scope, ngElement, i.createChild([new Module()]), parser, nodeAttrs, new NgAnimate()); + var model = new NgModel(scope, ngElement, i.createChild([new Module()]), + nodeAttrs, new NgAnimate()); dom.querySelector('body').append(element); var input = new InputTextLikeDirective(element, model, scope); element - ..value = 'abc' - ..selectionStart = 1 - ..selectionEnd = 2; + ..value = 'abc' + ..selectionStart = 1 + ..selectionEnd = 2; model.render('abc'); @@ -746,7 +763,8 @@ void main() { expect(element.selectionEnd).toEqual(0); }); - it('should only render the input value upon the next digest', (Scope scope) { + it('should only render the input value upon the next digest', + (Scope scope) { _.compile(''); Probe probe = _.rootScope.context['p']; var ngModel = probe.directive(NgModel); @@ -756,7 +774,7 @@ void main() { scope.context['model'] = 'xyz'; expect(inputElement.value).not.toEqual('xyz'); - + scope.apply(); expect(inputElement.value).toEqual('xyz'); @@ -765,9 +783,11 @@ void main() { describe('type="radio"', () { it('should update input value from model', () { - _.compile('' + - '' + - ''); + _.compile(""" + + + + """); _.rootScope.apply(); RadioButtonInputElement redBtn = _.rootScope.context['r'].element; @@ -808,9 +828,11 @@ void main() { }); it('should support ng-value', () { - _.compile('' + - '' + - ''); + _.compile(""" + + + + """); var red = {'name': 'RED'}; var green = {'name': 'GREEN'}; @@ -862,12 +884,12 @@ void main() { }); it('should render as dirty when checked', (Scope scope) { - var element = _.compile( - '
' + - ' ' + - ' ' + - '
' - ); + var element = _.compile(""" +
+ + +
' + """); Probe probe = _.rootScope.context['i']; var model = probe.directive(NgModel); @@ -904,12 +926,12 @@ void main() { }); it('should only render the input value upon the next digest', (Scope scope) { - var element = _.compile( - '
' + - ' ' + - ' ' + - '
' - ); + var element = _.compile(""" +
+ + +
+ """); Probe probe1 = _.rootScope.context['i']; var ngModel1 = probe1.directive(NgModel); @@ -924,7 +946,7 @@ void main() { expect(inputElement1.checked).toBe(false); expect(inputElement2.checked).toBe(false); - + scope.apply(); expect(inputElement1.checked).toBe(true); @@ -969,7 +991,8 @@ void main() { expect(_.rootScope.context['model']).toEqual('123'); }); - it('should only render the input value upon the next digest', (Scope scope) { + it('should only render the input value upon the next digest', + (Scope scope) { _.compile(''); Probe probe = _.rootScope.context['p']; var ngModel = probe.directive(NgModel); @@ -979,7 +1002,7 @@ void main() { scope.context['model'] = 'xyz'; expect(inputElement.value).not.toEqual('xyz'); - + scope.apply(); expect(inputElement.value).toEqual('xyz'); @@ -1011,7 +1034,8 @@ void main() { expect(_.rootScope.context['model']).toEqual('def'); }); - it('should only render the input value upon the next digest', (Scope scope) { + it('should only render the input value upon the next digest', + (Scope scope) { _.compile('
'); Probe probe = _.rootScope.context['p']; var ngModel = probe.directive(NgModel); @@ -1021,7 +1045,7 @@ void main() { scope.context['model'] = 'xyz'; expect(element.innerHtml).not.toEqual('xyz'); - + scope.apply(); expect(element.innerHtml).toEqual('xyz'); @@ -1030,7 +1054,7 @@ void main() { describe('pristine / dirty', () { it('should be set to pristine by default', (Scope scope) { - _.compile(''); + _.compile(''); Probe probe = _.rootScope.context['i']; var model = probe.directive(NgModel); @@ -1038,8 +1062,9 @@ void main() { expect(model.dirty).toEqual(false); }); - it('should add and remove the correct CSS classes when set to dirty and to pristine', (Scope scope) { - _.compile(''); + it('should add and remove the correct CSS classes when set to dirty and to pristine', + (Scope scope) { + _.compile(''); Probe probe = _.rootScope.context['i']; NgModel model = probe.directive(NgModel); InputElement element = probe.element; @@ -1063,14 +1088,15 @@ void main() { // TODO(matias): figure out why the 2nd apply is optional it('should render the parent form/fieldset as dirty but not the other models', - (Scope scope) { + (Scope scope) { - _.compile('
' + - '
' + - ' ' + - ' ' + - '
' + - '
'); + _.compile(""" +
+
+ + +
+
"""); var formElement = _.rootScope.context['myForm'].element.node; var fieldsetElement = _.rootScope.context['myFieldset'].element.node; @@ -1126,7 +1152,8 @@ void main() { expect(model.valid).toBe(true); }); - it('should happen automatically upon user input via the onInput event', () { + it('should happen automatically upon user input via the onInput event', + () { _.compile(''); Probe probe = _.rootScope.context['i']; @@ -1145,8 +1172,9 @@ void main() { }); describe('valid / invalid', () { - it('should add and remove the correct flags when set to valid and to invalid', (Scope scope) { - _.compile(''); + it('should add and remove the correct flags when set to valid and to invalid', + (Scope scope) { + _.compile(''); Probe probe = _.rootScope.context['i']; var model = probe.directive(NgModel); InputElement element = probe.element; @@ -1170,8 +1198,9 @@ void main() { // expect(element.classes.contains('ng-valid')).toBe(true); }); - it('should set the validity with respect to all existing validations when setValidity() is used', (Scope scope) { - _.compile(''); + it('should set the validity with respect to all existing validations when setValidity() is used', + (Scope scope) { + _.compile(''); Probe probe = _.rootScope.context['i']; var model = probe.directive(NgModel); @@ -1192,8 +1221,9 @@ void main() { expect(model.invalid).toEqual(false); }); - it('should register each error only once when invalid', (Scope scope) { - _.compile(''); + it('should register each error only once when invalid', + (Scope scope) { + _.compile(''); Probe probe = _.rootScope.context['i']; var model = probe.directive(NgModel); @@ -1213,9 +1243,10 @@ void main() { describe('error handling', () { it('should return true or false depending on if an error exists on a form', - (Scope scope, TestBed _) { + (Scope scope, TestBed _) { - var element = $(''); + var element = + $(''); _.compile(element); scope.apply(); @@ -1254,7 +1285,8 @@ void main() { describe('error messages', () { it('should produce a useful error for bad ng-model expressions', () { expect(async(() { - _.compile('
'); + _.compile(''); _.rootScope.apply('myModel = "animal"'); Probe probe = _.rootScope.context['i']; @@ -1293,7 +1325,7 @@ void main() { }); it('should set the model to be untouched when the model is reset', () { - var input = _.compile(''); + var input = _.compile(''); var model = _.rootScope.context['i'].directive(NgModel); expect(model.touched).toBe(false); @@ -1311,20 +1343,20 @@ void main() { }); describe('validators', () { - it('should display the valid and invalid CSS classes on the element for each validation', - (TestBed _, Scope scope) { + it('should display the valid and invalid CSS classes on the element for each validation', + (TestBed _, Scope scope) { - var input = _.compile(''); + var input = _.compile(''); scope.apply(() { - scope.context['myModel'] = 'value'; + scope.context['myModel'] = 'value'; }); expect(input.classes.contains('ng-email-invalid')).toBe(true); expect(input.classes.contains('ng-email-valid')).toBe(false); scope.apply(() { - scope.context['myModel'] = 'value@email.com'; + scope.context['myModel'] = 'value@email.com'; }); expect(input.classes.contains('ng-email-valid')).toBe(true); @@ -1332,9 +1364,10 @@ void main() { }); it('should display the valid and invalid CSS classes on the element for custom validations', - (TestBed _, Scope scope) { + (TestBed _, Scope scope) { - var input = _.compile(''); + var input = _.compile(''); scope.apply(); @@ -1342,7 +1375,7 @@ void main() { expect(input.classes.contains('custom-valid')).toBe(false); scope.apply(() { - scope.context['myModel'] = 'yes'; + scope.context['myModel'] = 'yes'; }); expect(input.classes.contains('custom-valid')).toBe(true); @@ -1351,7 +1384,8 @@ void main() { }); describe('converters', () { - it('should parse the model value according to the given parser', (Scope scope) { + it('should parse the model value according to the given parser', + (Scope scope) { _.compile(''); scope.apply(); @@ -1368,7 +1402,8 @@ void main() { expect(model.modelValue).toEqual('hello'); }); - it('should format the model value according to the given formatter', (Scope scope) { + it('should format the model value according to the given formatter', + (Scope scope) { _.compile(''); scope.apply(); @@ -1386,10 +1421,11 @@ void main() { }); it('should retain the current input value if the parser fails', (Scope scope) { - _.compile('
' + - ' ' + - ' ' + - '
'); + _.compile(""" +
+ + +
"""); scope.apply(); var probe1 = scope.context['i']; @@ -1415,7 +1451,8 @@ void main() { expect(model2.modelValue).toEqual(null); }); - it('should reformat the viewValue when the formatter is changed', (Scope scope) { + it('should reformat the viewValue when the formatter is changed', + (Scope scope) { _.compile(''); scope.apply(); @@ -1492,7 +1529,5 @@ class MyCustomInputValidator extends NgValidator { final String name = 'custom'; - bool isValid(name) { - return name != null && name == 'yes'; - } + bool isValid(name) => name != null && name == 'yes'; }