PageRenderTime 12ms CodeModel.GetById 6ms app.highlight 255ms RepoModel.GetById 4ms app.codeStats 1ms

/test/ng/parseSpec.js

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

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