PageRenderTime 71ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/test/ng/parseSpec.js

https://github.com/ruslanas/angular.js
JavaScript | 3510 lines | 3484 code | 22 blank | 4 comment | 7 complexity | a8c0895bf1a8fd4a62fad8e255d17ada MD5 | raw file
Possible License(s): JSON
  1. 'use strict';
  2. describe('parser', function() {
  3. beforeEach(function() {
  4. /* global getterFnCacheDefault: true */
  5. /* global getterFnCacheExpensive: true */
  6. // clear caches
  7. getterFnCacheDefault = createMap();
  8. getterFnCacheExpensive = createMap();
  9. });
  10. describe('lexer', function() {
  11. var lex;
  12. beforeEach(function() {
  13. /* global Lexer: false */
  14. lex = function() {
  15. var lexer = new Lexer({csp: false});
  16. return lexer.lex.apply(lexer, arguments);
  17. };
  18. });
  19. it('should only match number chars with isNumber', function() {
  20. expect(Lexer.prototype.isNumber('0')).toBe(true);
  21. expect(Lexer.prototype.isNumber('')).toBeFalsy();
  22. expect(Lexer.prototype.isNumber(' ')).toBeFalsy();
  23. expect(Lexer.prototype.isNumber(0)).toBeFalsy();
  24. expect(Lexer.prototype.isNumber(false)).toBeFalsy();
  25. expect(Lexer.prototype.isNumber(true)).toBeFalsy();
  26. expect(Lexer.prototype.isNumber(undefined)).toBeFalsy();
  27. expect(Lexer.prototype.isNumber(null)).toBeFalsy();
  28. });
  29. it('should tokenize a string', function() {
  30. var tokens = lex("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\"");
  31. var i = 0;
  32. expect(tokens[i].index).toEqual(0);
  33. expect(tokens[i].text).toEqual('a');
  34. i++;
  35. expect(tokens[i].index).toEqual(1);
  36. expect(tokens[i].text).toEqual('.');
  37. i++;
  38. expect(tokens[i].index).toEqual(2);
  39. expect(tokens[i].text).toEqual('bc');
  40. i++;
  41. expect(tokens[i].index).toEqual(4);
  42. expect(tokens[i].text).toEqual('[');
  43. i++;
  44. expect(tokens[i].index).toEqual(5);
  45. expect(tokens[i].text).toEqual('22');
  46. expect(tokens[i].value).toEqual(22);
  47. expect(tokens[i].constant).toEqual(true);
  48. i++;
  49. expect(tokens[i].index).toEqual(7);
  50. expect(tokens[i].text).toEqual(']');
  51. i++;
  52. expect(tokens[i].index).toEqual(8);
  53. expect(tokens[i].text).toEqual('+');
  54. i++;
  55. expect(tokens[i].index).toEqual(9);
  56. expect(tokens[i].text).toEqual('1.3');
  57. expect(tokens[i].value).toEqual(1.3);
  58. expect(tokens[i].constant).toEqual(true);
  59. i++;
  60. expect(tokens[i].index).toEqual(12);
  61. expect(tokens[i].text).toEqual('|');
  62. i++;
  63. expect(tokens[i].index).toEqual(13);
  64. expect(tokens[i].text).toEqual('f');
  65. i++;
  66. expect(tokens[i].index).toEqual(14);
  67. expect(tokens[i].text).toEqual(':');
  68. i++;
  69. expect(tokens[i].index).toEqual(15);
  70. expect(tokens[i].value).toEqual("a'c");
  71. i++;
  72. expect(tokens[i].index).toEqual(21);
  73. expect(tokens[i].text).toEqual(':');
  74. i++;
  75. expect(tokens[i].index).toEqual(22);
  76. expect(tokens[i].value).toEqual('d"e');
  77. });
  78. it('should tokenize identifiers with spaces around dots the same as without spaces', function() {
  79. function getText(t) { return t.text; }
  80. var spaces = lex('foo. bar . baz').map(getText);
  81. var noSpaces = lex('foo.bar.baz').map(getText);
  82. expect(spaces).toEqual(noSpaces);
  83. });
  84. it('should tokenize undefined', function() {
  85. var tokens = lex("undefined");
  86. var i = 0;
  87. expect(tokens[i].index).toEqual(0);
  88. expect(tokens[i].text).toEqual('undefined');
  89. });
  90. it('should tokenize quoted string', function() {
  91. var str = "['\\'', \"\\\"\"]";
  92. var tokens = lex(str);
  93. expect(tokens[1].index).toEqual(1);
  94. expect(tokens[1].value).toEqual("'");
  95. expect(tokens[3].index).toEqual(7);
  96. expect(tokens[3].value).toEqual('"');
  97. });
  98. it('should tokenize escaped quoted string', function() {
  99. var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
  100. var tokens = lex(str);
  101. expect(tokens[0].value).toEqual('"\n\f\r\t\v\u00A0');
  102. });
  103. it('should tokenize unicode', function() {
  104. var tokens = lex('"\\u00A0"');
  105. expect(tokens.length).toEqual(1);
  106. expect(tokens[0].value).toEqual('\u00a0');
  107. });
  108. it('should ignore whitespace', function() {
  109. var tokens = lex("a \t \n \r b");
  110. expect(tokens[0].text).toEqual('a');
  111. expect(tokens[1].text).toEqual('b');
  112. });
  113. it('should tokenize relation and equality', function() {
  114. var tokens = lex("! == != < > <= >= === !==");
  115. expect(tokens[0].text).toEqual('!');
  116. expect(tokens[1].text).toEqual('==');
  117. expect(tokens[2].text).toEqual('!=');
  118. expect(tokens[3].text).toEqual('<');
  119. expect(tokens[4].text).toEqual('>');
  120. expect(tokens[5].text).toEqual('<=');
  121. expect(tokens[6].text).toEqual('>=');
  122. expect(tokens[7].text).toEqual('===');
  123. expect(tokens[8].text).toEqual('!==');
  124. });
  125. it('should tokenize logical and ternary', function() {
  126. var tokens = lex("&& || ? :");
  127. expect(tokens[0].text).toEqual('&&');
  128. expect(tokens[1].text).toEqual('||');
  129. expect(tokens[2].text).toEqual('?');
  130. expect(tokens[3].text).toEqual(':');
  131. });
  132. it('should tokenize statements', function() {
  133. var tokens = lex("a;b;");
  134. expect(tokens[0].text).toEqual('a');
  135. expect(tokens[1].text).toEqual(';');
  136. expect(tokens[2].text).toEqual('b');
  137. expect(tokens[3].text).toEqual(';');
  138. });
  139. it('should tokenize function invocation', function() {
  140. var tokens = lex("a()");
  141. expect(tokens.map(function(t) { return t.text;})).toEqual(['a', '(', ')']);
  142. });
  143. it('should tokenize method invocation', function() {
  144. var tokens = lex("a.b.c (d) - e.f()");
  145. expect(tokens.map(function(t) { return t.text;})).
  146. toEqual(['a', '.', 'b', '.', 'c', '(', 'd', ')', '-', 'e', '.', 'f', '(', ')']);
  147. });
  148. it('should tokenize number', function() {
  149. var tokens = lex("0.5");
  150. expect(tokens[0].value).toEqual(0.5);
  151. });
  152. it('should tokenize negative number', inject(function($rootScope) {
  153. var value = $rootScope.$eval("-0.5");
  154. expect(value).toEqual(-0.5);
  155. value = $rootScope.$eval("{a:-0.5}");
  156. expect(value).toEqual({a:-0.5});
  157. }));
  158. it('should tokenize number with exponent', inject(function($rootScope) {
  159. var tokens = lex("0.5E-10");
  160. expect(tokens[0].value).toEqual(0.5E-10);
  161. expect($rootScope.$eval("0.5E-10")).toEqual(0.5E-10);
  162. tokens = lex("0.5E+10");
  163. expect(tokens[0].value).toEqual(0.5E+10);
  164. }));
  165. it('should throws exception for invalid exponent', function() {
  166. expect(function() {
  167. lex("0.5E-");
  168. }).toThrowMinErr('$parse', 'lexerr', 'Lexer Error: Invalid exponent at column 4 in expression [0.5E-].');
  169. expect(function() {
  170. lex("0.5E-A");
  171. }).toThrowMinErr('$parse', 'lexerr', 'Lexer Error: Invalid exponent at column 4 in expression [0.5E-A].');
  172. });
  173. it('should tokenize number starting with a dot', function() {
  174. var tokens = lex(".5");
  175. expect(tokens[0].value).toEqual(0.5);
  176. });
  177. it('should throw error on invalid unicode', function() {
  178. expect(function() {
  179. lex("'\\u1''bla'");
  180. }).toThrowMinErr("$parse", "lexerr", "Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla'].");
  181. });
  182. });
  183. describe('ast', function() {
  184. var createAst;
  185. beforeEach(function() {
  186. /* global AST: false */
  187. createAst = function() {
  188. var lexer = new Lexer({csp: false});
  189. var ast = new AST(lexer, {csp: false});
  190. return ast.ast.apply(ast, arguments);
  191. };
  192. });
  193. it('should handle an empty list of tokens', function() {
  194. expect(createAst('')).toEqual({type: 'Program', body: []});
  195. });
  196. it('should understand identifiers', function() {
  197. expect(createAst('foo')).toEqual(
  198. {
  199. type: 'Program',
  200. body: [
  201. {
  202. type: 'ExpressionStatement',
  203. expression: { type: 'Identifier', name: 'foo' }
  204. }
  205. ]
  206. }
  207. );
  208. });
  209. it('should understand non-computed member expressions', function() {
  210. expect(createAst('foo.bar')).toEqual(
  211. {
  212. type: 'Program',
  213. body: [
  214. {
  215. type: 'ExpressionStatement',
  216. expression: {
  217. type: 'MemberExpression',
  218. object: { type: 'Identifier', name: 'foo'},
  219. property: {type: 'Identifier', name: 'bar'},
  220. computed: false
  221. }
  222. }
  223. ]
  224. }
  225. );
  226. });
  227. it('should associate non-computed member expressions left-to-right', function() {
  228. expect(createAst('foo.bar.baz')).toEqual(
  229. {
  230. type: 'Program',
  231. body: [
  232. {
  233. type: 'ExpressionStatement',
  234. expression: {
  235. type: 'MemberExpression',
  236. object: {
  237. type: 'MemberExpression',
  238. object: { type: 'Identifier', name: 'foo'},
  239. property: { type: 'Identifier', name: 'bar' },
  240. computed: false
  241. },
  242. property: {type: 'Identifier', name: 'baz'},
  243. computed: false
  244. }
  245. }
  246. ]
  247. }
  248. );
  249. });
  250. it('should understand computed member expressions', function() {
  251. expect(createAst('foo[bar]')).toEqual(
  252. {
  253. type: 'Program',
  254. body: [
  255. {
  256. type: 'ExpressionStatement',
  257. expression: {
  258. type: 'MemberExpression',
  259. object: { type: 'Identifier', name: 'foo'},
  260. property: {type: 'Identifier', name: 'bar'},
  261. computed: true
  262. }
  263. }
  264. ]
  265. }
  266. );
  267. });
  268. it('should associate computed member expressions left-to-right', function() {
  269. expect(createAst('foo[bar][baz]')).toEqual(
  270. {
  271. type: 'Program',
  272. body: [
  273. {
  274. type: 'ExpressionStatement',
  275. expression: {
  276. type: 'MemberExpression',
  277. object: {
  278. type: 'MemberExpression',
  279. object: { type: 'Identifier', name: 'foo' },
  280. property: { type: 'Identifier', name: 'bar' },
  281. computed: true
  282. },
  283. property: { type: 'Identifier', name: 'baz' },
  284. computed: true
  285. }
  286. }
  287. ]
  288. }
  289. );
  290. });
  291. it('should understand call expressions', function() {
  292. expect(createAst('foo()')).toEqual(
  293. {
  294. type: 'Program',
  295. body: [
  296. {
  297. type: 'ExpressionStatement',
  298. expression: {
  299. type: 'CallExpression',
  300. callee: { type: 'Identifier', name: 'foo'},
  301. arguments: []
  302. }
  303. }
  304. ]
  305. }
  306. );
  307. });
  308. it('should parse call expression arguments', function() {
  309. expect(createAst('foo(bar, baz)')).toEqual(
  310. {
  311. type: 'Program',
  312. body: [
  313. {
  314. type: 'ExpressionStatement',
  315. expression: {
  316. type: 'CallExpression',
  317. callee: { type: 'Identifier', name: 'foo'},
  318. arguments: [
  319. { type: 'Identifier', name: 'bar' },
  320. { type: 'Identifier', name: 'baz' }
  321. ]
  322. }
  323. }
  324. ]
  325. }
  326. );
  327. });
  328. it('should parse call expression left-to-right', function() {
  329. expect(createAst('foo(bar, baz)(man, shell)')).toEqual(
  330. {
  331. type: 'Program',
  332. body: [
  333. {
  334. type: 'ExpressionStatement',
  335. expression: {
  336. type: 'CallExpression',
  337. callee: {
  338. type: 'CallExpression',
  339. callee: { type: 'Identifier', name: 'foo' },
  340. arguments: [
  341. { type: 'Identifier', name: 'bar' },
  342. { type: 'Identifier', name: 'baz' }
  343. ]
  344. },
  345. arguments: [
  346. { type: 'Identifier', name: 'man' },
  347. { type: 'Identifier', name: 'shell' }
  348. ]
  349. }
  350. }
  351. ]
  352. }
  353. );
  354. });
  355. it('should keep the context when having superfluous parenthesis', function() {
  356. expect(createAst('(foo)(bar, baz)')).toEqual(
  357. {
  358. type: 'Program',
  359. body: [
  360. {
  361. type: 'ExpressionStatement',
  362. expression: {
  363. type: 'CallExpression',
  364. callee: { type: 'Identifier', name: 'foo'},
  365. arguments: [
  366. { type: 'Identifier', name: 'bar' },
  367. { type: 'Identifier', name: 'baz' }
  368. ]
  369. }
  370. }
  371. ]
  372. }
  373. );
  374. });
  375. it('should treat member expressions and call expression with the same precedence', function() {
  376. expect(createAst('foo.bar[baz]()')).toEqual(
  377. {
  378. type: 'Program',
  379. body: [
  380. {
  381. type: 'ExpressionStatement',
  382. expression: {
  383. type: 'CallExpression',
  384. callee: {
  385. type: 'MemberExpression',
  386. object: {
  387. type: 'MemberExpression',
  388. object: { type: 'Identifier', name: 'foo' },
  389. property: { type: 'Identifier', name: 'bar' },
  390. computed: false
  391. },
  392. property: { type: 'Identifier', name: 'baz' },
  393. computed: true
  394. },
  395. arguments: []
  396. }
  397. }
  398. ]
  399. }
  400. );
  401. expect(createAst('foo[bar]().baz')).toEqual(
  402. {
  403. type: 'Program',
  404. body: [
  405. {
  406. type: 'ExpressionStatement',
  407. expression: {
  408. type: 'MemberExpression',
  409. object: {
  410. type: 'CallExpression',
  411. callee: {
  412. type: 'MemberExpression',
  413. object: { type: 'Identifier', name: 'foo' },
  414. property: { type: 'Identifier', name: 'bar' },
  415. computed: true
  416. },
  417. arguments: []
  418. },
  419. property: { type: 'Identifier', name: 'baz' },
  420. computed: false
  421. }
  422. }
  423. ]
  424. }
  425. );
  426. expect(createAst('foo().bar[baz]')).toEqual(
  427. {
  428. type: 'Program',
  429. body: [
  430. {
  431. type: 'ExpressionStatement',
  432. expression: {
  433. type: 'MemberExpression',
  434. object: {
  435. type: 'MemberExpression',
  436. object: {
  437. type: 'CallExpression',
  438. callee: { type: 'Identifier', name: 'foo' },
  439. arguments: [] },
  440. property: { type: 'Identifier', name: 'bar' },
  441. computed: false
  442. },
  443. property: { type: 'Identifier', name: 'baz' },
  444. computed: true
  445. }
  446. }
  447. ]
  448. }
  449. );
  450. });
  451. it('should understand literals', function() {
  452. // In a strict sense, `undefined` is not a literal but an identifier
  453. forEach({'123': 123, '"123"': '123', 'true': true, 'false': false, 'null': null, 'undefined': undefined}, function(value, expression) {
  454. expect(createAst(expression)).toEqual(
  455. {
  456. type: 'Program',
  457. body: [
  458. {
  459. type: 'ExpressionStatement',
  460. expression: { type: 'Literal', value: value }
  461. }
  462. ]
  463. }
  464. );
  465. });
  466. });
  467. it('should understand the `this` expression', function() {
  468. expect(createAst('this')).toEqual(
  469. {
  470. type: 'Program',
  471. body: [
  472. {
  473. type: 'ExpressionStatement',
  474. expression: { type: 'ThisExpression' }
  475. }
  476. ]
  477. }
  478. );
  479. });
  480. it('should not confuse `this`, `undefined`, `true`, `false`, `null` when used as identfiers', function() {
  481. forEach(['this', 'undefined', 'true', 'false', 'null'], function(identifier) {
  482. expect(createAst('foo.' + identifier)).toEqual(
  483. {
  484. type: 'Program',
  485. body: [
  486. {
  487. type: 'ExpressionStatement',
  488. expression: {
  489. type: 'MemberExpression',
  490. object: { type: 'Identifier', name: 'foo' },
  491. property: { type: 'Identifier', name: identifier },
  492. computed: false
  493. }
  494. }
  495. ]
  496. }
  497. );
  498. });
  499. });
  500. it('should throw when trying to use non-identifiers as identifiers', function() {
  501. expect(function() { createAst('foo.)'); }).toThrowMinErr('$parse', 'syntax',
  502. "Syntax Error: Token ')' is not a valid identifier at column 5 of the expression [foo.)");
  503. });
  504. it('should throw when all tokens are not consumed', function() {
  505. expect(function() { createAst('foo bar'); }).toThrowMinErr('$parse', 'syntax',
  506. "Syntax Error: Token 'bar' is an unexpected token at column 5 of the expression [foo bar] starting at [bar]");
  507. });
  508. it('should understand the unary operators `-`, `+` and `!`', function() {
  509. forEach(['-', '+', '!'], function(operator) {
  510. expect(createAst(operator + 'foo')).toEqual(
  511. {
  512. type: 'Program',
  513. body: [
  514. {
  515. type: 'ExpressionStatement',
  516. expression: {
  517. type: 'UnaryExpression',
  518. operator: operator,
  519. prefix: true,
  520. argument: { type: 'Identifier', name: 'foo' }
  521. }
  522. }
  523. ]
  524. }
  525. );
  526. });
  527. });
  528. it('should handle all unary operators with the same precedence', function() {
  529. forEach([['+', '-', '!'], ['-', '!', '+'], ['!', '+', '-']], function(operators) {
  530. expect(createAst(operators.join('') + 'foo')).toEqual(
  531. {
  532. type: 'Program',
  533. body: [
  534. {
  535. type: 'ExpressionStatement',
  536. expression: {
  537. type: 'UnaryExpression',
  538. operator: operators[0],
  539. prefix: true,
  540. argument: {
  541. type: 'UnaryExpression',
  542. operator: operators[1],
  543. prefix: true,
  544. argument: {
  545. type: 'UnaryExpression',
  546. operator: operators[2],
  547. prefix: true,
  548. argument: { type: 'Identifier', name: 'foo' }
  549. }
  550. }
  551. }
  552. }
  553. ]
  554. }
  555. );
  556. });
  557. });
  558. it('should be able to understand binary operators', function() {
  559. forEach(['*', '/', '%', '+', '-', '<', '>', '<=', '>=', '==','!=','===','!=='], function(operator) {
  560. expect(createAst('foo' + operator + 'bar')).toEqual(
  561. {
  562. type: 'Program',
  563. body: [
  564. {
  565. type: 'ExpressionStatement',
  566. expression: {
  567. type: 'BinaryExpression',
  568. operator: operator,
  569. left: { type: 'Identifier', name: 'foo' },
  570. right: { type: 'Identifier', name: 'bar' }
  571. }
  572. }
  573. ]
  574. }
  575. );
  576. });
  577. });
  578. it('should associate binary operators with the same precendence left-to-right', function() {
  579. var operatorsByPrecedence = [['*', '/', '%'], ['+', '-'], ['<', '>', '<=', '>='], ['==','!=','===','!==']];
  580. forEach(operatorsByPrecedence, function(operators) {
  581. forEach(operators, function(op1) {
  582. forEach(operators, function(op2) {
  583. expect(createAst('foo' + op1 + 'bar' + op2 + 'baz')).toEqual(
  584. {
  585. type: 'Program',
  586. body: [
  587. {
  588. type: 'ExpressionStatement',
  589. expression: {
  590. type: 'BinaryExpression',
  591. operator: op2,
  592. left: {
  593. type: 'BinaryExpression',
  594. operator: op1,
  595. left: { type: 'Identifier', name: 'foo' },
  596. right: { type: 'Identifier', name: 'bar' }
  597. },
  598. right: { type: 'Identifier', name: 'baz' }
  599. }
  600. }
  601. ]
  602. }
  603. );
  604. });
  605. });
  606. });
  607. });
  608. it('should give higher prcedence to member calls than to unary expressions', function() {
  609. forEach(['!', '+', '-'], function(operator) {
  610. expect(createAst(operator + 'foo()')).toEqual(
  611. {
  612. type: 'Program',
  613. body: [
  614. {
  615. type: 'ExpressionStatement',
  616. expression: {
  617. type: 'UnaryExpression',
  618. operator: operator,
  619. prefix: true,
  620. argument: {
  621. type: 'CallExpression',
  622. callee: { type: 'Identifier', name: 'foo' },
  623. arguments: []
  624. }
  625. }
  626. }
  627. ]
  628. }
  629. );
  630. expect(createAst(operator + 'foo.bar')).toEqual(
  631. {
  632. type: 'Program',
  633. body: [
  634. {
  635. type: 'ExpressionStatement',
  636. expression: {
  637. type: 'UnaryExpression',
  638. operator: operator,
  639. prefix: true,
  640. argument: {
  641. type: 'MemberExpression',
  642. object: { type: 'Identifier', name: 'foo' },
  643. property: { type: 'Identifier', name: 'bar' },
  644. computed: false
  645. }
  646. }
  647. }
  648. ]
  649. }
  650. );
  651. expect(createAst(operator + 'foo[bar]')).toEqual(
  652. {
  653. type: 'Program',
  654. body: [
  655. {
  656. type: 'ExpressionStatement',
  657. expression: {
  658. type: 'UnaryExpression',
  659. operator: operator,
  660. prefix: true,
  661. argument: {
  662. type: 'MemberExpression',
  663. object: { type: 'Identifier', name: 'foo' },
  664. property: { type: 'Identifier', name: 'bar' },
  665. computed: true
  666. }
  667. }
  668. }
  669. ]
  670. }
  671. );
  672. });
  673. });
  674. it('should give higher precedence to unary operators over multiplicative operators', function() {
  675. forEach(['!', '+', '-'], function(op1) {
  676. forEach(['*', '/', '%'], function(op2) {
  677. expect(createAst(op1 + 'foo' + op2 + op1 + 'bar')).toEqual(
  678. {
  679. type: 'Program',
  680. body: [
  681. {
  682. type: 'ExpressionStatement',
  683. expression: {
  684. type: 'BinaryExpression',
  685. operator: op2,
  686. left: {
  687. type: 'UnaryExpression',
  688. operator: op1,
  689. prefix: true,
  690. argument: { type: 'Identifier', name: 'foo' }
  691. },
  692. right: {
  693. type: 'UnaryExpression',
  694. operator: op1,
  695. prefix: true,
  696. argument: { type: 'Identifier', name: 'bar' }
  697. }
  698. }
  699. }
  700. ]
  701. }
  702. );
  703. });
  704. });
  705. });
  706. it('should give binary operators their right precedence', function() {
  707. var operatorsByPrecedence = [['*', '/', '%'], ['+', '-'], ['<', '>', '<=', '>='], ['==','!=','===','!==']];
  708. for (var i = 0; i < operatorsByPrecedence.length - 1; ++i) {
  709. forEach(operatorsByPrecedence[i], function(op1) {
  710. forEach(operatorsByPrecedence[i + 1], function(op2) {
  711. expect(createAst('foo' + op1 + 'bar' + op2 + 'baz' + op1 + 'man')).toEqual(
  712. {
  713. type: 'Program',
  714. body: [
  715. {
  716. type: 'ExpressionStatement',
  717. expression: {
  718. type: 'BinaryExpression',
  719. operator: op2,
  720. left: {
  721. type: 'BinaryExpression',
  722. operator: op1,
  723. left: { type: 'Identifier', name: 'foo' },
  724. right: { type: 'Identifier', name: 'bar' }
  725. },
  726. right: {
  727. type: 'BinaryExpression',
  728. operator: op1,
  729. left: { type: 'Identifier', name: 'baz' },
  730. right: { type: 'Identifier', name: 'man' }
  731. }
  732. }
  733. }
  734. ]
  735. }
  736. );
  737. });
  738. });
  739. }
  740. });
  741. it('should understand logical operators', function() {
  742. forEach(['||', '&&'], function(operator) {
  743. expect(createAst('foo' + operator + 'bar')).toEqual(
  744. {
  745. type: 'Program',
  746. body: [
  747. {
  748. type: 'ExpressionStatement',
  749. expression: {
  750. type: 'LogicalExpression',
  751. operator: operator,
  752. left: { type: 'Identifier', name: 'foo' },
  753. right: { type: 'Identifier', name: 'bar' }
  754. }
  755. }
  756. ]
  757. }
  758. );
  759. });
  760. });
  761. it('should associate logical operators left-to-right', function() {
  762. forEach(['||', '&&'], function(op) {
  763. expect(createAst('foo' + op + 'bar' + op + 'baz')).toEqual(
  764. {
  765. type: 'Program',
  766. body: [
  767. {
  768. type: 'ExpressionStatement',
  769. expression: {
  770. type: 'LogicalExpression',
  771. operator: op,
  772. left: {
  773. type: 'LogicalExpression',
  774. operator: op,
  775. left: { type: 'Identifier', name: 'foo' },
  776. right: { type: 'Identifier', name: 'bar' }
  777. },
  778. right: { type: 'Identifier', name: 'baz' }
  779. }
  780. }
  781. ]
  782. }
  783. );
  784. });
  785. });
  786. it('should understand ternary operators', function() {
  787. expect(createAst('foo?bar:baz')).toEqual(
  788. {
  789. type: 'Program',
  790. body: [
  791. {
  792. type: 'ExpressionStatement',
  793. expression: {
  794. type: 'ConditionalExpression',
  795. test: { type: 'Identifier', name: 'foo' },
  796. alternate: { type: 'Identifier', name: 'bar' },
  797. consequent: { type: 'Identifier', name: 'baz' }
  798. }
  799. }
  800. ]
  801. }
  802. );
  803. });
  804. it('should associate the conditional operator right-to-left', function() {
  805. expect(createAst('foo0?foo1:foo2?bar0?bar1:bar2:man0?man1:man2')).toEqual(
  806. {
  807. type: 'Program',
  808. body: [
  809. {
  810. type: 'ExpressionStatement',
  811. expression: {
  812. type: 'ConditionalExpression',
  813. test: { type: 'Identifier', name: 'foo0' },
  814. alternate: { type: 'Identifier', name: 'foo1' },
  815. consequent: {
  816. type: 'ConditionalExpression',
  817. test: { type: 'Identifier', name: 'foo2' },
  818. alternate: {
  819. type: 'ConditionalExpression',
  820. test: { type: 'Identifier', name: 'bar0' },
  821. alternate: { type: 'Identifier', name: 'bar1' },
  822. consequent: { type: 'Identifier', name: 'bar2' }
  823. },
  824. consequent: {
  825. type: 'ConditionalExpression',
  826. test: { type: 'Identifier', name: 'man0' },
  827. alternate: { type: 'Identifier', name: 'man1' },
  828. consequent: { type: 'Identifier', name: 'man2' }
  829. }
  830. }
  831. }
  832. }
  833. ]
  834. }
  835. );
  836. });
  837. it('should understand assignment operator', function() {
  838. // Currently, only `=` is supported
  839. expect(createAst('foo=bar')).toEqual(
  840. {
  841. type: 'Program',
  842. body: [
  843. {
  844. type: 'ExpressionStatement',
  845. expression: {
  846. type: 'AssignmentExpression',
  847. left: { type: 'Identifier', name: 'foo' },
  848. right: { type: 'Identifier', name: 'bar' },
  849. operator: '='
  850. }
  851. }
  852. ]
  853. }
  854. );
  855. });
  856. it('should associate assignments right-to-left', function() {
  857. // Currently, only `=` is supported
  858. expect(createAst('foo=bar=man')).toEqual(
  859. {
  860. type: 'Program',
  861. body: [
  862. {
  863. type: 'ExpressionStatement',
  864. expression: {
  865. type: 'AssignmentExpression',
  866. left: { type: 'Identifier', name: 'foo' },
  867. right: {
  868. type: 'AssignmentExpression',
  869. left: { type: 'Identifier', name: 'bar' },
  870. right: { type: 'Identifier', name: 'man' },
  871. operator: '='
  872. },
  873. operator: '='
  874. }
  875. }
  876. ]
  877. }
  878. );
  879. });
  880. it('should give higher precedence to equality than to the logical `and` operator', function() {
  881. forEach(['==','!=','===','!=='], function(operator) {
  882. expect(createAst('foo' + operator + 'bar && man' + operator + 'shell')).toEqual(
  883. {
  884. type: 'Program',
  885. body: [
  886. {
  887. type: 'ExpressionStatement',
  888. expression: {
  889. type: 'LogicalExpression',
  890. operator: '&&',
  891. left: {
  892. type: 'BinaryExpression',
  893. operator: operator,
  894. left: { type: 'Identifier', name: 'foo' },
  895. right: { type: 'Identifier', name: 'bar' }
  896. },
  897. right: {
  898. type: 'BinaryExpression',
  899. operator: operator,
  900. left: { type: 'Identifier', name: 'man' },
  901. right: { type: 'Identifier', name: 'shell' }
  902. }
  903. }
  904. }
  905. ]
  906. }
  907. );
  908. });
  909. });
  910. it('should give higher precedence to logical `and` than to logical `or`', function() {
  911. expect(createAst('foo&&bar||man&&shell')).toEqual(
  912. {
  913. type: 'Program',
  914. body: [
  915. {
  916. type: 'ExpressionStatement',
  917. expression: {
  918. type: 'LogicalExpression',
  919. operator: '||',
  920. left: {
  921. type: 'LogicalExpression',
  922. operator: '&&',
  923. left: { type: 'Identifier', name: 'foo' },
  924. right: { type: 'Identifier', name: 'bar' }
  925. },
  926. right: {
  927. type: 'LogicalExpression',
  928. operator: '&&',
  929. left: { type: 'Identifier', name: 'man' },
  930. right: { type: 'Identifier', name: 'shell' }
  931. }
  932. }
  933. }
  934. ]
  935. }
  936. );
  937. });
  938. it('should give higher precedence to the logical `or` than to the conditional operator', function() {
  939. expect(createAst('foo||bar?man:shell')).toEqual(
  940. {
  941. type: 'Program',
  942. body: [
  943. {
  944. type: 'ExpressionStatement',
  945. expression: {
  946. type: 'ConditionalExpression',
  947. test: {
  948. type: 'LogicalExpression',
  949. operator: '||',
  950. left: { type: 'Identifier', name: 'foo' },
  951. right: { type: 'Identifier', name: 'bar' }
  952. },
  953. alternate: { type: 'Identifier', name: 'man' },
  954. consequent: { type: 'Identifier', name: 'shell' }
  955. }
  956. }
  957. ]
  958. }
  959. );
  960. });
  961. it('should give higher precedence to the conditional operator than to assignment operators', function() {
  962. expect(createAst('foo=bar?man:shell')).toEqual(
  963. {
  964. type: 'Program',
  965. body: [
  966. {
  967. type: 'ExpressionStatement',
  968. expression: {
  969. type: 'AssignmentExpression',
  970. left: { type: 'Identifier', name: 'foo' },
  971. right: {
  972. type: 'ConditionalExpression',
  973. test: { type: 'Identifier', name: 'bar' },
  974. alternate: { type: 'Identifier', name: 'man' },
  975. consequent: { type: 'Identifier', name: 'shell' }
  976. },
  977. operator: '='
  978. }
  979. }
  980. ]
  981. }
  982. );
  983. });
  984. it('should understand array literals', function() {
  985. expect(createAst('[]')).toEqual(
  986. {
  987. type: 'Program',
  988. body: [
  989. {
  990. type: 'ExpressionStatement',
  991. expression: {
  992. type: 'ArrayExpression',
  993. elements: []
  994. }
  995. }
  996. ]
  997. }
  998. );
  999. expect(createAst('[foo]')).toEqual(
  1000. {
  1001. type: 'Program',
  1002. body: [
  1003. {
  1004. type: 'ExpressionStatement',
  1005. expression: {
  1006. type: 'ArrayExpression',
  1007. elements: [
  1008. { type: 'Identifier', name: 'foo' }
  1009. ]
  1010. }
  1011. }
  1012. ]
  1013. }
  1014. );
  1015. expect(createAst('[foo,]')).toEqual(
  1016. {
  1017. type: 'Program',
  1018. body: [
  1019. {
  1020. type: 'ExpressionStatement',
  1021. expression: {
  1022. type: 'ArrayExpression',
  1023. elements: [
  1024. { type: 'Identifier', name: 'foo' }
  1025. ]
  1026. }
  1027. }
  1028. ]
  1029. }
  1030. );
  1031. expect(createAst('[foo,bar,man,shell]')).toEqual(
  1032. {
  1033. type: 'Program',
  1034. body: [
  1035. {
  1036. type: 'ExpressionStatement',
  1037. expression: {
  1038. type: 'ArrayExpression',
  1039. elements: [
  1040. { type: 'Identifier', name: 'foo' },
  1041. { type: 'Identifier', name: 'bar' },
  1042. { type: 'Identifier', name: 'man' },
  1043. { type: 'Identifier', name: 'shell' }
  1044. ]
  1045. }
  1046. }
  1047. ]
  1048. }
  1049. );
  1050. expect(createAst('[foo,bar,man,shell,]')).toEqual(
  1051. {
  1052. type: 'Program',
  1053. body: [
  1054. {
  1055. type: 'ExpressionStatement',
  1056. expression: {
  1057. type: 'ArrayExpression',
  1058. elements: [
  1059. { type: 'Identifier', name: 'foo' },
  1060. { type: 'Identifier', name: 'bar' },
  1061. { type: 'Identifier', name: 'man' },
  1062. { type: 'Identifier', name: 'shell' }
  1063. ]
  1064. }
  1065. }
  1066. ]
  1067. }
  1068. );
  1069. });
  1070. it('should understand objects', function() {
  1071. expect(createAst('{}')).toEqual(
  1072. {
  1073. type: 'Program',
  1074. body: [
  1075. {
  1076. type: 'ExpressionStatement',
  1077. expression: {
  1078. type: 'ObjectExpression',
  1079. properties: []
  1080. }
  1081. }
  1082. ]
  1083. }
  1084. );
  1085. expect(createAst('{foo: bar}')).toEqual(
  1086. {
  1087. type: 'Program',
  1088. body: [
  1089. {
  1090. type: 'ExpressionStatement',
  1091. expression: {
  1092. type: 'ObjectExpression',
  1093. properties: [
  1094. {
  1095. type: 'Property',
  1096. kind: 'init',
  1097. key: { type: 'Identifier', name: 'foo' },
  1098. value: { type: 'Identifier', name: 'bar' }
  1099. }
  1100. ]
  1101. }
  1102. }
  1103. ]
  1104. }
  1105. );
  1106. expect(createAst('{foo: bar,}')).toEqual(
  1107. {
  1108. type: 'Program',
  1109. body: [
  1110. {
  1111. type: 'ExpressionStatement',
  1112. expression: {
  1113. type: 'ObjectExpression',
  1114. properties: [
  1115. {
  1116. type: 'Property',
  1117. kind: 'init',
  1118. key: { type: 'Identifier', name: 'foo' },
  1119. value: { type: 'Identifier', name: 'bar' }
  1120. }
  1121. ]
  1122. }
  1123. }
  1124. ]
  1125. }
  1126. );
  1127. expect(createAst('{foo: bar, "man": "shell", 42: 23}')).toEqual(
  1128. {
  1129. type: 'Program',
  1130. body: [
  1131. {
  1132. type: 'ExpressionStatement',
  1133. expression: {
  1134. type: 'ObjectExpression',
  1135. properties: [
  1136. {
  1137. type: 'Property',
  1138. kind: 'init',
  1139. key: { type: 'Identifier', name: 'foo' },
  1140. value: { type: 'Identifier', name: 'bar' }
  1141. },
  1142. {
  1143. type: 'Property',
  1144. kind: 'init',
  1145. key: { type: 'Literal', value: 'man' },
  1146. value: { type: 'Literal', value: 'shell' }
  1147. },
  1148. {
  1149. type: 'Property',
  1150. kind: 'init',
  1151. key: { type: 'Literal', value: 42 },
  1152. value: { type: 'Literal', value: 23 }
  1153. }
  1154. ]
  1155. }
  1156. }
  1157. ]
  1158. }
  1159. );
  1160. expect(createAst('{foo: bar, "man": "shell", 42: 23,}')).toEqual(
  1161. {
  1162. type: 'Program',
  1163. body: [
  1164. {
  1165. type: 'ExpressionStatement',
  1166. expression: {
  1167. type: 'ObjectExpression',
  1168. properties: [
  1169. {
  1170. type: 'Property',
  1171. kind: 'init',
  1172. key: { type: 'Identifier', name: 'foo' },
  1173. value: { type: 'Identifier', name: 'bar' }
  1174. },
  1175. {
  1176. type: 'Property',
  1177. kind: 'init',
  1178. key: { type: 'Literal', value: 'man' },
  1179. value: { type: 'Literal', value: 'shell' }
  1180. },
  1181. {
  1182. type: 'Property',
  1183. kind: 'init',
  1184. key: { type: 'Literal', value: 42 },
  1185. value: { type: 'Literal', value: 23 }
  1186. }
  1187. ]
  1188. }
  1189. }
  1190. ]
  1191. }
  1192. );
  1193. });
  1194. it('should understand multiple expressions', function() {
  1195. expect(createAst('foo = bar; man = shell')).toEqual(
  1196. {
  1197. type: 'Program',
  1198. body: [
  1199. {
  1200. type: 'ExpressionStatement',
  1201. expression: {
  1202. type: 'AssignmentExpression',
  1203. left: { type: 'Identifier', name: 'foo' },
  1204. right: { type: 'Identifier', name: 'bar' },
  1205. operator: '='
  1206. }
  1207. },
  1208. {
  1209. type: 'ExpressionStatement',
  1210. expression: {
  1211. type: 'AssignmentExpression',
  1212. left: { type: 'Identifier', name: 'man' },
  1213. right: { type: 'Identifier', name: 'shell' },
  1214. operator: '='
  1215. }
  1216. }
  1217. ]
  1218. }
  1219. );
  1220. });
  1221. // This is non-standard syntax
  1222. it('should understand filters', function() {
  1223. expect(createAst('foo | bar')).toEqual(
  1224. {
  1225. type: 'Program',
  1226. body: [
  1227. {
  1228. type: 'ExpressionStatement',
  1229. expression: {
  1230. type: 'CallExpression',
  1231. callee: { type: 'Identifier', name: 'bar'},
  1232. arguments: [
  1233. { type: 'Identifier', name: 'foo' }
  1234. ],
  1235. filter: true
  1236. }
  1237. }
  1238. ]
  1239. }
  1240. );
  1241. });
  1242. it('should understand filters with extra parameters', function() {
  1243. expect(createAst('foo | bar:baz')).toEqual(
  1244. {
  1245. type: 'Program',
  1246. body: [
  1247. {
  1248. type: 'ExpressionStatement',
  1249. expression: {
  1250. type: 'CallExpression',
  1251. callee: { type: 'Identifier', name: 'bar'},
  1252. arguments: [
  1253. { type: 'Identifier', name: 'foo' },
  1254. { type: 'Identifier', name: 'baz' }
  1255. ],
  1256. filter: true
  1257. }
  1258. }
  1259. ]
  1260. }
  1261. );
  1262. });
  1263. it('should associate filters right-to-left', function() {
  1264. expect(createAst('foo | bar:man | shell')).toEqual(
  1265. {
  1266. type: 'Program',
  1267. body: [
  1268. {
  1269. type: 'ExpressionStatement',
  1270. expression: {
  1271. type: 'CallExpression',
  1272. callee: { type: 'Identifier', name: 'shell' },
  1273. arguments: [
  1274. {
  1275. type: 'CallExpression',
  1276. callee: { type: 'Identifier', name: 'bar' },
  1277. arguments: [
  1278. { type: 'Identifier', name: 'foo' },
  1279. { type: 'Identifier', name: 'man' }
  1280. ],
  1281. filter: true
  1282. }
  1283. ],
  1284. filter: true
  1285. }
  1286. }
  1287. ]
  1288. }
  1289. );
  1290. });
  1291. it('should give higher precedence to assignments over filters', function() {
  1292. expect(createAst('foo=bar | man')).toEqual(
  1293. {
  1294. type: 'Program',
  1295. body: [
  1296. {
  1297. type: 'ExpressionStatement',
  1298. expression: {
  1299. type: 'CallExpression',
  1300. callee: { type: 'Identifier', name: 'man' },
  1301. arguments: [
  1302. {
  1303. type: 'AssignmentExpression',
  1304. left: { type: 'Identifier', name: 'foo' },
  1305. right: { type: 'Identifier', name: 'bar' },
  1306. operator: '='
  1307. }
  1308. ],
  1309. filter: true
  1310. }
  1311. }
  1312. ]
  1313. }
  1314. );
  1315. });
  1316. it('should accept expression as filters parameters', function() {
  1317. expect(createAst('foo | bar:baz=man')).toEqual(
  1318. {
  1319. type: 'Program',
  1320. body: [
  1321. {
  1322. type: 'ExpressionStatement',
  1323. expression: {
  1324. type: 'CallExpression',
  1325. callee: { type: 'Identifier', name: 'bar' },
  1326. arguments: [
  1327. { type: 'Identifier', name: 'foo' },
  1328. {
  1329. type: 'AssignmentExpression',
  1330. left: { type: 'Identifier', name: 'baz' },
  1331. right: { type: 'Identifier', name: 'man' },
  1332. operator: '='
  1333. }
  1334. ],
  1335. filter: true
  1336. }
  1337. }
  1338. ]
  1339. }
  1340. );
  1341. });
  1342. it('should accept expression as computer members', function() {
  1343. expect(createAst('foo[a = 1]')).toEqual(
  1344. {
  1345. type: 'Program',
  1346. body: [
  1347. {
  1348. type: 'ExpressionStatement',
  1349. expression: {
  1350. type: 'MemberExpression',
  1351. object: { type: 'Identifier', name: 'foo' },
  1352. property: {
  1353. type: 'AssignmentExpression',
  1354. left: { type: 'Identifier', name: 'a' },
  1355. right: { type: 'Literal', value: 1 },
  1356. operator: '='
  1357. },
  1358. computed: true
  1359. }
  1360. }
  1361. ]
  1362. }
  1363. );
  1364. });
  1365. it('should accept expression in function arguments', function() {
  1366. expect(createAst('foo(a = 1)')).toEqual(
  1367. {
  1368. type: 'Program',
  1369. body: [
  1370. {
  1371. type: 'ExpressionStatement',
  1372. expression: {
  1373. type: 'CallExpression',
  1374. callee: { type: 'Identifier', name: 'foo' },
  1375. arguments: [
  1376. {
  1377. type: 'AssignmentExpression',
  1378. left: { type: 'Identifier', name: 'a' },
  1379. right: { type: 'Literal', value: 1 },
  1380. operator: '='
  1381. }
  1382. ]
  1383. }
  1384. }
  1385. ]
  1386. }
  1387. );
  1388. });
  1389. it('should accept expression as part of ternary operators', function() {
  1390. expect(createAst('foo || bar ? man = 1 : shell = 1')).toEqual(
  1391. {
  1392. type: 'Program',
  1393. body: [
  1394. {
  1395. type: 'ExpressionStatement',
  1396. expression: {
  1397. type: 'ConditionalExpression',
  1398. test: {
  1399. type: 'LogicalExpression',
  1400. operator: '||',
  1401. left: { type: 'Identifier', name: 'foo' },
  1402. right: { type: 'Identifier', name: 'bar' }
  1403. },
  1404. alternate: {
  1405. type: 'AssignmentExpression',
  1406. left: { type: 'Identifier', name: 'man' },
  1407. right: { type: 'Literal', value: 1 },
  1408. operator: '='
  1409. },
  1410. consequent: {
  1411. type: 'AssignmentExpression',
  1412. left: { type: 'Identifier', name: 'shell' },
  1413. right: { type: 'Literal', value: 1 },
  1414. operator: '='
  1415. }
  1416. }
  1417. }
  1418. ]
  1419. }
  1420. );
  1421. });
  1422. it('should accept expression as part of array literals', function() {
  1423. expect(createAst('[foo = 1]')).toEqual(
  1424. {
  1425. type: 'Program',
  1426. body: [
  1427. {
  1428. type: 'ExpressionStatement',
  1429. expression: {
  1430. type: 'ArrayExpression',
  1431. elements: [
  1432. {
  1433. type: 'AssignmentExpression',
  1434. left: { type: 'Identifier', name: 'foo' },
  1435. right: { type: 'Literal', value: 1 },
  1436. operator: '='
  1437. }
  1438. ]
  1439. }
  1440. }
  1441. ]
  1442. }
  1443. );
  1444. });
  1445. it('should accept expression as part of object literals', function() {
  1446. expect(createAst('{foo: bar = 1}')).toEqual(
  1447. {
  1448. type: 'Program',
  1449. body: [
  1450. {
  1451. type: 'ExpressionStatement',
  1452. expression: {
  1453. type: 'ObjectExpression',
  1454. properties: [
  1455. {
  1456. type: 'Property',
  1457. kind: 'init',
  1458. key: { type: 'Identifier', name: 'foo' },
  1459. value: {
  1460. type: 'AssignmentExpression',
  1461. left: { type: 'Identifier', name: 'bar' },
  1462. right: { type: 'Literal', value: 1 },
  1463. operator: '='
  1464. }
  1465. }
  1466. ]
  1467. }
  1468. }
  1469. ]
  1470. }
  1471. );
  1472. });
  1473. it('should be possible to use parenthesis to indicate precedence', function() {
  1474. expect(createAst('(foo + bar).man')).toEqual(
  1475. {
  1476. type: 'Program',
  1477. body: [
  1478. {
  1479. type: 'ExpressionStatement',
  1480. expression: {
  1481. type: 'MemberExpression',
  1482. object: {
  1483. type: 'BinaryExpression',
  1484. operator: '+',
  1485. left: { type: 'Identifier', name: 'foo' },
  1486. right: { type: 'Identifier', name: 'bar' }
  1487. },
  1488. property: { type: 'Identifier', name: 'man' },
  1489. computed: false
  1490. }
  1491. }
  1492. ]
  1493. }
  1494. );
  1495. });
  1496. it('should skip empty expressions', function() {
  1497. expect(createAst('foo;;;;bar')).toEqual(
  1498. {
  1499. type: 'Program',
  1500. body: [
  1501. {
  1502. type: 'ExpressionStatement',
  1503. expression: { type: 'Identifier', name: 'foo' }
  1504. },
  1505. {
  1506. type: 'ExpressionStatement',
  1507. expression: { type: 'Identifier', name: 'bar' }
  1508. }
  1509. ]
  1510. }
  1511. );
  1512. expect(createAst(';foo')).toEqual(
  1513. {
  1514. type: 'Program',
  1515. body: [
  1516. {
  1517. type: 'ExpressionStatement',
  1518. expression: { type: 'Identifier', name: 'foo' }
  1519. }
  1520. ]
  1521. }
  1522. );
  1523. expect(createAst('foo;')).toEqual({
  1524. type: 'Program',
  1525. body: [
  1526. {
  1527. type: 'ExpressionStatement',
  1528. expression: { type: 'Identifier', name: 'foo' }
  1529. }
  1530. ]
  1531. });
  1532. expect(createAst(';;;;')).toEqual({type: 'Program', body: []});
  1533. expect(createAst('')).toEqual({type: 'Program', body: []});
  1534. });
  1535. });
  1536. var $filterProvider, scope;
  1537. beforeEach(module(['$filterProvider', function(filterProvider) {
  1538. $filterProvider = filterProvider;
  1539. }]));
  1540. forEach([true, false], function(cspEnabled) {
  1541. describe('csp: ' + cspEnabled, function() {
  1542. beforeEach(module(function($provide) {
  1543. $provide.decorator('$sniffer', function($delegate) {
  1544. $delegate.csp = cspEnabled;
  1545. return $delegate;
  1546. });
  1547. }, provideLog));
  1548. beforeEach(inject(function($rootScope) {
  1549. scope = $rootScope;
  1550. }));
  1551. it('should parse expressions', function() {
  1552. /*jshint -W006, -W007 */
  1553. expect(scope.$eval("-1")).toEqual(-1);
  1554. expect(scope.$eval("1 + 2.5")).toEqual(3.5);
  1555. expect(scope.$eval("1 + -2.5")).toEqual(-1.5);
  1556. expect(scope.$eval("1+2*3/4")).toEqual(1 + 2 * 3 / 4);
  1557. expect(scope.$eval("0--1+1.5")).toEqual(0 - -1 + 1.5);
  1558. expect(scope.$eval("-0--1++2*-3/-4")).toEqual(-0 - -1 + +2 * -3 / -4);
  1559. expect(scope.$eval("1/2*3")).toEqual(1 / 2 * 3);
  1560. });
  1561. it('should parse unary', function() {
  1562. expect(scope.$eval("+1")).toEqual(+1);
  1563. expect(scope.$eval("-1")).toEqual(-1);
  1564. expect(scope.$eval("+'1'")).toEqual(+'1');
  1565. expect(scope.$eval("-'1'")).toEqual(-'1');
  1566. expect(scope.$eval("+undefined")).toEqual(0);
  1567. expect(scope.$eval("-undefined")).toEqual(0);
  1568. expect(scope.$eval("+null")).toEqual(+null);
  1569. expect(scope.$eval("-null")).toEqual(-null);
  1570. expect(scope.$eval("+false")).toEqual(+false);
  1571. expect(scope.$eval("-false")).toEqual(-false);
  1572. expect(scope.$eval("+true")).toEqual(+true);
  1573. expect(scope.$eval("-true")).toEqual(-true);
  1574. });
  1575. it('should parse comparison', function() {
  1576. /* jshint -W041 */
  1577. expect(scope.$eval("false")).toBeFalsy();
  1578. expect(scope.$eval("!true")).toBeFalsy();
  1579. expect(scope.$eval("1==1")).toBeTruthy();
  1580. expect(scope.$eval("1==true")).toBeTruthy();
  1581. expect(scope.$eval("1===1")).toBeTruthy();
  1582. expect(scope.$eval("1==='1'")).toBeFalsy();
  1583. expect(scope.$eval("1===true")).toBeFalsy();
  1584. expect(scope.$eval("'true'===true")).toBeFalsy();
  1585. expect(scope.$eval("1!==2")).toBeTruthy();
  1586. expect(scope.$eval("1!=='1'")).toBeTruthy();
  1587. expect(scope.$eval("1!=2")).toBeTruthy();
  1588. expect(scope.$eval("1<2")).toBeTruthy();
  1589. expect(scope.$eval("1<=1")).toBeTruthy();
  1590. expect(scope.$eval("1>2")).toEqual(1 > 2);
  1591. expect(scope.$eval("2>=1")).toEqual(2 >= 1);
  1592. expect(scope.$eval("true==2<3")).toEqual(true == 2 < 3);
  1593. expect(scope.$eval("true===2<3")).toEqual(true === 2 < 3);
  1594. expect(scope.$eval("true===3===3")).toEqual(true === 3 === 3);
  1595. expect(scope.$eval("3===3===true")).toEqual(3 === 3 === true);
  1596. expect(scope.$eval("3 >= 3 > 2")).toEqual(3 >= 3 > 2);
  1597. });
  1598. it('should parse logical', function() {
  1599. expect(scope.$eval("0&&2")).toEqual(0 && 2);
  1600. expect(scope.$eval("0||2")).toEqual(0 || 2);
  1601. expect(scope.$eval("0||1&&2")).toEqual(0 || 1 && 2);
  1602. });
  1603. it('should parse ternary', function() {
  1604. var returnTrue = scope.returnTrue = function() { return true; };
  1605. var returnFalse = scope.returnFalse = function() { return false; };
  1606. var returnString = scope.returnString = function() { return 'asd'; };
  1607. var returnInt = scope.returnInt = function() { return 123; };
  1608. var identity = scope.identity = function(x) { return x; };
  1609. // Simple.
  1610. expect(scope.$eval('0?0:2')).toEqual(0 ? 0 : 2);
  1611. expect(scope.$eval('1?0:2')).toEqual(1 ? 0 : 2);
  1612. // Nested on the left.
  1613. expect(scope.$eval('0?0?0:0:2')).toEqual(0 ? 0 ? 0 : 0 : 2);
  1614. expect(scope.$eval('1?0?0:0:2')).toEqual(1 ? 0 ? 0 : 0 : 2);
  1615. expect(scope.$eval('0?1?0:0:2')).toEqual(0 ? 1 ? 0 : 0 : 2);
  1616. expect(scope.$eval('0?0?1:0:2')).toEqual(0 ? 0 ? 1 : 0 : 2);
  1617. expect(scope.$eval('0?0?0:2:3')).toEqual(0 ? 0 ? 0 : 2 : 3);
  1618. expect(scope.$eval('1?1?0:0:2')).toEqual(1 ? 1 ? 0 : 0 : 2);
  1619. expect(scope.$eval('1?1?1:0:2')).toEqual(1 ? 1 ? 1 : 0 : 2);
  1620. expect(scope.$eval('1?1?1:2:3')).toEqual(1 ? 1 ? 1 : 2 : 3);
  1621. expect(scope.$eval('1?1?1:2:3')).toEqual(1 ? 1 ? 1 : 2 : 3);
  1622. // Nested on the right.
  1623. expect(scope.$eval('0?0:0?0:2')).toEqual(0 ? 0 : 0 ? 0 : 2);
  1624. expect(scope.$eval('1?0:0?0:2')).toEqual(1 ? 0 : 0 ? 0 : 2);
  1625. expect(scope.$eval('0?1:0?0:2')).toEqual(0 ? 1 : 0 ? 0 : 2);
  1626. expect(scope.$eval('0?0:1?0:2')).toEqual(0 ? 0 : 1 ? 0 : 2);
  1627. expect(scope.$eval('0?0:0?2:3')).toEqual(0 ? 0 : 0 ? 2 : 3);
  1628. expect(scope.$eval('1?1:0?0:2')).toEqual(1 ? 1 : 0 ? 0 : 2);
  1629. expect(scope.$eval('1?1:1?0:2')).toEqual(1 ? 1 : 1 ? 0 : 2);
  1630. expect(scope.$eval('1?1:1?2:3')).toEqual(1 ? 1 : 1 ? 2 : 3);
  1631. expect(scope.$eval('1?1:1?2:3')).toEqual(1 ? 1 : 1 ? 2 : 3);
  1632. // Precedence with respect to logical operators.
  1633. expect(scope.$eval('0&&1?0:1')).toEqual(0 && 1 ? 0 : 1);
  1634. expect(scope.$eval('1||0?0:0')).toEqual(1 || 0 ? 0 : 0);
  1635. expect(scope.$eval('0?0&&1:2')).toEqual(0 ? 0 && 1 : 2);
  1636. expect(scope.$eval('0?1&&1:2')).toEqual(0 ? 1 && 1 : 2);
  1637. expect(scope.$eval('0?0||0:1')).toEqual(0 ? 0 || 0 : 1);
  1638. expect(scope.$eval('0?0||1:2')).toEqual(0 ? 0 || 1 : 2);
  1639. expect(scope.$eval('1?0&&1:2')).toEqual(1 ? 0 && 1 : 2);
  1640. expect(scope.$eval('1?1&&1:2')).toEqual(1 ? 1 && 1 : 2);
  1641. expect(scope.$eval('1?0||0:1')).toEqual(1 ? 0 || 0 : 1);
  1642. expect(scope.$eval('1?0||1:2')).toEqual(1 ? 0 || 1 : 2);
  1643. expect(scope.$eval('0?1:0&&1')).toEqual(0 ? 1 : 0 && 1);
  1644. expect(scope.$eval('0?2:1&&1')).toEqual(0 ? 2 : 1 && 1);
  1645. expect(scope.$eval('0?1:0||0')).toEqual(0 ? 1 : 0 || 0);
  1646. expect(scope.$eval('0?2:0||1')).toEqual(0 ? 2 : 0 || 1);
  1647. expect(scope.$eval('1?1:0&&1')).toEqual(1 ? 1 : 0 && 1);
  1648. expect(scope.$eval('1?2:1&&1')).toEqual(1 ? 2 : 1 && 1);
  1649. expect(scope.$eval('1?1:0||0')).toEqual(1 ? 1 : 0 || 0);
  1650. expect(scope.$eval('1?2:0||1')).toEqual(1 ? 2 : 0 || 1);
  1651. // Function calls.
  1652. expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt());
  1653. expect(scope.$eval('returnFalse() ? returnString() : returnInt()')).toEqual(returnFalse() ? returnString() : returnInt());
  1654. expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt());
  1655. expect(scope.$eval('identity(returnFalse() ? returnString() : returnInt())')).toEqual(identity(returnFalse() ? returnString() : returnInt()));
  1656. });
  1657. it('should parse string', function() {
  1658. expect(scope.$eval("'a' + 'b c'")).toEqual("ab c");
  1659. });
  1660. it('should parse filters', function() {
  1661. $filterProvider.register('substring', valueFn(function(input, start, end) {
  1662. return input.substring(start, end);
  1663. }));
  1664. expect(function() {
  1665. scope.$eval("1|nonexistent");
  1666. }).toThrowMinErr('$injector', 'unpr', 'Unknown provider: nonexistentFilterProvider <- nonexistentFilter');
  1667. scope.offset = 3;
  1668. expect(scope.$eval("'abcd'|substring:1:offset")).toEqual("bc");
  1669. expect(scope.$eval("'abcd'|substring:1:3|uppercase")).toEqual("BC");
  1670. });
  1671. it('should access scope', function() {
  1672. scope.a = 123;
  1673. scope.b = {c: 456};
  1674. expect(scope.$eval("a", scope)).toEqual(123);
  1675. expect(scope.$eval("b.c", scope)).toEqual(456);
  1676. expect(scope.$eval("x.y.z", scope)).not.toBeDefined();
  1677. });
  1678. it('should handle white-spaces around dots in paths', function() {
  1679. scope.a = {b: 4};
  1680. expect(scope.$eval("a . b", scope)).toEqual(4);
  1681. expect(scope.$eval("a. b", scope)).toEqual(4);
  1682. expect(scope.$eval("a .b", scope)).toEqual(4);
  1683. expect(scope.$eval("a . \nb", scope)).toEqual(4);
  1684. });
  1685. it('should handle white-spaces around dots in method invocations', function() {
  1686. scope.a = {b: function() { return this.c; }, c: 4};
  1687. expect(scope.$eval("a . b ()", scope)).toEqual(4);
  1688. expect(scope.$eval("a. b ()", scope)).toEqual(4);
  1689. expect(scope.$eval("a .b ()", scope)).toEqual(4);
  1690. expect(scope.$eval("a \n . \nb \n ()", scope)).toEqual(4);
  1691. });
  1692. it('should throw syntax error exception for identifiers ending with a dot', function() {
  1693. scope.a = {b: 4};
  1694. expect(function() {
  1695. scope.$eval("a.", scope);
  1696. }).toThrowMinErr('$parse', 'ueoe',
  1697. "Unexpected end of expression: a.");
  1698. expect(function() {
  1699. scope.$eval("a .", scope);
  1700. }).toThrowMinErr('$parse', 'ueoe',
  1701. "Unexpected end of expression: a .");
  1702. });
  1703. it('should resolve deeply nested paths (important for CSP mode)', function() {
  1704. scope.a = {b: {c: {d: {e: {f: {g: {h: {i: {j: {k: {l: {m: {n: 'nooo!'}}}}}}}}}}}}};
  1705. expect(scope.$eval("a.b.c.d.e.f.g.h.i.j.k.l.m.n", scope)).toBe('nooo!');
  1706. });
  1707. forEach([2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 42, 99], function(pathLength) {
  1708. it('should resolve nested paths of length ' + pathLength, function() {
  1709. // Create a nested object {x2: {x3: {x4: ... {x[n]: 42} ... }}}.
  1710. var obj = 42, locals = {};
  1711. for (var i = pathLength; i >= 2; i--) {
  1712. var newObj = {};
  1713. newObj['x' + i] = obj;
  1714. obj = newObj;
  1715. }
  1716. // Assign to x1 and build path 'x1.x2.x3. ... .x[n]' to access the final value.
  1717. scope.x1 = obj;
  1718. var path = 'x1';
  1719. for (i = 2; i <= pathLength; i++) {
  1720. path += '.x' + i;
  1721. }
  1722. expect(scope.$eval(path)).toBe(42);
  1723. locals['x' + pathLength] = 'not 42';
  1724. expect(scope.$eval(path, locals)).toBe(42);
  1725. });
  1726. });
  1727. it('should be forgiving', function() {
  1728. scope.a = {b: 23};
  1729. expect(scope.$eval('b')).toBeUndefined();
  1730. expect(scope.$eval('a.x')).toBeUndefined();
  1731. expect(scope.$eval('a.b.c.d')).toBeUndefined();
  1732. scope.a = undefined;
  1733. expect(scope.$eval('a - b')).toBe(0);
  1734. expect(scope.$eval('a + b')).toBe(undefined);
  1735. scope.a = 0;
  1736. expect(scope.$eval('a - b')).toBe(0);
  1737. expect(scope.$eval('a + b')).toBe(0);
  1738. scope.a = undefined;
  1739. scope.b = 0;
  1740. expect(scope.$eval('a - b')).toBe(0);
  1741. expect(scope.$eval('a + b')).toBe(0);
  1742. });
  1743. it('should support property names that collide with native object properties', function() {
  1744. // regression
  1745. scope.watch = 1;
  1746. scope.toString = function toString() {
  1747. return "custom toString";
  1748. };
  1749. expect(scope.$eval('watch', scope)).toBe(1);
  1750. expect(scope.$eval('toString()', scope)).toBe('custom toString');
  1751. });
  1752. it('should not break if hasOwnProperty is referenced in an expression', function() {
  1753. scope.obj = { value: 1};
  1754. // By evaluating an expression that calls hasOwnProperty, the getterFnCache
  1755. // will store a property called hasOwnProperty. This is effectively:
  1756. // getterFnCache['hasOwnProperty'] = null
  1757. scope.$eval('obj.hasOwnProperty("value")');
  1758. // If we rely on this property then evaluating any expression will fail
  1759. // because it is not able to find out if obj.value is there in the cache
  1760. expect(scope.$eval('obj.value')).toBe(1);
  1761. });
  1762. it('should not break if the expression is "hasOwnProperty"', function() {
  1763. scope.fooExp = 'barVal';
  1764. // By evaluating hasOwnProperty, the $parse cache will store a getter for
  1765. // the scope's own hasOwnProperty function, which will mess up future cache look ups.
  1766. // i.e. cache['hasOwnProperty'] = function(scope) { return scope.hasOwnProperty; }
  1767. scope.$eval('hasOwnProperty');
  1768. expect(scope.$eval('fooExp')).toBe('barVal');
  1769. });
  1770. it('should evaluate grouped expressions', function() {
  1771. expect(scope.$eval("(1+2)*3")).toEqual((1 + 2) * 3);
  1772. });
  1773. it('should evaluate assignments', function() {
  1774. expect(scope.$eval("a=12")).toEqual(12);
  1775. expect(scope.a).toEqual(12);
  1776. expect(scope.$eval("x.y.z=123;")).toEqual(123);
  1777. expect(scope.x.y.z).toEqual(123);
  1778. expect(scope.$eval("a=123; b=234")).toEqual(234);
  1779. expect(scope.a).toEqual(123);
  1780. expect(scope.b).toEqual(234);
  1781. });
  1782. it('should evaluate assignments in ternary operator', function() {
  1783. scope.$eval('a = 1 ? 2 : 3');
  1784. expect(scope.a).toBe(2);
  1785. scope.$eval('0 ? a = 2 : a = 3');
  1786. expect(scope.a).toBe(3);
  1787. scope.$eval('1 ? a = 2 : a = 3');
  1788. expect(scope.a).toBe(2);
  1789. });
  1790. it('should evaluate function call without arguments', function() {
  1791. scope['const'] = function(a, b) {return 123;};
  1792. expect(scope.$eval("const()")).toEqual(123);
  1793. });
  1794. it('should evaluate function call with arguments', function() {
  1795. scope.add = function(a, b) {
  1796. return a + b;
  1797. };
  1798. expect(scope.$eval("add(1,2)")).toEqual(3);
  1799. });
  1800. it('should evaluate function call from a return value', function() {
  1801. scope.getter = function() { return function() { return 33; }; };
  1802. expect(scope.$eval("getter()()")).toBe(33);
  1803. });
  1804. // There is no "strict mode" in IE9
  1805. if (!msie || msie > 9) {
  1806. it('should set no context to functions returned by other functions', function() {
  1807. scope.getter = function() { return function() { expect(this).toBeUndefined(); }; };
  1808. scope.$eval("getter()()");
  1809. });
  1810. }
  1811. it('should evaluate multiplication and division', function() {
  1812. scope.taxRate = 8;
  1813. scope.subTotal = 100;
  1814. expect(scope.$eval("taxRate / 100 * subTotal")).toEqual(8);
  1815. expect(scope.$eval("subTotal * taxRate / 100")).toEqual(8);
  1816. });
  1817. it('should evaluate array', function() {
  1818. expect(scope.$eval("[]").length).toEqual(0);
  1819. expect(scope.$eval("[1, 2]").length).toEqual(2);
  1820. expect(scope.$eval("[1, 2]")[0]).toEqual(1);
  1821. expect(scope.$eval("[1, 2]")[1]).toEqual(2);
  1822. expect(scope.$eval("[1, 2,]")[1]).toEqual(2);
  1823. expect(scope.$eval("[1, 2,]").length).toEqual(2);
  1824. });
  1825. it('should evaluate array access', function() {
  1826. expect(scope.$eval("[1][0]")).toEqual(1);
  1827. expect(scope.$eval("[[1]][0][0]")).toEqual(1);
  1828. expect(scope.$eval("[].length")).toEqual(0);
  1829. expect(scope.$eval("[1, 2].length")).toEqual(2);
  1830. });
  1831. it('should evaluate object', function() {
  1832. expect(scope.$eval("{}")).toEqual({});
  1833. expect(scope.$eval("{a:'b'}")).toEqual({a:"b"});
  1834. expect(scope.$eval("{'a':'b'}")).toEqual({a:"b"});
  1835. expect(scope.$eval("{\"a\":'b'}")).toEqual({a:"b"});
  1836. expect(scope.$eval("{a:'b',}")).toEqual({a:"b"});
  1837. expect(scope.$eval("{'a':'b',}")).toEqual({a:"b"});
  1838. expect(scope.$eval("{\"a\":'b',}")).toEqual({a:"b"});
  1839. expect(scope.$eval("{'0':1}")).toEqual({0:1});
  1840. expect(scope.$eval("{0:1}")).toEqual({0:1});
  1841. expect(scope.$eval("{1:1}")).toEqual({1:1});
  1842. expect(scope.$eval("{null:1}")).toEqual({null:1});
  1843. expect(scope.$eval("{'null':1}")).toEqual({null:1});
  1844. expect(scope.$eval("{false:1}")).toEqual({false:1});
  1845. expect(scope.$eval("{'false':1}")).toEqual({false:1});
  1846. expect(scope.$eval("{'':1,}")).toEqual({"":1});
  1847. });
  1848. it('should throw syntax error exception for non constant/identifier JSON keys', function() {
  1849. expect(function() { scope.$eval("{[:0}"); }).toThrowMinErr("$parse", "syntax",
  1850. "Syntax Error: Token '[' invalid key at column 2 of the expression [{[:0}] starting at [[:0}]");
  1851. expect(function() { scope.$eval("{{:0}"); }).toThrowMinErr("$parse", "syntax",
  1852. "Syntax Error: Token '{' invalid key at column 2 of the expression [{{:0}] starting at [{:0}]");
  1853. expect(function() { scope.$eval("{?:0}"); }).toThrowMinErr("$parse", "syntax",
  1854. "Syntax Error: Token '?' invalid key at column 2 of the expression [{?:0}] starting at [?:0}]");
  1855. expect(function() { scope.$eval("{):0}"); }).toThrowMinErr("$parse", "syntax",
  1856. "Syntax Error: Token ')' invalid key at column 2 of the expression [{):0}] starting at [):0}]");
  1857. });
  1858. it('should evaluate object access', function() {
  1859. expect(scope.$eval("{false:'WC', true:'CC'}[false]")).toEqual("WC");
  1860. });
  1861. it('should evaluate JSON', function() {
  1862. expect(scope.$eval("[{}]")).toEqual([{}]);
  1863. expect(scope.$eval("[{a:[]}, {b:1}]")).toEqual([{a:[]}, {b:1}]);
  1864. });
  1865. it('should evaluate multiple statements', function() {
  1866. expect(scope.$eval("a=1;b=3;a+b")).toEqual(4);
  1867. expect(scope.$eval(";;1;;")).toEqual(1);
  1868. });
  1869. it('should evaluate object methods in correct context (this)', function() {
  1870. var C = function() {
  1871. this.a = 123;
  1872. };
  1873. C.prototype.getA = function() {
  1874. return this.a;
  1875. };
  1876. scope.obj = new C();
  1877. expect(scope.$eval("obj.getA()")).toEqual(123);
  1878. expect(scope.$eval("obj['getA']()")).toEqual(123);
  1879. });
  1880. it('should evaluate methods in correct context (this) in argument', function() {
  1881. var C = function() {
  1882. this.a = 123;
  1883. };
  1884. C.prototype.sum = function(value) {
  1885. return this.a + value;
  1886. };
  1887. C.prototype.getA = function() {
  1888. return this.a;
  1889. };
  1890. scope.obj = new C();
  1891. expect(scope.$eval("obj.sum(obj.getA())")).toEqual(246);
  1892. expect(scope.$eval("obj['sum'](obj.getA())")).toEqual(246);
  1893. });
  1894. it('should evaluate objects on scope context', function() {
  1895. scope.a = "abc";
  1896. expect(scope.$eval("{a:a}").a).toEqual("abc");
  1897. });
  1898. it('should evaluate field access on function call result', function() {
  1899. scope.a = function() {
  1900. return {name:'misko'};
  1901. };
  1902. expect(scope.$eval("a().name")).toEqual("misko");
  1903. });
  1904. it('should evaluate field access after array access', function() {
  1905. scope.items = [{}, {name:'misko'}];
  1906. expect(scope.$eval('items[1].name')).toEqual("misko");
  1907. });
  1908. it('should evaluate array assignment', function() {
  1909. scope.items = [];
  1910. expect(scope.$eval('items[1] = "abc"')).toEqual("abc");
  1911. expect(scope.$eval('items[1]')).toEqual("abc");
  1912. // Dont know how to make this work....
  1913. // expect(scope.$eval('books[1] = "moby"')).toEqual("moby");
  1914. // expect(scope.$eval('books[1]')).toEqual("moby");
  1915. });
  1916. it('should evaluate grouped filters', function() {
  1917. scope.name = 'MISKO';
  1918. expect(scope.$eval('n = (name|lowercase)')).toEqual('misko');
  1919. expect(scope.$eval('n')).toEqual('misko');
  1920. });
  1921. it('should evaluate remainder', function() {
  1922. expect(scope.$eval('1%2')).toEqual(1);
  1923. });
  1924. it('should evaluate sum with undefined', function() {
  1925. expect(scope.$eval('1+undefined')).toEqual(1);
  1926. expect(scope.$eval('undefined+1')).toEqual(1);
  1927. });
  1928. it('should throw exception on non-closed bracket', function() {
  1929. expect(function() {
  1930. scope.$eval('[].count(');
  1931. }).toThrowMinErr('$parse', 'ueoe', 'Unexpected end of expression: [].count(');
  1932. });
  1933. it('should evaluate double negation', function() {
  1934. expect(scope.$eval('true')).toBeTruthy();
  1935. expect(scope.$eval('!true')).toBeFalsy();
  1936. expect(scope.$eval('!!true')).toBeTruthy();
  1937. expect(scope.$eval('{true:"a", false:"b"}[!!true]')).toEqual('a');
  1938. });
  1939. it('should evaluate negation', function() {
  1940. /* jshint -W018 */
  1941. expect(scope.$eval("!false || true")).toEqual(!false || true);
  1942. expect(scope.$eval("!11 == 10")).toEqual(!11 == 10);
  1943. expect(scope.$eval("12/6/2")).toEqual(12 / 6 / 2);
  1944. });
  1945. it('should evaluate exclamation mark', function() {
  1946. expect(scope.$eval('suffix = "!"')).toEqual('!');
  1947. });
  1948. it('should evaluate minus', function() {
  1949. expect(scope.$eval("{a:'-'}")).toEqual({a: "-"});
  1950. });
  1951. it('should evaluate undefined', function() {
  1952. expect(scope.$eval("undefined")).not.toBeDefined();
  1953. expect(scope.$eval("a=undefined")).not.toBeDefined();
  1954. expect(scope.a).not.toBeDefined();
  1955. });
  1956. it('should allow assignment after array dereference', function() {
  1957. scope.obj = [{}];
  1958. scope.$eval('obj[0].name=1');
  1959. expect(scope.obj.name).toBeUndefined();
  1960. expect(scope.obj[0].name).toEqual(1);
  1961. });
  1962. it('should short-circuit AND operator', function() {
  1963. scope.run = function() {
  1964. throw "IT SHOULD NOT HAVE RUN";
  1965. };
  1966. expect(scope.$eval('false && run()')).toBe(false);
  1967. expect(scope.$eval('false && true && run()')).toBe(false);
  1968. });
  1969. it('should short-circuit OR operator', function() {
  1970. scope.run = function() {
  1971. throw "IT SHOULD NOT HAVE RUN";
  1972. };
  1973. expect(scope.$eval('true || run()')).toBe(true);
  1974. expect(scope.$eval('true || false || run()')).toBe(true);
  1975. });
  1976. it('should support method calls on primitive types', function() {
  1977. scope.empty = '';
  1978. scope.zero = 0;
  1979. scope.bool = false;
  1980. expect(scope.$eval('empty.substr(0)')).toBe('');
  1981. expect(scope.$eval('zero.toString()')).toBe('0');
  1982. expect(scope.$eval('bool.toString()')).toBe('false');
  1983. });
  1984. it('should evaluate expressions with line terminators', function() {
  1985. scope.a = "a";
  1986. scope.b = {c: "bc"};
  1987. expect(scope.$eval('a + \n b.c + \r "\td" + \t \r\n\r "\r\n\n"')).toEqual("abc\td\r\n\n");
  1988. });
  1989. // https://github.com/angular/angular.js/issues/10968
  1990. it('should evaluate arrays literals initializers left-to-right', inject(function($parse) {
  1991. var s = {c:function() {return {b: 1}; }};
  1992. expect($parse("e=1;[a=c(),d=a.b+1]")(s)).toEqual([{b: 1}, 2]);
  1993. }));
  1994. it('should evaluate function arguments left-to-right', inject(function($parse) {
  1995. var s = {c:function() {return {b: 1}; }, i: function(x, y) { return [x, y];}};
  1996. expect($parse("e=1;i(a=c(),d=a.b+1)")(s)).toEqual([{b: 1}, 2]);
  1997. }));
  1998. it('should evaluate object properties expressions left-to-right', inject(function($parse) {
  1999. var s = {c:function() {return {b: 1}; }};
  2000. expect($parse("e=1;{x: a=c(), y: d=a.b+1}")(s)).toEqual({x: {b: 1}, y: 2});
  2001. }));
  2002. describe('sandboxing', function() {
  2003. describe('Function constructor', function() {
  2004. it('should not tranverse the Function constructor in the getter', function() {
  2005. expect(function() {
  2006. scope.$eval('{}.toString.constructor');
  2007. }).toThrowMinErr(
  2008. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2009. 'Expression: {}.toString.constructor');
  2010. });
  2011. it('should not allow access to the Function prototype in the getter', function() {
  2012. expect(function() {
  2013. scope.$eval('toString.constructor.prototype');
  2014. }).toThrowMinErr(
  2015. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2016. 'Expression: toString.constructor.prototype');
  2017. });
  2018. it('should NOT allow access to Function constructor in getter', function() {
  2019. expect(function() {
  2020. scope.$eval('{}.toString.constructor("alert(1)")');
  2021. }).toThrowMinErr(
  2022. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2023. 'Expression: {}.toString.constructor("alert(1)")');
  2024. });
  2025. it('should NOT allow access to Function constructor in setter', function() {
  2026. expect(function() {
  2027. scope.$eval('{}.toString.constructor.a = 1');
  2028. }).toThrowMinErr(
  2029. '$parse', 'isecfn','Referencing Function in Angular expressions is disallowed! ' +
  2030. 'Expression: {}.toString.constructor.a = 1');
  2031. expect(function() {
  2032. scope.$eval('{}.toString["constructor"]["constructor"] = 1');
  2033. }).toThrowMinErr(
  2034. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2035. 'Expression: {}.toString["constructor"]["constructor"] = 1');
  2036. scope.key1 = "const";
  2037. scope.key2 = "ructor";
  2038. expect(function() {
  2039. scope.$eval('{}.toString[key1 + key2].foo = 1');
  2040. }).toThrowMinErr(
  2041. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2042. 'Expression: {}.toString[key1 + key2].foo = 1');
  2043. expect(function() {
  2044. scope.$eval('{}.toString["constructor"]["a"] = 1');
  2045. }).toThrowMinErr(
  2046. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2047. 'Expression: {}.toString["constructor"]["a"] = 1');
  2048. scope.a = [];
  2049. expect(function() {
  2050. scope.$eval('a.toString.constructor = 1', scope);
  2051. }).toThrowMinErr(
  2052. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2053. 'Expression: a.toString.constructor');
  2054. });
  2055. it('should disallow traversing the Function object in a setter: E02', function() {
  2056. expect(function() {
  2057. // This expression by itself isn't dangerous. However, one can use this to
  2058. // automatically call an object (e.g. a Function object) when it is automatically
  2059. // toString'd/valueOf'd by setting the RHS to Function.prototype.call.
  2060. scope.$eval('hasOwnProperty.constructor.prototype.valueOf = 1');
  2061. }).toThrowMinErr(
  2062. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2063. 'Expression: hasOwnProperty.constructor.prototype.valueOf');
  2064. });
  2065. it('should disallow passing the Function object as a parameter: E03', function() {
  2066. expect(function() {
  2067. // This expression constructs a function but does not execute it. It does lead the
  2068. // way to execute it if one can get the toString/valueOf of it to call the function.
  2069. scope.$eval('["a", "alert(1)"].sort(hasOwnProperty.constructor)');
  2070. }).toThrow();
  2071. });
  2072. it('should prevent exploit E01', function() {
  2073. // This is a tracking exploit. The two individual tests, it('should … : E02') and
  2074. // it('should … : E03') test for two parts to block this exploit. This exploit works
  2075. // as follows:
  2076. //
  2077. // • Array.sort takes a comparison function and passes it 2 parameters to compare. If
  2078. // the result is non-primitive, sort then invokes valueOf() on the result.
  2079. // • The Function object conveniently accepts two string arguments so we can use this
  2080. // to construct a function. However, this doesn't do much unless we can execute it.
  2081. // • We set the valueOf property on Function.prototype to Function.prototype.call.
  2082. // This causes the function that we constructed to be executed when sort calls
  2083. // .valueOf() on the result of the comparison.
  2084. expect(function() {
  2085. scope.$eval('' +
  2086. 'hasOwnProperty.constructor.prototype.valueOf=valueOf.call;' +
  2087. '["a","alert(1)"].sort(hasOwnProperty.constructor)');
  2088. }).toThrow();
  2089. });
  2090. it('should NOT allow access to Function constructor that has been aliased in getters', function() {
  2091. scope.foo = { "bar": Function };
  2092. expect(function() {
  2093. scope.$eval('foo["bar"]');
  2094. }).toThrowMinErr(
  2095. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2096. 'Expression: foo["bar"]');
  2097. });
  2098. it('should NOT allow access to Function constructor that has been aliased in setters', function() {
  2099. scope.foo = { "bar": Function };
  2100. expect(function() {
  2101. scope.$eval('foo["bar"] = 1');
  2102. }).toThrowMinErr(
  2103. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2104. 'Expression: foo["bar"] = 1');
  2105. });
  2106. describe('expensiveChecks', function() {
  2107. it('should block access to window object even when aliased in getters', inject(function($parse, $window) {
  2108. scope.foo = {w: $window};
  2109. // This isn't blocked for performance.
  2110. expect(scope.$eval($parse('foo.w'))).toBe($window);
  2111. // Event handlers use the more expensive path for better protection since they expose
  2112. // the $event object on the scope.
  2113. expect(function() {
  2114. scope.$eval($parse('foo.w', null, true));
  2115. }).toThrowMinErr(
  2116. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
  2117. 'Expression: foo.w');
  2118. }));
  2119. it('should block access to window object even when aliased in setters', inject(function($parse, $window) {
  2120. scope.foo = {w: $window};
  2121. // This is blocked as it points to `window`.
  2122. expect(function() {
  2123. expect(scope.$eval($parse('foo.w = 1'))).toBe($window);
  2124. }).toThrowMinErr(
  2125. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
  2126. 'Expression: foo.w = 1');
  2127. // Event handlers use the more expensive path for better protection since they expose
  2128. // the $event object on the scope.
  2129. expect(function() {
  2130. scope.$eval($parse('foo.w = 1', null, true));
  2131. }).toThrowMinErr(
  2132. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
  2133. 'Expression: foo.w = 1');
  2134. }));
  2135. });
  2136. });
  2137. describe('Function prototype functions', function() {
  2138. it('should NOT allow invocation to Function.call', function() {
  2139. scope.fn = Function.prototype.call;
  2140. expect(function() {
  2141. scope.$eval('$eval.call()');
  2142. }).toThrowMinErr(
  2143. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2144. 'Expression: $eval.call()');
  2145. expect(function() {
  2146. scope.$eval('fn()');
  2147. }).toThrowMinErr(
  2148. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2149. 'Expression: fn()');
  2150. });
  2151. it('should NOT allow invocation to Function.apply', function() {
  2152. scope.apply = Function.prototype.apply;
  2153. expect(function() {
  2154. scope.$eval('$eval.apply()');
  2155. }).toThrowMinErr(
  2156. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2157. 'Expression: $eval.apply()');
  2158. expect(function() {
  2159. scope.$eval('apply()');
  2160. }).toThrowMinErr(
  2161. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2162. 'Expression: apply()');
  2163. });
  2164. it('should NOT allow invocation to Function.bind', function() {
  2165. scope.bind = Function.prototype.bind;
  2166. expect(function() {
  2167. scope.$eval('$eval.bind()');
  2168. }).toThrowMinErr(
  2169. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2170. 'Expression: $eval.bind()');
  2171. expect(function() {
  2172. scope.$eval('bind()');
  2173. }).toThrowMinErr(
  2174. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2175. 'Expression: bind()');
  2176. });
  2177. });
  2178. describe('Object constructor', function() {
  2179. it('should NOT allow access to Object constructor that has been aliased in getters', function() {
  2180. scope.foo = { "bar": Object };
  2181. expect(function() {
  2182. scope.$eval('foo.bar.keys(foo)');
  2183. }).toThrowMinErr(
  2184. '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
  2185. 'Expression: foo.bar.keys(foo)');
  2186. expect(function() {
  2187. scope.$eval('foo["bar"]["keys"](foo)');
  2188. }).toThrowMinErr(
  2189. '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
  2190. 'Expression: foo["bar"]["keys"](foo)');
  2191. });
  2192. it('should NOT allow access to Object constructor that has been aliased in setters', function() {
  2193. scope.foo = { "bar": Object };
  2194. expect(function() {
  2195. scope.$eval('foo.bar.keys(foo).bar = 1');
  2196. }).toThrowMinErr(
  2197. '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
  2198. 'Expression: foo.bar.keys(foo).bar = 1');
  2199. expect(function() {
  2200. scope.$eval('foo["bar"]["keys"](foo).bar = 1');
  2201. }).toThrowMinErr(
  2202. '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
  2203. 'Expression: foo["bar"]["keys"](foo).bar = 1');
  2204. });
  2205. });
  2206. describe('Window and $element/node', function() {
  2207. it('should NOT allow access to the Window or DOM when indexing', inject(function($window, $document) {
  2208. scope.wrap = {w: $window, d: $document};
  2209. expect(function() {
  2210. scope.$eval('wrap["w"]', scope);
  2211. }).toThrowMinErr(
  2212. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
  2213. 'disallowed! Expression: wrap["w"]');
  2214. expect(function() {
  2215. scope.$eval('wrap["d"]', scope);
  2216. }).toThrowMinErr(
  2217. '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
  2218. 'disallowed! Expression: wrap["d"]');
  2219. expect(function() {
  2220. scope.$eval('wrap["w"] = 1', scope);
  2221. }).toThrowMinErr(
  2222. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
  2223. 'disallowed! Expression: wrap["w"] = 1');
  2224. expect(function() {
  2225. scope.$eval('wrap["d"] = 1', scope);
  2226. }).toThrowMinErr(
  2227. '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
  2228. 'disallowed! Expression: wrap["d"] = 1');
  2229. }));
  2230. it('should NOT allow access to the Window or DOM returned from a function', inject(function($window, $document) {
  2231. scope.getWin = valueFn($window);
  2232. scope.getDoc = valueFn($document);
  2233. expect(function() {
  2234. scope.$eval('getWin()', scope);
  2235. }).toThrowMinErr(
  2236. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
  2237. 'disallowed! Expression: getWin()');
  2238. expect(function() {
  2239. scope.$eval('getDoc()', scope);
  2240. }).toThrowMinErr(
  2241. '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
  2242. 'disallowed! Expression: getDoc()');
  2243. }));
  2244. it('should NOT allow calling functions on Window or DOM', inject(function($window, $document) {
  2245. scope.a = {b: { win: $window, doc: $document }};
  2246. expect(function() {
  2247. scope.$eval('a.b.win.alert(1)', scope);
  2248. }).toThrowMinErr(
  2249. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
  2250. 'disallowed! Expression: a.b.win.alert(1)');
  2251. expect(function() {
  2252. scope.$eval('a.b.doc.on("click")', scope);
  2253. }).toThrowMinErr(
  2254. '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
  2255. 'disallowed! Expression: a.b.doc.on("click")');
  2256. }));
  2257. // Issue #4805
  2258. it('should NOT throw isecdom when referencing a Backbone Collection', function() {
  2259. // Backbone stuff is sort of hard to mock, if you have a better way of doing this,
  2260. // please fix this.
  2261. var fakeBackboneCollection = {
  2262. children: [{}, {}, {}],
  2263. find: function() {},
  2264. on: function() {},
  2265. off: function() {},
  2266. bind: function() {}
  2267. };
  2268. scope.backbone = fakeBackboneCollection;
  2269. expect(function() { scope.$eval('backbone'); }).not.toThrow();
  2270. });
  2271. it('should NOT throw isecdom when referencing an array with node properties', function() {
  2272. var array = [1,2,3];
  2273. array.on = array.attr = array.prop = array.bind = true;
  2274. scope.array = array;
  2275. expect(function() { scope.$eval('array'); }).not.toThrow();
  2276. });
  2277. });
  2278. describe('Disallowed fields', function() {
  2279. it('should NOT allow access or invocation of __defineGetter__', function() {
  2280. expect(function() {
  2281. scope.$eval('{}.__defineGetter__');
  2282. }).toThrowMinErr('$parse', 'isecfld');
  2283. expect(function() {
  2284. scope.$eval('{}.__defineGetter__("a", "".charAt)');
  2285. }).toThrowMinErr('$parse', 'isecfld');
  2286. expect(function() {
  2287. scope.$eval('{}["__defineGetter__"]');
  2288. }).toThrowMinErr('$parse', 'isecfld');
  2289. expect(function() {
  2290. scope.$eval('{}["__defineGetter__"]("a", "".charAt)');
  2291. }).toThrowMinErr('$parse', 'isecfld');
  2292. scope.a = "__define";
  2293. scope.b = "Getter__";
  2294. expect(function() {
  2295. scope.$eval('{}[a + b]');
  2296. }).toThrowMinErr('$parse', 'isecfld');
  2297. expect(function() {
  2298. scope.$eval('{}[a + b]("a", "".charAt)');
  2299. }).toThrowMinErr('$parse', 'isecfld');
  2300. });
  2301. it('should NOT allow access or invocation of __defineSetter__', function() {
  2302. expect(function() {
  2303. scope.$eval('{}.__defineSetter__');
  2304. }).toThrowMinErr('$parse', 'isecfld');
  2305. expect(function() {
  2306. scope.$eval('{}.__defineSetter__("a", "".charAt)');
  2307. }).toThrowMinErr('$parse', 'isecfld');
  2308. expect(function() {
  2309. scope.$eval('{}["__defineSetter__"]');
  2310. }).toThrowMinErr('$parse', 'isecfld');
  2311. expect(function() {
  2312. scope.$eval('{}["__defineSetter__"]("a", "".charAt)');
  2313. }).toThrowMinErr('$parse', 'isecfld');
  2314. scope.a = "__define";
  2315. scope.b = "Setter__";
  2316. expect(function() {
  2317. scope.$eval('{}[a + b]');
  2318. }).toThrowMinErr('$parse', 'isecfld');
  2319. expect(function() {
  2320. scope.$eval('{}[a + b]("a", "".charAt)');
  2321. }).toThrowMinErr('$parse', 'isecfld');
  2322. });
  2323. it('should NOT allow access or invocation of __lookupGetter__', function() {
  2324. expect(function() {
  2325. scope.$eval('{}.__lookupGetter__');
  2326. }).toThrowMinErr('$parse', 'isecfld');
  2327. expect(function() {
  2328. scope.$eval('{}.__lookupGetter__("a")');
  2329. }).toThrowMinErr('$parse', 'isecfld');
  2330. expect(function() {
  2331. scope.$eval('{}["__lookupGetter__"]');
  2332. }).toThrowMinErr('$parse', 'isecfld');
  2333. expect(function() {
  2334. scope.$eval('{}["__lookupGetter__"]("a")');
  2335. }).toThrowMinErr('$parse', 'isecfld');
  2336. scope.a = "__lookup";
  2337. scope.b = "Getter__";
  2338. expect(function() {
  2339. scope.$eval('{}[a + b]');
  2340. }).toThrowMinErr('$parse', 'isecfld');
  2341. expect(function() {
  2342. scope.$eval('{}[a + b]("a")');
  2343. }).toThrowMinErr('$parse', 'isecfld');
  2344. });
  2345. it('should NOT allow access or invocation of __lookupSetter__', function() {
  2346. expect(function() {
  2347. scope.$eval('{}.__lookupSetter__');
  2348. }).toThrowMinErr('$parse', 'isecfld');
  2349. expect(function() {
  2350. scope.$eval('{}.__lookupSetter__("a")');
  2351. }).toThrowMinErr('$parse', 'isecfld');
  2352. expect(function() {
  2353. scope.$eval('{}["__lookupSetter__"]');
  2354. }).toThrowMinErr('$parse', 'isecfld');
  2355. expect(function() {
  2356. scope.$eval('{}["__lookupSetter__"]("a")');
  2357. }).toThrowMinErr('$parse', 'isecfld');
  2358. scope.a = "__lookup";
  2359. scope.b = "Setter__";
  2360. expect(function() {
  2361. scope.$eval('{}[a + b]');
  2362. }).toThrowMinErr('$parse', 'isecfld');
  2363. expect(function() {
  2364. scope.$eval('{}[a + b]("a")');
  2365. }).toThrowMinErr('$parse', 'isecfld');
  2366. });
  2367. it('should NOT allow access to __proto__', function() {
  2368. expect(function() {
  2369. scope.$eval('__proto__');
  2370. }).toThrowMinErr('$parse', 'isecfld');
  2371. expect(function() {
  2372. scope.$eval('{}.__proto__');
  2373. }).toThrowMinErr('$parse', 'isecfld');
  2374. expect(function() {
  2375. scope.$eval('{}.__proto__.foo = 1');
  2376. }).toThrowMinErr('$parse', 'isecfld');
  2377. expect(function() {
  2378. scope.$eval('{}["__proto__"]');
  2379. }).toThrowMinErr('$parse', 'isecfld');
  2380. expect(function() {
  2381. scope.$eval('{}["__proto__"].foo = 1');
  2382. }).toThrowMinErr('$parse', 'isecfld');
  2383. scope.a = "__pro";
  2384. scope.b = "to__";
  2385. expect(function() {
  2386. scope.$eval('{}[a + b]');
  2387. }).toThrowMinErr('$parse', 'isecfld');
  2388. expect(function() {
  2389. scope.$eval('{}[a + b].foo = 1');
  2390. }).toThrowMinErr('$parse', 'isecfld');
  2391. });
  2392. });
  2393. it('should prevent the exploit', function() {
  2394. expect(function() {
  2395. scope.$eval('' +
  2396. ' "".sub.call.call(' +
  2397. '({})["constructor"].getOwnPropertyDescriptor("".sub.__proto__, "constructor").value,' +
  2398. 'null,' +
  2399. '"alert(1)"' +
  2400. ')()' +
  2401. '');
  2402. }).toThrow();
  2403. });
  2404. });
  2405. it('should call the function from the received instance and not from a new one', function() {
  2406. var n = 0;
  2407. scope.fn = function() {
  2408. var c = n++;
  2409. return { c: c, anotherFn: function() { return this.c == c; } };
  2410. };
  2411. expect(scope.$eval('fn().anotherFn()')).toBe(true);
  2412. });
  2413. it('should call the function once when it is part of the context', function() {
  2414. var count = 0;
  2415. scope.fn = function() {
  2416. count++;
  2417. return { anotherFn: function() { return "lucas"; } };
  2418. };
  2419. expect(scope.$eval('fn().anotherFn()')).toBe('lucas');
  2420. expect(count).toBe(1);
  2421. });
  2422. it('should call the function once when it is not part of the context', function() {
  2423. var count = 0;
  2424. scope.fn = function() {
  2425. count++;
  2426. return function() { return 'lucas'; };
  2427. };
  2428. expect(scope.$eval('fn()()')).toBe('lucas');
  2429. expect(count).toBe(1);
  2430. });
  2431. it('should call the function once when it is part of the context on assignments', function() {
  2432. var count = 0;
  2433. var element = {};
  2434. scope.fn = function() {
  2435. count++;
  2436. return element;
  2437. };
  2438. expect(scope.$eval('fn().name = "lucas"')).toBe('lucas');
  2439. expect(element.name).toBe('lucas');
  2440. expect(count).toBe(1);
  2441. });
  2442. it('should call the function once when it is part of the context on array lookups', function() {
  2443. var count = 0;
  2444. var element = [];
  2445. scope.fn = function() {
  2446. count++;
  2447. return element;
  2448. };
  2449. expect(scope.$eval('fn()[0] = "lucas"')).toBe('lucas');
  2450. expect(element[0]).toBe('lucas');
  2451. expect(count).toBe(1);
  2452. });
  2453. it('should call the function once when it is part of the context on array lookup function', function() {
  2454. var count = 0;
  2455. var element = [{anotherFn: function() { return 'lucas';} }];
  2456. scope.fn = function() {
  2457. count++;
  2458. return element;
  2459. };
  2460. expect(scope.$eval('fn()[0].anotherFn()')).toBe('lucas');
  2461. expect(count).toBe(1);
  2462. });
  2463. it('should call the function once when it is part of the context on property lookup function', function() {
  2464. var count = 0;
  2465. var element = {name: {anotherFn: function() { return 'lucas';} } };
  2466. scope.fn = function() {
  2467. count++;
  2468. return element;
  2469. };
  2470. expect(scope.$eval('fn().name.anotherFn()')).toBe('lucas');
  2471. expect(count).toBe(1);
  2472. });
  2473. it('should call the function once when it is part of a sub-expression', function() {
  2474. var count = 0;
  2475. scope.element = [{}];
  2476. scope.fn = function() {
  2477. count++;
  2478. return 0;
  2479. };
  2480. expect(scope.$eval('element[fn()].name = "lucas"')).toBe('lucas');
  2481. expect(scope.element[0].name).toBe('lucas');
  2482. expect(count).toBe(1);
  2483. });
  2484. describe('assignable', function() {
  2485. it('should expose assignment function', inject(function($parse) {
  2486. var fn = $parse('a');
  2487. expect(fn.assign).toBeTruthy();
  2488. var scope = {};
  2489. fn.assign(scope, 123);
  2490. expect(scope).toEqual({a:123});
  2491. }));
  2492. it('should expose working assignment function for expressions ending with brackets', inject(function($parse) {
  2493. var fn = $parse('a.b["c"]');
  2494. expect(fn.assign).toBeTruthy();
  2495. var scope = {};
  2496. fn.assign(scope, 123);
  2497. expect(scope.a.b.c).toEqual(123);
  2498. }));
  2499. it('should expose working assignment function for expressions with brackets in the middle', inject(function($parse) {
  2500. var fn = $parse('a["b"].c');
  2501. expect(fn.assign).toBeTruthy();
  2502. var scope = {};
  2503. fn.assign(scope, 123);
  2504. expect(scope.a.b.c).toEqual(123);
  2505. }));
  2506. it('should create objects when finding a null', inject(function($parse) {
  2507. var fn = $parse('foo.bar');
  2508. var scope = {foo: null};
  2509. fn.assign(scope, 123);
  2510. expect(scope.foo.bar).toEqual(123);
  2511. }));
  2512. it('should create objects when finding a null', inject(function($parse) {
  2513. var fn = $parse('foo["bar"]');
  2514. var scope = {foo: null};
  2515. fn.assign(scope, 123);
  2516. expect(scope.foo.bar).toEqual(123);
  2517. }));
  2518. it('should create objects when finding a null', inject(function($parse) {
  2519. var fn = $parse('foo.bar.baz');
  2520. var scope = {foo: null};
  2521. fn.assign(scope, 123);
  2522. expect(scope.foo.bar.baz).toEqual(123);
  2523. }));
  2524. });
  2525. describe('one-time binding', function() {
  2526. it('should always use the cache', inject(function($parse) {
  2527. expect($parse('foo')).toBe($parse('foo'));
  2528. expect($parse('::foo')).toBe($parse('::foo'));
  2529. }));
  2530. it('should not affect calling the parseFn directly', inject(function($parse, $rootScope) {
  2531. var fn = $parse('::foo');
  2532. $rootScope.$watch(fn);
  2533. $rootScope.foo = 'bar';
  2534. expect($rootScope.$$watchers.length).toBe(1);
  2535. expect(fn($rootScope)).toEqual('bar');
  2536. $rootScope.$digest();
  2537. expect($rootScope.$$watchers.length).toBe(0);
  2538. expect(fn($rootScope)).toEqual('bar');
  2539. $rootScope.foo = 'man';
  2540. $rootScope.$digest();
  2541. expect($rootScope.$$watchers.length).toBe(0);
  2542. expect(fn($rootScope)).toEqual('man');
  2543. $rootScope.foo = 'shell';
  2544. $rootScope.$digest();
  2545. expect($rootScope.$$watchers.length).toBe(0);
  2546. expect(fn($rootScope)).toEqual('shell');
  2547. }));
  2548. it('should stay stable once the value defined', inject(function($parse, $rootScope, log) {
  2549. var fn = $parse('::foo');
  2550. $rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });
  2551. $rootScope.$digest();
  2552. expect($rootScope.$$watchers.length).toBe(1);
  2553. $rootScope.foo = 'bar';
  2554. $rootScope.$digest();
  2555. expect($rootScope.$$watchers.length).toBe(0);
  2556. expect(log).toEqual('bar');
  2557. log.reset();
  2558. $rootScope.foo = 'man';
  2559. $rootScope.$digest();
  2560. expect($rootScope.$$watchers.length).toBe(0);
  2561. expect(log).toEqual('');
  2562. }));
  2563. it('should have a stable value if at the end of a $digest it has a defined value', inject(function($parse, $rootScope, log) {
  2564. var fn = $parse('::foo');
  2565. $rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });
  2566. $rootScope.$watch('foo', function() { if ($rootScope.foo === 'bar') {$rootScope.foo = undefined; } });
  2567. $rootScope.foo = 'bar';
  2568. $rootScope.$digest();
  2569. expect($rootScope.$$watchers.length).toBe(2);
  2570. expect(log).toEqual('');
  2571. $rootScope.foo = 'man';
  2572. $rootScope.$digest();
  2573. expect($rootScope.$$watchers.length).toBe(1);
  2574. expect(log).toEqual('; man');
  2575. $rootScope.foo = 'shell';
  2576. $rootScope.$digest();
  2577. expect($rootScope.$$watchers.length).toBe(1);
  2578. expect(log).toEqual('; man');
  2579. }));
  2580. it('should not throw if the stable value is `null`', inject(function($parse, $rootScope) {
  2581. var fn = $parse('::foo');
  2582. $rootScope.$watch(fn);
  2583. $rootScope.foo = null;
  2584. $rootScope.$digest();
  2585. $rootScope.foo = 'foo';
  2586. $rootScope.$digest();
  2587. expect(fn()).toEqual(null);
  2588. }));
  2589. describe('literal expressions', function() {
  2590. it('should mark an empty expressions as literal', inject(function($parse) {
  2591. expect($parse('').literal).toBe(true);
  2592. expect($parse(' ').literal).toBe(true);
  2593. expect($parse('::').literal).toBe(true);
  2594. expect($parse(':: ').literal).toBe(true);
  2595. }));
  2596. it('should only become stable when all the properties of an object have defined values', inject(function($parse, $rootScope, log) {
  2597. var fn = $parse('::{foo: foo, bar: bar}');
  2598. $rootScope.$watch(fn, function(value) { log(value); }, true);
  2599. expect(log.empty()).toEqual([]);
  2600. expect($rootScope.$$watchers.length).toBe(1);
  2601. $rootScope.$digest();
  2602. expect($rootScope.$$watchers.length).toBe(1);
  2603. expect(log.empty()).toEqual([{foo: undefined, bar: undefined}]);
  2604. $rootScope.foo = 'foo';
  2605. $rootScope.$digest();
  2606. expect($rootScope.$$watchers.length).toBe(1);
  2607. expect(log.empty()).toEqual([{foo: 'foo', bar: undefined}]);
  2608. $rootScope.foo = 'foobar';
  2609. $rootScope.bar = 'bar';
  2610. $rootScope.$digest();
  2611. expect($rootScope.$$watchers.length).toBe(0);
  2612. expect(log.empty()).toEqual([{foo: 'foobar', bar: 'bar'}]);
  2613. $rootScope.foo = 'baz';
  2614. $rootScope.$digest();
  2615. expect($rootScope.$$watchers.length).toBe(0);
  2616. expect(log.empty()).toEqual([]);
  2617. }));
  2618. it('should only become stable when all the elements of an array have defined values', inject(function($parse, $rootScope, log) {
  2619. var fn = $parse('::[foo,bar]');
  2620. $rootScope.$watch(fn, function(value) { log(value); }, true);
  2621. expect(log.empty()).toEqual([]);
  2622. expect($rootScope.$$watchers.length).toBe(1);
  2623. $rootScope.$digest();
  2624. expect($rootScope.$$watchers.length).toBe(1);
  2625. expect(log.empty()).toEqual([[undefined, undefined]]);
  2626. $rootScope.foo = 'foo';
  2627. $rootScope.$digest();
  2628. expect($rootScope.$$watchers.length).toBe(1);
  2629. expect(log.empty()).toEqual([['foo', undefined]]);
  2630. $rootScope.foo = 'foobar';
  2631. $rootScope.bar = 'bar';
  2632. $rootScope.$digest();
  2633. expect($rootScope.$$watchers.length).toBe(0);
  2634. expect(log.empty()).toEqual([['foobar', 'bar']]);
  2635. $rootScope.foo = 'baz';
  2636. $rootScope.$digest();
  2637. expect($rootScope.$$watchers.length).toBe(0);
  2638. expect(log.empty()).toEqual([]);
  2639. }));
  2640. it('should only become stable when all the elements of an array have defined values at the end of a $digest', inject(function($parse, $rootScope, log) {
  2641. var fn = $parse('::[foo]');
  2642. $rootScope.$watch(fn, function(value) { log(value); }, true);
  2643. $rootScope.$watch('foo', function() { if ($rootScope.foo === 'bar') {$rootScope.foo = undefined; } });
  2644. $rootScope.foo = 'bar';
  2645. $rootScope.$digest();
  2646. expect($rootScope.$$watchers.length).toBe(2);
  2647. expect(log.empty()).toEqual([['bar'], [undefined]]);
  2648. $rootScope.foo = 'baz';
  2649. $rootScope.$digest();
  2650. expect($rootScope.$$watchers.length).toBe(1);
  2651. expect(log.empty()).toEqual([['baz']]);
  2652. $rootScope.bar = 'qux';
  2653. $rootScope.$digest();
  2654. expect($rootScope.$$watchers.length).toBe(1);
  2655. expect(log).toEqual([]);
  2656. }));
  2657. });
  2658. });
  2659. describe('watched $parse expressions', function() {
  2660. it('should respect short-circuiting AND if it could have side effects', function() {
  2661. var bCalled = 0;
  2662. scope.b = function() { bCalled++; };
  2663. scope.$watch("a && b()");
  2664. scope.$digest();
  2665. scope.$digest();
  2666. expect(bCalled).toBe(0);
  2667. scope.a = true;
  2668. scope.$digest();
  2669. expect(bCalled).toBe(1);
  2670. scope.$digest();
  2671. expect(bCalled).toBe(2);
  2672. });
  2673. it('should respect short-circuiting OR if it could have side effects', function() {
  2674. var bCalled = false;
  2675. scope.b = function() { bCalled = true; };
  2676. scope.$watch("a || b()");
  2677. scope.$digest();
  2678. expect(bCalled).toBe(true);
  2679. bCalled = false;
  2680. scope.a = true;
  2681. scope.$digest();
  2682. expect(bCalled).toBe(false);
  2683. });
  2684. it('should respect the branching ternary operator if it could have side effects', function() {
  2685. var bCalled = false;
  2686. scope.b = function() { bCalled = true; };
  2687. scope.$watch("a ? b() : 1");
  2688. scope.$digest();
  2689. expect(bCalled).toBe(false);
  2690. scope.a = true;
  2691. scope.$digest();
  2692. expect(bCalled).toBe(true);
  2693. });
  2694. it('should not invoke filters unless the input/arguments change', function() {
  2695. var filterCalled = false;
  2696. $filterProvider.register('foo', valueFn(function(input) {
  2697. filterCalled = true;
  2698. return input;
  2699. }));
  2700. scope.$watch("a | foo:b:1");
  2701. scope.a = 0;
  2702. scope.$digest();
  2703. expect(filterCalled).toBe(true);
  2704. filterCalled = false;
  2705. scope.$digest();
  2706. expect(filterCalled).toBe(false);
  2707. scope.a++;
  2708. scope.$digest();
  2709. expect(filterCalled).toBe(true);
  2710. });
  2711. it('should invoke filters if they are marked as having $stateful', function() {
  2712. var filterCalled = false;
  2713. $filterProvider.register('foo', valueFn(extend(function(input) {
  2714. filterCalled = true;
  2715. return input;
  2716. }, {$stateful: true})));
  2717. scope.$watch("a | foo:b:1");
  2718. scope.a = 0;
  2719. scope.$digest();
  2720. expect(filterCalled).toBe(true);
  2721. filterCalled = false;
  2722. scope.$digest();
  2723. expect(filterCalled).toBe(true);
  2724. });
  2725. it('should not invoke interceptorFns unless the input changes', inject(function($parse) {
  2726. var called = false;
  2727. function interceptor(v) {
  2728. called = true;
  2729. return v;
  2730. }
  2731. scope.$watch($parse("a", interceptor));
  2732. scope.$watch($parse("a + b", interceptor));
  2733. scope.a = scope.b = 0;
  2734. scope.$digest();
  2735. expect(called).toBe(true);
  2736. called = false;
  2737. scope.$digest();
  2738. expect(called).toBe(false);
  2739. scope.a++;
  2740. scope.$digest();
  2741. expect(called).toBe(true);
  2742. }));
  2743. it('should treat filters with constant input as constants', inject(function($parse) {
  2744. var filterCalls = 0;
  2745. $filterProvider.register('foo', valueFn(function(input) {
  2746. filterCalls++;
  2747. return input;
  2748. }));
  2749. var parsed = $parse('{x: 1} | foo:1');
  2750. expect(parsed.constant).toBe(true);
  2751. var watcherCalls = 0;
  2752. scope.$watch(parsed, function(input) {
  2753. expect(input).toEqual({x:1});
  2754. watcherCalls++;
  2755. });
  2756. scope.$digest();
  2757. expect(filterCalls).toBe(1);
  2758. expect(watcherCalls).toBe(1);
  2759. scope.$digest();
  2760. expect(filterCalls).toBe(1);
  2761. expect(watcherCalls).toBe(1);
  2762. }));
  2763. it("should always reevaluate filters with non-primitive input that doesn't support valueOf()",
  2764. inject(function($parse) {
  2765. var filterCalls = 0;
  2766. $filterProvider.register('foo', valueFn(function(input) {
  2767. filterCalls++;
  2768. return input;
  2769. }));
  2770. var parsed = $parse('obj | foo');
  2771. var obj = scope.obj = {};
  2772. var watcherCalls = 0;
  2773. scope.$watch(parsed, function(input) {
  2774. expect(input).toBe(obj);
  2775. watcherCalls++;
  2776. });
  2777. scope.$digest();
  2778. expect(filterCalls).toBe(2);
  2779. expect(watcherCalls).toBe(1);
  2780. scope.$digest();
  2781. expect(filterCalls).toBe(3);
  2782. expect(watcherCalls).toBe(1);
  2783. }));
  2784. it("should always reevaluate filters with non-primitive input created with null prototype",
  2785. inject(function($parse) {
  2786. var filterCalls = 0;
  2787. $filterProvider.register('foo', valueFn(function(input) {
  2788. filterCalls++;
  2789. return input;
  2790. }));
  2791. var parsed = $parse('obj | foo');
  2792. var obj = scope.obj = Object.create(null);
  2793. var watcherCalls = 0;
  2794. scope.$watch(parsed, function(input) {
  2795. expect(input).toBe(obj);
  2796. watcherCalls++;
  2797. });
  2798. scope.$digest();
  2799. expect(filterCalls).toBe(2);
  2800. expect(watcherCalls).toBe(1);
  2801. scope.$digest();
  2802. expect(filterCalls).toBe(3);
  2803. expect(watcherCalls).toBe(1);
  2804. }));
  2805. it("should not reevaluate filters with non-primitive input that does support valueOf()",
  2806. inject(function($parse) {
  2807. var filterCalls = 0;
  2808. $filterProvider.register('foo', valueFn(function(input) {
  2809. filterCalls++;
  2810. expect(input instanceof Date).toBe(true);
  2811. return input;
  2812. }));
  2813. var parsed = $parse('date | foo:a');
  2814. var date = scope.date = new Date();
  2815. var watcherCalls = 0;
  2816. scope.$watch(parsed, function(input) {
  2817. expect(input).toBe(date);
  2818. watcherCalls++;
  2819. });
  2820. scope.$digest();
  2821. expect(filterCalls).toBe(1);
  2822. expect(watcherCalls).toBe(1);
  2823. scope.$digest();
  2824. expect(filterCalls).toBe(1);
  2825. expect(watcherCalls).toBe(1);
  2826. }));
  2827. it("should reevaluate filters with non-primitive input that does support valueOf() when" +
  2828. "valueOf() value changes", inject(function($parse) {
  2829. var filterCalls = 0;
  2830. $filterProvider.register('foo', valueFn(function(input) {
  2831. filterCalls++;
  2832. expect(input instanceof Date).toBe(true);
  2833. return input;
  2834. }));
  2835. var parsed = $parse('date | foo:a');
  2836. var date = scope.date = new Date();
  2837. var watcherCalls = 0;
  2838. scope.$watch(parsed, function(input) {
  2839. expect(input).toBe(date);
  2840. watcherCalls++;
  2841. });
  2842. scope.$digest();
  2843. expect(filterCalls).toBe(1);
  2844. expect(watcherCalls).toBe(1);
  2845. date.setYear(1901);
  2846. scope.$digest();
  2847. expect(filterCalls).toBe(2);
  2848. expect(watcherCalls).toBe(1);
  2849. }));
  2850. it('should invoke interceptorFns if they are flagged as having $stateful',
  2851. inject(function($parse) {
  2852. var called = false;
  2853. function interceptor() {
  2854. called = true;
  2855. }
  2856. interceptor.$stateful = true;
  2857. scope.$watch($parse("a", interceptor));
  2858. scope.a = 0;
  2859. scope.$digest();
  2860. expect(called).toBe(true);
  2861. called = false;
  2862. scope.$digest();
  2863. expect(called).toBe(true);
  2864. scope.a++;
  2865. called = false;
  2866. scope.$digest();
  2867. expect(called).toBe(true);
  2868. }));
  2869. it('should continue with the evaluation of the expression without invoking computed parts',
  2870. inject(function($parse) {
  2871. var value = 'foo';
  2872. var spy = jasmine.createSpy();
  2873. spy.andCallFake(function() { return value; });
  2874. scope.foo = spy;
  2875. scope.$watch("foo() | uppercase");
  2876. scope.$digest();
  2877. expect(spy.calls.length).toEqual(2);
  2878. scope.$digest();
  2879. expect(spy.calls.length).toEqual(3);
  2880. value = 'bar';
  2881. scope.$digest();
  2882. expect(spy.calls.length).toEqual(5);
  2883. }));
  2884. it('should invoke all statements in multi-statement expressions', inject(function($parse) {
  2885. var lastVal = NaN;
  2886. var listener = function(val) { lastVal = val; };
  2887. scope.setBarToOne = false;
  2888. scope.bar = 0;
  2889. scope.two = 2;
  2890. scope.foo = function() { if (scope.setBarToOne) scope.bar = 1; };
  2891. scope.$watch("foo(); bar + two", listener);
  2892. scope.$digest();
  2893. expect(lastVal).toBe(2);
  2894. scope.bar = 2;
  2895. scope.$digest();
  2896. expect(lastVal).toBe(4);
  2897. scope.setBarToOne = true;
  2898. scope.$digest();
  2899. expect(lastVal).toBe(3);
  2900. }));
  2901. it('should watch the left side of assignments', inject(function($parse) {
  2902. var lastVal = NaN;
  2903. var listener = function(val) { lastVal = val; };
  2904. var objA = {};
  2905. var objB = {};
  2906. scope.$watch("curObj.value = input", noop);
  2907. scope.curObj = objA;
  2908. scope.input = 1;
  2909. scope.$digest();
  2910. expect(objA.value).toBe(scope.input);
  2911. scope.curObj = objB;
  2912. scope.$digest();
  2913. expect(objB.value).toBe(scope.input);
  2914. scope.input = 2;
  2915. scope.$digest();
  2916. expect(objB.value).toBe(scope.input);
  2917. }));
  2918. });
  2919. describe('locals', function() {
  2920. it('should expose local variables', inject(function($parse) {
  2921. expect($parse('a')({a: 0}, {a: 1})).toEqual(1);
  2922. expect($parse('add(a,b)')({b: 1, add: function(a, b) { return a + b; }}, {a: 2})).toEqual(3);
  2923. }));
  2924. it('should expose traverse locals', inject(function($parse) {
  2925. expect($parse('a.b')({a: {b: 0}}, {a: {b:1}})).toEqual(1);
  2926. expect($parse('a.b')({a: null}, {a: {b:1}})).toEqual(1);
  2927. expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
  2928. expect($parse('a.b.c')({a: null}, {a: {b: {c: 1}}})).toEqual(1);
  2929. }));
  2930. it('should not use locals to resolve object properties', inject(function($parse) {
  2931. expect($parse('a[0].b')({a: [{b: 'scope'}]}, {b: 'locals'})).toBe('scope');
  2932. expect($parse('a[0]["b"]')({a: [{b: 'scope'}]}, {b: 'locals'})).toBe('scope');
  2933. expect($parse('a[0][0].b')({a: [[{b: 'scope'}]]}, {b: 'locals'})).toBe('scope');
  2934. expect($parse('a[0].b.c')({a: [{b: {c: 'scope'}}] }, {b: {c: 'locals'} })).toBe('scope');
  2935. }));
  2936. it('should assign directly to locals when the local property exists', inject(function($parse) {
  2937. var s = {}, l = {};
  2938. $parse("a = 1")(s, l);
  2939. expect(s.a).toBe(1);
  2940. expect(l.a).toBeUndefined();
  2941. l.a = 2;
  2942. $parse("a = 0")(s, l);
  2943. expect(s.a).toBe(1);
  2944. expect(l.a).toBe(0);
  2945. $parse("toString = 1")(s, l);
  2946. expect(isFunction(s.toString)).toBe(true);
  2947. expect(l.toString).toBe(1);
  2948. }));
  2949. });
  2950. describe('literal', function() {
  2951. it('should mark scalar value expressions as literal', inject(function($parse) {
  2952. expect($parse('0').literal).toBe(true);
  2953. expect($parse('"hello"').literal).toBe(true);
  2954. expect($parse('true').literal).toBe(true);
  2955. expect($parse('false').literal).toBe(true);
  2956. expect($parse('null').literal).toBe(true);
  2957. expect($parse('undefined').literal).toBe(true);
  2958. }));
  2959. it('should mark array expressions as literal', inject(function($parse) {
  2960. expect($parse('[]').literal).toBe(true);
  2961. expect($parse('[1, 2, 3]').literal).toBe(true);
  2962. expect($parse('[1, identifier]').literal).toBe(true);
  2963. }));
  2964. it('should mark object expressions as literal', inject(function($parse) {
  2965. expect($parse('{}').literal).toBe(true);
  2966. expect($parse('{x: 1}').literal).toBe(true);
  2967. expect($parse('{foo: bar}').literal).toBe(true);
  2968. }));
  2969. it('should not mark function calls or operator expressions as literal', inject(function($parse) {
  2970. expect($parse('1 + 1').literal).toBe(false);
  2971. expect($parse('call()').literal).toBe(false);
  2972. expect($parse('[].length').literal).toBe(false);
  2973. }));
  2974. });
  2975. describe('constant', function() {
  2976. it('should mark an empty expressions as constant', inject(function($parse) {
  2977. expect($parse('').constant).toBe(true);
  2978. expect($parse(' ').constant).toBe(true);
  2979. expect($parse('::').constant).toBe(true);
  2980. expect($parse(':: ').constant).toBe(true);
  2981. }));
  2982. it('should mark scalar value expressions as constant', inject(function($parse) {
  2983. expect($parse('12.3').constant).toBe(true);
  2984. expect($parse('"string"').constant).toBe(true);
  2985. expect($parse('true').constant).toBe(true);
  2986. expect($parse('false').constant).toBe(true);
  2987. expect($parse('null').constant).toBe(true);
  2988. expect($parse('undefined').constant).toBe(true);
  2989. }));
  2990. it('should mark arrays as constant if they only contain constant elements', inject(function($parse) {
  2991. expect($parse('[]').constant).toBe(true);
  2992. expect($parse('[1, 2, 3]').constant).toBe(true);
  2993. expect($parse('["string", null]').constant).toBe(true);
  2994. expect($parse('[[]]').constant).toBe(true);
  2995. expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true);
  2996. }));
  2997. it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) {
  2998. expect($parse('[foo]').constant).toBe(false);
  2999. expect($parse('[x + 1]').constant).toBe(false);
  3000. expect($parse('[bar[0]]').constant).toBe(false);
  3001. }));
  3002. it('should mark complex expressions involving constant values as constant', inject(function($parse) {
  3003. expect($parse('!true').constant).toBe(true);
  3004. expect($parse('-42').constant).toBe(true);
  3005. expect($parse('1 - 1').constant).toBe(true);
  3006. expect($parse('"foo" + "bar"').constant).toBe(true);
  3007. expect($parse('5 != null').constant).toBe(true);
  3008. expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true);
  3009. }));
  3010. it('should not mark any expression involving variables or function calls as constant', inject(function($parse) {
  3011. expect($parse('true.toString()').constant).toBe(false);
  3012. expect($parse('foo(1, 2, 3)').constant).toBe(false);
  3013. expect($parse('"name" + id').constant).toBe(false);
  3014. }));
  3015. });
  3016. describe('null/undefined in expressions', function() {
  3017. // simpleGetterFn1
  3018. it('should return null for `a` where `a` is null', inject(function($rootScope) {
  3019. $rootScope.a = null;
  3020. expect($rootScope.$eval('a')).toBe(null);
  3021. }));
  3022. it('should return undefined for `a` where `a` is undefined', inject(function($rootScope) {
  3023. expect($rootScope.$eval('a')).toBeUndefined();
  3024. }));
  3025. // simpleGetterFn2
  3026. it('should return undefined for properties of `null` constant', inject(function($rootScope) {
  3027. expect($rootScope.$eval('null.a')).toBeUndefined();
  3028. }));
  3029. it('should return undefined for properties of `null` values', inject(function($rootScope) {
  3030. $rootScope.a = null;
  3031. expect($rootScope.$eval('a.b')).toBeUndefined();
  3032. }));
  3033. it('should return null for `a.b` where `b` is null', inject(function($rootScope) {
  3034. $rootScope.a = { b: null };
  3035. expect($rootScope.$eval('a.b')).toBe(null);
  3036. }));
  3037. // cspSafeGetter && pathKeys.length < 6 || pathKeys.length > 2
  3038. it('should return null for `a.b.c.d.e` where `e` is null', inject(function($rootScope) {
  3039. $rootScope.a = { b: { c: { d: { e: null } } } };
  3040. expect($rootScope.$eval('a.b.c.d.e')).toBe(null);
  3041. }));
  3042. it('should return undefined for `a.b.c.d.e` where `d` is null', inject(function($rootScope) {
  3043. $rootScope.a = { b: { c: { d: null } } };
  3044. expect($rootScope.$eval('a.b.c.d.e')).toBeUndefined();
  3045. }));
  3046. // cspSafeGetter || pathKeys.length > 6
  3047. it('should return null for `a.b.c.d.e.f.g` where `g` is null', inject(function($rootScope) {
  3048. $rootScope.a = { b: { c: { d: { e: { f: { g: null } } } } } };
  3049. expect($rootScope.$eval('a.b.c.d.e.f.g')).toBe(null);
  3050. }));
  3051. it('should return undefined for `a.b.c.d.e.f.g` where `f` is null', inject(function($rootScope) {
  3052. $rootScope.a = { b: { c: { d: { e: { f: null } } } } };
  3053. expect($rootScope.$eval('a.b.c.d.e.f.g')).toBeUndefined();
  3054. }));
  3055. it('should return undefined if the return value of a function invocation is undefined',
  3056. inject(function($rootScope) {
  3057. $rootScope.fn = function() {};
  3058. expect($rootScope.$eval('fn()')).toBeUndefined();
  3059. }));
  3060. it('should ignore undefined values when doing addition/concatenation',
  3061. inject(function($rootScope) {
  3062. $rootScope.fn = function() {};
  3063. expect($rootScope.$eval('foo + "bar" + fn()')).toBe('bar');
  3064. }));
  3065. it('should treat properties named null/undefined as normal properties', inject(function($rootScope) {
  3066. expect($rootScope.$eval("a.null.undefined.b", {a:{null:{undefined:{b: 1}}}})).toBe(1);
  3067. }));
  3068. it('should not allow overriding null/undefined keywords', inject(function($rootScope) {
  3069. expect($rootScope.$eval('null.a', {null: {a: 42}})).toBeUndefined();
  3070. }));
  3071. it('should allow accessing null/undefined properties on `this`', inject(function($rootScope) {
  3072. $rootScope.null = {a: 42};
  3073. expect($rootScope.$eval('this.null.a')).toBe(42);
  3074. }));
  3075. });
  3076. });
  3077. });
  3078. });