PageRenderTime 64ms CodeModel.GetById 27ms RepoModel.GetById 0ms 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
Possible License(s): JSON

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

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

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