PageRenderTime 34ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/test/ng/parseSpec.js

https://gitlab.com/GeekSir/angular.js
JavaScript | 3514 lines | 3488 code | 22 blank | 4 comment | 7 complexity | f3f65493337652ebc5dcc9c0f436a6a2 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. expect(scope.$eval("true&&a")).toEqual(true && undefined);
  1603. expect(scope.$eval("true&&a.b")).toEqual(true && undefined);
  1604. expect(scope.$eval("false||a")).toEqual(false || undefined);
  1605. expect(scope.$eval("false||a.b")).toEqual(false || undefined);
  1606. });
  1607. it('should parse ternary', function() {
  1608. var returnTrue = scope.returnTrue = function() { return true; };
  1609. var returnFalse = scope.returnFalse = function() { return false; };
  1610. var returnString = scope.returnString = function() { return 'asd'; };
  1611. var returnInt = scope.returnInt = function() { return 123; };
  1612. var identity = scope.identity = function(x) { return x; };
  1613. // Simple.
  1614. expect(scope.$eval('0?0:2')).toEqual(0 ? 0 : 2);
  1615. expect(scope.$eval('1?0:2')).toEqual(1 ? 0 : 2);
  1616. // Nested on the left.
  1617. expect(scope.$eval('0?0?0:0:2')).toEqual(0 ? 0 ? 0 : 0 : 2);
  1618. expect(scope.$eval('1?0?0:0:2')).toEqual(1 ? 0 ? 0 : 0 : 2);
  1619. expect(scope.$eval('0?1?0:0:2')).toEqual(0 ? 1 ? 0 : 0 : 2);
  1620. expect(scope.$eval('0?0?1:0:2')).toEqual(0 ? 0 ? 1 : 0 : 2);
  1621. expect(scope.$eval('0?0?0:2:3')).toEqual(0 ? 0 ? 0 : 2 : 3);
  1622. expect(scope.$eval('1?1?0:0:2')).toEqual(1 ? 1 ? 0 : 0 : 2);
  1623. expect(scope.$eval('1?1?1:0:2')).toEqual(1 ? 1 ? 1 : 0 : 2);
  1624. expect(scope.$eval('1?1?1:2:3')).toEqual(1 ? 1 ? 1 : 2 : 3);
  1625. expect(scope.$eval('1?1?1:2:3')).toEqual(1 ? 1 ? 1 : 2 : 3);
  1626. // Nested on the right.
  1627. expect(scope.$eval('0?0:0?0:2')).toEqual(0 ? 0 : 0 ? 0 : 2);
  1628. expect(scope.$eval('1?0:0?0:2')).toEqual(1 ? 0 : 0 ? 0 : 2);
  1629. expect(scope.$eval('0?1:0?0:2')).toEqual(0 ? 1 : 0 ? 0 : 2);
  1630. expect(scope.$eval('0?0:1?0:2')).toEqual(0 ? 0 : 1 ? 0 : 2);
  1631. expect(scope.$eval('0?0:0?2:3')).toEqual(0 ? 0 : 0 ? 2 : 3);
  1632. expect(scope.$eval('1?1:0?0:2')).toEqual(1 ? 1 : 0 ? 0 : 2);
  1633. expect(scope.$eval('1?1:1?0:2')).toEqual(1 ? 1 : 1 ? 0 : 2);
  1634. expect(scope.$eval('1?1:1?2:3')).toEqual(1 ? 1 : 1 ? 2 : 3);
  1635. expect(scope.$eval('1?1:1?2:3')).toEqual(1 ? 1 : 1 ? 2 : 3);
  1636. // Precedence with respect to logical operators.
  1637. expect(scope.$eval('0&&1?0:1')).toEqual(0 && 1 ? 0 : 1);
  1638. expect(scope.$eval('1||0?0:0')).toEqual(1 || 0 ? 0 : 0);
  1639. expect(scope.$eval('0?0&&1:2')).toEqual(0 ? 0 && 1 : 2);
  1640. expect(scope.$eval('0?1&&1:2')).toEqual(0 ? 1 && 1 : 2);
  1641. expect(scope.$eval('0?0||0:1')).toEqual(0 ? 0 || 0 : 1);
  1642. expect(scope.$eval('0?0||1:2')).toEqual(0 ? 0 || 1 : 2);
  1643. expect(scope.$eval('1?0&&1:2')).toEqual(1 ? 0 && 1 : 2);
  1644. expect(scope.$eval('1?1&&1:2')).toEqual(1 ? 1 && 1 : 2);
  1645. expect(scope.$eval('1?0||0:1')).toEqual(1 ? 0 || 0 : 1);
  1646. expect(scope.$eval('1?0||1:2')).toEqual(1 ? 0 || 1 : 2);
  1647. expect(scope.$eval('0?1:0&&1')).toEqual(0 ? 1 : 0 && 1);
  1648. expect(scope.$eval('0?2:1&&1')).toEqual(0 ? 2 : 1 && 1);
  1649. expect(scope.$eval('0?1:0||0')).toEqual(0 ? 1 : 0 || 0);
  1650. expect(scope.$eval('0?2:0||1')).toEqual(0 ? 2 : 0 || 1);
  1651. expect(scope.$eval('1?1:0&&1')).toEqual(1 ? 1 : 0 && 1);
  1652. expect(scope.$eval('1?2:1&&1')).toEqual(1 ? 2 : 1 && 1);
  1653. expect(scope.$eval('1?1:0||0')).toEqual(1 ? 1 : 0 || 0);
  1654. expect(scope.$eval('1?2:0||1')).toEqual(1 ? 2 : 0 || 1);
  1655. // Function calls.
  1656. expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt());
  1657. expect(scope.$eval('returnFalse() ? returnString() : returnInt()')).toEqual(returnFalse() ? returnString() : returnInt());
  1658. expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt());
  1659. expect(scope.$eval('identity(returnFalse() ? returnString() : returnInt())')).toEqual(identity(returnFalse() ? returnString() : returnInt()));
  1660. });
  1661. it('should parse string', function() {
  1662. expect(scope.$eval("'a' + 'b c'")).toEqual("ab c");
  1663. });
  1664. it('should parse filters', function() {
  1665. $filterProvider.register('substring', valueFn(function(input, start, end) {
  1666. return input.substring(start, end);
  1667. }));
  1668. expect(function() {
  1669. scope.$eval("1|nonexistent");
  1670. }).toThrowMinErr('$injector', 'unpr', 'Unknown provider: nonexistentFilterProvider <- nonexistentFilter');
  1671. scope.offset = 3;
  1672. expect(scope.$eval("'abcd'|substring:1:offset")).toEqual("bc");
  1673. expect(scope.$eval("'abcd'|substring:1:3|uppercase")).toEqual("BC");
  1674. });
  1675. it('should access scope', function() {
  1676. scope.a = 123;
  1677. scope.b = {c: 456};
  1678. expect(scope.$eval("a", scope)).toEqual(123);
  1679. expect(scope.$eval("b.c", scope)).toEqual(456);
  1680. expect(scope.$eval("x.y.z", scope)).not.toBeDefined();
  1681. });
  1682. it('should handle white-spaces around dots in paths', function() {
  1683. scope.a = {b: 4};
  1684. expect(scope.$eval("a . b", scope)).toEqual(4);
  1685. expect(scope.$eval("a. b", scope)).toEqual(4);
  1686. expect(scope.$eval("a .b", scope)).toEqual(4);
  1687. expect(scope.$eval("a . \nb", scope)).toEqual(4);
  1688. });
  1689. it('should handle white-spaces around dots in method invocations', function() {
  1690. scope.a = {b: function() { return this.c; }, c: 4};
  1691. expect(scope.$eval("a . b ()", scope)).toEqual(4);
  1692. expect(scope.$eval("a. b ()", scope)).toEqual(4);
  1693. expect(scope.$eval("a .b ()", scope)).toEqual(4);
  1694. expect(scope.$eval("a \n . \nb \n ()", scope)).toEqual(4);
  1695. });
  1696. it('should throw syntax error exception for identifiers ending with a dot', function() {
  1697. scope.a = {b: 4};
  1698. expect(function() {
  1699. scope.$eval("a.", scope);
  1700. }).toThrowMinErr('$parse', 'ueoe',
  1701. "Unexpected end of expression: a.");
  1702. expect(function() {
  1703. scope.$eval("a .", scope);
  1704. }).toThrowMinErr('$parse', 'ueoe',
  1705. "Unexpected end of expression: a .");
  1706. });
  1707. it('should resolve deeply nested paths (important for CSP mode)', function() {
  1708. scope.a = {b: {c: {d: {e: {f: {g: {h: {i: {j: {k: {l: {m: {n: 'nooo!'}}}}}}}}}}}}};
  1709. expect(scope.$eval("a.b.c.d.e.f.g.h.i.j.k.l.m.n", scope)).toBe('nooo!');
  1710. });
  1711. forEach([2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 42, 99], function(pathLength) {
  1712. it('should resolve nested paths of length ' + pathLength, function() {
  1713. // Create a nested object {x2: {x3: {x4: ... {x[n]: 42} ... }}}.
  1714. var obj = 42, locals = {};
  1715. for (var i = pathLength; i >= 2; i--) {
  1716. var newObj = {};
  1717. newObj['x' + i] = obj;
  1718. obj = newObj;
  1719. }
  1720. // Assign to x1 and build path 'x1.x2.x3. ... .x[n]' to access the final value.
  1721. scope.x1 = obj;
  1722. var path = 'x1';
  1723. for (i = 2; i <= pathLength; i++) {
  1724. path += '.x' + i;
  1725. }
  1726. expect(scope.$eval(path)).toBe(42);
  1727. locals['x' + pathLength] = 'not 42';
  1728. expect(scope.$eval(path, locals)).toBe(42);
  1729. });
  1730. });
  1731. it('should be forgiving', function() {
  1732. scope.a = {b: 23};
  1733. expect(scope.$eval('b')).toBeUndefined();
  1734. expect(scope.$eval('a.x')).toBeUndefined();
  1735. expect(scope.$eval('a.b.c.d')).toBeUndefined();
  1736. scope.a = undefined;
  1737. expect(scope.$eval('a - b')).toBe(0);
  1738. expect(scope.$eval('a + b')).toBe(undefined);
  1739. scope.a = 0;
  1740. expect(scope.$eval('a - b')).toBe(0);
  1741. expect(scope.$eval('a + b')).toBe(0);
  1742. scope.a = undefined;
  1743. scope.b = 0;
  1744. expect(scope.$eval('a - b')).toBe(0);
  1745. expect(scope.$eval('a + b')).toBe(0);
  1746. });
  1747. it('should support property names that collide with native object properties', function() {
  1748. // regression
  1749. scope.watch = 1;
  1750. scope.toString = function toString() {
  1751. return "custom toString";
  1752. };
  1753. expect(scope.$eval('watch', scope)).toBe(1);
  1754. expect(scope.$eval('toString()', scope)).toBe('custom toString');
  1755. });
  1756. it('should not break if hasOwnProperty is referenced in an expression', function() {
  1757. scope.obj = { value: 1};
  1758. // By evaluating an expression that calls hasOwnProperty, the getterFnCache
  1759. // will store a property called hasOwnProperty. This is effectively:
  1760. // getterFnCache['hasOwnProperty'] = null
  1761. scope.$eval('obj.hasOwnProperty("value")');
  1762. // If we rely on this property then evaluating any expression will fail
  1763. // because it is not able to find out if obj.value is there in the cache
  1764. expect(scope.$eval('obj.value')).toBe(1);
  1765. });
  1766. it('should not break if the expression is "hasOwnProperty"', function() {
  1767. scope.fooExp = 'barVal';
  1768. // By evaluating hasOwnProperty, the $parse cache will store a getter for
  1769. // the scope's own hasOwnProperty function, which will mess up future cache look ups.
  1770. // i.e. cache['hasOwnProperty'] = function(scope) { return scope.hasOwnProperty; }
  1771. scope.$eval('hasOwnProperty');
  1772. expect(scope.$eval('fooExp')).toBe('barVal');
  1773. });
  1774. it('should evaluate grouped expressions', function() {
  1775. expect(scope.$eval("(1+2)*3")).toEqual((1 + 2) * 3);
  1776. });
  1777. it('should evaluate assignments', function() {
  1778. expect(scope.$eval("a=12")).toEqual(12);
  1779. expect(scope.a).toEqual(12);
  1780. expect(scope.$eval("x.y.z=123;")).toEqual(123);
  1781. expect(scope.x.y.z).toEqual(123);
  1782. expect(scope.$eval("a=123; b=234")).toEqual(234);
  1783. expect(scope.a).toEqual(123);
  1784. expect(scope.b).toEqual(234);
  1785. });
  1786. it('should evaluate assignments in ternary operator', function() {
  1787. scope.$eval('a = 1 ? 2 : 3');
  1788. expect(scope.a).toBe(2);
  1789. scope.$eval('0 ? a = 2 : a = 3');
  1790. expect(scope.a).toBe(3);
  1791. scope.$eval('1 ? a = 2 : a = 3');
  1792. expect(scope.a).toBe(2);
  1793. });
  1794. it('should evaluate function call without arguments', function() {
  1795. scope['const'] = function(a, b) {return 123;};
  1796. expect(scope.$eval("const()")).toEqual(123);
  1797. });
  1798. it('should evaluate function call with arguments', function() {
  1799. scope.add = function(a, b) {
  1800. return a + b;
  1801. };
  1802. expect(scope.$eval("add(1,2)")).toEqual(3);
  1803. });
  1804. it('should evaluate function call from a return value', function() {
  1805. scope.getter = function() { return function() { return 33; }; };
  1806. expect(scope.$eval("getter()()")).toBe(33);
  1807. });
  1808. // There is no "strict mode" in IE9
  1809. if (!msie || msie > 9) {
  1810. it('should set no context to functions returned by other functions', function() {
  1811. scope.getter = function() { return function() { expect(this).toBeUndefined(); }; };
  1812. scope.$eval("getter()()");
  1813. });
  1814. }
  1815. it('should evaluate multiplication and division', function() {
  1816. scope.taxRate = 8;
  1817. scope.subTotal = 100;
  1818. expect(scope.$eval("taxRate / 100 * subTotal")).toEqual(8);
  1819. expect(scope.$eval("subTotal * taxRate / 100")).toEqual(8);
  1820. });
  1821. it('should evaluate array', function() {
  1822. expect(scope.$eval("[]").length).toEqual(0);
  1823. expect(scope.$eval("[1, 2]").length).toEqual(2);
  1824. expect(scope.$eval("[1, 2]")[0]).toEqual(1);
  1825. expect(scope.$eval("[1, 2]")[1]).toEqual(2);
  1826. expect(scope.$eval("[1, 2,]")[1]).toEqual(2);
  1827. expect(scope.$eval("[1, 2,]").length).toEqual(2);
  1828. });
  1829. it('should evaluate array access', function() {
  1830. expect(scope.$eval("[1][0]")).toEqual(1);
  1831. expect(scope.$eval("[[1]][0][0]")).toEqual(1);
  1832. expect(scope.$eval("[].length")).toEqual(0);
  1833. expect(scope.$eval("[1, 2].length")).toEqual(2);
  1834. });
  1835. it('should evaluate object', function() {
  1836. expect(scope.$eval("{}")).toEqual({});
  1837. expect(scope.$eval("{a:'b'}")).toEqual({a:"b"});
  1838. expect(scope.$eval("{'a':'b'}")).toEqual({a:"b"});
  1839. expect(scope.$eval("{\"a\":'b'}")).toEqual({a:"b"});
  1840. expect(scope.$eval("{a:'b',}")).toEqual({a:"b"});
  1841. expect(scope.$eval("{'a':'b',}")).toEqual({a:"b"});
  1842. expect(scope.$eval("{\"a\":'b',}")).toEqual({a:"b"});
  1843. expect(scope.$eval("{'0':1}")).toEqual({0:1});
  1844. expect(scope.$eval("{0:1}")).toEqual({0:1});
  1845. expect(scope.$eval("{1:1}")).toEqual({1:1});
  1846. expect(scope.$eval("{null:1}")).toEqual({null:1});
  1847. expect(scope.$eval("{'null':1}")).toEqual({null:1});
  1848. expect(scope.$eval("{false:1}")).toEqual({false:1});
  1849. expect(scope.$eval("{'false':1}")).toEqual({false:1});
  1850. expect(scope.$eval("{'':1,}")).toEqual({"":1});
  1851. });
  1852. it('should throw syntax error exception for non constant/identifier JSON keys', function() {
  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. expect(function() { scope.$eval("{?:0}"); }).toThrowMinErr("$parse", "syntax",
  1858. "Syntax Error: Token '?' invalid key at column 2 of the expression [{?:0}] starting at [?:0}]");
  1859. expect(function() { scope.$eval("{):0}"); }).toThrowMinErr("$parse", "syntax",
  1860. "Syntax Error: Token ')' invalid key at column 2 of the expression [{):0}] starting at [):0}]");
  1861. });
  1862. it('should evaluate object access', function() {
  1863. expect(scope.$eval("{false:'WC', true:'CC'}[false]")).toEqual("WC");
  1864. });
  1865. it('should evaluate JSON', function() {
  1866. expect(scope.$eval("[{}]")).toEqual([{}]);
  1867. expect(scope.$eval("[{a:[]}, {b:1}]")).toEqual([{a:[]}, {b:1}]);
  1868. });
  1869. it('should evaluate multiple statements', function() {
  1870. expect(scope.$eval("a=1;b=3;a+b")).toEqual(4);
  1871. expect(scope.$eval(";;1;;")).toEqual(1);
  1872. });
  1873. it('should evaluate object methods in correct context (this)', function() {
  1874. var C = function() {
  1875. this.a = 123;
  1876. };
  1877. C.prototype.getA = function() {
  1878. return this.a;
  1879. };
  1880. scope.obj = new C();
  1881. expect(scope.$eval("obj.getA()")).toEqual(123);
  1882. expect(scope.$eval("obj['getA']()")).toEqual(123);
  1883. });
  1884. it('should evaluate methods in correct context (this) in argument', function() {
  1885. var C = function() {
  1886. this.a = 123;
  1887. };
  1888. C.prototype.sum = function(value) {
  1889. return this.a + value;
  1890. };
  1891. C.prototype.getA = function() {
  1892. return this.a;
  1893. };
  1894. scope.obj = new C();
  1895. expect(scope.$eval("obj.sum(obj.getA())")).toEqual(246);
  1896. expect(scope.$eval("obj['sum'](obj.getA())")).toEqual(246);
  1897. });
  1898. it('should evaluate objects on scope context', function() {
  1899. scope.a = "abc";
  1900. expect(scope.$eval("{a:a}").a).toEqual("abc");
  1901. });
  1902. it('should evaluate field access on function call result', function() {
  1903. scope.a = function() {
  1904. return {name:'misko'};
  1905. };
  1906. expect(scope.$eval("a().name")).toEqual("misko");
  1907. });
  1908. it('should evaluate field access after array access', function() {
  1909. scope.items = [{}, {name:'misko'}];
  1910. expect(scope.$eval('items[1].name')).toEqual("misko");
  1911. });
  1912. it('should evaluate array assignment', function() {
  1913. scope.items = [];
  1914. expect(scope.$eval('items[1] = "abc"')).toEqual("abc");
  1915. expect(scope.$eval('items[1]')).toEqual("abc");
  1916. // Dont know how to make this work....
  1917. // expect(scope.$eval('books[1] = "moby"')).toEqual("moby");
  1918. // expect(scope.$eval('books[1]')).toEqual("moby");
  1919. });
  1920. it('should evaluate grouped filters', function() {
  1921. scope.name = 'MISKO';
  1922. expect(scope.$eval('n = (name|lowercase)')).toEqual('misko');
  1923. expect(scope.$eval('n')).toEqual('misko');
  1924. });
  1925. it('should evaluate remainder', function() {
  1926. expect(scope.$eval('1%2')).toEqual(1);
  1927. });
  1928. it('should evaluate sum with undefined', function() {
  1929. expect(scope.$eval('1+undefined')).toEqual(1);
  1930. expect(scope.$eval('undefined+1')).toEqual(1);
  1931. });
  1932. it('should throw exception on non-closed bracket', function() {
  1933. expect(function() {
  1934. scope.$eval('[].count(');
  1935. }).toThrowMinErr('$parse', 'ueoe', 'Unexpected end of expression: [].count(');
  1936. });
  1937. it('should evaluate double negation', function() {
  1938. expect(scope.$eval('true')).toBeTruthy();
  1939. expect(scope.$eval('!true')).toBeFalsy();
  1940. expect(scope.$eval('!!true')).toBeTruthy();
  1941. expect(scope.$eval('{true:"a", false:"b"}[!!true]')).toEqual('a');
  1942. });
  1943. it('should evaluate negation', function() {
  1944. /* jshint -W018 */
  1945. expect(scope.$eval("!false || true")).toEqual(!false || true);
  1946. expect(scope.$eval("!11 == 10")).toEqual(!11 == 10);
  1947. expect(scope.$eval("12/6/2")).toEqual(12 / 6 / 2);
  1948. });
  1949. it('should evaluate exclamation mark', function() {
  1950. expect(scope.$eval('suffix = "!"')).toEqual('!');
  1951. });
  1952. it('should evaluate minus', function() {
  1953. expect(scope.$eval("{a:'-'}")).toEqual({a: "-"});
  1954. });
  1955. it('should evaluate undefined', function() {
  1956. expect(scope.$eval("undefined")).not.toBeDefined();
  1957. expect(scope.$eval("a=undefined")).not.toBeDefined();
  1958. expect(scope.a).not.toBeDefined();
  1959. });
  1960. it('should allow assignment after array dereference', function() {
  1961. scope.obj = [{}];
  1962. scope.$eval('obj[0].name=1');
  1963. expect(scope.obj.name).toBeUndefined();
  1964. expect(scope.obj[0].name).toEqual(1);
  1965. });
  1966. it('should short-circuit AND operator', function() {
  1967. scope.run = function() {
  1968. throw "IT SHOULD NOT HAVE RUN";
  1969. };
  1970. expect(scope.$eval('false && run()')).toBe(false);
  1971. expect(scope.$eval('false && true && run()')).toBe(false);
  1972. });
  1973. it('should short-circuit OR operator', function() {
  1974. scope.run = function() {
  1975. throw "IT SHOULD NOT HAVE RUN";
  1976. };
  1977. expect(scope.$eval('true || run()')).toBe(true);
  1978. expect(scope.$eval('true || false || run()')).toBe(true);
  1979. });
  1980. it('should support method calls on primitive types', function() {
  1981. scope.empty = '';
  1982. scope.zero = 0;
  1983. scope.bool = false;
  1984. expect(scope.$eval('empty.substr(0)')).toBe('');
  1985. expect(scope.$eval('zero.toString()')).toBe('0');
  1986. expect(scope.$eval('bool.toString()')).toBe('false');
  1987. });
  1988. it('should evaluate expressions with line terminators', function() {
  1989. scope.a = "a";
  1990. scope.b = {c: "bc"};
  1991. expect(scope.$eval('a + \n b.c + \r "\td" + \t \r\n\r "\r\n\n"')).toEqual("abc\td\r\n\n");
  1992. });
  1993. // https://github.com/angular/angular.js/issues/10968
  1994. it('should evaluate arrays literals initializers left-to-right', inject(function($parse) {
  1995. var s = {c:function() {return {b: 1}; }};
  1996. expect($parse("e=1;[a=c(),d=a.b+1]")(s)).toEqual([{b: 1}, 2]);
  1997. }));
  1998. it('should evaluate function arguments left-to-right', inject(function($parse) {
  1999. var s = {c:function() {return {b: 1}; }, i: function(x, y) { return [x, y];}};
  2000. expect($parse("e=1;i(a=c(),d=a.b+1)")(s)).toEqual([{b: 1}, 2]);
  2001. }));
  2002. it('should evaluate object properties expressions left-to-right', inject(function($parse) {
  2003. var s = {c:function() {return {b: 1}; }};
  2004. expect($parse("e=1;{x: a=c(), y: d=a.b+1}")(s)).toEqual({x: {b: 1}, y: 2});
  2005. }));
  2006. describe('sandboxing', function() {
  2007. describe('Function constructor', function() {
  2008. it('should not tranverse the Function constructor in the getter', function() {
  2009. expect(function() {
  2010. scope.$eval('{}.toString.constructor');
  2011. }).toThrowMinErr(
  2012. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2013. 'Expression: {}.toString.constructor');
  2014. });
  2015. it('should not allow access to the Function prototype in the getter', function() {
  2016. expect(function() {
  2017. scope.$eval('toString.constructor.prototype');
  2018. }).toThrowMinErr(
  2019. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2020. 'Expression: toString.constructor.prototype');
  2021. });
  2022. it('should NOT allow access to Function constructor in getter', function() {
  2023. expect(function() {
  2024. scope.$eval('{}.toString.constructor("alert(1)")');
  2025. }).toThrowMinErr(
  2026. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2027. 'Expression: {}.toString.constructor("alert(1)")');
  2028. });
  2029. it('should NOT allow access to Function constructor in setter', function() {
  2030. expect(function() {
  2031. scope.$eval('{}.toString.constructor.a = 1');
  2032. }).toThrowMinErr(
  2033. '$parse', 'isecfn','Referencing Function in Angular expressions is disallowed! ' +
  2034. 'Expression: {}.toString.constructor.a = 1');
  2035. expect(function() {
  2036. scope.$eval('{}.toString["constructor"]["constructor"] = 1');
  2037. }).toThrowMinErr(
  2038. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2039. 'Expression: {}.toString["constructor"]["constructor"] = 1');
  2040. scope.key1 = "const";
  2041. scope.key2 = "ructor";
  2042. expect(function() {
  2043. scope.$eval('{}.toString[key1 + key2].foo = 1');
  2044. }).toThrowMinErr(
  2045. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2046. 'Expression: {}.toString[key1 + key2].foo = 1');
  2047. expect(function() {
  2048. scope.$eval('{}.toString["constructor"]["a"] = 1');
  2049. }).toThrowMinErr(
  2050. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2051. 'Expression: {}.toString["constructor"]["a"] = 1');
  2052. scope.a = [];
  2053. expect(function() {
  2054. scope.$eval('a.toString.constructor = 1', scope);
  2055. }).toThrowMinErr(
  2056. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2057. 'Expression: a.toString.constructor');
  2058. });
  2059. it('should disallow traversing the Function object in a setter: E02', function() {
  2060. expect(function() {
  2061. // This expression by itself isn't dangerous. However, one can use this to
  2062. // automatically call an object (e.g. a Function object) when it is automatically
  2063. // toString'd/valueOf'd by setting the RHS to Function.prototype.call.
  2064. scope.$eval('hasOwnProperty.constructor.prototype.valueOf = 1');
  2065. }).toThrowMinErr(
  2066. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2067. 'Expression: hasOwnProperty.constructor.prototype.valueOf');
  2068. });
  2069. it('should disallow passing the Function object as a parameter: E03', function() {
  2070. expect(function() {
  2071. // This expression constructs a function but does not execute it. It does lead the
  2072. // way to execute it if one can get the toString/valueOf of it to call the function.
  2073. scope.$eval('["a", "alert(1)"].sort(hasOwnProperty.constructor)');
  2074. }).toThrow();
  2075. });
  2076. it('should prevent exploit E01', function() {
  2077. // This is a tracking exploit. The two individual tests, it('should … : E02') and
  2078. // it('should … : E03') test for two parts to block this exploit. This exploit works
  2079. // as follows:
  2080. //
  2081. // • Array.sort takes a comparison function and passes it 2 parameters to compare. If
  2082. // the result is non-primitive, sort then invokes valueOf() on the result.
  2083. // • The Function object conveniently accepts two string arguments so we can use this
  2084. // to construct a function. However, this doesn't do much unless we can execute it.
  2085. // • We set the valueOf property on Function.prototype to Function.prototype.call.
  2086. // This causes the function that we constructed to be executed when sort calls
  2087. // .valueOf() on the result of the comparison.
  2088. expect(function() {
  2089. scope.$eval('' +
  2090. 'hasOwnProperty.constructor.prototype.valueOf=valueOf.call;' +
  2091. '["a","alert(1)"].sort(hasOwnProperty.constructor)');
  2092. }).toThrow();
  2093. });
  2094. it('should NOT allow access to Function constructor that has been aliased in getters', function() {
  2095. scope.foo = { "bar": Function };
  2096. expect(function() {
  2097. scope.$eval('foo["bar"]');
  2098. }).toThrowMinErr(
  2099. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2100. 'Expression: foo["bar"]');
  2101. });
  2102. it('should NOT allow access to Function constructor that has been aliased in setters', function() {
  2103. scope.foo = { "bar": Function };
  2104. expect(function() {
  2105. scope.$eval('foo["bar"] = 1');
  2106. }).toThrowMinErr(
  2107. '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
  2108. 'Expression: foo["bar"] = 1');
  2109. });
  2110. describe('expensiveChecks', function() {
  2111. it('should block access to window object even when aliased in getters', inject(function($parse, $window) {
  2112. scope.foo = {w: $window};
  2113. // This isn't blocked for performance.
  2114. expect(scope.$eval($parse('foo.w'))).toBe($window);
  2115. // Event handlers use the more expensive path for better protection since they expose
  2116. // the $event object on the scope.
  2117. expect(function() {
  2118. scope.$eval($parse('foo.w', null, true));
  2119. }).toThrowMinErr(
  2120. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
  2121. 'Expression: foo.w');
  2122. }));
  2123. it('should block access to window object even when aliased in setters', inject(function($parse, $window) {
  2124. scope.foo = {w: $window};
  2125. // This is blocked as it points to `window`.
  2126. expect(function() {
  2127. expect(scope.$eval($parse('foo.w = 1'))).toBe($window);
  2128. }).toThrowMinErr(
  2129. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
  2130. 'Expression: foo.w = 1');
  2131. // Event handlers use the more expensive path for better protection since they expose
  2132. // the $event object on the scope.
  2133. expect(function() {
  2134. scope.$eval($parse('foo.w = 1', null, true));
  2135. }).toThrowMinErr(
  2136. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
  2137. 'Expression: foo.w = 1');
  2138. }));
  2139. });
  2140. });
  2141. describe('Function prototype functions', function() {
  2142. it('should NOT allow invocation to Function.call', function() {
  2143. scope.fn = Function.prototype.call;
  2144. expect(function() {
  2145. scope.$eval('$eval.call()');
  2146. }).toThrowMinErr(
  2147. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2148. 'Expression: $eval.call()');
  2149. expect(function() {
  2150. scope.$eval('fn()');
  2151. }).toThrowMinErr(
  2152. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2153. 'Expression: fn()');
  2154. });
  2155. it('should NOT allow invocation to Function.apply', function() {
  2156. scope.apply = Function.prototype.apply;
  2157. expect(function() {
  2158. scope.$eval('$eval.apply()');
  2159. }).toThrowMinErr(
  2160. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2161. 'Expression: $eval.apply()');
  2162. expect(function() {
  2163. scope.$eval('apply()');
  2164. }).toThrowMinErr(
  2165. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2166. 'Expression: apply()');
  2167. });
  2168. it('should NOT allow invocation to Function.bind', function() {
  2169. scope.bind = Function.prototype.bind;
  2170. expect(function() {
  2171. scope.$eval('$eval.bind()');
  2172. }).toThrowMinErr(
  2173. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2174. 'Expression: $eval.bind()');
  2175. expect(function() {
  2176. scope.$eval('bind()');
  2177. }).toThrowMinErr(
  2178. '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
  2179. 'Expression: bind()');
  2180. });
  2181. });
  2182. describe('Object constructor', function() {
  2183. it('should NOT allow access to Object constructor that has been aliased in getters', function() {
  2184. scope.foo = { "bar": Object };
  2185. expect(function() {
  2186. scope.$eval('foo.bar.keys(foo)');
  2187. }).toThrowMinErr(
  2188. '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
  2189. 'Expression: foo.bar.keys(foo)');
  2190. expect(function() {
  2191. scope.$eval('foo["bar"]["keys"](foo)');
  2192. }).toThrowMinErr(
  2193. '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
  2194. 'Expression: foo["bar"]["keys"](foo)');
  2195. });
  2196. it('should NOT allow access to Object constructor that has been aliased in setters', function() {
  2197. scope.foo = { "bar": Object };
  2198. expect(function() {
  2199. scope.$eval('foo.bar.keys(foo).bar = 1');
  2200. }).toThrowMinErr(
  2201. '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
  2202. 'Expression: foo.bar.keys(foo).bar = 1');
  2203. expect(function() {
  2204. scope.$eval('foo["bar"]["keys"](foo).bar = 1');
  2205. }).toThrowMinErr(
  2206. '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
  2207. 'Expression: foo["bar"]["keys"](foo).bar = 1');
  2208. });
  2209. });
  2210. describe('Window and $element/node', function() {
  2211. it('should NOT allow access to the Window or DOM when indexing', inject(function($window, $document) {
  2212. scope.wrap = {w: $window, d: $document};
  2213. expect(function() {
  2214. scope.$eval('wrap["w"]', scope);
  2215. }).toThrowMinErr(
  2216. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
  2217. 'disallowed! Expression: wrap["w"]');
  2218. expect(function() {
  2219. scope.$eval('wrap["d"]', scope);
  2220. }).toThrowMinErr(
  2221. '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
  2222. 'disallowed! Expression: wrap["d"]');
  2223. expect(function() {
  2224. scope.$eval('wrap["w"] = 1', scope);
  2225. }).toThrowMinErr(
  2226. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
  2227. 'disallowed! Expression: wrap["w"] = 1');
  2228. expect(function() {
  2229. scope.$eval('wrap["d"] = 1', scope);
  2230. }).toThrowMinErr(
  2231. '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
  2232. 'disallowed! Expression: wrap["d"] = 1');
  2233. }));
  2234. it('should NOT allow access to the Window or DOM returned from a function', inject(function($window, $document) {
  2235. scope.getWin = valueFn($window);
  2236. scope.getDoc = valueFn($document);
  2237. expect(function() {
  2238. scope.$eval('getWin()', scope);
  2239. }).toThrowMinErr(
  2240. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
  2241. 'disallowed! Expression: getWin()');
  2242. expect(function() {
  2243. scope.$eval('getDoc()', scope);
  2244. }).toThrowMinErr(
  2245. '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
  2246. 'disallowed! Expression: getDoc()');
  2247. }));
  2248. it('should NOT allow calling functions on Window or DOM', inject(function($window, $document) {
  2249. scope.a = {b: { win: $window, doc: $document }};
  2250. expect(function() {
  2251. scope.$eval('a.b.win.alert(1)', scope);
  2252. }).toThrowMinErr(
  2253. '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
  2254. 'disallowed! Expression: a.b.win.alert(1)');
  2255. expect(function() {
  2256. scope.$eval('a.b.doc.on("click")', scope);
  2257. }).toThrowMinErr(
  2258. '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
  2259. 'disallowed! Expression: a.b.doc.on("click")');
  2260. }));
  2261. // Issue #4805
  2262. it('should NOT throw isecdom when referencing a Backbone Collection', function() {
  2263. // Backbone stuff is sort of hard to mock, if you have a better way of doing this,
  2264. // please fix this.
  2265. var fakeBackboneCollection = {
  2266. children: [{}, {}, {}],
  2267. find: function() {},
  2268. on: function() {},
  2269. off: function() {},
  2270. bind: function() {}
  2271. };
  2272. scope.backbone = fakeBackboneCollection;
  2273. expect(function() { scope.$eval('backbone'); }).not.toThrow();
  2274. });
  2275. it('should NOT throw isecdom when referencing an array with node properties', function() {
  2276. var array = [1,2,3];
  2277. array.on = array.attr = array.prop = array.bind = true;
  2278. scope.array = array;
  2279. expect(function() { scope.$eval('array'); }).not.toThrow();
  2280. });
  2281. });
  2282. describe('Disallowed fields', function() {
  2283. it('should NOT allow access or invocation of __defineGetter__', function() {
  2284. expect(function() {
  2285. scope.$eval('{}.__defineGetter__');
  2286. }).toThrowMinErr('$parse', 'isecfld');
  2287. expect(function() {
  2288. scope.$eval('{}.__defineGetter__("a", "".charAt)');
  2289. }).toThrowMinErr('$parse', 'isecfld');
  2290. expect(function() {
  2291. scope.$eval('{}["__defineGetter__"]');
  2292. }).toThrowMinErr('$parse', 'isecfld');
  2293. expect(function() {
  2294. scope.$eval('{}["__defineGetter__"]("a", "".charAt)');
  2295. }).toThrowMinErr('$parse', 'isecfld');
  2296. scope.a = "__define";
  2297. scope.b = "Getter__";
  2298. expect(function() {
  2299. scope.$eval('{}[a + b]');
  2300. }).toThrowMinErr('$parse', 'isecfld');
  2301. expect(function() {
  2302. scope.$eval('{}[a + b]("a", "".charAt)');
  2303. }).toThrowMinErr('$parse', 'isecfld');
  2304. });
  2305. it('should NOT allow access or invocation of __defineSetter__', function() {
  2306. expect(function() {
  2307. scope.$eval('{}.__defineSetter__');
  2308. }).toThrowMinErr('$parse', 'isecfld');
  2309. expect(function() {
  2310. scope.$eval('{}.__defineSetter__("a", "".charAt)');
  2311. }).toThrowMinErr('$parse', 'isecfld');
  2312. expect(function() {
  2313. scope.$eval('{}["__defineSetter__"]');
  2314. }).toThrowMinErr('$parse', 'isecfld');
  2315. expect(function() {
  2316. scope.$eval('{}["__defineSetter__"]("a", "".charAt)');
  2317. }).toThrowMinErr('$parse', 'isecfld');
  2318. scope.a = "__define";
  2319. scope.b = "Setter__";
  2320. expect(function() {
  2321. scope.$eval('{}[a + b]');
  2322. }).toThrowMinErr('$parse', 'isecfld');
  2323. expect(function() {
  2324. scope.$eval('{}[a + b]("a", "".charAt)');
  2325. }).toThrowMinErr('$parse', 'isecfld');
  2326. });
  2327. it('should NOT allow access or invocation of __lookupGetter__', function() {
  2328. expect(function() {
  2329. scope.$eval('{}.__lookupGetter__');
  2330. }).toThrowMinErr('$parse', 'isecfld');
  2331. expect(function() {
  2332. scope.$eval('{}.__lookupGetter__("a")');
  2333. }).toThrowMinErr('$parse', 'isecfld');
  2334. expect(function() {
  2335. scope.$eval('{}["__lookupGetter__"]');
  2336. }).toThrowMinErr('$parse', 'isecfld');
  2337. expect(function() {
  2338. scope.$eval('{}["__lookupGetter__"]("a")');
  2339. }).toThrowMinErr('$parse', 'isecfld');
  2340. scope.a = "__lookup";
  2341. scope.b = "Getter__";
  2342. expect(function() {
  2343. scope.$eval('{}[a + b]');
  2344. }).toThrowMinErr('$parse', 'isecfld');
  2345. expect(function() {
  2346. scope.$eval('{}[a + b]("a")');
  2347. }).toThrowMinErr('$parse', 'isecfld');
  2348. });
  2349. it('should NOT allow access or invocation of __lookupSetter__', function() {
  2350. expect(function() {
  2351. scope.$eval('{}.__lookupSetter__');
  2352. }).toThrowMinErr('$parse', 'isecfld');
  2353. expect(function() {
  2354. scope.$eval('{}.__lookupSetter__("a")');
  2355. }).toThrowMinErr('$parse', 'isecfld');
  2356. expect(function() {
  2357. scope.$eval('{}["__lookupSetter__"]');
  2358. }).toThrowMinErr('$parse', 'isecfld');
  2359. expect(function() {
  2360. scope.$eval('{}["__lookupSetter__"]("a")');
  2361. }).toThrowMinErr('$parse', 'isecfld');
  2362. scope.a = "__lookup";
  2363. scope.b = "Setter__";
  2364. expect(function() {
  2365. scope.$eval('{}[a + b]');
  2366. }).toThrowMinErr('$parse', 'isecfld');
  2367. expect(function() {
  2368. scope.$eval('{}[a + b]("a")');
  2369. }).toThrowMinErr('$parse', 'isecfld');
  2370. });
  2371. it('should NOT allow access to __proto__', function() {
  2372. expect(function() {
  2373. scope.$eval('__proto__');
  2374. }).toThrowMinErr('$parse', 'isecfld');
  2375. expect(function() {
  2376. scope.$eval('{}.__proto__');
  2377. }).toThrowMinErr('$parse', 'isecfld');
  2378. expect(function() {
  2379. scope.$eval('{}.__proto__.foo = 1');
  2380. }).toThrowMinErr('$parse', 'isecfld');
  2381. expect(function() {
  2382. scope.$eval('{}["__proto__"]');
  2383. }).toThrowMinErr('$parse', 'isecfld');
  2384. expect(function() {
  2385. scope.$eval('{}["__proto__"].foo = 1');
  2386. }).toThrowMinErr('$parse', 'isecfld');
  2387. scope.a = "__pro";
  2388. scope.b = "to__";
  2389. expect(function() {
  2390. scope.$eval('{}[a + b]');
  2391. }).toThrowMinErr('$parse', 'isecfld');
  2392. expect(function() {
  2393. scope.$eval('{}[a + b].foo = 1');
  2394. }).toThrowMinErr('$parse', 'isecfld');
  2395. });
  2396. });
  2397. it('should prevent the exploit', function() {
  2398. expect(function() {
  2399. scope.$eval('' +
  2400. ' "".sub.call.call(' +
  2401. '({})["constructor"].getOwnPropertyDescriptor("".sub.__proto__, "constructor").value,' +
  2402. 'null,' +
  2403. '"alert(1)"' +
  2404. ')()' +
  2405. '');
  2406. }).toThrow();
  2407. });
  2408. });
  2409. it('should call the function from the received instance and not from a new one', function() {
  2410. var n = 0;
  2411. scope.fn = function() {
  2412. var c = n++;
  2413. return { c: c, anotherFn: function() { return this.c == c; } };
  2414. };
  2415. expect(scope.$eval('fn().anotherFn()')).toBe(true);
  2416. });
  2417. it('should call the function once when it is part of the context', function() {
  2418. var count = 0;
  2419. scope.fn = function() {
  2420. count++;
  2421. return { anotherFn: function() { return "lucas"; } };
  2422. };
  2423. expect(scope.$eval('fn().anotherFn()')).toBe('lucas');
  2424. expect(count).toBe(1);
  2425. });
  2426. it('should call the function once when it is not part of the context', function() {
  2427. var count = 0;
  2428. scope.fn = function() {
  2429. count++;
  2430. return function() { return 'lucas'; };
  2431. };
  2432. expect(scope.$eval('fn()()')).toBe('lucas');
  2433. expect(count).toBe(1);
  2434. });
  2435. it('should call the function once when it is part of the context on assignments', function() {
  2436. var count = 0;
  2437. var element = {};
  2438. scope.fn = function() {
  2439. count++;
  2440. return element;
  2441. };
  2442. expect(scope.$eval('fn().name = "lucas"')).toBe('lucas');
  2443. expect(element.name).toBe('lucas');
  2444. expect(count).toBe(1);
  2445. });
  2446. it('should call the function once when it is part of the context on array lookups', function() {
  2447. var count = 0;
  2448. var element = [];
  2449. scope.fn = function() {
  2450. count++;
  2451. return element;
  2452. };
  2453. expect(scope.$eval('fn()[0] = "lucas"')).toBe('lucas');
  2454. expect(element[0]).toBe('lucas');
  2455. expect(count).toBe(1);
  2456. });
  2457. it('should call the function once when it is part of the context on array lookup function', function() {
  2458. var count = 0;
  2459. var element = [{anotherFn: function() { return 'lucas';} }];
  2460. scope.fn = function() {
  2461. count++;
  2462. return element;
  2463. };
  2464. expect(scope.$eval('fn()[0].anotherFn()')).toBe('lucas');
  2465. expect(count).toBe(1);
  2466. });
  2467. it('should call the function once when it is part of the context on property lookup function', function() {
  2468. var count = 0;
  2469. var element = {name: {anotherFn: function() { return 'lucas';} } };
  2470. scope.fn = function() {
  2471. count++;
  2472. return element;
  2473. };
  2474. expect(scope.$eval('fn().name.anotherFn()')).toBe('lucas');
  2475. expect(count).toBe(1);
  2476. });
  2477. it('should call the function once when it is part of a sub-expression', function() {
  2478. var count = 0;
  2479. scope.element = [{}];
  2480. scope.fn = function() {
  2481. count++;
  2482. return 0;
  2483. };
  2484. expect(scope.$eval('element[fn()].name = "lucas"')).toBe('lucas');
  2485. expect(scope.element[0].name).toBe('lucas');
  2486. expect(count).toBe(1);
  2487. });
  2488. describe('assignable', function() {
  2489. it('should expose assignment function', inject(function($parse) {
  2490. var fn = $parse('a');
  2491. expect(fn.assign).toBeTruthy();
  2492. var scope = {};
  2493. fn.assign(scope, 123);
  2494. expect(scope).toEqual({a:123});
  2495. }));
  2496. it('should expose working assignment function for expressions ending with brackets', inject(function($parse) {
  2497. var fn = $parse('a.b["c"]');
  2498. expect(fn.assign).toBeTruthy();
  2499. var scope = {};
  2500. fn.assign(scope, 123);
  2501. expect(scope.a.b.c).toEqual(123);
  2502. }));
  2503. it('should expose working assignment function for expressions with brackets in the middle', inject(function($parse) {
  2504. var fn = $parse('a["b"].c');
  2505. expect(fn.assign).toBeTruthy();
  2506. var scope = {};
  2507. fn.assign(scope, 123);
  2508. expect(scope.a.b.c).toEqual(123);
  2509. }));
  2510. it('should create objects when finding a null', inject(function($parse) {
  2511. var fn = $parse('foo.bar');
  2512. var scope = {foo: null};
  2513. fn.assign(scope, 123);
  2514. expect(scope.foo.bar).toEqual(123);
  2515. }));
  2516. it('should create objects when finding a null', inject(function($parse) {
  2517. var fn = $parse('foo["bar"]');
  2518. var scope = {foo: null};
  2519. fn.assign(scope, 123);
  2520. expect(scope.foo.bar).toEqual(123);
  2521. }));
  2522. it('should create objects when finding a null', inject(function($parse) {
  2523. var fn = $parse('foo.bar.baz');
  2524. var scope = {foo: null};
  2525. fn.assign(scope, 123);
  2526. expect(scope.foo.bar.baz).toEqual(123);
  2527. }));
  2528. });
  2529. describe('one-time binding', function() {
  2530. it('should always use the cache', inject(function($parse) {
  2531. expect($parse('foo')).toBe($parse('foo'));
  2532. expect($parse('::foo')).toBe($parse('::foo'));
  2533. }));
  2534. it('should not affect calling the parseFn directly', inject(function($parse, $rootScope) {
  2535. var fn = $parse('::foo');
  2536. $rootScope.$watch(fn);
  2537. $rootScope.foo = 'bar';
  2538. expect($rootScope.$$watchers.length).toBe(1);
  2539. expect(fn($rootScope)).toEqual('bar');
  2540. $rootScope.$digest();
  2541. expect($rootScope.$$watchers.length).toBe(0);
  2542. expect(fn($rootScope)).toEqual('bar');
  2543. $rootScope.foo = 'man';
  2544. $rootScope.$digest();
  2545. expect($rootScope.$$watchers.length).toBe(0);
  2546. expect(fn($rootScope)).toEqual('man');
  2547. $rootScope.foo = 'shell';
  2548. $rootScope.$digest();
  2549. expect($rootScope.$$watchers.length).toBe(0);
  2550. expect(fn($rootScope)).toEqual('shell');
  2551. }));
  2552. it('should stay stable once the value defined', inject(function($parse, $rootScope, log) {
  2553. var fn = $parse('::foo');
  2554. $rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });
  2555. $rootScope.$digest();
  2556. expect($rootScope.$$watchers.length).toBe(1);
  2557. $rootScope.foo = 'bar';
  2558. $rootScope.$digest();
  2559. expect($rootScope.$$watchers.length).toBe(0);
  2560. expect(log).toEqual('bar');
  2561. log.reset();
  2562. $rootScope.foo = 'man';
  2563. $rootScope.$digest();
  2564. expect($rootScope.$$watchers.length).toBe(0);
  2565. expect(log).toEqual('');
  2566. }));
  2567. it('should have a stable value if at the end of a $digest it has a defined value', inject(function($parse, $rootScope, log) {
  2568. var fn = $parse('::foo');
  2569. $rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });
  2570. $rootScope.$watch('foo', function() { if ($rootScope.foo === 'bar') {$rootScope.foo = undefined; } });
  2571. $rootScope.foo = 'bar';
  2572. $rootScope.$digest();
  2573. expect($rootScope.$$watchers.length).toBe(2);
  2574. expect(log).toEqual('');
  2575. $rootScope.foo = 'man';
  2576. $rootScope.$digest();
  2577. expect($rootScope.$$watchers.length).toBe(1);
  2578. expect(log).toEqual('; man');
  2579. $rootScope.foo = 'shell';
  2580. $rootScope.$digest();
  2581. expect($rootScope.$$watchers.length).toBe(1);
  2582. expect(log).toEqual('; man');
  2583. }));
  2584. it('should not throw if the stable value is `null`', inject(function($parse, $rootScope) {
  2585. var fn = $parse('::foo');
  2586. $rootScope.$watch(fn);
  2587. $rootScope.foo = null;
  2588. $rootScope.$digest();
  2589. $rootScope.foo = 'foo';
  2590. $rootScope.$digest();
  2591. expect(fn()).toEqual(null);
  2592. }));
  2593. describe('literal expressions', function() {
  2594. it('should mark an empty expressions as literal', inject(function($parse) {
  2595. expect($parse('').literal).toBe(true);
  2596. expect($parse(' ').literal).toBe(true);
  2597. expect($parse('::').literal).toBe(true);
  2598. expect($parse(':: ').literal).toBe(true);
  2599. }));
  2600. it('should only become stable when all the properties of an object have defined values', inject(function($parse, $rootScope, log) {
  2601. var fn = $parse('::{foo: foo, bar: bar}');
  2602. $rootScope.$watch(fn, function(value) { log(value); }, true);
  2603. expect(log.empty()).toEqual([]);
  2604. expect($rootScope.$$watchers.length).toBe(1);
  2605. $rootScope.$digest();
  2606. expect($rootScope.$$watchers.length).toBe(1);
  2607. expect(log.empty()).toEqual([{foo: undefined, bar: undefined}]);
  2608. $rootScope.foo = 'foo';
  2609. $rootScope.$digest();
  2610. expect($rootScope.$$watchers.length).toBe(1);
  2611. expect(log.empty()).toEqual([{foo: 'foo', bar: undefined}]);
  2612. $rootScope.foo = 'foobar';
  2613. $rootScope.bar = 'bar';
  2614. $rootScope.$digest();
  2615. expect($rootScope.$$watchers.length).toBe(0);
  2616. expect(log.empty()).toEqual([{foo: 'foobar', bar: 'bar'}]);
  2617. $rootScope.foo = 'baz';
  2618. $rootScope.$digest();
  2619. expect($rootScope.$$watchers.length).toBe(0);
  2620. expect(log.empty()).toEqual([]);
  2621. }));
  2622. it('should only become stable when all the elements of an array have defined values', inject(function($parse, $rootScope, log) {
  2623. var fn = $parse('::[foo,bar]');
  2624. $rootScope.$watch(fn, function(value) { log(value); }, true);
  2625. expect(log.empty()).toEqual([]);
  2626. expect($rootScope.$$watchers.length).toBe(1);
  2627. $rootScope.$digest();
  2628. expect($rootScope.$$watchers.length).toBe(1);
  2629. expect(log.empty()).toEqual([[undefined, undefined]]);
  2630. $rootScope.foo = 'foo';
  2631. $rootScope.$digest();
  2632. expect($rootScope.$$watchers.length).toBe(1);
  2633. expect(log.empty()).toEqual([['foo', undefined]]);
  2634. $rootScope.foo = 'foobar';
  2635. $rootScope.bar = 'bar';
  2636. $rootScope.$digest();
  2637. expect($rootScope.$$watchers.length).toBe(0);
  2638. expect(log.empty()).toEqual([['foobar', 'bar']]);
  2639. $rootScope.foo = 'baz';
  2640. $rootScope.$digest();
  2641. expect($rootScope.$$watchers.length).toBe(0);
  2642. expect(log.empty()).toEqual([]);
  2643. }));
  2644. 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) {
  2645. var fn = $parse('::[foo]');
  2646. $rootScope.$watch(fn, function(value) { log(value); }, true);
  2647. $rootScope.$watch('foo', function() { if ($rootScope.foo === 'bar') {$rootScope.foo = undefined; } });
  2648. $rootScope.foo = 'bar';
  2649. $rootScope.$digest();
  2650. expect($rootScope.$$watchers.length).toBe(2);
  2651. expect(log.empty()).toEqual([['bar'], [undefined]]);
  2652. $rootScope.foo = 'baz';
  2653. $rootScope.$digest();
  2654. expect($rootScope.$$watchers.length).toBe(1);
  2655. expect(log.empty()).toEqual([['baz']]);
  2656. $rootScope.bar = 'qux';
  2657. $rootScope.$digest();
  2658. expect($rootScope.$$watchers.length).toBe(1);
  2659. expect(log).toEqual([]);
  2660. }));
  2661. });
  2662. });
  2663. describe('watched $parse expressions', function() {
  2664. it('should respect short-circuiting AND if it could have side effects', function() {
  2665. var bCalled = 0;
  2666. scope.b = function() { bCalled++; };
  2667. scope.$watch("a && b()");
  2668. scope.$digest();
  2669. scope.$digest();
  2670. expect(bCalled).toBe(0);
  2671. scope.a = true;
  2672. scope.$digest();
  2673. expect(bCalled).toBe(1);
  2674. scope.$digest();
  2675. expect(bCalled).toBe(2);
  2676. });
  2677. it('should respect short-circuiting OR if it could have side effects', function() {
  2678. var bCalled = false;
  2679. scope.b = function() { bCalled = true; };
  2680. scope.$watch("a || b()");
  2681. scope.$digest();
  2682. expect(bCalled).toBe(true);
  2683. bCalled = false;
  2684. scope.a = true;
  2685. scope.$digest();
  2686. expect(bCalled).toBe(false);
  2687. });
  2688. it('should respect the branching ternary operator if it could have side effects', function() {
  2689. var bCalled = false;
  2690. scope.b = function() { bCalled = true; };
  2691. scope.$watch("a ? b() : 1");
  2692. scope.$digest();
  2693. expect(bCalled).toBe(false);
  2694. scope.a = true;
  2695. scope.$digest();
  2696. expect(bCalled).toBe(true);
  2697. });
  2698. it('should not invoke filters unless the input/arguments change', function() {
  2699. var filterCalled = false;
  2700. $filterProvider.register('foo', valueFn(function(input) {
  2701. filterCalled = true;
  2702. return input;
  2703. }));
  2704. scope.$watch("a | foo:b:1");
  2705. scope.a = 0;
  2706. scope.$digest();
  2707. expect(filterCalled).toBe(true);
  2708. filterCalled = false;
  2709. scope.$digest();
  2710. expect(filterCalled).toBe(false);
  2711. scope.a++;
  2712. scope.$digest();
  2713. expect(filterCalled).toBe(true);
  2714. });
  2715. it('should invoke filters if they are marked as having $stateful', function() {
  2716. var filterCalled = false;
  2717. $filterProvider.register('foo', valueFn(extend(function(input) {
  2718. filterCalled = true;
  2719. return input;
  2720. }, {$stateful: true})));
  2721. scope.$watch("a | foo:b:1");
  2722. scope.a = 0;
  2723. scope.$digest();
  2724. expect(filterCalled).toBe(true);
  2725. filterCalled = false;
  2726. scope.$digest();
  2727. expect(filterCalled).toBe(true);
  2728. });
  2729. it('should not invoke interceptorFns unless the input changes', inject(function($parse) {
  2730. var called = false;
  2731. function interceptor(v) {
  2732. called = true;
  2733. return v;
  2734. }
  2735. scope.$watch($parse("a", interceptor));
  2736. scope.$watch($parse("a + b", interceptor));
  2737. scope.a = scope.b = 0;
  2738. scope.$digest();
  2739. expect(called).toBe(true);
  2740. called = false;
  2741. scope.$digest();
  2742. expect(called).toBe(false);
  2743. scope.a++;
  2744. scope.$digest();
  2745. expect(called).toBe(true);
  2746. }));
  2747. it('should treat filters with constant input as constants', inject(function($parse) {
  2748. var filterCalls = 0;
  2749. $filterProvider.register('foo', valueFn(function(input) {
  2750. filterCalls++;
  2751. return input;
  2752. }));
  2753. var parsed = $parse('{x: 1} | foo:1');
  2754. expect(parsed.constant).toBe(true);
  2755. var watcherCalls = 0;
  2756. scope.$watch(parsed, function(input) {
  2757. expect(input).toEqual({x:1});
  2758. watcherCalls++;
  2759. });
  2760. scope.$digest();
  2761. expect(filterCalls).toBe(1);
  2762. expect(watcherCalls).toBe(1);
  2763. scope.$digest();
  2764. expect(filterCalls).toBe(1);
  2765. expect(watcherCalls).toBe(1);
  2766. }));
  2767. it("should always reevaluate filters with non-primitive input that doesn't support valueOf()",
  2768. inject(function($parse) {
  2769. var filterCalls = 0;
  2770. $filterProvider.register('foo', valueFn(function(input) {
  2771. filterCalls++;
  2772. return input;
  2773. }));
  2774. var parsed = $parse('obj | foo');
  2775. var obj = scope.obj = {};
  2776. var watcherCalls = 0;
  2777. scope.$watch(parsed, function(input) {
  2778. expect(input).toBe(obj);
  2779. watcherCalls++;
  2780. });
  2781. scope.$digest();
  2782. expect(filterCalls).toBe(2);
  2783. expect(watcherCalls).toBe(1);
  2784. scope.$digest();
  2785. expect(filterCalls).toBe(3);
  2786. expect(watcherCalls).toBe(1);
  2787. }));
  2788. it("should always reevaluate filters with non-primitive input created with null prototype",
  2789. inject(function($parse) {
  2790. var filterCalls = 0;
  2791. $filterProvider.register('foo', valueFn(function(input) {
  2792. filterCalls++;
  2793. return input;
  2794. }));
  2795. var parsed = $parse('obj | foo');
  2796. var obj = scope.obj = Object.create(null);
  2797. var watcherCalls = 0;
  2798. scope.$watch(parsed, function(input) {
  2799. expect(input).toBe(obj);
  2800. watcherCalls++;
  2801. });
  2802. scope.$digest();
  2803. expect(filterCalls).toBe(2);
  2804. expect(watcherCalls).toBe(1);
  2805. scope.$digest();
  2806. expect(filterCalls).toBe(3);
  2807. expect(watcherCalls).toBe(1);
  2808. }));
  2809. it("should not reevaluate filters with non-primitive input that does support valueOf()",
  2810. inject(function($parse) {
  2811. var filterCalls = 0;
  2812. $filterProvider.register('foo', valueFn(function(input) {
  2813. filterCalls++;
  2814. expect(input instanceof Date).toBe(true);
  2815. return input;
  2816. }));
  2817. var parsed = $parse('date | foo:a');
  2818. var date = scope.date = new Date();
  2819. var watcherCalls = 0;
  2820. scope.$watch(parsed, function(input) {
  2821. expect(input).toBe(date);
  2822. watcherCalls++;
  2823. });
  2824. scope.$digest();
  2825. expect(filterCalls).toBe(1);
  2826. expect(watcherCalls).toBe(1);
  2827. scope.$digest();
  2828. expect(filterCalls).toBe(1);
  2829. expect(watcherCalls).toBe(1);
  2830. }));
  2831. it("should reevaluate filters with non-primitive input that does support valueOf() when" +
  2832. "valueOf() value changes", inject(function($parse) {
  2833. var filterCalls = 0;
  2834. $filterProvider.register('foo', valueFn(function(input) {
  2835. filterCalls++;
  2836. expect(input instanceof Date).toBe(true);
  2837. return input;
  2838. }));
  2839. var parsed = $parse('date | foo:a');
  2840. var date = scope.date = new Date();
  2841. var watcherCalls = 0;
  2842. scope.$watch(parsed, function(input) {
  2843. expect(input).toBe(date);
  2844. watcherCalls++;
  2845. });
  2846. scope.$digest();
  2847. expect(filterCalls).toBe(1);
  2848. expect(watcherCalls).toBe(1);
  2849. date.setYear(1901);
  2850. scope.$digest();
  2851. expect(filterCalls).toBe(2);
  2852. expect(watcherCalls).toBe(1);
  2853. }));
  2854. it('should invoke interceptorFns if they are flagged as having $stateful',
  2855. inject(function($parse) {
  2856. var called = false;
  2857. function interceptor() {
  2858. called = true;
  2859. }
  2860. interceptor.$stateful = true;
  2861. scope.$watch($parse("a", interceptor));
  2862. scope.a = 0;
  2863. scope.$digest();
  2864. expect(called).toBe(true);
  2865. called = false;
  2866. scope.$digest();
  2867. expect(called).toBe(true);
  2868. scope.a++;
  2869. called = false;
  2870. scope.$digest();
  2871. expect(called).toBe(true);
  2872. }));
  2873. it('should continue with the evaluation of the expression without invoking computed parts',
  2874. inject(function($parse) {
  2875. var value = 'foo';
  2876. var spy = jasmine.createSpy();
  2877. spy.andCallFake(function() { return value; });
  2878. scope.foo = spy;
  2879. scope.$watch("foo() | uppercase");
  2880. scope.$digest();
  2881. expect(spy.calls.length).toEqual(2);
  2882. scope.$digest();
  2883. expect(spy.calls.length).toEqual(3);
  2884. value = 'bar';
  2885. scope.$digest();
  2886. expect(spy.calls.length).toEqual(5);
  2887. }));
  2888. it('should invoke all statements in multi-statement expressions', inject(function($parse) {
  2889. var lastVal = NaN;
  2890. var listener = function(val) { lastVal = val; };
  2891. scope.setBarToOne = false;
  2892. scope.bar = 0;
  2893. scope.two = 2;
  2894. scope.foo = function() { if (scope.setBarToOne) scope.bar = 1; };
  2895. scope.$watch("foo(); bar + two", listener);
  2896. scope.$digest();
  2897. expect(lastVal).toBe(2);
  2898. scope.bar = 2;
  2899. scope.$digest();
  2900. expect(lastVal).toBe(4);
  2901. scope.setBarToOne = true;
  2902. scope.$digest();
  2903. expect(lastVal).toBe(3);
  2904. }));
  2905. it('should watch the left side of assignments', inject(function($parse) {
  2906. var lastVal = NaN;
  2907. var listener = function(val) { lastVal = val; };
  2908. var objA = {};
  2909. var objB = {};
  2910. scope.$watch("curObj.value = input", noop);
  2911. scope.curObj = objA;
  2912. scope.input = 1;
  2913. scope.$digest();
  2914. expect(objA.value).toBe(scope.input);
  2915. scope.curObj = objB;
  2916. scope.$digest();
  2917. expect(objB.value).toBe(scope.input);
  2918. scope.input = 2;
  2919. scope.$digest();
  2920. expect(objB.value).toBe(scope.input);
  2921. }));
  2922. });
  2923. describe('locals', function() {
  2924. it('should expose local variables', inject(function($parse) {
  2925. expect($parse('a')({a: 0}, {a: 1})).toEqual(1);
  2926. expect($parse('add(a,b)')({b: 1, add: function(a, b) { return a + b; }}, {a: 2})).toEqual(3);
  2927. }));
  2928. it('should expose traverse locals', inject(function($parse) {
  2929. expect($parse('a.b')({a: {b: 0}}, {a: {b:1}})).toEqual(1);
  2930. expect($parse('a.b')({a: null}, {a: {b:1}})).toEqual(1);
  2931. expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
  2932. expect($parse('a.b.c')({a: null}, {a: {b: {c: 1}}})).toEqual(1);
  2933. }));
  2934. it('should not use locals to resolve object properties', inject(function($parse) {
  2935. expect($parse('a[0].b')({a: [{b: 'scope'}]}, {b: 'locals'})).toBe('scope');
  2936. expect($parse('a[0]["b"]')({a: [{b: 'scope'}]}, {b: 'locals'})).toBe('scope');
  2937. expect($parse('a[0][0].b')({a: [[{b: 'scope'}]]}, {b: 'locals'})).toBe('scope');
  2938. expect($parse('a[0].b.c')({a: [{b: {c: 'scope'}}] }, {b: {c: 'locals'} })).toBe('scope');
  2939. }));
  2940. it('should assign directly to locals when the local property exists', inject(function($parse) {
  2941. var s = {}, l = {};
  2942. $parse("a = 1")(s, l);
  2943. expect(s.a).toBe(1);
  2944. expect(l.a).toBeUndefined();
  2945. l.a = 2;
  2946. $parse("a = 0")(s, l);
  2947. expect(s.a).toBe(1);
  2948. expect(l.a).toBe(0);
  2949. $parse("toString = 1")(s, l);
  2950. expect(isFunction(s.toString)).toBe(true);
  2951. expect(l.toString).toBe(1);
  2952. }));
  2953. });
  2954. describe('literal', function() {
  2955. it('should mark scalar value expressions as literal', inject(function($parse) {
  2956. expect($parse('0').literal).toBe(true);
  2957. expect($parse('"hello"').literal).toBe(true);
  2958. expect($parse('true').literal).toBe(true);
  2959. expect($parse('false').literal).toBe(true);
  2960. expect($parse('null').literal).toBe(true);
  2961. expect($parse('undefined').literal).toBe(true);
  2962. }));
  2963. it('should mark array expressions as literal', inject(function($parse) {
  2964. expect($parse('[]').literal).toBe(true);
  2965. expect($parse('[1, 2, 3]').literal).toBe(true);
  2966. expect($parse('[1, identifier]').literal).toBe(true);
  2967. }));
  2968. it('should mark object expressions as literal', inject(function($parse) {
  2969. expect($parse('{}').literal).toBe(true);
  2970. expect($parse('{x: 1}').literal).toBe(true);
  2971. expect($parse('{foo: bar}').literal).toBe(true);
  2972. }));
  2973. it('should not mark function calls or operator expressions as literal', inject(function($parse) {
  2974. expect($parse('1 + 1').literal).toBe(false);
  2975. expect($parse('call()').literal).toBe(false);
  2976. expect($parse('[].length').literal).toBe(false);
  2977. }));
  2978. });
  2979. describe('constant', function() {
  2980. it('should mark an empty expressions as constant', inject(function($parse) {
  2981. expect($parse('').constant).toBe(true);
  2982. expect($parse(' ').constant).toBe(true);
  2983. expect($parse('::').constant).toBe(true);
  2984. expect($parse(':: ').constant).toBe(true);
  2985. }));
  2986. it('should mark scalar value expressions as constant', inject(function($parse) {
  2987. expect($parse('12.3').constant).toBe(true);
  2988. expect($parse('"string"').constant).toBe(true);
  2989. expect($parse('true').constant).toBe(true);
  2990. expect($parse('false').constant).toBe(true);
  2991. expect($parse('null').constant).toBe(true);
  2992. expect($parse('undefined').constant).toBe(true);
  2993. }));
  2994. it('should mark arrays as constant if they only contain constant elements', inject(function($parse) {
  2995. expect($parse('[]').constant).toBe(true);
  2996. expect($parse('[1, 2, 3]').constant).toBe(true);
  2997. expect($parse('["string", null]').constant).toBe(true);
  2998. expect($parse('[[]]').constant).toBe(true);
  2999. expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true);
  3000. }));
  3001. it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) {
  3002. expect($parse('[foo]').constant).toBe(false);
  3003. expect($parse('[x + 1]').constant).toBe(false);
  3004. expect($parse('[bar[0]]').constant).toBe(false);
  3005. }));
  3006. it('should mark complex expressions involving constant values as constant', inject(function($parse) {
  3007. expect($parse('!true').constant).toBe(true);
  3008. expect($parse('-42').constant).toBe(true);
  3009. expect($parse('1 - 1').constant).toBe(true);
  3010. expect($parse('"foo" + "bar"').constant).toBe(true);
  3011. expect($parse('5 != null').constant).toBe(true);
  3012. expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true);
  3013. }));
  3014. it('should not mark any expression involving variables or function calls as constant', inject(function($parse) {
  3015. expect($parse('true.toString()').constant).toBe(false);
  3016. expect($parse('foo(1, 2, 3)').constant).toBe(false);
  3017. expect($parse('"name" + id').constant).toBe(false);
  3018. }));
  3019. });
  3020. describe('null/undefined in expressions', function() {
  3021. // simpleGetterFn1
  3022. it('should return null for `a` where `a` is null', inject(function($rootScope) {
  3023. $rootScope.a = null;
  3024. expect($rootScope.$eval('a')).toBe(null);
  3025. }));
  3026. it('should return undefined for `a` where `a` is undefined', inject(function($rootScope) {
  3027. expect($rootScope.$eval('a')).toBeUndefined();
  3028. }));
  3029. // simpleGetterFn2
  3030. it('should return undefined for properties of `null` constant', inject(function($rootScope) {
  3031. expect($rootScope.$eval('null.a')).toBeUndefined();
  3032. }));
  3033. it('should return undefined for properties of `null` values', inject(function($rootScope) {
  3034. $rootScope.a = null;
  3035. expect($rootScope.$eval('a.b')).toBeUndefined();
  3036. }));
  3037. it('should return null for `a.b` where `b` is null', inject(function($rootScope) {
  3038. $rootScope.a = { b: null };
  3039. expect($rootScope.$eval('a.b')).toBe(null);
  3040. }));
  3041. // cspSafeGetter && pathKeys.length < 6 || pathKeys.length > 2
  3042. it('should return null for `a.b.c.d.e` where `e` is null', inject(function($rootScope) {
  3043. $rootScope.a = { b: { c: { d: { e: null } } } };
  3044. expect($rootScope.$eval('a.b.c.d.e')).toBe(null);
  3045. }));
  3046. it('should return undefined for `a.b.c.d.e` where `d` is null', inject(function($rootScope) {
  3047. $rootScope.a = { b: { c: { d: null } } };
  3048. expect($rootScope.$eval('a.b.c.d.e')).toBeUndefined();
  3049. }));
  3050. // cspSafeGetter || pathKeys.length > 6
  3051. it('should return null for `a.b.c.d.e.f.g` where `g` is null', inject(function($rootScope) {
  3052. $rootScope.a = { b: { c: { d: { e: { f: { g: null } } } } } };
  3053. expect($rootScope.$eval('a.b.c.d.e.f.g')).toBe(null);
  3054. }));
  3055. it('should return undefined for `a.b.c.d.e.f.g` where `f` is null', inject(function($rootScope) {
  3056. $rootScope.a = { b: { c: { d: { e: { f: null } } } } };
  3057. expect($rootScope.$eval('a.b.c.d.e.f.g')).toBeUndefined();
  3058. }));
  3059. it('should return undefined if the return value of a function invocation is undefined',
  3060. inject(function($rootScope) {
  3061. $rootScope.fn = function() {};
  3062. expect($rootScope.$eval('fn()')).toBeUndefined();
  3063. }));
  3064. it('should ignore undefined values when doing addition/concatenation',
  3065. inject(function($rootScope) {
  3066. $rootScope.fn = function() {};
  3067. expect($rootScope.$eval('foo + "bar" + fn()')).toBe('bar');
  3068. }));
  3069. it('should treat properties named null/undefined as normal properties', inject(function($rootScope) {
  3070. expect($rootScope.$eval("a.null.undefined.b", {a:{null:{undefined:{b: 1}}}})).toBe(1);
  3071. }));
  3072. it('should not allow overriding null/undefined keywords', inject(function($rootScope) {
  3073. expect($rootScope.$eval('null.a', {null: {a: 42}})).toBeUndefined();
  3074. }));
  3075. it('should allow accessing null/undefined properties on `this`', inject(function($rootScope) {
  3076. $rootScope.null = {a: 42};
  3077. expect($rootScope.$eval('this.null.a')).toBe(42);
  3078. }));
  3079. });
  3080. });
  3081. });
  3082. });