PageRenderTime 57ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/test/ng/parseSpec.js

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

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