diff options
| -rw-r--r-- | src/ng/parse.js | 332 | ||||
| -rw-r--r-- | test/ng/parseSpec.js | 363 | 
2 files changed, 484 insertions, 211 deletions
| diff --git a/src/ng/parse.js b/src/ng/parse.js index 701647c5..aad740e2 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -1,6 +1,8 @@  'use strict';  var $parseMinErr = minErr('$parse'); +var promiseWarningCache = {}; +var promiseWarning;  // Sandboxing Angular Expressions  // ------------------------------ @@ -99,8 +101,8 @@ var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'  /**   * @constructor   */ -var Lexer = function (csp) { -  this.csp = csp; +var Lexer = function (options) { +  this.options = options;  };  Lexer.prototype = { @@ -108,6 +110,7 @@ Lexer.prototype = {    lex: function (text) {      this.text = text; +      this.index = 0;      this.ch = undefined;      this.lastCh = ':'; // can start regexp @@ -295,12 +298,12 @@ Lexer.prototype = {        token.fn = OPERATORS[ident];        token.json = OPERATORS[ident];      } else { -      var getter = getterFn(ident, this.csp, this.text); +      var getter = getterFn(ident, this.options, this.text);        token.fn = extend(function(self, locals) {          return (getter(self, locals));        }, {          assign: function(self, value) { -          return setter(self, ident, value, parser.text); +          return setter(self, ident, value, parser.text, parser.options);          }        });      } @@ -371,10 +374,10 @@ Lexer.prototype = {  /**   * @constructor   */ -var Parser = function (lexer, $filter, csp) { +var Parser = function (lexer, $filter, options) {    this.lexer = lexer;    this.$filter = $filter; -  this.csp = csp; +  this.options = options;  };  Parser.ZERO = function () { return 0; }; @@ -388,7 +391,7 @@ Parser.prototype = {      //TODO(i): strip all the obsolte json stuff from this file      this.json = json; -    this.tokens = this.lexer.lex(text, this.csp); +    this.tokens = this.lexer.lex(text);      if (json) {        // The extra level of aliasing is here, just in case the lexer misses something, so that @@ -688,13 +691,13 @@ Parser.prototype = {    fieldAccess: function(object) {      var parser = this;      var field = this.expect().text; -    var getter = getterFn(field, this.csp, this.text); +    var getter = getterFn(field, this.options, this.text);      return extend(function(scope, locals, self) {        return getter(self || object(scope, locals), locals);      }, {        assign: function(scope, value, locals) { -        return setter(object(scope, locals), field, value, parser.text); +        return setter(object(scope, locals), field, value, parser.text, parser.options);        }      });    }, @@ -712,7 +715,7 @@ Parser.prototype = {        if (!o) return undefined;        v = ensureSafeObject(o[i], parser.text); -      if (v && v.then) { +      if (v && v.then && parser.options.unwrapPromises) {          p = v;          if (!('$$v' in v)) {            p.$$v = undefined; @@ -759,7 +762,7 @@ Parser.prototype = {              : fnPtr(args[0], args[1], args[2], args[3], args[4]);        // Check for promise -      if (v && v.then) { +      if (v && v.then && parser.options.unwrapPromises) {          var p = v;          if (!('$$v' in v)) {            p.$$v = undefined; @@ -827,7 +830,7 @@ Parser.prototype = {        literal: true,        constant: allConstant      }); -  }, +  }  }; @@ -835,7 +838,10 @@ Parser.prototype = {  // Parser helper functions  ////////////////////////////////////////////////// -function setter(obj, path, setValue, fullExp) { +function setter(obj, path, setValue, fullExp, options) { +  //needed? +  options = options || {}; +    var element = path.split('.'), key;    for (var i = 0; element.length > 1; i++) {      key = ensureSafeMemberName(element.shift(), fullExp); @@ -845,7 +851,8 @@ function setter(obj, path, setValue, fullExp) {        obj[key] = propertyObj;      }      obj = propertyObj; -    if (obj.then) { +    if (obj.then && options.unwrapPromises) { +      promiseWarning(fullExp);        if (!("$$v" in obj)) {          (function(promise) {            promise.then(function(val) { promise.$$v = val; }); } @@ -869,76 +876,103 @@ var getterFnCache = {};   * - http://jsperf.com/angularjs-parse-getter/4   * - http://jsperf.com/path-evaluation-simplified/7   */ -function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) { +function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {    ensureSafeMemberName(key0, fullExp);    ensureSafeMemberName(key1, fullExp);    ensureSafeMemberName(key2, fullExp);    ensureSafeMemberName(key3, fullExp);    ensureSafeMemberName(key4, fullExp); -  return function(scope, locals) { -    var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, -        promise; - -    if (pathVal === null || pathVal === undefined) return pathVal; - -    pathVal = pathVal[key0]; -    if (pathVal && pathVal.then) { -      if (!("$$v" in pathVal)) { -        promise = pathVal; -        promise.$$v = undefined; -        promise.then(function(val) { promise.$$v = val; }); -      } -      pathVal = pathVal.$$v; -    } -    if (!key1 || pathVal === null || pathVal === undefined) return pathVal; - -    pathVal = pathVal[key1]; -    if (pathVal && pathVal.then) { -      if (!("$$v" in pathVal)) { -        promise = pathVal; -        promise.$$v = undefined; -        promise.then(function(val) { promise.$$v = val; }); -      } -      pathVal = pathVal.$$v; -    } -    if (!key2 || pathVal === null || pathVal === undefined) return pathVal; - -    pathVal = pathVal[key2]; -    if (pathVal && pathVal.then) { -      if (!("$$v" in pathVal)) { -        promise = pathVal; -        promise.$$v = undefined; -        promise.then(function(val) { promise.$$v = val; }); -      } -      pathVal = pathVal.$$v; -    } -    if (!key3 || pathVal === null || pathVal === undefined) return pathVal; - -    pathVal = pathVal[key3]; -    if (pathVal && pathVal.then) { -      if (!("$$v" in pathVal)) { -        promise = pathVal; -        promise.$$v = undefined; -        promise.then(function(val) { promise.$$v = val; }); -      } -      pathVal = pathVal.$$v; -    } -    if (!key4 || pathVal === null || pathVal === undefined) return pathVal; - -    pathVal = pathVal[key4]; -    if (pathVal && pathVal.then) { -      if (!("$$v" in pathVal)) { -        promise = pathVal; -        promise.$$v = undefined; -        promise.then(function(val) { promise.$$v = val; }); -      } -      pathVal = pathVal.$$v; -    } -    return pathVal; -  }; + +  return !options.unwrapPromises +      ? function cspSafeGetter(scope, locals) { +          var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; + +          if (pathVal === null || pathVal === undefined) return pathVal; +          pathVal = pathVal[key0]; + +          if (!key1 || pathVal === null || pathVal === undefined) return pathVal; +          pathVal = pathVal[key1]; + +          if (!key2 || pathVal === null || pathVal === undefined) return pathVal; +          pathVal = pathVal[key2]; + +          if (!key3 || pathVal === null || pathVal === undefined) return pathVal; +          pathVal = pathVal[key3]; + +          if (!key4 || pathVal === null || pathVal === undefined) return pathVal; +          pathVal = pathVal[key4]; + +          return pathVal; +        } +      : function cspSafePromiseEnabledGetter(scope, locals) { +          var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, +              promise; + +          if (pathVal === null || pathVal === undefined) return pathVal; + +          pathVal = pathVal[key0]; +          if (pathVal && pathVal.then) { +            promiseWarning(fullExp); +            if (!("$$v" in pathVal)) { +              promise = pathVal; +              promise.$$v = undefined; +              promise.then(function(val) { promise.$$v = val; }); +            } +            pathVal = pathVal.$$v; +          } +          if (!key1 || pathVal === null || pathVal === undefined) return pathVal; + +          pathVal = pathVal[key1]; +          if (pathVal && pathVal.then) { +            promiseWarning(fullExp); +            if (!("$$v" in pathVal)) { +              promise = pathVal; +              promise.$$v = undefined; +              promise.then(function(val) { promise.$$v = val; }); +            } +            pathVal = pathVal.$$v; +          } +          if (!key2 || pathVal === null || pathVal === undefined) return pathVal; + +          pathVal = pathVal[key2]; +          if (pathVal && pathVal.then) { +            promiseWarning(fullExp); +            if (!("$$v" in pathVal)) { +              promise = pathVal; +              promise.$$v = undefined; +              promise.then(function(val) { promise.$$v = val; }); +            } +            pathVal = pathVal.$$v; +          } +          if (!key3 || pathVal === null || pathVal === undefined) return pathVal; + +          pathVal = pathVal[key3]; +          if (pathVal && pathVal.then) { +            promiseWarning(fullExp); +            if (!("$$v" in pathVal)) { +              promise = pathVal; +              promise.$$v = undefined; +              promise.then(function(val) { promise.$$v = val; }); +            } +            pathVal = pathVal.$$v; +          } +          if (!key4 || pathVal === null || pathVal === undefined) return pathVal; + +          pathVal = pathVal[key4]; +          if (pathVal && pathVal.then) { +            promiseWarning(fullExp); +            if (!("$$v" in pathVal)) { +              promise = pathVal; +              promise.$$v = undefined; +              promise.then(function(val) { promise.$$v = val; }); +            } +            pathVal = pathVal.$$v; +          } +          return pathVal; +        }  } -function getterFn(path, csp, fullExp) { +function getterFn(path, options, fullExp) {    // Check whether the cache has this getter already.    // We can use hasOwnProperty directly on the cache because we ensure,    // see below, that the cache never stores a path called 'hasOwnProperty' @@ -950,14 +984,14 @@ function getterFn(path, csp, fullExp) {        pathKeysLength = pathKeys.length,        fn; -  if (csp) { +  if (options.csp) {      fn = (pathKeysLength < 6) -        ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp) +        ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, options)          : function(scope, locals) {            var i = 0, val;            do {              val = cspSafeGetterFn( -                    pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], fullExp +                    pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], fullExp, options                    )(scope, locals);              locals = undefined; // clear after first iteration @@ -976,18 +1010,25 @@ function getterFn(path, csp, fullExp) {                        ? 's'                        // but if we are first then we check locals first, and if so read it first                        : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + -              'if (s && s.then) {\n' + -                ' if (!("$$v" in s)) {\n' + -                  ' p=s;\n' + -                  ' p.$$v = undefined;\n' + -                  ' p.then(function(v) {p.$$v=v;});\n' + -                  '}\n' + -                ' s=s.$$v\n' + -              '}\n'; +              (options.unwrapPromises +                ? 'if (s && s.then) {\n' + +                  ' pw("' + fullExp.replace(/\"/g, '\\"') + '");\n' + +                  ' if (!("$$v" in s)) {\n' + +                    ' p=s;\n' + +                    ' p.$$v = undefined;\n' + +                    ' p.then(function(v) {p.$$v=v;});\n' + +                    '}\n' + +                  ' s=s.$$v\n' + +                '}\n' +                : '');      });      code += 'return s;'; -    fn = Function('s', 'k', code); // s=scope, k=locals -    fn.toString = function() { return code; }; + +    var evaledFnGetter = Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning +    evaledFnGetter.toString = function() { return code; }; +    fn = function(scope, locals) { +      return evaledFnGetter(scope, locals, promiseWarning); +    };    }    // Only cache the value if it's not going to mess up the cache object @@ -1039,20 +1080,125 @@ function getterFn(path, csp, fullExp) {   *        set to a function to change its value on the given context.   *   */ + + +/** + * @ngdoc object + * @name ng.$parseProvider + * @function + * + * @description + * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} service. + */  function $ParseProvider() {    var cache = {}; -  this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { + +  var $parseOptions = { +    csp: false, +    unwrapPromises: false, +    logPromiseWarnings: true +  }; + + +  /** +   * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. +   * +   * @ngdoc method +   * @name ng.$parseProvider#unwrapPromises +   * @methodOf ng.$parseProvider +   * @description +   * +   * **This feature is deprecated, see deprecation notes below for more info** +   * +   * If set to true (default is false), $parse will unwrap promises automatically when a promise is found at any part of +   * the expression. In other words, if set to true, the expression will always result in a non-promise value. +   * +   * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, the fulfillment value +   * is used in place of the promise while evaluating the expression. +   * +   * **Deprecation notice** +   * +   * This is a feature that didn't prove to be wildly useful or popular, primarily because of the dichotomy between data +   * access in templates (accessed as raw values) and controller code (accessed as promises). +   * +   * In most code we ended up resolving promises manually in controllers anyway and thus unifying the model access there. +   * +   * Other downsides of automatic promise unwrapping: +   * +   * - when building components it's often desirable to receive the raw promises +   * - adds complexity and slows down expression evaluation +   * - makes expression code pre-generation unattractive due to the amount of code that needs to be generated +   * - makes IDE auto-completion and tool support hard +   * +   * **Warning Logs** +   * +   * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a promise (to reduce +   * the noise, each expression is logged only once). To disable this logging use +   * `$parseProvider.logPromiseWarnings(false)` api. +   * +   * +   * @param {boolean=} value New value. +   * @returns {boolean|self} Returns the current setting when used as getter and self if used as setter. +   */ +  this.unwrapPromises = function(value) { +    if (isDefined(value)) { +      $parseOptions.unwrapPromises = !!value; +      return this; +    } else { +      return $parseOptions.unwrapPromises; +    } +  }; + + +  /** +   * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. +   * +   * @ngdoc method +   * @name ng.$parseProvider#logPromiseWarnings +   * @methodOf ng.$parseProvider +   * @description +   * +   * Controls whether Angular should log a warning on any encounter of a promise in an expression. +   * +   * The default is set to `true`. +   * +   * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. +   * +   * @param {boolean=} value New value. +   * @returns {boolean|self} Returns the current setting when used as getter and self if used as setter. +   */ + this.logPromiseWarnings = function(value) { +    if (isDefined(value)) { +      $parseOptions.logPromiseWarnings = value; +      return this; +    } else { +      return $parseOptions.logPromiseWarnings; +    } +  }; + + +  this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { +    $parseOptions.csp = $sniffer.csp; + +    promiseWarning = function promiseWarningFn(fullExp) { +      if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; +      promiseWarningCache[fullExp] = true; +      $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + +          'Automatic unwrapping of promises in Angular expressions is deprecated.'); +    }; +      return function(exp) {        var parsedExpression;        switch (typeof exp) {          case 'string': +            if (cache.hasOwnProperty(exp)) {              return cache[exp];            } -          var lexer = new Lexer($sniffer.csp); -          var parser = new Parser(lexer, $filter, $sniffer.csp); +          var lexer = new Lexer($parseOptions); +          var parser = new Parser(lexer, $filter, $parseOptions);            parsedExpression = parser.parse(exp, false);            if (exp !== 'hasOwnProperty') { diff --git a/test/ng/parseSpec.js b/test/ng/parseSpec.js index 19182332..277178a1 100644 --- a/test/ng/parseSpec.js +++ b/test/ng/parseSpec.js @@ -1,12 +1,20 @@  'use strict';  describe('parser', function() { + +  beforeEach(function() { +    // clear caches +    getterFnCache = {}; +    promiseWarningCache = {}; +  }); + +    describe('lexer', function() {      var lex;      beforeEach(function () {        lex = function () { -        var lexer = new Lexer(); +        var lexer = new Lexer({csp: false, unwrapPromises: false});          return lexer.lex.apply(lexer, arguments);        };      }); @@ -198,7 +206,6 @@ describe('parser', function() {        beforeEach(inject(function ($rootScope, $sniffer) {          scope = $rootScope;          $sniffer.csp = cspEnabled; -        getterFnCache = {}; // clear cache        })); @@ -854,15 +861,228 @@ describe('parser', function() {        }); -      describe('promises', function() { -        var deferred, promise, q; +      describe('assignable', function() { +        it('should expose assignment function', inject(function($parse) { +          var fn = $parse('a'); +          expect(fn.assign).toBeTruthy(); +          var scope = {}; +          fn.assign(scope, 123); +          expect(scope).toEqual({a:123}); +        })); +      }); + + +      describe('locals', function() { +        it('should expose local variables', inject(function($parse) { +          expect($parse('a')({a: 0}, {a: 1})).toEqual(1); +          expect($parse('add(a,b)')({b: 1, add: function(a, b) { return a + b; }}, {a: 2})).toEqual(3); +        })); + +        it('should expose traverse locals', inject(function($parse) { +          expect($parse('a.b')({a: {b: 0}}, {a: {b:1}})).toEqual(1); +          expect($parse('a.b')({a: null}, {a: {b:1}})).toEqual(1); +          expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined); +        })); +      }); + +      describe('literal', function() { +        it('should mark scalar value expressions as literal', inject(function($parse) { +          expect($parse('0').literal).toBe(true); +          expect($parse('"hello"').literal).toBe(true); +          expect($parse('true').literal).toBe(true); +          expect($parse('false').literal).toBe(true); +          expect($parse('null').literal).toBe(true); +          expect($parse('undefined').literal).toBe(true); +        })); + +        it('should mark array expressions as literal', inject(function($parse) { +          expect($parse('[]').literal).toBe(true); +          expect($parse('[1, 2, 3]').literal).toBe(true); +          expect($parse('[1, identifier]').literal).toBe(true); +        })); + +        it('should mark object expressions as literal', inject(function($parse) { +          expect($parse('{}').literal).toBe(true); +          expect($parse('{x: 1}').literal).toBe(true); +          expect($parse('{foo: bar}').literal).toBe(true); +        })); + +        it('should not mark function calls or operator expressions as literal', inject(function($parse) { +          expect($parse('1 + 1').literal).toBe(false); +          expect($parse('call()').literal).toBe(false); +          expect($parse('[].length').literal).toBe(false); +        })); +      }); + +      describe('constant', function() { +        it('should mark scalar value expressions as constant', inject(function($parse) { +          expect($parse('12.3').constant).toBe(true); +          expect($parse('"string"').constant).toBe(true); +          expect($parse('true').constant).toBe(true); +          expect($parse('false').constant).toBe(true); +          expect($parse('null').constant).toBe(true); +          expect($parse('undefined').constant).toBe(true); +        })); + +        it('should mark arrays as constant if they only contain constant elements', inject(function($parse) { +          expect($parse('[]').constant).toBe(true); +          expect($parse('[1, 2, 3]').constant).toBe(true); +          expect($parse('["string", null]').constant).toBe(true); +          expect($parse('[[]]').constant).toBe(true); +          expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true); +        })); + +        it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) { +          expect($parse('[foo]').constant).toBe(false); +          expect($parse('[x + 1]').constant).toBe(false); +          expect($parse('[bar[0]]').constant).toBe(false); +        })); + +        it('should mark complex expressions involving constant values as constant', inject(function($parse) { +          expect($parse('!true').constant).toBe(true); +          expect($parse('1 - 1').constant).toBe(true); +          expect($parse('"foo" + "bar"').constant).toBe(true); +          expect($parse('5 != null').constant).toBe(true); +          expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true); +        })); + +        it('should not mark any expression involving variables or function calls as constant', inject(function($parse) { +          expect($parse('true.toString()').constant).toBe(false); +          expect($parse('foo(1, 2, 3)').constant).toBe(false); +          expect($parse('"name" + id').constant).toBe(false); +        })); +      }); +    }); +  }); + + +  describe('promises', function() { + +    var deferred, promise, q; + +    describe('unwrapPromises setting', function () { + +      beforeEach(inject(function($rootScope, $q) { +        scope = $rootScope; + +        $rootScope.$apply(function() { +          deferred = $q.defer(); +          deferred.resolve('Bobo'); +          promise = deferred.promise; +        }); +      })); + +      it('should not unwrap promises by default', inject(function ($parse) { +        scope.person = promise; +        scope.things = {person: promise}; +        scope.getPerson = function () { return promise; }; + +        var getter = $parse('person'); +        var propGetter = $parse('things.person'); +        var fnGetter = $parse('getPerson()'); + +        expect(getter(scope)).toBe(promise); +        expect(propGetter(scope)).toBe(promise); +        expect(fnGetter(scope)).toBe(promise); +      })); +    }); + + +    forEach([true, false], function(cspEnabled) { + +      describe('promise logging (csp:' + cspEnabled + ')', function() { + +        var $log; +        var PROMISE_WARNING_REGEXP = /\[\$parse\] Promise found in the expression `[^`]+`. Automatic unwrapping of promises in Angular expressions is deprecated\./; + +        beforeEach(module(function($parseProvider) { +          $parseProvider.unwrapPromises(true); +        })); + +        beforeEach(inject(function($rootScope, $q, _$log_) { +          scope = $rootScope; + +          $rootScope.$apply(function() { +            deferred = $q.defer(); +            deferred.resolve('Bobo'); +            promise = deferred.promise; +          }); + +          $log = _$log_; +        })); + +        it('should log warnings by default', function() { +          scope.person = promise; +          scope.$eval('person'); +          expect($log.warn.logs.pop()).toEqual(['[$parse] Promise found in the expression `person`. ' + +              'Automatic unwrapping of promises in Angular expressions is deprecated.']); +        }); + + +        it('should log warnings for deep promises', function() { +          scope.car = {wheel: {disc: promise}}; +          scope.$eval('car.wheel.disc.pad'); +          expect($log.warn.logs.pop()).toMatch(PROMISE_WARNING_REGEXP); +        }); + + +        it('should log warnings for setters', function() { +          scope.person = promise; +          scope.$eval('person.name = "Bubu"'); +          expect($log.warn.logs.pop()).toMatch(PROMISE_WARNING_REGEXP); +        }); + + +        it('should log only a single warning for each expression', function() { +          scope.person1 = promise; +          scope.person2 = promise; + +          scope.$eval('person1'); +          scope.$eval('person1'); +          expect($log.warn.logs.pop()).toMatch(/`person1`/); +          expect($log.warn.logs).toEqual([]); + +          scope.$eval('person1'); +          scope.$eval('person2'); +          scope.$eval('person1'); +          scope.$eval('person2'); +          expect($log.warn.logs.pop()).toMatch(/`person2`/); +          expect($log.warn.logs).toEqual([]); +        }); + + +        it('should log warning for complex expressions', function() { +          scope.person1 = promise; +          scope.person2 = promise; + +          scope.$eval('person1 + person2'); +          expect($log.warn.logs.pop()).toMatch(/`person1 \+ person2`/); +          expect($log.warn.logs).toEqual([]); +        }); +      }); +    }); + + +    forEach([true, false], function(cspEnabled) { + +      describe('csp ' + cspEnabled, function() { + +        beforeEach(module(function($parseProvider) { +          $parseProvider.unwrapPromises(true); +          $parseProvider.logPromiseWarnings(false); +        })); + + +        beforeEach(inject(function($rootScope, $sniffer, $q) { +          scope = $rootScope; +          $sniffer.csp = cspEnabled; -        beforeEach(inject(function($q) {            q = $q;            deferred = q.defer();            promise = deferred.promise;          })); +          describe('{{promise}}', function() {            it('should evaluated resolved promise and get its value', function() {              deferred.resolve('hello!'); @@ -876,7 +1096,7 @@ describe('parser', function() {            it('should evaluated rejected promise and ignore the rejection reason', function() {              deferred.reject('sorry');              scope.greeting = promise; -            expect(scope.$eval('gretting')).toBe(undefined); +            expect(scope.$eval('greeting')).toBe(undefined);              scope.$digest();              expect(scope.$eval('greeting')).toBe(undefined);            }); @@ -929,7 +1149,7 @@ describe('parser', function() {                scope.person = promise;                deferred.resolve({'name': 'Bill Gates'}); -              var getter = $parse('person.name'); +              var getter = $parse('person.name', { unwrapPromises: true });                expect(getter(scope)).toBe(undefined);                scope.$digest(); @@ -943,7 +1163,7 @@ describe('parser', function() {                scope.greeting = promise;                deferred.resolve('Salut!'); -              var getter = $parse('greeting'); +              var getter = $parse('greeting', { unwrapPromises: true });                expect(getter(scope)).toBe(undefined);                scope.$digest(); @@ -957,7 +1177,7 @@ describe('parser', function() {              it('should evaluate an unresolved promise and set and remember its value', inject(function($parse) {                scope.person = promise; -              var getter = $parse('person.name'); +              var getter = $parse('person.name', { unwrapPromises: true });                expect(getter(scope)).toBe(undefined);                scope.$digest(); @@ -968,7 +1188,7 @@ describe('parser', function() {                expect(getter(scope)).toBe('Bonjour'); -              var c1Getter = $parse('person.A.B.C1'); +              var c1Getter = $parse('person.A.B.C1', { unwrapPromises: true });                scope.$digest();                expect(c1Getter(scope)).toBe(undefined);                c1Getter.assign(scope, 'c1_value'); @@ -976,7 +1196,7 @@ describe('parser', function() {                expect(c1Getter(scope)).toBe('c1_value');                // Set another property on the person.A.B -              var c2Getter = $parse('person.A.B.C2'); +              var c2Getter = $parse('person.A.B.C2', { unwrapPromises: true });                scope.$digest();                expect(c2Getter(scope)).toBe(undefined);                c2Getter.assign(scope, 'c2_value'); @@ -984,15 +1204,15 @@ describe('parser', function() {                expect(c2Getter(scope)).toBe('c2_value');                // c1 should be unchanged. -              expect($parse('person.A')(scope)).toEqual( +              expect($parse('person.A', { unwrapPromises: true })(scope)).toEqual(                    {B: {C1: 'c1_value', C2: 'c2_value'}});              }));              it('should evaluate a resolved promise and overwrite the previous set value in the absense of the getter', -               inject(function($parse) { +                inject(function($parse) {                scope.person = promise; -              var c1Getter = $parse('person.A.B.C1'); +              var c1Getter = $parse('person.A.B.C1', { unwrapPromises: true });                c1Getter.assign(scope, 'c1_value');                // resolving the promise should update the tree.                deferred.resolve({A: {B: {C1: 'resolved_c1'}}}); @@ -1037,19 +1257,19 @@ describe('parser', function() {            it('should evaluate and dereference array references leading to and from a promise',                function() { -            scope.greetings = [promise]; -            expect(scope.$eval('greetings[0]')).toBe(undefined); -            expect(scope.$eval('greetings[0][0]')).toBe(undefined); +                scope.greetings = [promise]; +                expect(scope.$eval('greetings[0]')).toBe(undefined); +                expect(scope.$eval('greetings[0][0]')).toBe(undefined); -            scope.$digest(); -            expect(scope.$eval('greetings[0]')).toBe(undefined); -            expect(scope.$eval('greetings[0][0]')).toBe(undefined); +                scope.$digest(); +                expect(scope.$eval('greetings[0]')).toBe(undefined); +                expect(scope.$eval('greetings[0][0]')).toBe(undefined); -            deferred.resolve(['Hi!', 'Cau!']); -            scope.$digest(); -            expect(scope.$eval('greetings[0]')).toEqual(['Hi!', 'Cau!']); -            expect(scope.$eval('greetings[0][0]')).toBe('Hi!'); -          }); +                deferred.resolve(['Hi!', 'Cau!']); +                scope.$digest(); +                expect(scope.$eval('greetings[0]')).toEqual(['Hi!', 'Cau!']); +                expect(scope.$eval('greetings[0][0]')).toBe('Hi!'); +              });            it('should evaluate and dereference promises used as function arguments', function() { @@ -1109,99 +1329,6 @@ describe('parser', function() {            });          });        }); - - -      describe('assignable', function() { -        it('should expose assignment function', inject(function($parse) { -          var fn = $parse('a'); -          expect(fn.assign).toBeTruthy(); -          var scope = {}; -          fn.assign(scope, 123); -          expect(scope).toEqual({a:123}); -        })); -      }); - - -      describe('locals', function() { -        it('should expose local variables', inject(function($parse) { -          expect($parse('a')({a: 0}, {a: 1})).toEqual(1); -          expect($parse('add(a,b)')({b: 1, add: function(a, b) { return a + b; }}, {a: 2})).toEqual(3); -        })); - -        it('should expose traverse locals', inject(function($parse) { -          expect($parse('a.b')({a: {b: 0}}, {a: {b:1}})).toEqual(1); -          expect($parse('a.b')({a: null}, {a: {b:1}})).toEqual(1); -          expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined); -        })); -      }); - -      describe('literal', function() { -        it('should mark scalar value expressions as literal', inject(function($parse) { -          expect($parse('0').literal).toBe(true); -          expect($parse('"hello"').literal).toBe(true); -          expect($parse('true').literal).toBe(true); -          expect($parse('false').literal).toBe(true); -          expect($parse('null').literal).toBe(true); -          expect($parse('undefined').literal).toBe(true); -        })); - -        it('should mark array expressions as literal', inject(function($parse) { -          expect($parse('[]').literal).toBe(true); -          expect($parse('[1, 2, 3]').literal).toBe(true); -          expect($parse('[1, identifier]').literal).toBe(true); -        })); - -        it('should mark object expressions as literal', inject(function($parse) { -          expect($parse('{}').literal).toBe(true); -          expect($parse('{x: 1}').literal).toBe(true); -          expect($parse('{foo: bar}').literal).toBe(true); -        })); - -        it('should not mark function calls or operator expressions as literal', inject(function($parse) { -          expect($parse('1 + 1').literal).toBe(false); -          expect($parse('call()').literal).toBe(false); -          expect($parse('[].length').literal).toBe(false); -        })); -      }); - -      describe('constant', function() { -        it('should mark scalar value expressions as constant', inject(function($parse) { -          expect($parse('12.3').constant).toBe(true); -          expect($parse('"string"').constant).toBe(true); -          expect($parse('true').constant).toBe(true); -          expect($parse('false').constant).toBe(true); -          expect($parse('null').constant).toBe(true); -          expect($parse('undefined').constant).toBe(true); -        })); - -        it('should mark arrays as constant if they only contain constant elements', inject(function($parse) { -          expect($parse('[]').constant).toBe(true); -          expect($parse('[1, 2, 3]').constant).toBe(true); -          expect($parse('["string", null]').constant).toBe(true); -          expect($parse('[[]]').constant).toBe(true); -          expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true); -        })); - -        it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) { -          expect($parse('[foo]').constant).toBe(false); -          expect($parse('[x + 1]').constant).toBe(false); -          expect($parse('[bar[0]]').constant).toBe(false); -        })); - -        it('should mark complex expressions involving constant values as constant', inject(function($parse) { -          expect($parse('!true').constant).toBe(true); -          expect($parse('1 - 1').constant).toBe(true); -          expect($parse('"foo" + "bar"').constant).toBe(true); -          expect($parse('5 != null').constant).toBe(true); -          expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true); -        })); - -        it('should not mark any expression involving variables or function calls as constant', inject(function($parse) { -          expect($parse('true.toString()').constant).toBe(false); -          expect($parse('foo(1, 2, 3)').constant).toBe(false); -          expect($parse('"name" + id').constant).toBe(false); -        })); -      });      });    });  }); | 
