PageRenderTime 1263ms CodeModel.GetById 151ms app.highlight 628ms RepoModel.GetById 149ms app.codeStats 0ms

/test/ng/parseSpec.js

https://github.com/myitcv/angular.js
JavaScript | 1279 lines | 1257 code | 19 blank | 3 comment | 5 complexity | 120e1783f0642149821de64462822e95 MD5 | raw file
   1'use strict';
   2
   3describe('parser', function() {
   4
   5  beforeEach(function() {
   6    /* global getterFnCache: true */
   7    // clear cache
   8    getterFnCache = {};
   9  });
  10
  11
  12  describe('lexer', function() {
  13    var lex;
  14
  15    beforeEach(function () {
  16      /* global Lexer: false */
  17      lex = function () {
  18        var lexer = new Lexer({csp: false});
  19        return lexer.lex.apply(lexer, arguments);
  20      };
  21    });
  22
  23    it('should tokenize a string', function() {
  24      var tokens = lex("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\"");
  25      var i = 0;
  26      expect(tokens[i].index).toEqual(0);
  27      expect(tokens[i].text).toEqual('a.bc');
  28
  29      i++;
  30      expect(tokens[i].index).toEqual(4);
  31      expect(tokens[i].text).toEqual('[');
  32
  33      i++;
  34      expect(tokens[i].index).toEqual(5);
  35      expect(tokens[i].text).toEqual(22);
  36
  37      i++;
  38      expect(tokens[i].index).toEqual(7);
  39      expect(tokens[i].text).toEqual(']');
  40
  41      i++;
  42      expect(tokens[i].index).toEqual(8);
  43      expect(tokens[i].text).toEqual('+');
  44
  45      i++;
  46      expect(tokens[i].index).toEqual(9);
  47      expect(tokens[i].text).toEqual(1.3);
  48
  49      i++;
  50      expect(tokens[i].index).toEqual(12);
  51      expect(tokens[i].text).toEqual('|');
  52
  53      i++;
  54      expect(tokens[i].index).toEqual(13);
  55      expect(tokens[i].text).toEqual('f');
  56
  57      i++;
  58      expect(tokens[i].index).toEqual(14);
  59      expect(tokens[i].text).toEqual(':');
  60
  61      i++;
  62      expect(tokens[i].index).toEqual(15);
  63      expect(tokens[i].string).toEqual("a'c");
  64
  65      i++;
  66      expect(tokens[i].index).toEqual(21);
  67      expect(tokens[i].text).toEqual(':');
  68
  69      i++;
  70      expect(tokens[i].index).toEqual(22);
  71      expect(tokens[i].string).toEqual('d"e');
  72    });
  73
  74    it('should tokenize undefined', function() {
  75      var tokens = lex("undefined");
  76      var i = 0;
  77      expect(tokens[i].index).toEqual(0);
  78      expect(tokens[i].text).toEqual('undefined');
  79      expect(undefined).toEqual(tokens[i].fn());
  80    });
  81
  82    it('should tokenize quoted string', function() {
  83      var str = "['\\'', \"\\\"\"]";
  84      var tokens = lex(str);
  85
  86      expect(tokens[1].index).toEqual(1);
  87      expect(tokens[1].string).toEqual("'");
  88
  89      expect(tokens[3].index).toEqual(7);
  90      expect(tokens[3].string).toEqual('"');
  91    });
  92
  93    it('should tokenize escaped quoted string', function() {
  94      var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
  95      var tokens = lex(str);
  96
  97      expect(tokens[0].string).toEqual('"\n\f\r\t\v\u00A0');
  98    });
  99
 100    it('should tokenize unicode', function() {
 101      var tokens = lex('"\\u00A0"');
 102      expect(tokens.length).toEqual(1);
 103      expect(tokens[0].string).toEqual('\u00a0');
 104    });
 105
 106    it('should ignore whitespace', function() {
 107      var tokens = lex("a \t \n \r b");
 108      expect(tokens[0].text).toEqual('a');
 109      expect(tokens[1].text).toEqual('b');
 110    });
 111
 112    it('should tokenize relation and equality', function() {
 113      var tokens = lex("! == != < > <= >= === !==");
 114      expect(tokens[0].text).toEqual('!');
 115      expect(tokens[1].text).toEqual('==');
 116      expect(tokens[2].text).toEqual('!=');
 117      expect(tokens[3].text).toEqual('<');
 118      expect(tokens[4].text).toEqual('>');
 119      expect(tokens[5].text).toEqual('<=');
 120      expect(tokens[6].text).toEqual('>=');
 121      expect(tokens[7].text).toEqual('===');
 122      expect(tokens[8].text).toEqual('!==');
 123    });
 124
 125    it('should tokenize logical and ternary', function() {
 126      var tokens = lex("&& || ? :");
 127      expect(tokens[0].text).toEqual('&&');
 128      expect(tokens[1].text).toEqual('||');
 129      expect(tokens[2].text).toEqual('?');
 130      expect(tokens[3].text).toEqual(':');
 131    });
 132
 133    it('should tokenize statements', function() {
 134      var tokens = lex("a;b;");
 135      expect(tokens[0].text).toEqual('a');
 136      expect(tokens[1].text).toEqual(';');
 137      expect(tokens[2].text).toEqual('b');
 138      expect(tokens[3].text).toEqual(';');
 139    });
 140
 141    it('should tokenize function invocation', function() {
 142      var tokens = lex("a()");
 143      expect(map(tokens, function(t) { return t.text;})).toEqual(['a', '(', ')']);
 144    });
 145
 146    it('should tokenize method invocation', function() {
 147      var tokens = lex("a.b.c (d) - e.f()");
 148      expect(map(tokens, function(t) { return t.text;})).
 149          toEqual(['a.b', '.', 'c',  '(', 'd', ')', '-', 'e', '.', 'f', '(', ')']);
 150    });
 151
 152    it('should tokenize number', function() {
 153      var tokens = lex("0.5");
 154      expect(tokens[0].text).toEqual(0.5);
 155    });
 156
 157    it('should tokenize negative number', inject(function($rootScope) {
 158      var value = $rootScope.$eval("-0.5");
 159      expect(value).toEqual(-0.5);
 160
 161      value = $rootScope.$eval("{a:-0.5}");
 162      expect(value).toEqual({a:-0.5});
 163    }));
 164
 165    it('should tokenize number with exponent', inject(function($rootScope) {
 166      var tokens = lex("0.5E-10");
 167      expect(tokens[0].text).toEqual(0.5E-10);
 168      expect($rootScope.$eval("0.5E-10")).toEqual(0.5E-10);
 169
 170      tokens = lex("0.5E+10");
 171      expect(tokens[0].text).toEqual(0.5E+10);
 172    }));
 173
 174    it('should throws exception for invalid exponent', function() {
 175      expect(function() {
 176        lex("0.5E-");
 177      }).toThrowMinErr('$parse', 'lexerr', 'Lexer Error: Invalid exponent at column 4 in expression [0.5E-].');
 178
 179      expect(function() {
 180        lex("0.5E-A");
 181      }).toThrowMinErr('$parse', 'lexerr', 'Lexer Error: Invalid exponent at column 4 in expression [0.5E-A].');
 182    });
 183
 184    it('should tokenize number starting with a dot', function() {
 185      var tokens = lex(".5");
 186      expect(tokens[0].text).toEqual(0.5);
 187    });
 188
 189    it('should throw error on invalid unicode', function() {
 190      expect(function() {
 191        lex("'\\u1''bla'");
 192      }).toThrowMinErr("$parse", "lexerr", "Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla'].");
 193    });
 194  });
 195
 196  var $filterProvider, scope;
 197
 198  beforeEach(module(['$filterProvider', function (filterProvider) {
 199    $filterProvider = filterProvider;
 200  }]));
 201
 202
 203  forEach([true, false], function(cspEnabled) {
 204    describe('csp: ' + cspEnabled, function() {
 205
 206      var originalSecurityPolicy;
 207
 208
 209      beforeEach(function() {
 210        originalSecurityPolicy = window.document.securityPolicy;
 211        window.document.securityPolicy = {isActive : cspEnabled};
 212      });
 213
 214      afterEach(function() {
 215        window.document.securityPolicy = originalSecurityPolicy;
 216      });
 217
 218      beforeEach(inject(function ($rootScope) {
 219        scope = $rootScope;
 220      }));
 221
 222      it('should parse expressions', function() {
 223        /*jshint -W006, -W007 */
 224        expect(scope.$eval("-1")).toEqual(-1);
 225        expect(scope.$eval("1 + 2.5")).toEqual(3.5);
 226        expect(scope.$eval("1 + -2.5")).toEqual(-1.5);
 227        expect(scope.$eval("1+2*3/4")).toEqual(1+2*3/4);
 228        expect(scope.$eval("0--1+1.5")).toEqual(0- -1 + 1.5);
 229        expect(scope.$eval("-0--1++2*-3/-4")).toEqual(-0- -1+ +2*-3/-4);
 230        expect(scope.$eval("1/2*3")).toEqual(1/2*3);
 231      });
 232
 233      it('should parse comparison', function() {
 234        /* jshint -W041 */
 235        expect(scope.$eval("false")).toBeFalsy();
 236        expect(scope.$eval("!true")).toBeFalsy();
 237        expect(scope.$eval("1==1")).toBeTruthy();
 238        expect(scope.$eval("1==true")).toBeTruthy();
 239        expect(scope.$eval("1===1")).toBeTruthy();
 240        expect(scope.$eval("1==='1'")).toBeFalsy();
 241        expect(scope.$eval("1===true")).toBeFalsy();
 242        expect(scope.$eval("'true'===true")).toBeFalsy();
 243        expect(scope.$eval("1!==2")).toBeTruthy();
 244        expect(scope.$eval("1!=='1'")).toBeTruthy();
 245        expect(scope.$eval("1!=2")).toBeTruthy();
 246        expect(scope.$eval("1<2")).toBeTruthy();
 247        expect(scope.$eval("1<=1")).toBeTruthy();
 248        expect(scope.$eval("1>2")).toEqual(1>2);
 249        expect(scope.$eval("2>=1")).toEqual(2>=1);
 250        expect(scope.$eval("true==2<3")).toEqual(true == 2<3);
 251        expect(scope.$eval("true===2<3")).toEqual(true === 2<3);
 252      });
 253
 254      it('should parse logical', function() {
 255        expect(scope.$eval("0&&2")).toEqual(0&&2);
 256        expect(scope.$eval("0||2")).toEqual(0||2);
 257        expect(scope.$eval("0||1&&2")).toEqual(0||1&&2);
 258      });
 259
 260      it('should parse ternary', function(){
 261        var returnTrue = scope.returnTrue = function(){ return true; };
 262        var returnFalse = scope.returnFalse = function(){ return false; };
 263        var returnString = scope.returnString = function(){ return 'asd'; };
 264        var returnInt = scope.returnInt = function(){ return 123; };
 265        var identity = scope.identity = function(x){ return x; };
 266
 267        // Simple.
 268        expect(scope.$eval('0?0:2')).toEqual(0?0:2);
 269        expect(scope.$eval('1?0:2')).toEqual(1?0:2);
 270
 271        // Nested on the left.
 272        expect(scope.$eval('0?0?0:0:2')).toEqual(0?0?0:0:2);
 273        expect(scope.$eval('1?0?0:0:2')).toEqual(1?0?0:0:2);
 274        expect(scope.$eval('0?1?0:0:2')).toEqual(0?1?0:0:2);
 275        expect(scope.$eval('0?0?1:0:2')).toEqual(0?0?1:0:2);
 276        expect(scope.$eval('0?0?0:2:3')).toEqual(0?0?0:2:3);
 277        expect(scope.$eval('1?1?0:0:2')).toEqual(1?1?0:0:2);
 278        expect(scope.$eval('1?1?1:0:2')).toEqual(1?1?1:0:2);
 279        expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3);
 280        expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3);
 281
 282        // Nested on the right.
 283        expect(scope.$eval('0?0:0?0:2')).toEqual(0?0:0?0:2);
 284        expect(scope.$eval('1?0:0?0:2')).toEqual(1?0:0?0:2);
 285        expect(scope.$eval('0?1:0?0:2')).toEqual(0?1:0?0:2);
 286        expect(scope.$eval('0?0:1?0:2')).toEqual(0?0:1?0:2);
 287        expect(scope.$eval('0?0:0?2:3')).toEqual(0?0:0?2:3);
 288        expect(scope.$eval('1?1:0?0:2')).toEqual(1?1:0?0:2);
 289        expect(scope.$eval('1?1:1?0:2')).toEqual(1?1:1?0:2);
 290        expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3);
 291        expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3);
 292
 293        // Precedence with respect to logical operators.
 294        expect(scope.$eval('0&&1?0:1')).toEqual(0&&1?0:1);
 295        expect(scope.$eval('1||0?0:0')).toEqual(1||0?0:0);
 296
 297        expect(scope.$eval('0?0&&1:2')).toEqual(0?0&&1:2);
 298        expect(scope.$eval('0?1&&1:2')).toEqual(0?1&&1:2);
 299        expect(scope.$eval('0?0||0:1')).toEqual(0?0||0:1);
 300        expect(scope.$eval('0?0||1:2')).toEqual(0?0||1:2);
 301
 302        expect(scope.$eval('1?0&&1:2')).toEqual(1?0&&1:2);
 303        expect(scope.$eval('1?1&&1:2')).toEqual(1?1&&1:2);
 304        expect(scope.$eval('1?0||0:1')).toEqual(1?0||0:1);
 305        expect(scope.$eval('1?0||1:2')).toEqual(1?0||1:2);
 306
 307        expect(scope.$eval('0?1:0&&1')).toEqual(0?1:0&&1);
 308        expect(scope.$eval('0?2:1&&1')).toEqual(0?2:1&&1);
 309        expect(scope.$eval('0?1:0||0')).toEqual(0?1:0||0);
 310        expect(scope.$eval('0?2:0||1')).toEqual(0?2:0||1);
 311
 312        expect(scope.$eval('1?1:0&&1')).toEqual(1?1:0&&1);
 313        expect(scope.$eval('1?2:1&&1')).toEqual(1?2:1&&1);
 314        expect(scope.$eval('1?1:0||0')).toEqual(1?1:0||0);
 315        expect(scope.$eval('1?2:0||1')).toEqual(1?2:0||1);
 316
 317        // Function calls.
 318        expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt());
 319        expect(scope.$eval('returnFalse() ? returnString() : returnInt()')).toEqual(returnFalse() ? returnString() : returnInt());
 320        expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt());
 321        expect(scope.$eval('identity(returnFalse() ? returnString() : returnInt())')).toEqual(identity(returnFalse() ? returnString() : returnInt()));
 322      });
 323
 324      it('should parse string', function() {
 325        expect(scope.$eval("'a' + 'b c'")).toEqual("ab c");
 326      });
 327
 328      it('should parse filters', function() {
 329        $filterProvider.register('substring', valueFn(function(input, start, end) {
 330          return input.substring(start, end);
 331        }));
 332
 333        expect(function() {
 334          scope.$eval("1|nonexistent");
 335        }).toThrowMinErr('$injector', 'unpr', 'Unknown provider: nonexistentFilterProvider <- nonexistentFilter');
 336
 337        scope.offset =  3;
 338        expect(scope.$eval("'abcd'|substring:1:offset")).toEqual("bc");
 339        expect(scope.$eval("'abcd'|substring:1:3|uppercase")).toEqual("BC");
 340      });
 341
 342      it('should access scope', function() {
 343        scope.a =  123;
 344        scope.b = {c: 456};
 345        expect(scope.$eval("a", scope)).toEqual(123);
 346        expect(scope.$eval("b.c", scope)).toEqual(456);
 347        expect(scope.$eval("x.y.z", scope)).not.toBeDefined();
 348      });
 349
 350      it('should resolve deeply nested paths (important for CSP mode)', function() {
 351        scope.a = {b: {c: {d: {e: {f: {g: {h: {i: {j: {k: {l: {m: {n: 'nooo!'}}}}}}}}}}}}};
 352        expect(scope.$eval("a.b.c.d.e.f.g.h.i.j.k.l.m.n", scope)).toBe('nooo!');
 353      });
 354
 355      forEach([2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 42, 99], function(pathLength) {
 356        it('should resolve nested paths of length ' + pathLength, function() {
 357          // Create a nested object {x2: {x3: {x4: ... {x[n]: 42} ... }}}.
 358          var obj = 42, locals = {};
 359          for (var i = pathLength; i >= 2; i--) {
 360            var newObj = {};
 361            newObj['x' + i] = obj;
 362            obj = newObj;
 363          }
 364          // Assign to x1 and build path 'x1.x2.x3. ... .x[n]' to access the final value.
 365          scope.x1 = obj;
 366          var path = 'x1';
 367          for (i = 2; i <= pathLength; i++) {
 368            path += '.x' + i;
 369          }
 370          expect(scope.$eval(path)).toBe(42);
 371          locals['x' + pathLength] = 'not 42';
 372          expect(scope.$eval(path, locals)).toBe(42);
 373        });
 374      });
 375
 376      it('should be forgiving', function() {
 377        scope.a = {b: 23};
 378        expect(scope.$eval('b')).toBeUndefined();
 379        expect(scope.$eval('a.x')).toBeUndefined();
 380        expect(scope.$eval('a.b.c.d')).toBeUndefined();
 381      });
 382
 383      it('should support property names that collide with native object properties', function() {
 384        // regression
 385        scope.watch = 1;
 386        scope.toString = function toString() {
 387          return "custom toString";
 388        };
 389
 390        expect(scope.$eval('watch', scope)).toBe(1);
 391        expect(scope.$eval('toString()', scope)).toBe('custom toString');
 392      });
 393
 394      it('should not break if hasOwnProperty is referenced in an expression', function() {
 395        scope.obj = { value: 1};
 396        // By evaluating an expression that calls hasOwnProperty, the getterFnCache
 397        // will store a property called hasOwnProperty.  This is effectively:
 398        // getterFnCache['hasOwnProperty'] = null
 399        scope.$eval('obj.hasOwnProperty("value")');
 400        // If we rely on this property then evaluating any expression will fail
 401        // because it is not able to find out if obj.value is there in the cache
 402        expect(scope.$eval('obj.value')).toBe(1);
 403      });
 404
 405      it('should not break if the expression is "hasOwnProperty"', function() {
 406        scope.fooExp = 'barVal';
 407        // By evaluating hasOwnProperty, the $parse cache will store a getter for
 408        // the scope's own hasOwnProperty function, which will mess up future cache look ups.
 409        // i.e. cache['hasOwnProperty'] = function(scope) { return scope.hasOwnProperty; }
 410        scope.$eval('hasOwnProperty');
 411        expect(scope.$eval('fooExp')).toBe('barVal');
 412      });
 413
 414      it('should evaluate grouped expressions', function() {
 415        expect(scope.$eval("(1+2)*3")).toEqual((1+2)*3);
 416      });
 417
 418      it('should evaluate assignments', function() {
 419        expect(scope.$eval("a=12")).toEqual(12);
 420        expect(scope.a).toEqual(12);
 421
 422        expect(scope.$eval("x.y.z=123;")).toEqual(123);
 423        expect(scope.x.y.z).toEqual(123);
 424
 425        expect(scope.$eval("a=123; b=234")).toEqual(234);
 426        expect(scope.a).toEqual(123);
 427        expect(scope.b).toEqual(234);
 428      });
 429
 430      it('should evaluate function call without arguments', function() {
 431        scope['const'] =  function(a,b){return 123;};
 432        expect(scope.$eval("const()")).toEqual(123);
 433      });
 434
 435      it('should evaluate function call with arguments', function() {
 436        scope.add =  function(a,b) {
 437          return a+b;
 438        };
 439        expect(scope.$eval("add(1,2)")).toEqual(3);
 440      });
 441
 442      it('should evaluate function call from a return value', function() {
 443        scope.val = 33;
 444        scope.getter = function() { return function() { return this.val; }; };
 445        expect(scope.$eval("getter()()")).toBe(33);
 446      });
 447
 448      it('should evaluate multiplication and division', function() {
 449        scope.taxRate =  8;
 450        scope.subTotal =  100;
 451        expect(scope.$eval("taxRate / 100 * subTotal")).toEqual(8);
 452        expect(scope.$eval("subTotal * taxRate / 100")).toEqual(8);
 453      });
 454
 455      it('should evaluate array', function() {
 456        expect(scope.$eval("[]").length).toEqual(0);
 457        expect(scope.$eval("[1, 2]").length).toEqual(2);
 458        expect(scope.$eval("[1, 2]")[0]).toEqual(1);
 459        expect(scope.$eval("[1, 2]")[1]).toEqual(2);
 460        expect(scope.$eval("[1, 2,]")[1]).toEqual(2);
 461        expect(scope.$eval("[1, 2,]").length).toEqual(2);
 462      });
 463
 464      it('should evaluate array access', function() {
 465        expect(scope.$eval("[1][0]")).toEqual(1);
 466        expect(scope.$eval("[[1]][0][0]")).toEqual(1);
 467        expect(scope.$eval("[].length")).toEqual(0);
 468        expect(scope.$eval("[1, 2].length")).toEqual(2);
 469      });
 470
 471      it('should evaluate object', function() {
 472        expect(toJson(scope.$eval("{}"))).toEqual("{}");
 473        expect(toJson(scope.$eval("{a:'b'}"))).toEqual('{"a":"b"}');
 474        expect(toJson(scope.$eval("{'a':'b'}"))).toEqual('{"a":"b"}');
 475        expect(toJson(scope.$eval("{\"a\":'b'}"))).toEqual('{"a":"b"}');
 476        expect(toJson(scope.$eval("{a:'b',}"))).toEqual('{"a":"b"}');
 477        expect(toJson(scope.$eval("{'a':'b',}"))).toEqual('{"a":"b"}');
 478        expect(toJson(scope.$eval("{\"a\":'b',}"))).toEqual('{"a":"b"}');
 479      });
 480
 481      it('should evaluate object access', function() {
 482        expect(scope.$eval("{false:'WC', true:'CC'}[false]")).toEqual("WC");
 483      });
 484
 485      it('should evaluate JSON', function() {
 486        expect(toJson(scope.$eval("[{}]"))).toEqual("[{}]");
 487        expect(toJson(scope.$eval("[{a:[]}, {b:1}]"))).toEqual('[{"a":[]},{"b":1}]');
 488      });
 489
 490      it('should evaluate multiple statements', function() {
 491        expect(scope.$eval("a=1;b=3;a+b")).toEqual(4);
 492        expect(scope.$eval(";;1;;")).toEqual(1);
 493      });
 494
 495      it('should evaluate object methods in correct context (this)', function() {
 496        var C = function () {
 497          this.a = 123;
 498        };
 499        C.prototype.getA = function() {
 500          return this.a;
 501        };
 502
 503        scope.obj = new C();
 504        expect(scope.$eval("obj.getA()")).toEqual(123);
 505        expect(scope.$eval("obj['getA']()")).toEqual(123);
 506      });
 507
 508      it('should evaluate methods in correct context (this) in argument', function() {
 509        var C = function () {
 510          this.a = 123;
 511        };
 512        C.prototype.sum = function(value) {
 513          return this.a + value;
 514        };
 515        C.prototype.getA = function() {
 516          return this.a;
 517        };
 518
 519        scope.obj = new C();
 520        expect(scope.$eval("obj.sum(obj.getA())")).toEqual(246);
 521        expect(scope.$eval("obj['sum'](obj.getA())")).toEqual(246);
 522      });
 523
 524      it('should evaluate objects on scope context', function() {
 525        scope.a =  "abc";
 526        expect(scope.$eval("{a:a}").a).toEqual("abc");
 527      });
 528
 529      it('should evaluate field access on function call result', function() {
 530        scope.a =  function() {
 531          return {name:'misko'};
 532        };
 533        expect(scope.$eval("a().name")).toEqual("misko");
 534      });
 535
 536      it('should evaluate field access after array access', function () {
 537        scope.items =  [{}, {name:'misko'}];
 538        expect(scope.$eval('items[1].name')).toEqual("misko");
 539      });
 540
 541      it('should evaluate array assignment', function() {
 542        scope.items =  [];
 543
 544        expect(scope.$eval('items[1] = "abc"')).toEqual("abc");
 545        expect(scope.$eval('items[1]')).toEqual("abc");
 546    //    Dont know how to make this work....
 547    //    expect(scope.$eval('books[1] = "moby"')).toEqual("moby");
 548    //    expect(scope.$eval('books[1]')).toEqual("moby");
 549      });
 550
 551      it('should evaluate grouped filters', function() {
 552        scope.name = 'MISKO';
 553        expect(scope.$eval('n = (name|lowercase)')).toEqual('misko');
 554        expect(scope.$eval('n')).toEqual('misko');
 555      });
 556
 557      it('should evaluate remainder', function() {
 558        expect(scope.$eval('1%2')).toEqual(1);
 559      });
 560
 561      it('should evaluate sum with undefined', function() {
 562        expect(scope.$eval('1+undefined')).toEqual(1);
 563        expect(scope.$eval('undefined+1')).toEqual(1);
 564      });
 565
 566      it('should throw exception on non-closed bracket', function() {
 567        expect(function() {
 568          scope.$eval('[].count(');
 569        }).toThrowMinErr('$parse', 'ueoe', 'Unexpected end of expression: [].count(');
 570      });
 571
 572      it('should evaluate double negation', function() {
 573        expect(scope.$eval('true')).toBeTruthy();
 574        expect(scope.$eval('!true')).toBeFalsy();
 575        expect(scope.$eval('!!true')).toBeTruthy();
 576        expect(scope.$eval('{true:"a", false:"b"}[!!true]')).toEqual('a');
 577      });
 578
 579      it('should evaluate negation', function() {
 580        /* jshint -W018 */
 581        expect(scope.$eval("!false || true")).toEqual(!false || true);
 582        expect(scope.$eval("!11 == 10")).toEqual(!11 == 10);
 583        expect(scope.$eval("12/6/2")).toEqual(12/6/2);
 584      });
 585
 586      it('should evaluate exclamation mark', function() {
 587        expect(scope.$eval('suffix = "!"')).toEqual('!');
 588      });
 589
 590      it('should evaluate minus', function() {
 591        expect(scope.$eval("{a:'-'}")).toEqual({a: "-"});
 592      });
 593
 594      it('should evaluate undefined', function() {
 595        expect(scope.$eval("undefined")).not.toBeDefined();
 596        expect(scope.$eval("a=undefined")).not.toBeDefined();
 597        expect(scope.a).not.toBeDefined();
 598      });
 599
 600      it('should allow assignment after array dereference', function() {
 601        scope.obj = [{}];
 602        scope.$eval('obj[0].name=1');
 603        expect(scope.obj.name).toBeUndefined();
 604        expect(scope.obj[0].name).toEqual(1);
 605      });
 606
 607      it('should short-circuit AND operator', function() {
 608        scope.run = function() {
 609          throw "IT SHOULD NOT HAVE RUN";
 610        };
 611        expect(scope.$eval('false && run()')).toBe(false);
 612      });
 613
 614      it('should short-circuit OR operator', function() {
 615        scope.run = function() {
 616          throw "IT SHOULD NOT HAVE RUN";
 617        };
 618        expect(scope.$eval('true || run()')).toBe(true);
 619      });
 620
 621
 622      it('should support method calls on primitive types', function() {
 623        scope.empty = '';
 624        scope.zero = 0;
 625        scope.bool = false;
 626
 627        expect(scope.$eval('empty.substr(0)')).toBe('');
 628        expect(scope.$eval('zero.toString()')).toBe('0');
 629        expect(scope.$eval('bool.toString()')).toBe('false');
 630      });
 631
 632      it('should evaluate expressions with line terminators', function() {
 633        scope.a = "a";
 634        scope.b = {c: "bc"};
 635        expect(scope.$eval('a + \n b.c + \r "\td" + \t \r\n\r "\r\n\n"')).toEqual("abc\td\r\n\n");
 636      });
 637
 638      describe('sandboxing', function() {
 639        describe('Function constructor', function() {
 640          it('should NOT allow access to Function constructor in getter', function() {
 641
 642            expect(function() {
 643              scope.$eval('{}.toString.constructor("alert(1)")');
 644            }).toThrowMinErr(
 645                    '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
 646                    'Expression: {}.toString.constructor("alert(1)")');
 647
 648          });
 649
 650          it('should NOT allow access to Function constructor in setter', function() {
 651
 652            expect(function() {
 653              scope.$eval('{}.toString.constructor.a = 1');
 654            }).toThrowMinErr(
 655                    '$parse', 'isecfn','Referencing Function in Angular expressions is disallowed! ' +
 656                    'Expression: {}.toString.constructor.a = 1');
 657
 658            expect(function() {
 659              scope.$eval('{}.toString["constructor"]["constructor"] = 1');
 660            }).toThrowMinErr(
 661                    '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
 662                    'Expression: {}.toString["constructor"]["constructor"] = 1');
 663
 664            scope.key1 = "const";
 665            scope.key2 = "ructor";
 666            expect(function() {
 667              scope.$eval('{}.toString[key1 + key2].foo = 1');
 668            }).toThrowMinErr(
 669                    '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
 670                    'Expression: {}.toString[key1 + key2].foo = 1');
 671
 672            expect(function() {
 673              scope.$eval('{}.toString["constructor"]["a"] = 1');
 674            }).toThrowMinErr(
 675                    '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
 676                    'Expression: {}.toString["constructor"]["a"] = 1');
 677
 678            scope.a = [];
 679            expect(function() {
 680              scope.$eval('a.toString.constructor = 1', scope);
 681            }).toThrowMinErr(
 682                    '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
 683                    'Expression: a.toString.constructor = 1');
 684          });
 685
 686
 687          it('should NOT allow access to Function constructor that has been aliased', function() {
 688            scope.foo = { "bar": Function };
 689            expect(function() {
 690              scope.$eval('foo["bar"]');
 691            }).toThrowMinErr(
 692                    '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
 693                    'Expression: foo["bar"]');
 694
 695          });
 696        });
 697
 698        describe('Function prototype functions', function () {
 699          it('should NOT allow invocation to Function.call', function() {
 700            scope.fn = Function.prototype.call;
 701
 702            expect(function() {
 703              scope.$eval('$eval.call()')
 704            }).toThrowMinErr(
 705                    '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
 706                    'Expression: $eval.call()');
 707
 708            expect(function() {
 709              scope.$eval('fn()')
 710            }).toThrowMinErr(
 711              '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
 712                'Expression: fn()');
 713          });
 714
 715          it('should NOT allow invocation to Function.apply', function() {
 716            scope.apply = Function.prototype.apply;
 717
 718            expect(function() {
 719              scope.$eval('$eval.apply()')
 720            }).toThrowMinErr(
 721              '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
 722                'Expression: $eval.apply()');
 723
 724            expect(function() {
 725              scope.$eval('apply()')
 726            }).toThrowMinErr(
 727              '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
 728                'Expression: apply()');
 729          });
 730
 731          it('should NOT allow invocation to Function.bind', function() {
 732            scope.bind = Function.prototype.bind;
 733
 734            expect(function() {
 735              scope.$eval('$eval.bind()')
 736            }).toThrowMinErr(
 737              '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
 738                'Expression: $eval.bind()');
 739
 740            expect(function() {
 741              scope.$eval('bind()')
 742            }).toThrowMinErr(
 743              '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
 744                'Expression: bind()');
 745          });
 746        });
 747
 748        describe('Object constructor', function() {
 749
 750          it('should NOT allow access to Object constructor that has been aliased', function() {
 751            scope.foo = { "bar": Object };
 752
 753            expect(function() {
 754              scope.$eval('foo.bar.keys(foo)');
 755            }).toThrowMinErr(
 756                    '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
 757                    'Expression: foo.bar.keys(foo)');
 758
 759            expect(function() {
 760              scope.$eval('foo["bar"]["keys"](foo)');
 761            }).toThrowMinErr(
 762                    '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
 763                    'Expression: foo["bar"]["keys"](foo)');
 764          });
 765        });
 766
 767        describe('Window and $element/node', function() {
 768          it('should NOT allow access to the Window or DOM when indexing', inject(function($window, $document) {
 769            scope.wrap = {w: $window, d: $document};
 770
 771            expect(function() {
 772              scope.$eval('wrap["w"]', scope);
 773            }).toThrowMinErr(
 774                    '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
 775                    'disallowed! Expression: wrap["w"]');
 776            expect(function() {
 777              scope.$eval('wrap["d"]', scope);
 778            }).toThrowMinErr(
 779                    '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
 780                    'disallowed! Expression: wrap["d"]');
 781          }));
 782
 783
 784          it('should NOT allow access to the Window or DOM returned from a function', inject(function($window, $document) {
 785            scope.getWin = valueFn($window);
 786            scope.getDoc = valueFn($document);
 787
 788            expect(function() {
 789              scope.$eval('getWin()', scope);
 790            }).toThrowMinErr(
 791                    '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
 792                    'disallowed! Expression: getWin()');
 793            expect(function() {
 794              scope.$eval('getDoc()', scope);
 795            }).toThrowMinErr(
 796                    '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
 797                    'disallowed! Expression: getDoc()');
 798          }));
 799
 800          it('should NOT allow calling functions on Window or DOM', inject(function($window, $document) {
 801            scope.a = {b: { win: $window, doc: $document }};
 802            expect(function() {
 803              scope.$eval('a.b.win.alert(1)', scope);
 804            }).toThrowMinErr(
 805                    '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
 806                    'disallowed! Expression: a.b.win.alert(1)');
 807            expect(function() {
 808              scope.$eval('a.b.doc.on("click")', scope);
 809            }).toThrowMinErr(
 810                    '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
 811                    'disallowed! Expression: a.b.doc.on("click")');
 812          }));
 813
 814          // Issue #4805
 815          it('should NOT throw isecdom when referencing a Backbone Collection', function() {
 816            // Backbone stuff is sort of hard to mock, if you have a better way of doing this,
 817            // please fix this.
 818            var fakeBackboneCollection = {
 819              children: [{}, {}, {}],
 820              find: function() {},
 821              on: function() {},
 822              off: function() {},
 823              bind: function() {}
 824            };
 825            scope.backbone = fakeBackboneCollection;
 826            expect(function() { scope.$eval('backbone'); }).not.toThrow();
 827          });
 828
 829          it('should NOT throw isecdom when referencing an array with node properties', function() {
 830            var array = [1,2,3];
 831            array.on = array.attr = array.prop = array.bind = true;
 832            scope.array = array;
 833            expect(function() { scope.$eval('array'); }).not.toThrow();
 834          });
 835        });
 836
 837        describe('Disallowed fields', function() {
 838          it('should NOT allow access or invocation of __defineGetter__', function() {
 839            expect(function() { 
 840              scope.$eval('{}.__defineGetter__'); }).toThrowMinErr('$parse', 'isecfld');
 841            expect(function() { 
 842              scope.$eval('{}.__defineGetter__("a", "".charAt)'); }).toThrowMinErr('$parse', 'isecfld');
 843
 844            expect(function() { 
 845              scope.$eval('{}["__defineGetter__"]'); }).toThrowMinErr('$parse', 'isecfld');
 846            expect(function() { 
 847              scope.$eval('{}["__defineGetter__"]("a", "".charAt)'); }).toThrowMinErr('$parse', 'isecfld');
 848
 849            scope.a = "__define";
 850            scope.b = "Getter__";
 851            expect(function() { 
 852              scope.$eval('{}[a + b]'); }).toThrowMinErr('$parse', 'isecfld');
 853            expect(function() { 
 854              scope.$eval('{}[a + b]("a", "".charAt)'); }).toThrowMinErr('$parse', 'isecfld');
 855          });
 856
 857          it('should NOT allow access or invocation of __defineSetter__', function() {
 858            expect(function() {
 859              scope.$eval('{}.__defineSetter__'); }).toThrowMinErr('$parse', 'isecfld');
 860            expect(function() {
 861              scope.$eval('{}.__defineSetter__("a", "".charAt)'); }).toThrowMinErr('$parse', 'isecfld');
 862
 863            expect(function() {
 864              scope.$eval('{}["__defineSetter__"]'); }).toThrowMinErr('$parse', 'isecfld');
 865            expect(function() {
 866              scope.$eval('{}["__defineSetter__"]("a", "".charAt)'); }).toThrowMinErr('$parse', 'isecfld');
 867
 868            scope.a = "__define";
 869            scope.b = "Setter__";
 870            expect(function() {
 871              scope.$eval('{}[a + b]'); }).toThrowMinErr('$parse', 'isecfld');
 872            expect(function() {
 873              scope.$eval('{}[a + b]("a", "".charAt)'); }).toThrowMinErr('$parse', 'isecfld');
 874          });
 875
 876          it('should NOT allow access or invocation of __lookupGetter__', function() {
 877            expect(function() {
 878              scope.$eval('{}.__lookupGetter__'); }).toThrowMinErr('$parse', 'isecfld');
 879            expect(function() {
 880              scope.$eval('{}.__lookupGetter__("a")'); }).toThrowMinErr('$parse', 'isecfld');
 881
 882            expect(function() {
 883              scope.$eval('{}["__lookupGetter__"]'); }).toThrowMinErr('$parse', 'isecfld');
 884            expect(function() {
 885              scope.$eval('{}["__lookupGetter__"]("a")'); }).toThrowMinErr('$parse', 'isecfld');
 886
 887            scope.a = "__lookup";
 888            scope.b = "Getter__";
 889            expect(function() {
 890              scope.$eval('{}[a + b]'); }).toThrowMinErr('$parse', 'isecfld');
 891            expect(function() {
 892              scope.$eval('{}[a + b]("a")'); }).toThrowMinErr('$parse', 'isecfld');
 893          });
 894
 895          it('should NOT allow access or invocation of __lookupSetter__', function() {
 896            expect(function() {
 897              scope.$eval('{}.__lookupSetter__'); }).toThrowMinErr('$parse', 'isecfld');
 898            expect(function() {
 899              scope.$eval('{}.__lookupSetter__("a")'); }).toThrowMinErr('$parse', 'isecfld');
 900
 901            expect(function() {
 902              scope.$eval('{}["__lookupSetter__"]'); }).toThrowMinErr('$parse', 'isecfld');
 903            expect(function() {
 904              scope.$eval('{}["__lookupSetter__"]("a")'); }).toThrowMinErr('$parse', 'isecfld');
 905
 906            scope.a = "__lookup";
 907            scope.b = "Setter__";
 908            expect(function() {
 909              scope.$eval('{}[a + b]'); }).toThrowMinErr('$parse', 'isecfld');
 910            expect(function() {
 911              scope.$eval('{}[a + b]("a")'); }).toThrowMinErr('$parse', 'isecfld');
 912          });
 913
 914          it('should NOT allow access to __proto__', function() {
 915            expect(function() {
 916              scope.$eval('{}.__proto__'); }).toThrowMinErr('$parse', 'isecfld');
 917            expect(function() {
 918              scope.$eval('{}.__proto__.foo = 1'); }).toThrowMinErr('$parse', 'isecfld');
 919
 920            expect(function() {
 921              scope.$eval('{}["__proto__"]'); }).toThrowMinErr('$parse', 'isecfld');
 922            expect(function() {
 923              scope.$eval('{}["__proto__"].foo = 1'); }).toThrowMinErr('$parse', 'isecfld');
 924
 925            scope.a = "__pro";
 926            scope.b = "to__";
 927            expect(function() {
 928              scope.$eval('{}[a + b]'); }).toThrowMinErr('$parse', 'isecfld');
 929            expect(function() {
 930              scope.$eval('{}[a + b].foo = 1'); }).toThrowMinErr('$parse', 'isecfld');
 931          });
 932        });
 933
 934        it('should prevent the exploit', function() {
 935          expect(function() {
 936            scope.$eval('' +
 937              ' "".sub.call.call(' +
 938                '({})["constructor"].getOwnPropertyDescriptor("".sub.__proto__, "constructor").value,' +
 939                'null,' +
 940                '"alert(1)"' +
 941              ')()' +
 942              '')
 943          }).toThrow();
 944        })
 945      });
 946
 947      it('should call the function from the received instance and not from a new one', function() {
 948        var n = 0;
 949        scope.fn = function() {
 950          var c = n++;
 951          return { c: c, anotherFn: function() { return this.c == c; } };
 952        };
 953        expect(scope.$eval('fn().anotherFn()')).toBe(true);
 954      });
 955
 956
 957      it('should call the function once when it is part of the context', function() {
 958        var count = 0;
 959        scope.fn = function() {
 960          count++;
 961          return { anotherFn: function() { return "lucas"; } };
 962        };
 963        expect(scope.$eval('fn().anotherFn()')).toBe('lucas');
 964        expect(count).toBe(1);
 965      });
 966
 967
 968      it('should call the function once when it is not part of the context', function() {
 969        var count = 0;
 970        scope.fn = function() {
 971          count++;
 972          return function() { return 'lucas'; };
 973        };
 974        expect(scope.$eval('fn()()')).toBe('lucas');
 975        expect(count).toBe(1);
 976      });
 977
 978
 979      it('should call the function once when it is part of the context on assignments', function() {
 980        var count = 0;
 981        var element = {};
 982        scope.fn = function() {
 983          count++;
 984          return element;
 985        };
 986        expect(scope.$eval('fn().name = "lucas"')).toBe('lucas');
 987        expect(element.name).toBe('lucas');
 988        expect(count).toBe(1);
 989      });
 990
 991
 992      it('should call the function once when it is part of the context on array lookups', function() {
 993        var count = 0;
 994        var element = [];
 995        scope.fn = function() {
 996          count++;
 997          return element;
 998        };
 999        expect(scope.$eval('fn()[0] = "lucas"')).toBe('lucas');
1000        expect(element[0]).toBe('lucas');
1001        expect(count).toBe(1);
1002      });
1003
1004
1005      it('should call the function once when it is part of the context on array lookup function', function() {
1006        var count = 0;
1007        var element = [{anotherFn: function() { return 'lucas';} }];
1008        scope.fn = function() {
1009          count++;
1010          return element;
1011        };
1012        expect(scope.$eval('fn()[0].anotherFn()')).toBe('lucas');
1013        expect(count).toBe(1);
1014      });
1015
1016
1017      it('should call the function once when it is part of the context on property lookup function', function() {
1018        var count = 0;
1019        var element = {name: {anotherFn: function() { return 'lucas';} } };
1020        scope.fn = function() {
1021          count++;
1022          return element;
1023        };
1024        expect(scope.$eval('fn().name.anotherFn()')).toBe('lucas');
1025        expect(count).toBe(1);
1026      });
1027
1028
1029      it('should call the function once when it is part of a sub-expression', function() {
1030        var count = 0;
1031        scope.element = [{}];
1032        scope.fn = function() {
1033          count++;
1034          return 0;
1035        };
1036        expect(scope.$eval('element[fn()].name = "lucas"')).toBe('lucas');
1037        expect(scope.element[0].name).toBe('lucas');
1038        expect(count).toBe(1);
1039      });
1040
1041
1042      describe('assignable', function() {
1043        it('should expose assignment function', inject(function($parse) {
1044          var fn = $parse('a');
1045          expect(fn.assign).toBeTruthy();
1046          var scope = {};
1047          fn.assign(scope, 123);
1048          expect(scope).toEqual({a:123});
1049        }));
1050      });
1051
1052
1053      describe('one-time binding', function() {
1054        it('should only use the cache when it is not a one-time binding', inject(function($parse) {
1055          expect($parse('foo')).toBe($parse('foo'));
1056          expect($parse('::foo')).not.toBe($parse('::foo'));
1057        }));
1058
1059        it('should stay stable once the value defined', inject(function($parse, $rootScope) {
1060          var fn = $parse('::foo');
1061          expect(fn.$$unwatch).not.toBe(true);
1062          $rootScope.$watch(fn);
1063
1064          $rootScope.$digest();
1065          expect(fn.$$unwatch).not.toBe(true);
1066
1067          $rootScope.foo = 'bar';
1068          $rootScope.$digest();
1069          expect(fn.$$unwatch).toBe(true);
1070          expect(fn($rootScope)).toBe('bar');
1071          expect(fn()).toBe('bar');
1072
1073          $rootScope.foo = 'man';
1074          $rootScope.$digest();
1075          expect(fn.$$unwatch).toBe(true);
1076          expect(fn($rootScope)).toBe('bar');
1077          expect(fn()).toBe('bar');
1078        }));
1079
1080        it('should have a stable value if at the end of a $digest it has a defined value', inject(function($parse, $rootScope) {
1081          var fn = $parse('::foo');
1082          $rootScope.$watch(fn);
1083          $rootScope.$watch('foo', function() { if ($rootScope.foo === 'bar') {$rootScope.foo = undefined; } });
1084
1085          $rootScope.foo = 'bar';
1086          $rootScope.$digest();
1087          expect(fn.$$unwatch).toBe(false);
1088
1089          $rootScope.foo = 'man';
1090          $rootScope.$digest();
1091          expect(fn.$$unwatch).toBe(true);
1092          expect(fn($rootScope)).toBe('man');
1093          expect(fn()).toBe('man');
1094
1095          $rootScope.foo = 'shell';
1096          $rootScope.$digest();
1097          expect(fn.$$unwatch).toBe(true);
1098          expect(fn($rootScope)).toBe('man');
1099          expect(fn()).toBe('man');
1100        }));
1101
1102        it('should keep a copy of the stable element', inject(function($parse, $rootScope) {
1103          var fn = $parse('::foo'),
1104              value = {bar: 'bar'};
1105          $rootScope.$watch(fn);
1106          $rootScope.foo = value;
1107          $rootScope.$digest();
1108
1109          value.baz = 'baz';
1110          expect(fn()).toEqual({bar: 'bar'});
1111        }));
1112
1113        it('should not throw if the stable value is `null`', inject(function($parse, $rootScope) {
1114          var fn = $parse('::foo');
1115          $rootScope.$watch(fn);
1116          $rootScope.foo = null;
1117          $rootScope.$digest();
1118          $rootScope.foo = 'foo';
1119          $rootScope.$digest();
1120          expect(fn()).toEqual(null);
1121        }));
1122
1123      });
1124
1125
1126      describe('locals', function() {
1127        it('should expose local variables', inject(function($parse) {
1128          expect($parse('a')({a: 0}, {a: 1})).toEqual(1);
1129          expect($parse('add(a,b)')({b: 1, add: function(a, b) { return a + b; }}, {a: 2})).toEqual(3);
1130        }));
1131
1132        it('should expose traverse locals', inject(function($parse) {
1133          expect($parse('a.b')({a: {b: 0}}, {a: {b:1}})).toEqual(1);
1134          expect($parse('a.b')({a: null}, {a: {b:1}})).toEqual(1);
1135          expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
1136          expect($parse('a.b.c')({a: null}, {a: {b: {c: 1}}})).toEqual(1);
1137        }));
1138
1139        it('should not use locals to resolve object properties', inject(function($parse) {
1140          expect($parse('a[0].b')({a: [ {b : 'scope'} ]}, {b : 'locals'})).toBe('scope');
1141          expect($parse('a[0]["b"]')({a: [ {b : 'scope'} ]}, {b : 'locals'})).toBe('scope');
1142          expect($parse('a[0][0].b')({a: [[{b : 'scope'}]]}, {b : 'locals'})).toBe('scope');
1143          expect($parse('a[0].b.c')({a: [ {b: {c: 'scope'}}] }, {b : {c: 'locals'} })).toBe('scope');
1144        }));
1145      });
1146
1147      describe('literal', function() {
1148        it('should mark scalar value expressions as literal', inject(function($parse) {
1149          expect($parse('0').literal).toBe(true);
1150          expect($parse('"hello"').literal).toBe(true);
1151          expect($parse('true').literal).toBe(true);
1152          expect($parse('false').literal).toBe(true);
1153          expect($parse('null').literal).toBe(true);
1154          expect($parse('undefined').literal).toBe(true);
1155        }));
1156
1157        it('should mark array expressions as literal', inject(function($parse) {
1158          expect($parse('[]').literal).toBe(true);
1159          expect($parse('[1, 2, 3]').literal).toBe(true);
1160          expect($parse('[1, identifier]').literal).toBe(true);
1161        }));
1162
1163        it('should mark object expressions as literal', inject(function($parse) {
1164          expect($parse('{}').literal).toBe(true);
1165          expect($parse('{x: 1}').literal).toBe(true);
1166          expect($parse('{foo: bar}').literal).toBe(true);
1167        }));
1168
1169        it('should not mark function calls or operator expressions as literal', inject(function($parse) {
1170          expect($parse('1 + 1').literal).toBe(false);
1171          expect($parse('call()').literal).toBe(false);
1172          expect($parse('[].length').literal).toBe(false);
1173        }));
1174      });
1175
1176      describe('constant', function() {
1177        it('should mark scalar value expressions as constant', inject(function($parse) {
1178          expect($parse('12.3').constant).toBe(true);
1179          expect($parse('"string"').constant).toBe(true);
1180          expect($parse('true').constant).toBe(true);
1181          expect($parse('false').constant).toBe(true);
1182          expect($parse('null').constant).toBe(true);
1183          expect($parse('undefined').constant).toBe(true);
1184        }));
1185
1186        it('should mark arrays as constant if they only contain constant elements', inject(function($parse) {
1187          expect($parse('[]').constant).toBe(true);
1188          expect($parse('[1, 2, 3]').constant).toBe(true);
1189          expect($parse('["string", null]').constant).toBe(true);
1190          expect($parse('[[]]').constant).toBe(true);
1191          expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true);
1192        }));
1193
1194        it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) {
1195          expect($parse('[foo]').constant).toBe(false);
1196          expect($parse('[x + 1]').constant).toBe(false);
1197          expect($parse('[bar[0]]').constant).toBe(false);
1198        }));
1199
1200        it('should mark complex expressions involving constant values as constant', inject(function($parse) {
1201          expect($parse('!true').constant).toBe(true);
1202          expect($parse('-42').constant).toBe(true);
1203          expect($parse('1 - 1').constant).toBe(true);
1204          expect($parse('"foo" + "bar"').constant).toBe(true);
1205          expect($parse('5 != null').constant).toBe(true);
1206          expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true);
1207        }));
1208
1209        it('should not mark any expression involving variables or function calls as constant', inject(function($parse) {
1210          expect($parse('true.toString()').constant).toBe(false);
1211          expect($parse('foo(1, 2, 3)').constant).toBe(false);
1212          expect($parse('"name" + id').constant).toBe(false);
1213        }));
1214      });
1215
1216      describe('null/undefined in expressions', function() {
1217        // simpleGetterFn1
1218        it('should return null for `a` where `a` is null', inject(function($rootScope) {
1219          $rootScope.a = null;
1220          expect($rootScope.$eval('a')).toBe(null);
1221        }));
1222
1223        it('should return undefined for `a` where `a` is undefined', inject(function($rootScope) {
1224          expect($rootScope.$eval('a')).toBeUndefined();
1225        }));
1226
1227        // simpleGetterFn2
1228        it('should return undefined for properties of `null` constant', inject(function($rootScope) {
1229          expect($rootScope.$eval('null.a')).toBeUndefined();
1230        }));
1231
1232        it('should return undefined for properties of `null` values', inject(function($rootScope) {
1233          $rootScope.a = null;
1234          expect($rootScope.$eval('a.b')).toBeUndefined();
1235        }));
1236
1237        it('should return null for `a.b` where `b` is null', inject(function($rootScope) {
1238          $rootScope.a = { b: null };
1239          expect($rootScope.$eval('a.b')).toBe(null);
1240        }));
1241
1242        // cspSafeGetter && pathKeys.length < 6 || pathKeys.length > 2
1243        it('should return null for `a.b.c.d.e` where `e` is null', inject(function($rootScope) {
1244          $rootScope.a = { b: { c: { d: { e: null } } } };
1245          expect($rootScope.$eval('a.b.c.d.e')).toBe(null);
1246        }));
1247
1248        it('should return undefined for `a.b.c.d.e` where `d` is null', inject(function($rootScope) {
1249          $rootScope.a = { b: { c: { d: null } } };
1250          expect($rootScope.$eval('a.b.c.d.e')).toBeUndefined();
1251        }));
1252
1253        // cspSafeGetter || pathKeys.length > 6
1254        it('should return null for `a.b.c.d.e.f.g` where `g` is null', inject(function($rootScope) {
1255          $rootScope.a = { b: { c: { d: { e: { f: { g: null } } } } } };
1256          expect($rootScope.$eval('a.b.c.d.e.f.g')).toBe(null);
1257        }));
1258
1259        it('should return undefined for `a.b.c.d.e.f.g` where `f` is null', inject(function($rootScope) {
1260          $rootScope.a = { b: { c: { d: { e: { f: null } } } } };
1261          expect($rootScope.$eval('a.b.c.d.e.f.g')).toBeUndefined();
1262        }));
1263
1264
1265        it('should return undefined if the return value of a function invocation is undefined',
1266            inject(function($rootScope) {
1267          $rootScope.fn = function() {};
1268          expect($rootScope.$eval('fn()')).toBeUndefined();
1269        }));
1270
1271        it('should ignore undefined values when doing addition/concatenation',
1272            inject(function($rootScope) {
1273          $rootScope.fn = function() {};
1274          expect($rootScope.$eval('foo + "bar" + fn()')).toBe('bar');
1275        }));
1276      });
1277    });
1278  });
1279});