PageRenderTime 70ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/test/ng/parseSpec.js

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