PageRenderTime 121ms CodeModel.GetById 3ms app.highlight 81ms RepoModel.GetById 2ms app.codeStats 0ms

/test/ng/parseSpec.js

https://github.com/thebigredgeek/angular.js
JavaScript | 1430 lines | 1408 code | 19 blank | 3 comment | 5 complexity | 168d28ae572ca7b854d88afa13defea2 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file