2011-07-17 01:05:43 -07:00
|
|
|
'use strict';
|
|
|
|
|
|
2016-07-20 15:45:04 +02:00
|
|
|
// This file has many tests which read nicely if constant conditions
|
|
|
|
|
// are used.
|
|
|
|
|
/* eslint-disable no-constant-condition */
|
|
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
describe('parser', function() {
|
2013-10-07 09:58:37 -07:00
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
describe('lexer', function() {
|
2013-08-20 17:19:49 -07:00
|
|
|
var lex;
|
|
|
|
|
|
2014-10-22 17:31:27 -04:00
|
|
|
beforeEach(function() {
|
2014-04-26 23:07:28 +03:00
|
|
|
/* global Lexer: false */
|
2014-10-22 17:31:27 -04:00
|
|
|
lex = function() {
|
2014-05-22 08:43:24 -07:00
|
|
|
var lexer = new Lexer({csp: false});
|
2013-08-20 17:19:49 -07:00
|
|
|
return lexer.lex.apply(lexer, arguments);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2014-10-04 17:09:22 -07:00
|
|
|
it('should only match number chars with isNumber', function() {
|
|
|
|
|
expect(Lexer.prototype.isNumber('0')).toBe(true);
|
|
|
|
|
expect(Lexer.prototype.isNumber('')).toBeFalsy();
|
|
|
|
|
expect(Lexer.prototype.isNumber(' ')).toBeFalsy();
|
|
|
|
|
expect(Lexer.prototype.isNumber(0)).toBeFalsy();
|
|
|
|
|
expect(Lexer.prototype.isNumber(false)).toBeFalsy();
|
|
|
|
|
expect(Lexer.prototype.isNumber(true)).toBeFalsy();
|
|
|
|
|
expect(Lexer.prototype.isNumber(undefined)).toBeFalsy();
|
|
|
|
|
expect(Lexer.prototype.isNumber(null)).toBeFalsy();
|
|
|
|
|
});
|
|
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
it('should tokenize a string', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
var tokens = lex('a.bc[22]+1.3|f:\'a\\\'c\':"d\\"e"');
|
2010-10-15 13:44:53 -07:00
|
|
|
var i = 0;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(0);
|
2014-10-04 17:09:22 -07:00
|
|
|
expect(tokens[i].text).toEqual('a');
|
|
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
expect(tokens[i].index).toEqual(1);
|
|
|
|
|
expect(tokens[i].text).toEqual('.');
|
|
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
expect(tokens[i].index).toEqual(2);
|
|
|
|
|
expect(tokens[i].text).toEqual('bc');
|
2010-10-15 13:44:53 -07:00
|
|
|
|
|
|
|
|
i++;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(4);
|
|
|
|
|
expect(tokens[i].text).toEqual('[');
|
2010-10-15 13:44:53 -07:00
|
|
|
|
|
|
|
|
i++;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(5);
|
2014-10-04 17:09:22 -07:00
|
|
|
expect(tokens[i].text).toEqual('22');
|
|
|
|
|
expect(tokens[i].value).toEqual(22);
|
|
|
|
|
expect(tokens[i].constant).toEqual(true);
|
2010-10-15 13:44:53 -07:00
|
|
|
|
|
|
|
|
i++;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(7);
|
|
|
|
|
expect(tokens[i].text).toEqual(']');
|
2010-10-15 13:44:53 -07:00
|
|
|
|
|
|
|
|
i++;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(8);
|
|
|
|
|
expect(tokens[i].text).toEqual('+');
|
2010-10-15 13:44:53 -07:00
|
|
|
|
|
|
|
|
i++;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(9);
|
2014-10-04 17:09:22 -07:00
|
|
|
expect(tokens[i].text).toEqual('1.3');
|
|
|
|
|
expect(tokens[i].value).toEqual(1.3);
|
|
|
|
|
expect(tokens[i].constant).toEqual(true);
|
2010-10-15 13:44:53 -07:00
|
|
|
|
|
|
|
|
i++;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(12);
|
|
|
|
|
expect(tokens[i].text).toEqual('|');
|
2010-10-15 13:44:53 -07:00
|
|
|
|
|
|
|
|
i++;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(13);
|
|
|
|
|
expect(tokens[i].text).toEqual('f');
|
2010-10-15 13:44:53 -07:00
|
|
|
|
|
|
|
|
i++;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(14);
|
|
|
|
|
expect(tokens[i].text).toEqual(':');
|
2010-10-15 13:44:53 -07:00
|
|
|
|
|
|
|
|
i++;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(15);
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(tokens[i].value).toEqual('a\'c');
|
2010-10-15 13:44:53 -07:00
|
|
|
|
|
|
|
|
i++;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(21);
|
|
|
|
|
expect(tokens[i].text).toEqual(':');
|
2010-10-15 13:44:53 -07:00
|
|
|
|
|
|
|
|
i++;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(22);
|
2014-10-04 17:09:22 -07:00
|
|
|
expect(tokens[i].value).toEqual('d"e');
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|
|
|
|
|
|
2014-10-04 17:09:22 -07:00
|
|
|
it('should tokenize identifiers with spaces around dots the same as without spaces', function() {
|
|
|
|
|
function getText(t) { return t.text; }
|
|
|
|
|
var spaces = lex('foo. bar . baz').map(getText);
|
|
|
|
|
var noSpaces = lex('foo.bar.baz').map(getText);
|
|
|
|
|
|
|
|
|
|
expect(spaces).toEqual(noSpaces);
|
2014-08-09 22:55:21 +02:00
|
|
|
});
|
|
|
|
|
|
2015-09-20 19:39:06 +02:00
|
|
|
it('should use callback functions to know when an identifier is valid', function() {
|
|
|
|
|
function getText(t) { return t.text; }
|
|
|
|
|
var isIdentifierStart = jasmine.createSpy('start');
|
|
|
|
|
var isIdentifierContinue = jasmine.createSpy('continue');
|
|
|
|
|
isIdentifierStart.and.returnValue(true);
|
|
|
|
|
var lex = new Lexer({csp: false, isIdentifierStart: isIdentifierStart, isIdentifierContinue: isIdentifierContinue});
|
|
|
|
|
|
|
|
|
|
isIdentifierContinue.and.returnValue(true);
|
|
|
|
|
var tokens = lex.lex('πΣε').map(getText);
|
|
|
|
|
expect(tokens).toEqual(['πΣε']);
|
|
|
|
|
|
|
|
|
|
isIdentifierContinue.and.returnValue(false);
|
|
|
|
|
tokens = lex.lex('πΣε').map(getText);
|
|
|
|
|
expect(tokens).toEqual(['π', 'Σ', 'ε']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should send the unicode characters and code points', function() {
|
|
|
|
|
function getText(t) { return t.text; }
|
|
|
|
|
var isIdentifierStart = jasmine.createSpy('start');
|
|
|
|
|
var isIdentifierContinue = jasmine.createSpy('continue');
|
|
|
|
|
isIdentifierStart.and.returnValue(true);
|
|
|
|
|
isIdentifierContinue.and.returnValue(true);
|
|
|
|
|
var lex = new Lexer({csp: false, isIdentifierStart: isIdentifierStart, isIdentifierContinue: isIdentifierContinue});
|
|
|
|
|
var tokens = lex.lex('\uD801\uDC37\uD852\uDF62\uDBFF\uDFFF');
|
|
|
|
|
expect(isIdentifierStart).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(isIdentifierStart.calls.argsFor(0)).toEqual(['\uD801\uDC37', 0x10437]);
|
|
|
|
|
expect(isIdentifierContinue).toHaveBeenCalledTimes(2);
|
|
|
|
|
expect(isIdentifierContinue.calls.argsFor(0)).toEqual(['\uD852\uDF62', 0x24B62]);
|
|
|
|
|
expect(isIdentifierContinue.calls.argsFor(1)).toEqual(['\uDBFF\uDFFF', 0x10FFFF]);
|
|
|
|
|
});
|
|
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
it('should tokenize undefined', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
var tokens = lex('undefined');
|
2010-10-15 13:44:53 -07:00
|
|
|
var i = 0;
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[i].index).toEqual(0);
|
|
|
|
|
expect(tokens[i].text).toEqual('undefined');
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|
2011-01-19 15:42:11 -08:00
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
it('should tokenize quoted string', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
var str = '[\'\\\'\', "\\""]';
|
2010-10-15 13:44:53 -07:00
|
|
|
var tokens = lex(str);
|
|
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[1].index).toEqual(1);
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(tokens[1].value).toEqual('\'');
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[3].index).toEqual(7);
|
2014-10-04 17:09:22 -07:00
|
|
|
expect(tokens[3].value).toEqual('"');
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|
|
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
it('should tokenize escaped quoted string', function() {
|
2010-10-15 13:44:53 -07:00
|
|
|
var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
|
|
|
|
|
var tokens = lex(str);
|
|
|
|
|
|
2014-10-04 17:09:22 -07:00
|
|
|
expect(tokens[0].value).toEqual('"\n\f\r\t\v\u00A0');
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|
|
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
it('should tokenize unicode', function() {
|
2010-10-15 13:44:53 -07:00
|
|
|
var tokens = lex('"\\u00A0"');
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens.length).toEqual(1);
|
2014-10-04 17:09:22 -07:00
|
|
|
expect(tokens[0].value).toEqual('\u00a0');
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|
|
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
it('should ignore whitespace', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
var tokens = lex('a \t \n \r b');
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[0].text).toEqual('a');
|
|
|
|
|
expect(tokens[1].text).toEqual('b');
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|
|
|
|
|
|
2012-11-27 11:00:46 -05:00
|
|
|
it('should tokenize relation and equality', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
var tokens = lex('! == != < > <= >= === !==');
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[0].text).toEqual('!');
|
|
|
|
|
expect(tokens[1].text).toEqual('==');
|
|
|
|
|
expect(tokens[2].text).toEqual('!=');
|
|
|
|
|
expect(tokens[3].text).toEqual('<');
|
|
|
|
|
expect(tokens[4].text).toEqual('>');
|
|
|
|
|
expect(tokens[5].text).toEqual('<=');
|
|
|
|
|
expect(tokens[6].text).toEqual('>=');
|
2012-11-27 11:00:46 -05:00
|
|
|
expect(tokens[7].text).toEqual('===');
|
|
|
|
|
expect(tokens[8].text).toEqual('!==');
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|
2013-06-24 14:14:54 -07:00
|
|
|
|
2013-04-22 17:42:34 -07:00
|
|
|
it('should tokenize logical and ternary', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
var tokens = lex('&& || ? :');
|
2013-04-22 17:42:34 -07:00
|
|
|
expect(tokens[0].text).toEqual('&&');
|
|
|
|
|
expect(tokens[1].text).toEqual('||');
|
|
|
|
|
expect(tokens[2].text).toEqual('?');
|
|
|
|
|
expect(tokens[3].text).toEqual(':');
|
|
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
it('should tokenize statements', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
var tokens = lex('a;b;');
|
2010-11-08 22:29:41 +00:00
|
|
|
expect(tokens[0].text).toEqual('a');
|
|
|
|
|
expect(tokens[1].text).toEqual(';');
|
|
|
|
|
expect(tokens[2].text).toEqual('b');
|
|
|
|
|
expect(tokens[3].text).toEqual(';');
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|
|
|
|
|
|
2012-01-24 02:33:35 -08:00
|
|
|
it('should tokenize function invocation', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
var tokens = lex('a()');
|
2014-09-04 23:51:02 +02:00
|
|
|
expect(tokens.map(function(t) { return t.text;})).toEqual(['a', '(', ')']);
|
2012-01-24 02:33:35 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should tokenize method invocation', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
var tokens = lex('a.b.c (d) - e.f()');
|
2014-09-04 23:51:02 +02:00
|
|
|
expect(tokens.map(function(t) { return t.text;})).
|
2014-10-04 17:09:22 -07:00
|
|
|
toEqual(['a', '.', 'b', '.', 'c', '(', 'd', ')', '-', 'e', '.', 'f', '(', ')']);
|
2012-01-24 02:33:35 -08:00
|
|
|
});
|
|
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
it('should tokenize number', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
var tokens = lex('0.5');
|
2014-10-04 17:09:22 -07:00
|
|
|
expect(tokens[0].value).toEqual(0.5);
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|
|
|
|
|
|
2011-10-17 16:56:56 -07:00
|
|
|
it('should tokenize negative number', inject(function($rootScope) {
|
2016-08-10 12:13:14 +02:00
|
|
|
var value = $rootScope.$eval('-0.5');
|
2010-10-15 13:44:53 -07:00
|
|
|
expect(value).toEqual(-0.5);
|
|
|
|
|
|
2016-08-10 12:13:14 +02:00
|
|
|
value = $rootScope.$eval('{a:-0.5}');
|
2010-10-15 13:44:53 -07:00
|
|
|
expect(value).toEqual({a:-0.5});
|
2011-10-17 16:56:56 -07:00
|
|
|
}));
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2011-10-17 16:56:56 -07:00
|
|
|
it('should tokenize number with exponent', inject(function($rootScope) {
|
2016-08-10 12:13:14 +02:00
|
|
|
var tokens = lex('0.5E-10');
|
2014-10-04 17:09:22 -07:00
|
|
|
expect(tokens[0].value).toEqual(0.5E-10);
|
2016-08-10 12:13:14 +02:00
|
|
|
expect($rootScope.$eval('0.5E-10')).toEqual(0.5E-10);
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2016-08-10 12:13:14 +02:00
|
|
|
tokens = lex('0.5E+10');
|
2014-10-04 17:09:22 -07:00
|
|
|
expect(tokens[0].value).toEqual(0.5E+10);
|
2011-10-17 16:56:56 -07:00
|
|
|
}));
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2010-12-21 17:38:04 -08:00
|
|
|
it('should throws exception for invalid exponent', function() {
|
|
|
|
|
expect(function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
lex('0.5E-');
|
2013-08-13 15:30:52 -07:00
|
|
|
}).toThrowMinErr('$parse', 'lexerr', 'Lexer Error: Invalid exponent at column 4 in expression [0.5E-].');
|
2011-01-19 15:42:11 -08:00
|
|
|
|
2010-12-21 17:38:04 -08:00
|
|
|
expect(function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
lex('0.5E-A');
|
2013-08-13 15:30:52 -07:00
|
|
|
}).toThrowMinErr('$parse', 'lexerr', 'Lexer Error: Invalid exponent at column 4 in expression [0.5E-A].');
|
2010-12-21 17:38:04 -08:00
|
|
|
});
|
|
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
it('should tokenize number starting with a dot', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
var tokens = lex('.5');
|
2014-10-04 17:09:22 -07:00
|
|
|
expect(tokens[0].value).toEqual(0.5);
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|
2010-10-15 14:06:30 -07:00
|
|
|
|
2010-11-08 22:29:41 +00:00
|
|
|
it('should throw error on invalid unicode', function() {
|
|
|
|
|
expect(function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
lex('\'\\u1\'\'bla\'');
|
|
|
|
|
}).toThrowMinErr('$parse', 'lexerr', 'Lexer Error: Invalid unicode escape [\\u1\'\'b] at column 2 in expression [\'\\u1\'\'bla\'].');
|
2010-10-15 14:06:30 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|
2011-01-19 15:42:11 -08:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
describe('ast', function() {
|
|
|
|
|
var createAst;
|
|
|
|
|
|
|
|
|
|
beforeEach(function() {
|
|
|
|
|
/* global AST: false */
|
|
|
|
|
createAst = function() {
|
|
|
|
|
var lexer = new Lexer({csp: false});
|
2016-03-07 23:14:32 +01:00
|
|
|
var ast = new AST(lexer, {csp: false, literals: {'true': true, 'false': false, 'undefined': undefined, 'null': null}});
|
2014-11-23 23:08:33 +01:00
|
|
|
return ast.ast.apply(ast, arguments);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle an empty list of tokens', function() {
|
|
|
|
|
expect(createAst('')).toEqual({type: 'Program', body: []});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand identifiers', function() {
|
|
|
|
|
expect(createAst('foo')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: { type: 'Identifier', name: 'foo' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand non-computed member expressions', function() {
|
|
|
|
|
expect(createAst('foo.bar')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo'},
|
|
|
|
|
property: {type: 'Identifier', name: 'bar'},
|
|
|
|
|
computed: false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should associate non-computed member expressions left-to-right', function() {
|
|
|
|
|
expect(createAst('foo.bar.baz')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo'},
|
|
|
|
|
property: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
computed: false
|
|
|
|
|
},
|
|
|
|
|
property: {type: 'Identifier', name: 'baz'},
|
|
|
|
|
computed: false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand computed member expressions', function() {
|
|
|
|
|
expect(createAst('foo[bar]')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo'},
|
|
|
|
|
property: {type: 'Identifier', name: 'bar'},
|
|
|
|
|
computed: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should associate computed member expressions left-to-right', function() {
|
|
|
|
|
expect(createAst('foo[bar][baz]')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
property: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
computed: true
|
|
|
|
|
},
|
|
|
|
|
property: { type: 'Identifier', name: 'baz' },
|
|
|
|
|
computed: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand call expressions', function() {
|
|
|
|
|
expect(createAst('foo()')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'foo'},
|
|
|
|
|
arguments: []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should parse call expression arguments', function() {
|
|
|
|
|
expect(createAst('foo(bar, baz)')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'foo'},
|
|
|
|
|
arguments: [
|
|
|
|
|
{ type: 'Identifier', name: 'bar' },
|
|
|
|
|
{ type: 'Identifier', name: 'baz' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should parse call expression left-to-right', function() {
|
|
|
|
|
expect(createAst('foo(bar, baz)(man, shell)')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
arguments: [
|
|
|
|
|
{ type: 'Identifier', name: 'bar' },
|
|
|
|
|
{ type: 'Identifier', name: 'baz' }
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
arguments: [
|
|
|
|
|
{ type: 'Identifier', name: 'man' },
|
|
|
|
|
{ type: 'Identifier', name: 'shell' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should keep the context when having superfluous parenthesis', function() {
|
|
|
|
|
expect(createAst('(foo)(bar, baz)')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'foo'},
|
|
|
|
|
arguments: [
|
|
|
|
|
{ type: 'Identifier', name: 'bar' },
|
|
|
|
|
{ type: 'Identifier', name: 'baz' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should treat member expressions and call expression with the same precedence', function() {
|
|
|
|
|
expect(createAst('foo.bar[baz]()')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
property: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
computed: false
|
|
|
|
|
},
|
|
|
|
|
property: { type: 'Identifier', name: 'baz' },
|
|
|
|
|
computed: true
|
|
|
|
|
},
|
|
|
|
|
arguments: []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('foo[bar]().baz')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
property: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
computed: true
|
|
|
|
|
},
|
|
|
|
|
arguments: []
|
|
|
|
|
},
|
|
|
|
|
property: { type: 'Identifier', name: 'baz' },
|
|
|
|
|
computed: false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('foo().bar[baz]')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
arguments: [] },
|
|
|
|
|
property: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
computed: false
|
|
|
|
|
},
|
|
|
|
|
property: { type: 'Identifier', name: 'baz' },
|
|
|
|
|
computed: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand literals', function() {
|
|
|
|
|
// In a strict sense, `undefined` is not a literal but an identifier
|
|
|
|
|
forEach({'123': 123, '"123"': '123', 'true': true, 'false': false, 'null': null, 'undefined': undefined}, function(value, expression) {
|
|
|
|
|
expect(createAst(expression)).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: { type: 'Literal', value: value }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand the `this` expression', function() {
|
|
|
|
|
expect(createAst('this')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: { type: 'ThisExpression' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2015-12-06 17:03:15 +01:00
|
|
|
it('should understand the `$locals` expression', function() {
|
|
|
|
|
expect(createAst('$locals')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: { type: 'LocalsExpression' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2016-10-28 10:51:03 +00:00
|
|
|
it('should not confuse `this`, `$locals`, `undefined`, `true`, `false`, `null` when used as identifiers', function() {
|
2015-12-06 17:03:15 +01:00
|
|
|
forEach(['this', '$locals', 'undefined', 'true', 'false', 'null'], function(identifier) {
|
2014-11-23 23:08:33 +01:00
|
|
|
expect(createAst('foo.' + identifier)).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
property: { type: 'Identifier', name: identifier },
|
|
|
|
|
computed: false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should throw when trying to use non-identifiers as identifiers', function() {
|
|
|
|
|
expect(function() { createAst('foo.)'); }).toThrowMinErr('$parse', 'syntax',
|
2016-08-10 12:13:14 +02:00
|
|
|
'Syntax Error: Token \')\' is not a valid identifier at column 5 of the expression [foo.)');
|
2014-11-23 23:08:33 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should throw when all tokens are not consumed', function() {
|
|
|
|
|
expect(function() { createAst('foo bar'); }).toThrowMinErr('$parse', 'syntax',
|
2016-08-10 12:13:14 +02:00
|
|
|
'Syntax Error: Token \'bar\' is an unexpected token at column 5 of the expression [foo bar] starting at [bar]');
|
2014-11-23 23:08:33 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand the unary operators `-`, `+` and `!`', function() {
|
|
|
|
|
forEach(['-', '+', '!'], function(operator) {
|
|
|
|
|
expect(createAst(operator + 'foo')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'UnaryExpression',
|
|
|
|
|
operator: operator,
|
|
|
|
|
prefix: true,
|
|
|
|
|
argument: { type: 'Identifier', name: 'foo' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should handle all unary operators with the same precedence', function() {
|
|
|
|
|
forEach([['+', '-', '!'], ['-', '!', '+'], ['!', '+', '-']], function(operators) {
|
|
|
|
|
expect(createAst(operators.join('') + 'foo')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'UnaryExpression',
|
|
|
|
|
operator: operators[0],
|
|
|
|
|
prefix: true,
|
|
|
|
|
argument: {
|
|
|
|
|
type: 'UnaryExpression',
|
|
|
|
|
operator: operators[1],
|
|
|
|
|
prefix: true,
|
|
|
|
|
argument: {
|
|
|
|
|
type: 'UnaryExpression',
|
|
|
|
|
operator: operators[2],
|
|
|
|
|
prefix: true,
|
|
|
|
|
argument: { type: 'Identifier', name: 'foo' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should be able to understand binary operators', function() {
|
|
|
|
|
forEach(['*', '/', '%', '+', '-', '<', '>', '<=', '>=', '==','!=','===','!=='], function(operator) {
|
|
|
|
|
expect(createAst('foo' + operator + 'bar')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
|
operator: operator,
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2016-10-28 10:51:03 +00:00
|
|
|
it('should associate binary operators with the same precedence left-to-right', function() {
|
2014-11-23 23:08:33 +01:00
|
|
|
var operatorsByPrecedence = [['*', '/', '%'], ['+', '-'], ['<', '>', '<=', '>='], ['==','!=','===','!==']];
|
|
|
|
|
forEach(operatorsByPrecedence, function(operators) {
|
|
|
|
|
forEach(operators, function(op1) {
|
|
|
|
|
forEach(operators, function(op2) {
|
|
|
|
|
expect(createAst('foo' + op1 + 'bar' + op2 + 'baz')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
|
operator: op2,
|
|
|
|
|
left: {
|
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
|
operator: op1,
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
},
|
|
|
|
|
right: { type: 'Identifier', name: 'baz' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2016-10-28 10:51:03 +00:00
|
|
|
it('should give higher precedence to member calls than to unary expressions', function() {
|
2014-11-23 23:08:33 +01:00
|
|
|
forEach(['!', '+', '-'], function(operator) {
|
|
|
|
|
expect(createAst(operator + 'foo()')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'UnaryExpression',
|
|
|
|
|
operator: operator,
|
|
|
|
|
prefix: true,
|
|
|
|
|
argument: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
arguments: []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst(operator + 'foo.bar')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'UnaryExpression',
|
|
|
|
|
operator: operator,
|
|
|
|
|
prefix: true,
|
|
|
|
|
argument: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
property: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
computed: false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst(operator + 'foo[bar]')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'UnaryExpression',
|
|
|
|
|
operator: operator,
|
|
|
|
|
prefix: true,
|
|
|
|
|
argument: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
property: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
computed: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should give higher precedence to unary operators over multiplicative operators', function() {
|
|
|
|
|
forEach(['!', '+', '-'], function(op1) {
|
|
|
|
|
forEach(['*', '/', '%'], function(op2) {
|
|
|
|
|
expect(createAst(op1 + 'foo' + op2 + op1 + 'bar')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
|
operator: op2,
|
|
|
|
|
left: {
|
|
|
|
|
type: 'UnaryExpression',
|
|
|
|
|
operator: op1,
|
|
|
|
|
prefix: true,
|
|
|
|
|
argument: { type: 'Identifier', name: 'foo' }
|
|
|
|
|
},
|
|
|
|
|
right: {
|
|
|
|
|
type: 'UnaryExpression',
|
|
|
|
|
operator: op1,
|
|
|
|
|
prefix: true,
|
|
|
|
|
argument: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should give binary operators their right precedence', function() {
|
|
|
|
|
var operatorsByPrecedence = [['*', '/', '%'], ['+', '-'], ['<', '>', '<=', '>='], ['==','!=','===','!==']];
|
|
|
|
|
for (var i = 0; i < operatorsByPrecedence.length - 1; ++i) {
|
|
|
|
|
forEach(operatorsByPrecedence[i], function(op1) {
|
|
|
|
|
forEach(operatorsByPrecedence[i + 1], function(op2) {
|
|
|
|
|
expect(createAst('foo' + op1 + 'bar' + op2 + 'baz' + op1 + 'man')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
|
operator: op2,
|
|
|
|
|
left: {
|
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
|
operator: op1,
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
},
|
|
|
|
|
right: {
|
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
|
operator: op1,
|
|
|
|
|
left: { type: 'Identifier', name: 'baz' },
|
|
|
|
|
right: { type: 'Identifier', name: 'man' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand logical operators', function() {
|
|
|
|
|
forEach(['||', '&&'], function(operator) {
|
|
|
|
|
expect(createAst('foo' + operator + 'bar')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'LogicalExpression',
|
|
|
|
|
operator: operator,
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should associate logical operators left-to-right', function() {
|
|
|
|
|
forEach(['||', '&&'], function(op) {
|
|
|
|
|
expect(createAst('foo' + op + 'bar' + op + 'baz')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'LogicalExpression',
|
|
|
|
|
operator: op,
|
|
|
|
|
left: {
|
|
|
|
|
type: 'LogicalExpression',
|
|
|
|
|
operator: op,
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
},
|
|
|
|
|
right: { type: 'Identifier', name: 'baz' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand ternary operators', function() {
|
|
|
|
|
expect(createAst('foo?bar:baz')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ConditionalExpression',
|
|
|
|
|
test: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
alternate: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
consequent: { type: 'Identifier', name: 'baz' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should associate the conditional operator right-to-left', function() {
|
|
|
|
|
expect(createAst('foo0?foo1:foo2?bar0?bar1:bar2:man0?man1:man2')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ConditionalExpression',
|
|
|
|
|
test: { type: 'Identifier', name: 'foo0' },
|
|
|
|
|
alternate: { type: 'Identifier', name: 'foo1' },
|
|
|
|
|
consequent: {
|
|
|
|
|
type: 'ConditionalExpression',
|
|
|
|
|
test: { type: 'Identifier', name: 'foo2' },
|
|
|
|
|
alternate: {
|
|
|
|
|
type: 'ConditionalExpression',
|
|
|
|
|
test: { type: 'Identifier', name: 'bar0' },
|
|
|
|
|
alternate: { type: 'Identifier', name: 'bar1' },
|
|
|
|
|
consequent: { type: 'Identifier', name: 'bar2' }
|
|
|
|
|
},
|
|
|
|
|
consequent: {
|
|
|
|
|
type: 'ConditionalExpression',
|
|
|
|
|
test: { type: 'Identifier', name: 'man0' },
|
|
|
|
|
alternate: { type: 'Identifier', name: 'man1' },
|
|
|
|
|
consequent: { type: 'Identifier', name: 'man2' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand assignment operator', function() {
|
|
|
|
|
// Currently, only `=` is supported
|
|
|
|
|
expect(createAst('foo=bar')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
operator: '='
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should associate assignments right-to-left', function() {
|
|
|
|
|
// Currently, only `=` is supported
|
|
|
|
|
expect(createAst('foo=bar=man')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: {
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
right: { type: 'Identifier', name: 'man' },
|
|
|
|
|
operator: '='
|
|
|
|
|
},
|
|
|
|
|
operator: '='
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should give higher precedence to equality than to the logical `and` operator', function() {
|
|
|
|
|
forEach(['==','!=','===','!=='], function(operator) {
|
|
|
|
|
expect(createAst('foo' + operator + 'bar && man' + operator + 'shell')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'LogicalExpression',
|
|
|
|
|
operator: '&&',
|
|
|
|
|
left: {
|
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
|
operator: operator,
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
},
|
|
|
|
|
right: {
|
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
|
operator: operator,
|
|
|
|
|
left: { type: 'Identifier', name: 'man' },
|
|
|
|
|
right: { type: 'Identifier', name: 'shell' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should give higher precedence to logical `and` than to logical `or`', function() {
|
|
|
|
|
expect(createAst('foo&&bar||man&&shell')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'LogicalExpression',
|
|
|
|
|
operator: '||',
|
|
|
|
|
left: {
|
|
|
|
|
type: 'LogicalExpression',
|
|
|
|
|
operator: '&&',
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
},
|
|
|
|
|
right: {
|
|
|
|
|
type: 'LogicalExpression',
|
|
|
|
|
operator: '&&',
|
|
|
|
|
left: { type: 'Identifier', name: 'man' },
|
|
|
|
|
right: { type: 'Identifier', name: 'shell' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should give higher precedence to the logical `or` than to the conditional operator', function() {
|
|
|
|
|
expect(createAst('foo||bar?man:shell')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ConditionalExpression',
|
|
|
|
|
test: {
|
|
|
|
|
type: 'LogicalExpression',
|
|
|
|
|
operator: '||',
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
},
|
|
|
|
|
alternate: { type: 'Identifier', name: 'man' },
|
|
|
|
|
consequent: { type: 'Identifier', name: 'shell' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should give higher precedence to the conditional operator than to assignment operators', function() {
|
|
|
|
|
expect(createAst('foo=bar?man:shell')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: {
|
|
|
|
|
type: 'ConditionalExpression',
|
|
|
|
|
test: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
alternate: { type: 'Identifier', name: 'man' },
|
|
|
|
|
consequent: { type: 'Identifier', name: 'shell' }
|
|
|
|
|
},
|
|
|
|
|
operator: '='
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand array literals', function() {
|
|
|
|
|
expect(createAst('[]')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ArrayExpression',
|
|
|
|
|
elements: []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('[foo]')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ArrayExpression',
|
|
|
|
|
elements: [
|
|
|
|
|
{ type: 'Identifier', name: 'foo' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('[foo,]')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ArrayExpression',
|
|
|
|
|
elements: [
|
|
|
|
|
{ type: 'Identifier', name: 'foo' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('[foo,bar,man,shell]')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ArrayExpression',
|
|
|
|
|
elements: [
|
|
|
|
|
{ type: 'Identifier', name: 'foo' },
|
|
|
|
|
{ type: 'Identifier', name: 'bar' },
|
|
|
|
|
{ type: 'Identifier', name: 'man' },
|
|
|
|
|
{ type: 'Identifier', name: 'shell' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('[foo,bar,man,shell,]')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ArrayExpression',
|
|
|
|
|
elements: [
|
|
|
|
|
{ type: 'Identifier', name: 'foo' },
|
|
|
|
|
{ type: 'Identifier', name: 'bar' },
|
|
|
|
|
{ type: 'Identifier', name: 'man' },
|
|
|
|
|
{ type: 'Identifier', name: 'shell' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand objects', function() {
|
|
|
|
|
expect(createAst('{}')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ObjectExpression',
|
|
|
|
|
properties: []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('{foo: bar}')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ObjectExpression',
|
|
|
|
|
properties: [
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Identifier', name: 'foo' },
|
2016-04-10 12:19:56 +02:00
|
|
|
computed: false,
|
2014-11-23 23:08:33 +01:00
|
|
|
value: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('{foo: bar,}')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ObjectExpression',
|
|
|
|
|
properties: [
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Identifier', name: 'foo' },
|
2016-04-10 12:19:56 +02:00
|
|
|
computed: false,
|
2014-11-23 23:08:33 +01:00
|
|
|
value: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('{foo: bar, "man": "shell", 42: 23}')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ObjectExpression',
|
|
|
|
|
properties: [
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Identifier', name: 'foo' },
|
2016-04-10 12:19:56 +02:00
|
|
|
computed: false,
|
2014-11-23 23:08:33 +01:00
|
|
|
value: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Literal', value: 'man' },
|
2016-04-10 12:19:56 +02:00
|
|
|
computed: false,
|
2014-11-23 23:08:33 +01:00
|
|
|
value: { type: 'Literal', value: 'shell' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Literal', value: 42 },
|
2016-04-10 12:19:56 +02:00
|
|
|
computed: false,
|
2014-11-23 23:08:33 +01:00
|
|
|
value: { type: 'Literal', value: 23 }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('{foo: bar, "man": "shell", 42: 23,}')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ObjectExpression',
|
|
|
|
|
properties: [
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Identifier', name: 'foo' },
|
2016-04-10 12:19:56 +02:00
|
|
|
computed: false,
|
2014-11-23 23:08:33 +01:00
|
|
|
value: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Literal', value: 'man' },
|
2016-04-10 12:19:56 +02:00
|
|
|
computed: false,
|
2014-11-23 23:08:33 +01:00
|
|
|
value: { type: 'Literal', value: 'shell' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Literal', value: 42 },
|
2016-04-10 12:19:56 +02:00
|
|
|
computed: false,
|
2014-11-23 23:08:33 +01:00
|
|
|
value: { type: 'Literal', value: 23 }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
|
2016-04-10 12:19:56 +02:00
|
|
|
it('should understand ES6 object initializer', function() {
|
|
|
|
|
// Shorthand properties definitions.
|
|
|
|
|
expect(createAst('{x, y, z}')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ObjectExpression',
|
|
|
|
|
properties: [
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Identifier', name: 'x' },
|
|
|
|
|
computed: false,
|
|
|
|
|
value: { type: 'Identifier', name: 'x' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Identifier', name: 'y' },
|
|
|
|
|
computed: false,
|
|
|
|
|
value: { type: 'Identifier', name: 'y' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Identifier', name: 'z' },
|
|
|
|
|
computed: false,
|
|
|
|
|
value: { type: 'Identifier', name: 'z' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(function() { createAst('{"foo"}'); }).toThrow();
|
|
|
|
|
|
|
|
|
|
// Computed properties
|
|
|
|
|
expect(createAst('{[x]: x}')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ObjectExpression',
|
|
|
|
|
properties: [
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Identifier', name: 'x' },
|
|
|
|
|
computed: true,
|
|
|
|
|
value: { type: 'Identifier', name: 'x' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('{[x + 1]: x}')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ObjectExpression',
|
|
|
|
|
properties: [
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: {
|
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
|
operator: '+',
|
|
|
|
|
left: { type: 'Identifier', name: 'x' },
|
|
|
|
|
right: { type: 'Literal', value: 1 }
|
|
|
|
|
},
|
|
|
|
|
computed: true,
|
|
|
|
|
value: { type: 'Identifier', name: 'x' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
2014-11-23 23:08:33 +01:00
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should understand multiple expressions', function() {
|
|
|
|
|
expect(createAst('foo = bar; man = shell')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
operator: '='
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'man' },
|
|
|
|
|
right: { type: 'Identifier', name: 'shell' },
|
|
|
|
|
operator: '='
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This is non-standard syntax
|
|
|
|
|
it('should understand filters', function() {
|
|
|
|
|
expect(createAst('foo | bar')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'bar'},
|
|
|
|
|
arguments: [
|
|
|
|
|
{ type: 'Identifier', name: 'foo' }
|
|
|
|
|
],
|
|
|
|
|
filter: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should understand filters with extra parameters', function() {
|
|
|
|
|
expect(createAst('foo | bar:baz')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'bar'},
|
|
|
|
|
arguments: [
|
|
|
|
|
{ type: 'Identifier', name: 'foo' },
|
|
|
|
|
{ type: 'Identifier', name: 'baz' }
|
|
|
|
|
],
|
|
|
|
|
filter: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should associate filters right-to-left', function() {
|
|
|
|
|
expect(createAst('foo | bar:man | shell')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'shell' },
|
|
|
|
|
arguments: [
|
|
|
|
|
{
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
arguments: [
|
|
|
|
|
{ type: 'Identifier', name: 'foo' },
|
|
|
|
|
{ type: 'Identifier', name: 'man' }
|
|
|
|
|
],
|
|
|
|
|
filter: true
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
filter: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should give higher precedence to assignments over filters', function() {
|
|
|
|
|
expect(createAst('foo=bar | man')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'man' },
|
|
|
|
|
arguments: [
|
|
|
|
|
{
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
operator: '='
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
filter: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should accept expression as filters parameters', function() {
|
|
|
|
|
expect(createAst('foo | bar:baz=man')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
arguments: [
|
|
|
|
|
{ type: 'Identifier', name: 'foo' },
|
|
|
|
|
{
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'baz' },
|
|
|
|
|
right: { type: 'Identifier', name: 'man' },
|
|
|
|
|
operator: '='
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
filter: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should accept expression as computer members', function() {
|
|
|
|
|
expect(createAst('foo[a = 1]')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
property: {
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'a' },
|
|
|
|
|
right: { type: 'Literal', value: 1 },
|
|
|
|
|
operator: '='
|
|
|
|
|
},
|
|
|
|
|
computed: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should accept expression in function arguments', function() {
|
|
|
|
|
expect(createAst('foo(a = 1)')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'CallExpression',
|
|
|
|
|
callee: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
arguments: [
|
|
|
|
|
{
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'a' },
|
|
|
|
|
right: { type: 'Literal', value: 1 },
|
|
|
|
|
operator: '='
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should accept expression as part of ternary operators', function() {
|
|
|
|
|
expect(createAst('foo || bar ? man = 1 : shell = 1')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ConditionalExpression',
|
|
|
|
|
test: {
|
|
|
|
|
type: 'LogicalExpression',
|
|
|
|
|
operator: '||',
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
},
|
|
|
|
|
alternate: {
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'man' },
|
|
|
|
|
right: { type: 'Literal', value: 1 },
|
|
|
|
|
operator: '='
|
|
|
|
|
},
|
|
|
|
|
consequent: {
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'shell' },
|
|
|
|
|
right: { type: 'Literal', value: 1 },
|
|
|
|
|
operator: '='
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should accept expression as part of array literals', function() {
|
|
|
|
|
expect(createAst('[foo = 1]')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ArrayExpression',
|
|
|
|
|
elements: [
|
|
|
|
|
{
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Literal', value: 1 },
|
|
|
|
|
operator: '='
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should accept expression as part of object literals', function() {
|
|
|
|
|
expect(createAst('{foo: bar = 1}')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'ObjectExpression',
|
|
|
|
|
properties: [
|
|
|
|
|
{
|
|
|
|
|
type: 'Property',
|
|
|
|
|
kind: 'init',
|
|
|
|
|
key: { type: 'Identifier', name: 'foo' },
|
2016-04-10 12:19:56 +02:00
|
|
|
computed: false,
|
2014-11-23 23:08:33 +01:00
|
|
|
value: {
|
|
|
|
|
type: 'AssignmentExpression',
|
|
|
|
|
left: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
right: { type: 'Literal', value: 1 },
|
|
|
|
|
operator: '='
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should be possible to use parenthesis to indicate precedence', function() {
|
|
|
|
|
expect(createAst('(foo + bar).man')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: {
|
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
|
operator: '+',
|
|
|
|
|
left: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
right: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
},
|
|
|
|
|
property: { type: 'Identifier', name: 'man' },
|
|
|
|
|
computed: false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should skip empty expressions', function() {
|
|
|
|
|
expect(createAst('foo;;;;bar')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: { type: 'Identifier', name: 'foo' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: { type: 'Identifier', name: 'bar' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst(';foo')).toEqual(
|
|
|
|
|
{
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: { type: 'Identifier', name: 'foo' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
expect(createAst('foo;')).toEqual({
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: { type: 'Identifier', name: 'foo' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
expect(createAst(';;;;')).toEqual({type: 'Program', body: []});
|
|
|
|
|
expect(createAst('')).toEqual({type: 'Program', body: []});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2012-04-27 15:20:54 -07:00
|
|
|
var $filterProvider, scope;
|
|
|
|
|
|
2014-10-22 17:31:27 -04:00
|
|
|
beforeEach(module(['$filterProvider', function(filterProvider) {
|
2012-01-12 11:06:10 -08:00
|
|
|
$filterProvider = filterProvider;
|
|
|
|
|
}]));
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2012-04-27 15:20:54 -07:00
|
|
|
forEach([true, false], function(cspEnabled) {
|
2016-07-21 23:28:35 +03:00
|
|
|
beforeEach(module(function($parseProvider) {
|
|
|
|
|
$parseProvider.addLiteral('Infinity', Infinity);
|
|
|
|
|
csp().noUnsafeEval = cspEnabled;
|
|
|
|
|
}));
|
2016-03-07 23:14:32 +01:00
|
|
|
|
|
|
|
|
it('should allow extending literals with csp ' + cspEnabled, inject(function($rootScope) {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect($rootScope.$eval('Infinity')).toEqual(Infinity);
|
|
|
|
|
expect($rootScope.$eval('-Infinity')).toEqual(-Infinity);
|
|
|
|
|
expect(function() {$rootScope.$eval('Infinity = 1');}).toThrow();
|
|
|
|
|
expect($rootScope.$eval('Infinity')).toEqual(Infinity);
|
2016-03-07 23:14:32 +01:00
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
forEach([true, false], function(cspEnabled) {
|
2014-05-22 08:43:24 -07:00
|
|
|
describe('csp: ' + cspEnabled, function() {
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2015-09-13 14:10:49 +02:00
|
|
|
beforeEach(module(function() {
|
|
|
|
|
expect(csp().noUnsafeEval === true ||
|
|
|
|
|
csp().noUnsafeEval === false).toEqual(true);
|
|
|
|
|
csp().noUnsafeEval = cspEnabled;
|
2014-09-12 08:25:41 -04:00
|
|
|
}, provideLog));
|
2014-06-04 13:49:23 -07:00
|
|
|
|
2014-10-22 17:31:27 -04:00
|
|
|
beforeEach(inject(function($rootScope) {
|
2014-05-22 08:43:24 -07:00
|
|
|
scope = $rootScope;
|
|
|
|
|
}));
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should parse expressions', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('-1')).toEqual(-1);
|
|
|
|
|
expect(scope.$eval('1 + 2.5')).toEqual(3.5);
|
|
|
|
|
expect(scope.$eval('1 + -2.5')).toEqual(-1.5);
|
|
|
|
|
expect(scope.$eval('1+2*3/4')).toEqual(1 + 2 * 3 / 4);
|
|
|
|
|
expect(scope.$eval('0--1+1.5')).toEqual(0 - -1 + 1.5);
|
|
|
|
|
expect(scope.$eval('-0--1++2*-3/-4')).toEqual(-0 - -1 + +2 * -3 / -4);
|
|
|
|
|
expect(scope.$eval('1/2*3')).toEqual(1 / 2 * 3);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2012-03-07 17:07:25 -08:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should parse unary', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('+1')).toEqual(+1);
|
|
|
|
|
expect(scope.$eval('-1')).toEqual(-1);
|
|
|
|
|
expect(scope.$eval('+\'1\'')).toEqual(+'1');
|
|
|
|
|
expect(scope.$eval('-\'1\'')).toEqual(-'1');
|
|
|
|
|
expect(scope.$eval('+undefined')).toEqual(0);
|
2017-03-22 12:08:05 +01:00
|
|
|
|
|
|
|
|
// Note: don't change toEqual to toBe as toBe collapses 0 & -0.
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('-undefined')).toEqual(-0);
|
|
|
|
|
expect(scope.$eval('+null')).toEqual(+null);
|
|
|
|
|
expect(scope.$eval('-null')).toEqual(-null);
|
|
|
|
|
expect(scope.$eval('+false')).toEqual(+false);
|
|
|
|
|
expect(scope.$eval('-false')).toEqual(-false);
|
|
|
|
|
expect(scope.$eval('+true')).toEqual(+true);
|
|
|
|
|
expect(scope.$eval('-true')).toEqual(-true);
|
2014-11-23 23:08:33 +01:00
|
|
|
});
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should parse comparison', function() {
|
2016-07-20 15:45:04 +02:00
|
|
|
/* eslint-disable eqeqeq, no-self-compare */
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('false')).toBeFalsy();
|
|
|
|
|
expect(scope.$eval('!true')).toBeFalsy();
|
|
|
|
|
expect(scope.$eval('1==1')).toBeTruthy();
|
|
|
|
|
expect(scope.$eval('1==true')).toBeTruthy();
|
2016-03-21 18:25:32 +01:00
|
|
|
expect(scope.$eval('1!=true')).toBeFalsy();
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('1===1')).toBeTruthy();
|
|
|
|
|
expect(scope.$eval('1===\'1\'')).toBeFalsy();
|
|
|
|
|
expect(scope.$eval('1===true')).toBeFalsy();
|
|
|
|
|
expect(scope.$eval('\'true\'===true')).toBeFalsy();
|
|
|
|
|
expect(scope.$eval('1!==2')).toBeTruthy();
|
|
|
|
|
expect(scope.$eval('1!==\'1\'')).toBeTruthy();
|
|
|
|
|
expect(scope.$eval('1!=2')).toBeTruthy();
|
|
|
|
|
expect(scope.$eval('1<2')).toBeTruthy();
|
|
|
|
|
expect(scope.$eval('1<=1')).toBeTruthy();
|
|
|
|
|
expect(scope.$eval('1>2')).toEqual(1 > 2);
|
|
|
|
|
expect(scope.$eval('2>=1')).toEqual(2 >= 1);
|
|
|
|
|
expect(scope.$eval('true==2<3')).toEqual(true == 2 < 3);
|
|
|
|
|
expect(scope.$eval('true===2<3')).toEqual(true === 2 < 3);
|
|
|
|
|
|
|
|
|
|
expect(scope.$eval('true===3===3')).toEqual(true === 3 === 3);
|
|
|
|
|
expect(scope.$eval('3===3===true')).toEqual(3 === 3 === true);
|
|
|
|
|
expect(scope.$eval('3 >= 3 > 2')).toEqual(3 >= 3 > 2);
|
2016-07-20 15:45:04 +02:00
|
|
|
/* eslint-enable */
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2013-06-24 14:14:54 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should parse logical', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('0&&2')).toEqual(0 && 2);
|
|
|
|
|
expect(scope.$eval('0||2')).toEqual(0 || 2);
|
|
|
|
|
expect(scope.$eval('0||1&&2')).toEqual(0 || 1 && 2);
|
|
|
|
|
expect(scope.$eval('true&&a')).toEqual(true && undefined);
|
|
|
|
|
expect(scope.$eval('true&&a()')).toEqual(true && undefined);
|
|
|
|
|
expect(scope.$eval('true&&a()()')).toEqual(true && undefined);
|
|
|
|
|
expect(scope.$eval('true&&a.b')).toEqual(true && undefined);
|
|
|
|
|
expect(scope.$eval('true&&a.b.c')).toEqual(true && undefined);
|
|
|
|
|
expect(scope.$eval('false||a')).toEqual(false || undefined);
|
|
|
|
|
expect(scope.$eval('false||a()')).toEqual(false || undefined);
|
|
|
|
|
expect(scope.$eval('false||a()()')).toEqual(false || undefined);
|
|
|
|
|
expect(scope.$eval('false||a.b')).toEqual(false || undefined);
|
|
|
|
|
expect(scope.$eval('false||a.b.c')).toEqual(false || undefined);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2012-03-07 17:07:25 -08:00
|
|
|
|
2014-10-20 21:59:40 -04:00
|
|
|
it('should parse ternary', function() {
|
|
|
|
|
var returnTrue = scope.returnTrue = function() { return true; };
|
|
|
|
|
var returnFalse = scope.returnFalse = function() { return false; };
|
|
|
|
|
var returnString = scope.returnString = function() { return 'asd'; };
|
|
|
|
|
var returnInt = scope.returnInt = function() { return 123; };
|
|
|
|
|
var identity = scope.identity = function(x) { return x; };
|
2014-05-22 08:43:24 -07:00
|
|
|
|
|
|
|
|
// Simple.
|
2014-11-08 11:33:04 -05:00
|
|
|
expect(scope.$eval('0?0:2')).toEqual(0 ? 0 : 2);
|
|
|
|
|
expect(scope.$eval('1?0:2')).toEqual(1 ? 0 : 2);
|
2014-05-22 08:43:24 -07:00
|
|
|
|
|
|
|
|
// Nested on the left.
|
2014-11-08 11:33:04 -05:00
|
|
|
expect(scope.$eval('0?0?0:0:2')).toEqual(0 ? 0 ? 0 : 0 : 2);
|
|
|
|
|
expect(scope.$eval('1?0?0:0:2')).toEqual(1 ? 0 ? 0 : 0 : 2);
|
|
|
|
|
expect(scope.$eval('0?1?0:0:2')).toEqual(0 ? 1 ? 0 : 0 : 2);
|
|
|
|
|
expect(scope.$eval('0?0?1:0:2')).toEqual(0 ? 0 ? 1 : 0 : 2);
|
|
|
|
|
expect(scope.$eval('0?0?0:2:3')).toEqual(0 ? 0 ? 0 : 2 : 3);
|
|
|
|
|
expect(scope.$eval('1?1?0:0:2')).toEqual(1 ? 1 ? 0 : 0 : 2);
|
|
|
|
|
expect(scope.$eval('1?1?1:0:2')).toEqual(1 ? 1 ? 1 : 0 : 2);
|
|
|
|
|
expect(scope.$eval('1?1?1:2:3')).toEqual(1 ? 1 ? 1 : 2 : 3);
|
|
|
|
|
expect(scope.$eval('1?1?1:2:3')).toEqual(1 ? 1 ? 1 : 2 : 3);
|
2014-05-22 08:43:24 -07:00
|
|
|
|
|
|
|
|
// Nested on the right.
|
2014-11-08 11:33:04 -05:00
|
|
|
expect(scope.$eval('0?0:0?0:2')).toEqual(0 ? 0 : 0 ? 0 : 2);
|
|
|
|
|
expect(scope.$eval('1?0:0?0:2')).toEqual(1 ? 0 : 0 ? 0 : 2);
|
|
|
|
|
expect(scope.$eval('0?1:0?0:2')).toEqual(0 ? 1 : 0 ? 0 : 2);
|
|
|
|
|
expect(scope.$eval('0?0:1?0:2')).toEqual(0 ? 0 : 1 ? 0 : 2);
|
|
|
|
|
expect(scope.$eval('0?0:0?2:3')).toEqual(0 ? 0 : 0 ? 2 : 3);
|
|
|
|
|
expect(scope.$eval('1?1:0?0:2')).toEqual(1 ? 1 : 0 ? 0 : 2);
|
|
|
|
|
expect(scope.$eval('1?1:1?0:2')).toEqual(1 ? 1 : 1 ? 0 : 2);
|
|
|
|
|
expect(scope.$eval('1?1:1?2:3')).toEqual(1 ? 1 : 1 ? 2 : 3);
|
|
|
|
|
expect(scope.$eval('1?1:1?2:3')).toEqual(1 ? 1 : 1 ? 2 : 3);
|
2014-05-22 08:43:24 -07:00
|
|
|
|
|
|
|
|
// Precedence with respect to logical operators.
|
2014-11-08 11:33:04 -05:00
|
|
|
expect(scope.$eval('0&&1?0:1')).toEqual(0 && 1 ? 0 : 1);
|
|
|
|
|
expect(scope.$eval('1||0?0:0')).toEqual(1 || 0 ? 0 : 0);
|
|
|
|
|
|
|
|
|
|
expect(scope.$eval('0?0&&1:2')).toEqual(0 ? 0 && 1 : 2);
|
|
|
|
|
expect(scope.$eval('0?1&&1:2')).toEqual(0 ? 1 && 1 : 2);
|
|
|
|
|
expect(scope.$eval('0?0||0:1')).toEqual(0 ? 0 || 0 : 1);
|
|
|
|
|
expect(scope.$eval('0?0||1:2')).toEqual(0 ? 0 || 1 : 2);
|
|
|
|
|
|
|
|
|
|
expect(scope.$eval('1?0&&1:2')).toEqual(1 ? 0 && 1 : 2);
|
|
|
|
|
expect(scope.$eval('1?1&&1:2')).toEqual(1 ? 1 && 1 : 2);
|
|
|
|
|
expect(scope.$eval('1?0||0:1')).toEqual(1 ? 0 || 0 : 1);
|
|
|
|
|
expect(scope.$eval('1?0||1:2')).toEqual(1 ? 0 || 1 : 2);
|
|
|
|
|
|
|
|
|
|
expect(scope.$eval('0?1:0&&1')).toEqual(0 ? 1 : 0 && 1);
|
|
|
|
|
expect(scope.$eval('0?2:1&&1')).toEqual(0 ? 2 : 1 && 1);
|
|
|
|
|
expect(scope.$eval('0?1:0||0')).toEqual(0 ? 1 : 0 || 0);
|
|
|
|
|
expect(scope.$eval('0?2:0||1')).toEqual(0 ? 2 : 0 || 1);
|
|
|
|
|
|
|
|
|
|
expect(scope.$eval('1?1:0&&1')).toEqual(1 ? 1 : 0 && 1);
|
|
|
|
|
expect(scope.$eval('1?2:1&&1')).toEqual(1 ? 2 : 1 && 1);
|
|
|
|
|
expect(scope.$eval('1?1:0||0')).toEqual(1 ? 1 : 0 || 0);
|
|
|
|
|
expect(scope.$eval('1?2:0||1')).toEqual(1 ? 2 : 0 || 1);
|
2014-05-22 08:43:24 -07:00
|
|
|
|
|
|
|
|
// Function calls.
|
|
|
|
|
expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt());
|
|
|
|
|
expect(scope.$eval('returnFalse() ? returnString() : returnInt()')).toEqual(returnFalse() ? returnString() : returnInt());
|
|
|
|
|
expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt());
|
|
|
|
|
expect(scope.$eval('identity(returnFalse() ? returnString() : returnInt())')).toEqual(identity(returnFalse() ? returnString() : returnInt()));
|
|
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should parse string', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('\'a\' + \'b c\'')).toEqual('ab c');
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should parse filters', function() {
|
|
|
|
|
$filterProvider.register('substring', valueFn(function(input, start, end) {
|
|
|
|
|
return input.substring(start, end);
|
|
|
|
|
}));
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
expect(function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.$eval('1|nonexistent');
|
2014-05-22 08:43:24 -07:00
|
|
|
}).toThrowMinErr('$injector', 'unpr', 'Unknown provider: nonexistentFilterProvider <- nonexistentFilter');
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
scope.offset = 3;
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('\'abcd\'|substring:1:offset')).toEqual('bc');
|
|
|
|
|
expect(scope.$eval('\'abcd\'|substring:1:3|uppercase')).toEqual('BC');
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should access scope', function() {
|
|
|
|
|
scope.a = 123;
|
|
|
|
|
scope.b = {c: 456};
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('a', scope)).toEqual(123);
|
|
|
|
|
expect(scope.$eval('b.c', scope)).toEqual(456);
|
|
|
|
|
expect(scope.$eval('x.y.z', scope)).not.toBeDefined();
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2012-01-24 02:33:35 -08:00
|
|
|
|
2014-10-22 17:31:27 -04:00
|
|
|
it('should handle white-spaces around dots in paths', function() {
|
2014-08-09 22:55:21 +02:00
|
|
|
scope.a = {b: 4};
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('a . b', scope)).toEqual(4);
|
|
|
|
|
expect(scope.$eval('a. b', scope)).toEqual(4);
|
|
|
|
|
expect(scope.$eval('a .b', scope)).toEqual(4);
|
|
|
|
|
expect(scope.$eval('a . \nb', scope)).toEqual(4);
|
2014-08-09 22:55:21 +02:00
|
|
|
});
|
|
|
|
|
|
2014-10-04 17:09:22 -07:00
|
|
|
it('should handle white-spaces around dots in method invocations', function() {
|
|
|
|
|
scope.a = {b: function() { return this.c; }, c: 4};
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('a . b ()', scope)).toEqual(4);
|
|
|
|
|
expect(scope.$eval('a. b ()', scope)).toEqual(4);
|
|
|
|
|
expect(scope.$eval('a .b ()', scope)).toEqual(4);
|
|
|
|
|
expect(scope.$eval('a \n . \nb \n ()', scope)).toEqual(4);
|
2014-10-04 17:09:22 -07:00
|
|
|
});
|
|
|
|
|
|
2014-10-22 17:31:27 -04:00
|
|
|
it('should throw syntax error exception for identifiers ending with a dot', function() {
|
2014-08-09 22:55:21 +02:00
|
|
|
scope.a = {b: 4};
|
|
|
|
|
|
|
|
|
|
expect(function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.$eval('a.', scope);
|
2014-10-04 17:09:22 -07:00
|
|
|
}).toThrowMinErr('$parse', 'ueoe',
|
2016-08-10 12:13:14 +02:00
|
|
|
'Unexpected end of expression: a.');
|
2014-08-09 22:55:21 +02:00
|
|
|
|
|
|
|
|
expect(function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.$eval('a .', scope);
|
2014-10-04 17:09:22 -07:00
|
|
|
}).toThrowMinErr('$parse', 'ueoe',
|
2016-08-10 12:13:14 +02:00
|
|
|
'Unexpected end of expression: a .');
|
2014-08-09 22:55:21 +02:00
|
|
|
});
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should resolve deeply nested paths (important for CSP mode)', function() {
|
|
|
|
|
scope.a = {b: {c: {d: {e: {f: {g: {h: {i: {j: {k: {l: {m: {n: 'nooo!'}}}}}}}}}}}}};
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('a.b.c.d.e.f.g.h.i.j.k.l.m.n', scope)).toBe('nooo!');
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2014-01-02 00:42:59 -05:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
forEach([2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 42, 99], function(pathLength) {
|
|
|
|
|
it('should resolve nested paths of length ' + pathLength, function() {
|
|
|
|
|
// Create a nested object {x2: {x3: {x4: ... {x[n]: 42} ... }}}.
|
|
|
|
|
var obj = 42, locals = {};
|
|
|
|
|
for (var i = pathLength; i >= 2; i--) {
|
|
|
|
|
var newObj = {};
|
|
|
|
|
newObj['x' + i] = obj;
|
|
|
|
|
obj = newObj;
|
|
|
|
|
}
|
|
|
|
|
// Assign to x1 and build path 'x1.x2.x3. ... .x[n]' to access the final value.
|
|
|
|
|
scope.x1 = obj;
|
|
|
|
|
var path = 'x1';
|
|
|
|
|
for (i = 2; i <= pathLength; i++) {
|
|
|
|
|
path += '.x' + i;
|
|
|
|
|
}
|
|
|
|
|
expect(scope.$eval(path)).toBe(42);
|
|
|
|
|
locals['x' + pathLength] = 'not 42';
|
|
|
|
|
expect(scope.$eval(path, locals)).toBe(42);
|
2013-10-30 15:14:00 -04:00
|
|
|
});
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should be forgiving', function() {
|
|
|
|
|
scope.a = {b: 23};
|
|
|
|
|
expect(scope.$eval('b')).toBeUndefined();
|
|
|
|
|
expect(scope.$eval('a.x')).toBeUndefined();
|
|
|
|
|
expect(scope.$eval('a.b.c.d')).toBeUndefined();
|
2014-11-23 23:08:33 +01:00
|
|
|
scope.a = undefined;
|
|
|
|
|
expect(scope.$eval('a - b')).toBe(0);
|
2016-03-07 01:39:56 +02:00
|
|
|
expect(scope.$eval('a + b')).toBeUndefined();
|
2014-11-23 23:08:33 +01:00
|
|
|
scope.a = 0;
|
|
|
|
|
expect(scope.$eval('a - b')).toBe(0);
|
|
|
|
|
expect(scope.$eval('a + b')).toBe(0);
|
|
|
|
|
scope.a = undefined;
|
|
|
|
|
scope.b = 0;
|
|
|
|
|
expect(scope.$eval('a - b')).toBe(0);
|
|
|
|
|
expect(scope.$eval('a + b')).toBe(0);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2013-10-05 10:49:09 +01:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should support property names that collide with native object properties', function() {
|
|
|
|
|
// regression
|
|
|
|
|
scope.watch = 1;
|
|
|
|
|
scope.toString = function toString() {
|
2016-08-10 12:13:14 +02:00
|
|
|
return 'custom toString';
|
2014-05-22 08:43:24 -07:00
|
|
|
};
|
2013-10-05 10:49:09 +01:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
expect(scope.$eval('watch', scope)).toBe(1);
|
|
|
|
|
expect(scope.$eval('toString()', scope)).toBe('custom toString');
|
|
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should not break if hasOwnProperty is referenced in an expression', function() {
|
|
|
|
|
scope.obj = { value: 1};
|
|
|
|
|
// By evaluating an expression that calls hasOwnProperty, the getterFnCache
|
|
|
|
|
// will store a property called hasOwnProperty. This is effectively:
|
|
|
|
|
// getterFnCache['hasOwnProperty'] = null
|
|
|
|
|
scope.$eval('obj.hasOwnProperty("value")');
|
|
|
|
|
// If we rely on this property then evaluating any expression will fail
|
|
|
|
|
// because it is not able to find out if obj.value is there in the cache
|
|
|
|
|
expect(scope.$eval('obj.value')).toBe(1);
|
|
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should not break if the expression is "hasOwnProperty"', function() {
|
|
|
|
|
scope.fooExp = 'barVal';
|
|
|
|
|
// By evaluating hasOwnProperty, the $parse cache will store a getter for
|
|
|
|
|
// the scope's own hasOwnProperty function, which will mess up future cache look ups.
|
|
|
|
|
// i.e. cache['hasOwnProperty'] = function(scope) { return scope.hasOwnProperty; }
|
|
|
|
|
scope.$eval('hasOwnProperty');
|
|
|
|
|
expect(scope.$eval('fooExp')).toBe('barVal');
|
|
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate grouped expressions', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('(1+2)*3')).toEqual((1 + 2) * 3);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate assignments', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('a=12')).toEqual(12);
|
2014-05-22 08:43:24 -07:00
|
|
|
expect(scope.a).toEqual(12);
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('x.y.z=123;')).toEqual(123);
|
2014-05-22 08:43:24 -07:00
|
|
|
expect(scope.x.y.z).toEqual(123);
|
2011-01-19 15:42:11 -08:00
|
|
|
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('a=123; b=234')).toEqual(234);
|
2014-05-22 08:43:24 -07:00
|
|
|
expect(scope.a).toEqual(123);
|
|
|
|
|
expect(scope.b).toEqual(234);
|
|
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2016-10-09 13:13:50 -07:00
|
|
|
it('should throw with invalid left-val in assignments', function() {
|
|
|
|
|
expect(function() { scope.$eval('1 = 1'); }).toThrowMinErr('$parse', 'lval');
|
|
|
|
|
expect(function() { scope.$eval('{} = 1'); }).toThrowMinErr('$parse', 'lval');
|
|
|
|
|
expect(function() { scope.$eval('[] = 1'); }).toThrowMinErr('$parse', 'lval');
|
|
|
|
|
expect(function() { scope.$eval('true = 1'); }).toThrowMinErr('$parse', 'lval');
|
|
|
|
|
expect(function() { scope.$eval('(a=b) = 1'); }).toThrowMinErr('$parse', 'lval');
|
|
|
|
|
expect(function() { scope.$eval('(1<2) = 1'); }).toThrowMinErr('$parse', 'lval');
|
|
|
|
|
expect(function() { scope.$eval('(1+2) = 1'); }).toThrowMinErr('$parse', 'lval');
|
|
|
|
|
expect(function() { scope.$eval('!v = 1'); }).toThrowMinErr('$parse', 'lval');
|
|
|
|
|
expect(function() { scope.$eval('this = 1'); }).toThrowMinErr('$parse', 'lval');
|
|
|
|
|
expect(function() { scope.$eval('+v = 1'); }).toThrowMinErr('$parse', 'lval');
|
|
|
|
|
expect(function() { scope.$eval('(1?v1:v2) = 1'); }).toThrowMinErr('$parse', 'lval');
|
|
|
|
|
});
|
|
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
it('should evaluate assignments in ternary operator', function() {
|
|
|
|
|
scope.$eval('a = 1 ? 2 : 3');
|
|
|
|
|
expect(scope.a).toBe(2);
|
2013-12-16 13:02:25 -05:00
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
scope.$eval('0 ? a = 2 : a = 3');
|
|
|
|
|
expect(scope.a).toBe(3);
|
2013-12-16 13:02:25 -05:00
|
|
|
|
2016-07-21 23:28:35 +03:00
|
|
|
scope.$eval('1 ? a = 2 : a = 3');
|
|
|
|
|
expect(scope.a).toBe(2);
|
|
|
|
|
});
|
2013-12-16 13:02:25 -05:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate function call without arguments', function() {
|
2014-10-20 21:59:40 -04:00
|
|
|
scope['const'] = function(a, b) {return 123;};
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('const()')).toEqual(123);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate function call with arguments', function() {
|
2014-10-19 20:24:53 -04:00
|
|
|
scope.add = function(a, b) {
|
2014-11-08 11:26:29 -05:00
|
|
|
return a + b;
|
2014-05-22 08:43:24 -07:00
|
|
|
};
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('add(1,2)')).toEqual(3);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2016-06-06 14:29:34 +01:00
|
|
|
it('should allow filter chains as arguments', function() {
|
|
|
|
|
scope.concat = function(a, b) {
|
|
|
|
|
return a + b;
|
|
|
|
|
};
|
|
|
|
|
scope.begin = 1;
|
|
|
|
|
scope.limit = 2;
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('concat(\'abcd\'|limitTo:limit:begin,\'abcd\'|limitTo:2:1|uppercase)')).toEqual('bcBC');
|
2016-06-06 14:29:34 +01:00
|
|
|
});
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate function call from a return value', function() {
|
2014-12-02 10:46:09 +01:00
|
|
|
scope.getter = function() { return function() { return 33; }; };
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('getter()()')).toBe(33);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2016-11-18 15:00:12 +01:00
|
|
|
// Support: IE 9 only
|
2014-12-02 10:46:09 +01:00
|
|
|
// There is no "strict mode" in IE9
|
2016-11-18 15:00:12 +01:00
|
|
|
if (msie !== 9) {
|
2014-12-02 10:46:09 +01:00
|
|
|
it('should set no context to functions returned by other functions', function() {
|
|
|
|
|
scope.getter = function() { return function() { expect(this).toBeUndefined(); }; };
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.$eval('getter()()');
|
2014-12-02 10:46:09 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate multiplication and division', function() {
|
|
|
|
|
scope.taxRate = 8;
|
|
|
|
|
scope.subTotal = 100;
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('taxRate / 100 * subTotal')).toEqual(8);
|
|
|
|
|
expect(scope.$eval('subTotal * taxRate / 100')).toEqual(8);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate array', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('[]').length).toEqual(0);
|
|
|
|
|
expect(scope.$eval('[1, 2]').length).toEqual(2);
|
|
|
|
|
expect(scope.$eval('[1, 2]')[0]).toEqual(1);
|
|
|
|
|
expect(scope.$eval('[1, 2]')[1]).toEqual(2);
|
|
|
|
|
expect(scope.$eval('[1, 2,]')[1]).toEqual(2);
|
|
|
|
|
expect(scope.$eval('[1, 2,]').length).toEqual(2);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate array access', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('[1][0]')).toEqual(1);
|
|
|
|
|
expect(scope.$eval('[[1]][0][0]')).toEqual(1);
|
|
|
|
|
expect(scope.$eval('[].length')).toEqual(0);
|
|
|
|
|
expect(scope.$eval('[1, 2].length')).toEqual(2);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate object', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('{}')).toEqual({});
|
|
|
|
|
expect(scope.$eval('{a:\'b\'}')).toEqual({a:'b'});
|
|
|
|
|
expect(scope.$eval('{\'a\':\'b\'}')).toEqual({a:'b'});
|
|
|
|
|
expect(scope.$eval('{"a":\'b\'}')).toEqual({a:'b'});
|
|
|
|
|
expect(scope.$eval('{a:\'b\',}')).toEqual({a:'b'});
|
|
|
|
|
expect(scope.$eval('{\'a\':\'b\',}')).toEqual({a:'b'});
|
|
|
|
|
expect(scope.$eval('{"a":\'b\',}')).toEqual({a:'b'});
|
|
|
|
|
expect(scope.$eval('{\'0\':1}')).toEqual({0:1});
|
|
|
|
|
expect(scope.$eval('{0:1}')).toEqual({0:1});
|
|
|
|
|
expect(scope.$eval('{1:1}')).toEqual({1:1});
|
|
|
|
|
expect(scope.$eval('{null:1}')).toEqual({null:1});
|
|
|
|
|
expect(scope.$eval('{\'null\':1}')).toEqual({null:1});
|
|
|
|
|
expect(scope.$eval('{false:1}')).toEqual({false:1});
|
|
|
|
|
expect(scope.$eval('{\'false\':1}')).toEqual({false:1});
|
|
|
|
|
expect(scope.$eval('{\'\':1,}')).toEqual({'':1});
|
2016-04-10 12:19:56 +02:00
|
|
|
|
|
|
|
|
// ES6 object initializers.
|
|
|
|
|
expect(scope.$eval('{x, y}', {x: 'foo', y: 'bar'})).toEqual({x: 'foo', y: 'bar'});
|
|
|
|
|
expect(scope.$eval('{[x]: x}', {x: 'foo'})).toEqual({foo: 'foo'});
|
|
|
|
|
expect(scope.$eval('{[x + "z"]: x}', {x: 'foo'})).toEqual({fooz: 'foo'});
|
|
|
|
|
expect(scope.$eval('{x, 1: x, [x = x + 1]: x, 3: x + 1, [x = x + 2]: x, 5: x + 1}', {x: 1}))
|
|
|
|
|
.toEqual({x: 1, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5});
|
2014-10-04 17:09:22 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw syntax error exception for non constant/identifier JSON keys', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(function() { scope.$eval('{[:0}'); }).toThrowMinErr('$parse', 'syntax',
|
|
|
|
|
'Syntax Error: Token \':\' not a primary expression at column 3 of the expression [{[:0}] starting at [:0}]');
|
|
|
|
|
expect(function() { scope.$eval('{{:0}'); }).toThrowMinErr('$parse', 'syntax',
|
|
|
|
|
'Syntax Error: Token \'{\' invalid key at column 2 of the expression [{{:0}] starting at [{:0}]');
|
|
|
|
|
expect(function() { scope.$eval('{?:0}'); }).toThrowMinErr('$parse', 'syntax',
|
|
|
|
|
'Syntax Error: Token \'?\' invalid key at column 2 of the expression [{?:0}] starting at [?:0}]');
|
|
|
|
|
expect(function() { scope.$eval('{):0}'); }).toThrowMinErr('$parse', 'syntax',
|
|
|
|
|
'Syntax Error: Token \')\' invalid key at column 2 of the expression [{):0}] starting at [):0}]');
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate object access', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('{false:\'WC\', true:\'CC\'}[false]')).toEqual('WC');
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate JSON', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('[{}]')).toEqual([{}]);
|
|
|
|
|
expect(scope.$eval('[{a:[]}, {b:1}]')).toEqual([{a:[]}, {b:1}]);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate multiple statements', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('a=1;b=3;a+b')).toEqual(4);
|
|
|
|
|
expect(scope.$eval(';;1;;')).toEqual(1);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate object methods in correct context (this)', function() {
|
2016-07-20 15:45:04 +02:00
|
|
|
function C() {
|
2014-05-22 08:43:24 -07:00
|
|
|
this.a = 123;
|
2016-07-20 15:45:04 +02:00
|
|
|
}
|
2014-05-22 08:43:24 -07:00
|
|
|
C.prototype.getA = function() {
|
|
|
|
|
return this.a;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
scope.obj = new C();
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('obj.getA()')).toEqual(123);
|
|
|
|
|
expect(scope.$eval('obj[\'getA\']()')).toEqual(123);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate methods in correct context (this) in argument', function() {
|
2016-07-20 15:45:04 +02:00
|
|
|
function C() {
|
2014-05-22 08:43:24 -07:00
|
|
|
this.a = 123;
|
2016-07-20 15:45:04 +02:00
|
|
|
}
|
2014-05-22 08:43:24 -07:00
|
|
|
C.prototype.sum = function(value) {
|
|
|
|
|
return this.a + value;
|
|
|
|
|
};
|
|
|
|
|
C.prototype.getA = function() {
|
|
|
|
|
return this.a;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
scope.obj = new C();
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('obj.sum(obj.getA())')).toEqual(246);
|
|
|
|
|
expect(scope.$eval('obj[\'sum\'](obj.getA())')).toEqual(246);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate objects on scope context', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.a = 'abc';
|
|
|
|
|
expect(scope.$eval('{a:a}').a).toEqual('abc');
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2011-01-19 15:42:11 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate field access on function call result', function() {
|
|
|
|
|
scope.a = function() {
|
|
|
|
|
return {name:'misko'};
|
|
|
|
|
};
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('a().name')).toEqual('misko');
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2011-01-19 15:42:11 -08:00
|
|
|
|
2014-10-22 17:31:27 -04:00
|
|
|
it('should evaluate field access after array access', function() {
|
2014-05-22 08:43:24 -07:00
|
|
|
scope.items = [{}, {name:'misko'}];
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('items[1].name')).toEqual('misko');
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2011-09-30 16:55:01 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate array assignment', function() {
|
|
|
|
|
scope.items = [];
|
2011-09-30 16:55:01 -07:00
|
|
|
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('items[1] = "abc"')).toEqual('abc');
|
|
|
|
|
expect(scope.$eval('items[1]')).toEqual('abc');
|
|
|
|
|
expect(scope.$eval('books[1] = "moby"')).toEqual('moby');
|
|
|
|
|
expect(scope.$eval('books[1]')).toEqual('moby');
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2011-01-19 15:42:11 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate grouped filters', function() {
|
|
|
|
|
scope.name = 'MISKO';
|
|
|
|
|
expect(scope.$eval('n = (name|lowercase)')).toEqual('misko');
|
|
|
|
|
expect(scope.$eval('n')).toEqual('misko');
|
|
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate remainder', function() {
|
|
|
|
|
expect(scope.$eval('1%2')).toEqual(1);
|
|
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate sum with undefined', function() {
|
|
|
|
|
expect(scope.$eval('1+undefined')).toEqual(1);
|
|
|
|
|
expect(scope.$eval('undefined+1')).toEqual(1);
|
|
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should throw exception on non-closed bracket', function() {
|
|
|
|
|
expect(function() {
|
|
|
|
|
scope.$eval('[].count(');
|
|
|
|
|
}).toThrowMinErr('$parse', 'ueoe', 'Unexpected end of expression: [].count(');
|
|
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate double negation', function() {
|
|
|
|
|
expect(scope.$eval('true')).toBeTruthy();
|
|
|
|
|
expect(scope.$eval('!true')).toBeFalsy();
|
|
|
|
|
expect(scope.$eval('!!true')).toBeTruthy();
|
|
|
|
|
expect(scope.$eval('{true:"a", false:"b"}[!!true]')).toEqual('a');
|
|
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate negation', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('!false || true')).toEqual(!false || true);
|
2016-07-20 15:45:04 +02:00
|
|
|
// eslint-disable-next-line eqeqeq
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('!11 == 10')).toEqual(!11 == 10);
|
|
|
|
|
expect(scope.$eval('12/6/2')).toEqual(12 / 6 / 2);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate exclamation mark', function() {
|
|
|
|
|
expect(scope.$eval('suffix = "!"')).toEqual('!');
|
|
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate minus', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('{a:\'-\'}')).toEqual({a: '-'});
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate undefined', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect(scope.$eval('undefined')).not.toBeDefined();
|
|
|
|
|
expect(scope.$eval('a=undefined')).not.toBeDefined();
|
2014-05-22 08:43:24 -07:00
|
|
|
expect(scope.a).not.toBeDefined();
|
|
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should allow assignment after array dereference', function() {
|
|
|
|
|
scope.obj = [{}];
|
|
|
|
|
scope.$eval('obj[0].name=1');
|
|
|
|
|
expect(scope.obj.name).toBeUndefined();
|
|
|
|
|
expect(scope.obj[0].name).toEqual(1);
|
|
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should short-circuit AND operator', function() {
|
|
|
|
|
scope.run = function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
throw new Error('IT SHOULD NOT HAVE RUN');
|
2014-05-22 08:43:24 -07:00
|
|
|
};
|
|
|
|
|
expect(scope.$eval('false && run()')).toBe(false);
|
2014-11-23 14:06:45 +01:00
|
|
|
expect(scope.$eval('false && true && run()')).toBe(false);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should short-circuit OR operator', function() {
|
|
|
|
|
scope.run = function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
throw new Error('IT SHOULD NOT HAVE RUN');
|
2014-05-22 08:43:24 -07:00
|
|
|
};
|
|
|
|
|
expect(scope.$eval('true || run()')).toBe(true);
|
2014-11-23 14:06:45 +01:00
|
|
|
expect(scope.$eval('true || false || run()')).toBe(true);
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2013-08-09 14:47:13 -07:00
|
|
|
|
2015-12-19 01:32:54 +02:00
|
|
|
it('should throw TypeError on using a \'broken\' object as a key to access a property', function() {
|
|
|
|
|
scope.object = {};
|
|
|
|
|
forEach([
|
|
|
|
|
{ toString: 2 },
|
|
|
|
|
{ toString: null },
|
|
|
|
|
{ toString: function() { return {}; } }
|
|
|
|
|
], function(brokenObject) {
|
|
|
|
|
scope.brokenObject = brokenObject;
|
|
|
|
|
expect(function() {
|
|
|
|
|
scope.$eval('object[brokenObject]');
|
|
|
|
|
}).toThrow();
|
|
|
|
|
});
|
|
|
|
|
});
|
2013-08-09 14:47:13 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should support method calls on primitive types', function() {
|
|
|
|
|
scope.empty = '';
|
|
|
|
|
scope.zero = 0;
|
|
|
|
|
scope.bool = false;
|
2013-08-09 14:47:13 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
expect(scope.$eval('empty.substr(0)')).toBe('');
|
|
|
|
|
expect(scope.$eval('zero.toString()')).toBe('0');
|
|
|
|
|
expect(scope.$eval('bool.toString()')).toBe('false');
|
|
|
|
|
});
|
2013-08-09 14:47:13 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should evaluate expressions with line terminators', function() {
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.a = 'a';
|
|
|
|
|
scope.b = {c: 'bc'};
|
|
|
|
|
expect(scope.$eval('a + \n b.c + \r "\td" + \t \r\n\r "\r\n\n"')).toEqual('abc\td\r\n\n');
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2013-08-09 14:47:13 -07:00
|
|
|
|
2015-02-05 00:14:39 +01:00
|
|
|
|
|
|
|
|
// https://github.com/angular/angular.js/issues/10968
|
|
|
|
|
it('should evaluate arrays literals initializers left-to-right', inject(function($parse) {
|
|
|
|
|
var s = {c:function() {return {b: 1}; }};
|
2016-08-10 12:13:14 +02:00
|
|
|
expect($parse('e=1;[a=c(),d=a.b+1]')(s)).toEqual([{b: 1}, 2]);
|
2015-02-05 00:14:39 +01:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should evaluate function arguments left-to-right', inject(function($parse) {
|
|
|
|
|
var s = {c:function() {return {b: 1}; }, i: function(x, y) { return [x, y];}};
|
2016-08-10 12:13:14 +02:00
|
|
|
expect($parse('e=1;i(a=c(),d=a.b+1)')(s)).toEqual([{b: 1}, 2]);
|
2015-02-05 00:14:39 +01:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should evaluate object properties expressions left-to-right', inject(function($parse) {
|
|
|
|
|
var s = {c:function() {return {b: 1}; }};
|
2016-08-10 12:13:14 +02:00
|
|
|
expect($parse('e=1;{x: a=c(), y: d=a.b+1}')(s)).toEqual({x: {b: 1}, y: 2});
|
2015-02-05 00:14:39 +01:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should call the function from the received instance and not from a new one', function() {
|
|
|
|
|
var n = 0;
|
|
|
|
|
scope.fn = function() {
|
|
|
|
|
var c = n++;
|
2016-07-20 15:45:04 +02:00
|
|
|
return { c: c, anotherFn: function() { return this.c === c; } };
|
2014-05-22 08:43:24 -07:00
|
|
|
};
|
|
|
|
|
expect(scope.$eval('fn().anotherFn()')).toBe(true);
|
|
|
|
|
});
|
2013-10-07 09:58:37 -07:00
|
|
|
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should call the function once when it is part of the context', function() {
|
|
|
|
|
var count = 0;
|
|
|
|
|
scope.fn = function() {
|
|
|
|
|
count++;
|
2016-08-10 12:13:14 +02:00
|
|
|
return { anotherFn: function() { return 'lucas'; } };
|
2014-05-22 08:43:24 -07:00
|
|
|
};
|
|
|
|
|
expect(scope.$eval('fn().anotherFn()')).toBe('lucas');
|
|
|
|
|
expect(count).toBe(1);
|
|
|
|
|
});
|
2013-10-07 09:58:37 -07:00
|
|
|
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should call the function once when it is not part of the context', function() {
|
|
|
|
|
var count = 0;
|
|
|
|
|
scope.fn = function() {
|
|
|
|
|
count++;
|
|
|
|
|
return function() { return 'lucas'; };
|
|
|
|
|
};
|
|
|
|
|
expect(scope.$eval('fn()()')).toBe('lucas');
|
|
|
|
|
expect(count).toBe(1);
|
|
|
|
|
});
|
2013-10-07 09:58:37 -07:00
|
|
|
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should call the function once when it is part of the context on assignments', function() {
|
|
|
|
|
var count = 0;
|
|
|
|
|
var element = {};
|
|
|
|
|
scope.fn = function() {
|
|
|
|
|
count++;
|
|
|
|
|
return element;
|
|
|
|
|
};
|
|
|
|
|
expect(scope.$eval('fn().name = "lucas"')).toBe('lucas');
|
|
|
|
|
expect(element.name).toBe('lucas');
|
|
|
|
|
expect(count).toBe(1);
|
|
|
|
|
});
|
2013-10-07 09:58:37 -07:00
|
|
|
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should call the function once when it is part of the context on array lookups', function() {
|
|
|
|
|
var count = 0;
|
|
|
|
|
var element = [];
|
|
|
|
|
scope.fn = function() {
|
|
|
|
|
count++;
|
|
|
|
|
return element;
|
|
|
|
|
};
|
|
|
|
|
expect(scope.$eval('fn()[0] = "lucas"')).toBe('lucas');
|
|
|
|
|
expect(element[0]).toBe('lucas');
|
|
|
|
|
expect(count).toBe(1);
|
|
|
|
|
});
|
2013-10-07 09:58:37 -07:00
|
|
|
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should call the function once when it is part of the context on array lookup function', function() {
|
|
|
|
|
var count = 0;
|
|
|
|
|
var element = [{anotherFn: function() { return 'lucas';} }];
|
|
|
|
|
scope.fn = function() {
|
|
|
|
|
count++;
|
|
|
|
|
return element;
|
|
|
|
|
};
|
|
|
|
|
expect(scope.$eval('fn()[0].anotherFn()')).toBe('lucas');
|
|
|
|
|
expect(count).toBe(1);
|
|
|
|
|
});
|
2013-10-07 09:58:37 -07:00
|
|
|
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should call the function once when it is part of the context on property lookup function', function() {
|
|
|
|
|
var count = 0;
|
|
|
|
|
var element = {name: {anotherFn: function() { return 'lucas';} } };
|
|
|
|
|
scope.fn = function() {
|
|
|
|
|
count++;
|
|
|
|
|
return element;
|
|
|
|
|
};
|
|
|
|
|
expect(scope.$eval('fn().name.anotherFn()')).toBe('lucas');
|
|
|
|
|
expect(count).toBe(1);
|
|
|
|
|
});
|
2014-01-02 00:42:59 -05:00
|
|
|
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should call the function once when it is part of a sub-expression', function() {
|
|
|
|
|
var count = 0;
|
|
|
|
|
scope.element = [{}];
|
|
|
|
|
scope.fn = function() {
|
|
|
|
|
count++;
|
|
|
|
|
return 0;
|
|
|
|
|
};
|
|
|
|
|
expect(scope.$eval('element[fn()].name = "lucas"')).toBe('lucas');
|
|
|
|
|
expect(scope.element[0].name).toBe('lucas');
|
|
|
|
|
expect(count).toBe(1);
|
|
|
|
|
});
|
2014-01-02 00:42:59 -05:00
|
|
|
|
2013-10-07 09:58:37 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
describe('assignable', function() {
|
|
|
|
|
it('should expose assignment function', inject(function($parse) {
|
|
|
|
|
var fn = $parse('a');
|
|
|
|
|
expect(fn.assign).toBeTruthy();
|
|
|
|
|
var scope = {};
|
|
|
|
|
fn.assign(scope, 123);
|
|
|
|
|
expect(scope).toEqual({a:123});
|
2013-10-07 09:58:37 -07:00
|
|
|
}));
|
2014-07-08 14:54:44 -07:00
|
|
|
|
2015-08-30 19:21:15 +02:00
|
|
|
it('should return the assigned value', inject(function($parse) {
|
|
|
|
|
var fn = $parse('a');
|
|
|
|
|
var scope = {};
|
|
|
|
|
expect(fn.assign(scope, 123)).toBe(123);
|
|
|
|
|
var someObject = {};
|
|
|
|
|
expect(fn.assign(scope, someObject)).toBe(someObject);
|
|
|
|
|
}));
|
|
|
|
|
|
2014-07-08 14:54:44 -07:00
|
|
|
it('should expose working assignment function for expressions ending with brackets', inject(function($parse) {
|
|
|
|
|
var fn = $parse('a.b["c"]');
|
|
|
|
|
expect(fn.assign).toBeTruthy();
|
|
|
|
|
var scope = {};
|
|
|
|
|
fn.assign(scope, 123);
|
|
|
|
|
expect(scope.a.b.c).toEqual(123);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should expose working assignment function for expressions with brackets in the middle', inject(function($parse) {
|
|
|
|
|
var fn = $parse('a["b"].c');
|
|
|
|
|
expect(fn.assign).toBeTruthy();
|
|
|
|
|
var scope = {};
|
|
|
|
|
fn.assign(scope, 123);
|
|
|
|
|
expect(scope.a.b.c).toEqual(123);
|
|
|
|
|
}));
|
2015-02-03 18:22:42 +01:00
|
|
|
|
|
|
|
|
it('should create objects when finding a null', inject(function($parse) {
|
|
|
|
|
var fn = $parse('foo.bar');
|
|
|
|
|
var scope = {foo: null};
|
|
|
|
|
fn.assign(scope, 123);
|
|
|
|
|
expect(scope.foo.bar).toEqual(123);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should create objects when finding a null', inject(function($parse) {
|
|
|
|
|
var fn = $parse('foo["bar"]');
|
|
|
|
|
var scope = {foo: null};
|
|
|
|
|
fn.assign(scope, 123);
|
|
|
|
|
expect(scope.foo.bar).toEqual(123);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should create objects when finding a null', inject(function($parse) {
|
|
|
|
|
var fn = $parse('foo.bar.baz');
|
|
|
|
|
var scope = {foo: null};
|
|
|
|
|
fn.assign(scope, 123);
|
|
|
|
|
expect(scope.foo.bar.baz).toEqual(123);
|
|
|
|
|
}));
|
2014-05-22 08:43:24 -07:00
|
|
|
});
|
2013-10-07 09:58:37 -07:00
|
|
|
|
2014-06-04 13:49:23 -07:00
|
|
|
describe('one-time binding', function() {
|
|
|
|
|
it('should always use the cache', inject(function($parse) {
|
|
|
|
|
expect($parse('foo')).toBe($parse('foo'));
|
|
|
|
|
expect($parse('::foo')).toBe($parse('::foo'));
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should not affect calling the parseFn directly', inject(function($parse, $rootScope) {
|
|
|
|
|
var fn = $parse('::foo');
|
|
|
|
|
$rootScope.$watch(fn);
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'bar';
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
expect(fn($rootScope)).toEqual('bar');
|
|
|
|
|
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(0);
|
|
|
|
|
expect(fn($rootScope)).toEqual('bar');
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'man';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(0);
|
|
|
|
|
expect(fn($rootScope)).toEqual('man');
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'shell';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(0);
|
|
|
|
|
expect(fn($rootScope)).toEqual('shell');
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should stay stable once the value defined', inject(function($parse, $rootScope, log) {
|
|
|
|
|
var fn = $parse('::foo');
|
|
|
|
|
$rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });
|
|
|
|
|
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'bar';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(0);
|
|
|
|
|
expect(log).toEqual('bar');
|
|
|
|
|
log.reset();
|
|
|
|
|
|
2016-01-26 15:11:52 +01:00
|
|
|
$rootScope.foo = 'man';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(0);
|
|
|
|
|
expect(log).toEqual('');
|
|
|
|
|
}));
|
|
|
|
|
|
2014-06-04 13:49:23 -07:00
|
|
|
it('should have a stable value if at the end of a $digest it has a defined value', inject(function($parse, $rootScope, log) {
|
|
|
|
|
var fn = $parse('::foo');
|
|
|
|
|
$rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });
|
|
|
|
|
$rootScope.$watch('foo', function() { if ($rootScope.foo === 'bar') {$rootScope.foo = undefined; } });
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'bar';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(2);
|
|
|
|
|
expect(log).toEqual('');
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'man';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
expect(log).toEqual('; man');
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'shell';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
expect(log).toEqual('; man');
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should not throw if the stable value is `null`', inject(function($parse, $rootScope) {
|
|
|
|
|
var fn = $parse('::foo');
|
|
|
|
|
$rootScope.$watch(fn);
|
|
|
|
|
$rootScope.foo = null;
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
$rootScope.foo = 'foo';
|
|
|
|
|
$rootScope.$digest();
|
2016-03-13 13:20:30 +01:00
|
|
|
expect(fn()).toEqual(undefined);
|
2014-06-04 13:49:23 -07:00
|
|
|
}));
|
2014-06-04 17:15:18 -07:00
|
|
|
|
2016-05-11 22:12:05 +02:00
|
|
|
it('should invoke a stateless filter once when the parsed expression has an interceptor',
|
|
|
|
|
inject(function($parse, $rootScope) {
|
|
|
|
|
var countFilter = jasmine.createSpy();
|
|
|
|
|
var interceptor = jasmine.createSpy();
|
|
|
|
|
countFilter.and.returnValue(1);
|
|
|
|
|
$filterProvider.register('count', valueFn(countFilter));
|
|
|
|
|
$rootScope.foo = function() { return 1; };
|
|
|
|
|
$rootScope.$watch($parse(':: foo() | count', interceptor));
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect(countFilter.calls.count()).toBe(1);
|
|
|
|
|
}));
|
|
|
|
|
|
2014-10-22 17:31:27 -04:00
|
|
|
describe('literal expressions', function() {
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should mark an empty expressions as literal', inject(function($parse) {
|
|
|
|
|
expect($parse('').literal).toBe(true);
|
|
|
|
|
expect($parse(' ').literal).toBe(true);
|
|
|
|
|
expect($parse('::').literal).toBe(true);
|
|
|
|
|
expect($parse(':: ').literal).toBe(true);
|
|
|
|
|
}));
|
|
|
|
|
|
2017-03-31 00:36:19 -07:00
|
|
|
[true, false].forEach(function(isDeep) {
|
|
|
|
|
describe(isDeep ? 'deepWatch' : 'watch', function() {
|
|
|
|
|
it('should only become stable when all the properties of an object have defined values', inject(function($parse, $rootScope, log) {
|
|
|
|
|
var fn = $parse('::{foo: foo, bar: bar}');
|
|
|
|
|
$rootScope.$watch(fn, function(value) { log(value); }, isDeep);
|
|
|
|
|
|
|
|
|
|
expect(log.empty()).toEqual([]);
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
expect(log.empty()).toEqual([{foo: undefined, bar: undefined}]);
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'foo';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
expect(log.empty()).toEqual([{foo: 'foo', bar: undefined}]);
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'foobar';
|
|
|
|
|
$rootScope.bar = 'bar';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(0);
|
|
|
|
|
expect(log.empty()).toEqual([{foo: 'foobar', bar: 'bar'}]);
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'baz';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(0);
|
|
|
|
|
expect(log.empty()).toEqual([]);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should only become stable when all the elements of an array have defined values', inject(function($parse, $rootScope, log) {
|
|
|
|
|
var fn = $parse('::[foo,bar]');
|
|
|
|
|
$rootScope.$watch(fn, function(value) { log(value); }, isDeep);
|
|
|
|
|
|
|
|
|
|
expect(log.empty()).toEqual([]);
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
expect(log.empty()).toEqual([[undefined, undefined]]);
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'foo';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
expect(log.empty()).toEqual([['foo', undefined]]);
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'foobar';
|
|
|
|
|
$rootScope.bar = 'bar';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(0);
|
|
|
|
|
expect(log.empty()).toEqual([['foobar', 'bar']]);
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'baz';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(0);
|
|
|
|
|
expect(log.empty()).toEqual([]);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
var fn = $parse('::[foo]');
|
|
|
|
|
$rootScope.$watch(fn, function(value) { log(value); }, isDeep);
|
|
|
|
|
$rootScope.$watch('foo', function() { if ($rootScope.foo === 'bar') {$rootScope.foo = undefined; } });
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'bar';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(2);
|
|
|
|
|
expect(log.empty()).toEqual([['bar'], [undefined]]);
|
|
|
|
|
|
|
|
|
|
$rootScope.foo = 'baz';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
expect(log.empty()).toEqual([['baz']]);
|
|
|
|
|
|
|
|
|
|
$rootScope.bar = 'qux';
|
|
|
|
|
$rootScope.$digest();
|
|
|
|
|
expect($rootScope.$$watchers.length).toBe(1);
|
|
|
|
|
expect(log).toEqual([]);
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
});
|
2014-06-04 17:15:18 -07:00
|
|
|
});
|
2014-06-04 13:49:23 -07:00
|
|
|
});
|
|
|
|
|
|
2014-09-09 22:03:10 -07:00
|
|
|
|
|
|
|
|
describe('watched $parse expressions', function() {
|
|
|
|
|
|
|
|
|
|
it('should respect short-circuiting AND if it could have side effects', function() {
|
|
|
|
|
var bCalled = 0;
|
|
|
|
|
scope.b = function() { bCalled++; };
|
|
|
|
|
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.$watch('a && b()');
|
2014-09-09 22:03:10 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(bCalled).toBe(0);
|
|
|
|
|
|
|
|
|
|
scope.a = true;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(bCalled).toBe(1);
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(bCalled).toBe(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should respect short-circuiting OR if it could have side effects', function() {
|
|
|
|
|
var bCalled = false;
|
|
|
|
|
scope.b = function() { bCalled = true; };
|
|
|
|
|
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.$watch('a || b()');
|
2014-09-09 22:03:10 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(bCalled).toBe(true);
|
|
|
|
|
|
|
|
|
|
bCalled = false;
|
|
|
|
|
scope.a = true;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(bCalled).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should respect the branching ternary operator if it could have side effects', function() {
|
|
|
|
|
var bCalled = false;
|
|
|
|
|
scope.b = function() { bCalled = true; };
|
|
|
|
|
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.$watch('a ? b() : 1');
|
2014-09-09 22:03:10 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(bCalled).toBe(false);
|
|
|
|
|
|
|
|
|
|
scope.a = true;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(bCalled).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
describe('filters', function() {
|
|
|
|
|
|
|
|
|
|
it('should not be invoked unless the input/arguments change', function() {
|
|
|
|
|
var filterCalled = false;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalled = true;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
scope.$watch('a | foo:b:1');
|
|
|
|
|
scope.a = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalled).toBe(true);
|
|
|
|
|
|
|
|
|
|
filterCalled = false;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalled).toBe(false);
|
|
|
|
|
|
|
|
|
|
scope.a++;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalled).toBe(true);
|
|
|
|
|
});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2018-04-03 12:34:32 -07:00
|
|
|
it('should not be invoked unless the input/arguments change within literals', function() {
|
|
|
|
|
var filterCalls = [];
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls.push(input);
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
scope.$watch('[(a | foo:b:1), undefined]');
|
|
|
|
|
scope.a = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toEqual([0]);
|
|
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toEqual([0]);
|
|
|
|
|
|
|
|
|
|
scope.a++;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toEqual([0, 1]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not be invoked unless the input/arguments change within literals (one-time)', function() {
|
|
|
|
|
var filterCalls = [];
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls.push(input);
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
scope.$watch('::[(a | foo:b:1), undefined]');
|
|
|
|
|
scope.a = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toEqual([0]);
|
|
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toEqual([0]);
|
|
|
|
|
|
|
|
|
|
scope.a++;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toEqual([0, 1]);
|
|
|
|
|
});
|
|
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should always be invoked if they are marked as having $stateful', function() {
|
|
|
|
|
var filterCalled = false;
|
|
|
|
|
$filterProvider.register('foo', valueFn(extend(function(input) {
|
|
|
|
|
filterCalled = true;
|
|
|
|
|
return input;
|
|
|
|
|
}, {$stateful: true})));
|
|
|
|
|
|
|
|
|
|
scope.$watch('a | foo:b:1');
|
|
|
|
|
scope.a = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalled).toBe(true);
|
|
|
|
|
|
|
|
|
|
filterCalled = false;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalled).toBe(true);
|
|
|
|
|
});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should be treated as constant when input are constant', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var parsed = $parse('{x: 1} | foo:1');
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
expect(parsed.constant).toBe(true);
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch(parsed, function(input) {
|
|
|
|
|
expect(input).toEqual({x:1});
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2018-04-03 12:34:32 -07:00
|
|
|
it('should ignore changes within nested objects', function() {
|
|
|
|
|
var watchCalls = [];
|
|
|
|
|
scope.$watch('[a]', function(a) { watchCalls.push(a[0]); });
|
|
|
|
|
scope.a = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watchCalls).toEqual([0]);
|
|
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watchCalls).toEqual([0]);
|
|
|
|
|
|
|
|
|
|
scope.a++;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watchCalls).toEqual([0, 1]);
|
|
|
|
|
|
|
|
|
|
scope.a = {};
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watchCalls).toEqual([0, 1, {}]);
|
|
|
|
|
|
|
|
|
|
scope.a.foo = 42;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watchCalls).toEqual([0, 1, {foo: 42}]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should ignore changes within nested objects (one-time)', function() {
|
|
|
|
|
var watchCalls = [];
|
|
|
|
|
scope.$watch('::[a, undefined]', function(a) { watchCalls.push(a[0]); });
|
|
|
|
|
scope.a = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watchCalls).toEqual([0]);
|
|
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watchCalls).toEqual([0]);
|
|
|
|
|
|
|
|
|
|
scope.a++;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watchCalls).toEqual([0, 1]);
|
|
|
|
|
|
|
|
|
|
scope.a = {};
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watchCalls).toEqual([0, 1, {}]);
|
|
|
|
|
|
|
|
|
|
scope.a.foo = 42;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watchCalls).toEqual([0, 1, {foo: 42}]);
|
|
|
|
|
});
|
|
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
describe('with non-primitive input', function() {
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
describe('that does NOT support valueOf()', function() {
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should always be reevaluated', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var parsed = $parse('obj | foo');
|
|
|
|
|
var obj = scope.obj = {};
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch(parsed, function(input) {
|
|
|
|
|
expect(input).toBe(obj);
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(2);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(3);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
2015-11-24 22:48:52 +01:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should always be reevaluated in literals', inject(function($parse) {
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
return input.b > 0;
|
|
|
|
|
}));
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$watch('[(a | foo)]', function() {});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
// Would be great if filter-output was checked for changes and this didn't throw...
|
|
|
|
|
expect(function() { scope.$apply('a = {b: 1}'); }).toThrowMinErr('$rootScope', 'infdig');
|
|
|
|
|
}));
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should always be reevaluated when passed literals', inject(function($parse) {
|
|
|
|
|
scope.$watch('[a] | filter', function() {});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('a = 1');
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
// Would be great if filter-output was checked for changes and this didn't throw...
|
|
|
|
|
expect(function() { scope.$apply('a = {}'); }).toThrowMinErr('$rootScope', 'infdig');
|
|
|
|
|
}));
|
|
|
|
|
});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
describe('that does support valueOf()', function() {
|
|
|
|
|
|
|
|
|
|
it('should not be reevaluated',
|
|
|
|
|
inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
expect(input instanceof Date).toBe(true);
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
var parsed = $parse('date | foo:a');
|
|
|
|
|
var date = scope.date = new Date();
|
|
|
|
|
|
|
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch(parsed, function(input) {
|
|
|
|
|
expect(input).toBe(date);
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should not be reevaluated in literals', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.date = new Date(1234567890123);
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch('[(date | foo)]', function(input) {
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should be reevaluated when valueOf() changes', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
expect(input instanceof Date).toBe(true);
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var parsed = $parse('date | foo:a');
|
|
|
|
|
var date = scope.date = new Date();
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch(parsed, function(input) {
|
|
|
|
|
expect(input).toBe(date);
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
date.setYear(1901);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(2);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should be reevaluated in literals when valueOf() changes', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.date = new Date(1234567890123);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch('[(date | foo)]', function(input) {
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.date.setTime(1234567890);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(2);
|
|
|
|
|
expect(watcherCalls).toBe(2);
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should not be reevaluated when the instance changes but valueOf() does not', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.date = new Date(1234567890123);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch($parse('[(date | foo)]'), function(input) {
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
expect(filterCalls).toBe(1);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.date = new Date(1234567890123);
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
}));
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should not be reevaluated when input is simplified via unary operators', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.obj = {};
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch('!obj | foo:!obj', function(input) {
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should not be reevaluated when input is simplified via non-plus/concat binary operators', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.obj = {};
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch('1 - obj | foo:(1 * obj)', function(input) {
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should be reevaluated when input is simplified via plus/concat', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.obj = {};
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch('1 + obj | foo', function(input) {
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(2);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(3);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should reevaluate computed member expressions', inject(function($parse) {
|
|
|
|
|
var toStringCalls = 0;
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.obj = {};
|
|
|
|
|
scope.key = {
|
|
|
|
|
toString: function() {
|
|
|
|
|
toStringCalls++;
|
|
|
|
|
return 'foo';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch('obj[key]', function(input) {
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(toStringCalls).toBe(2);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(toStringCalls).toBe(3);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should be reevaluated with input created with null prototype', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var parsed = $parse('obj | foo');
|
|
|
|
|
var obj = scope.obj = Object.create(null);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch(parsed, function(input) {
|
|
|
|
|
expect(input).toBe(obj);
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(2);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(3);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
});
|
|
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
describe('with primitive input', function() {
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should not be reevaluated when passed literals', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch('[a] | foo', function(input) {
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
scope.$apply('a = 1');
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
|
|
|
|
|
scope.$apply('a = 2');
|
|
|
|
|
expect(filterCalls).toBe(2);
|
|
|
|
|
expect(watcherCalls).toBe(2);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should not be reevaluated in literals', inject(function($parse) {
|
|
|
|
|
var filterCalls = 0;
|
|
|
|
|
$filterProvider.register('foo', valueFn(function(input) {
|
|
|
|
|
filterCalls++;
|
|
|
|
|
return input;
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.prim = 1234567890123;
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch('[(prim | foo)]', function(input) {
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(filterCalls).toBe(1);
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
describe('interceptorFns', function() {
|
2017-07-11 00:35:30 -07:00
|
|
|
it('should only be passed the intercepted value', inject(function($parse) {
|
|
|
|
|
var args;
|
|
|
|
|
function interceptor(v) {
|
|
|
|
|
args = sliceArgs(arguments);
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scope.$watch($parse('a', interceptor));
|
|
|
|
|
|
|
|
|
|
scope.a = 1;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(args).toEqual([1]);
|
|
|
|
|
}));
|
|
|
|
|
|
2017-07-19 22:38:10 -07:00
|
|
|
it('should only be passed the intercepted value when wrapping one-time', inject(function($parse) {
|
|
|
|
|
var args;
|
|
|
|
|
function interceptor(v) {
|
|
|
|
|
args = sliceArgs(arguments);
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scope.$watch($parse('::a', interceptor));
|
|
|
|
|
|
|
|
|
|
scope.a = 1;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(args).toEqual([1]);
|
|
|
|
|
}));
|
|
|
|
|
|
2017-07-11 00:35:30 -07:00
|
|
|
it('should only be passed the intercepted value when double-intercepted',
|
|
|
|
|
inject(function($parse) {
|
|
|
|
|
var args1;
|
|
|
|
|
function int1(v) {
|
|
|
|
|
args1 = sliceArgs(arguments);
|
|
|
|
|
return v + 2;
|
|
|
|
|
}
|
|
|
|
|
var args2;
|
|
|
|
|
function int2(v) {
|
|
|
|
|
args2 = sliceArgs(arguments);
|
|
|
|
|
return v + 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scope.$watch($parse($parse('a', int1), int2));
|
|
|
|
|
|
|
|
|
|
scope.a = 1;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(args1).toEqual([1]);
|
|
|
|
|
expect(args2).toEqual([3]);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should support locals', inject(function($parse) {
|
|
|
|
|
var args;
|
|
|
|
|
function interceptor(v) {
|
|
|
|
|
args = sliceArgs(arguments);
|
|
|
|
|
return v + 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var exp = $parse('a + b', interceptor);
|
|
|
|
|
scope.a = 1;
|
|
|
|
|
|
|
|
|
|
expect(exp(scope, {b: 2})).toBe(7);
|
|
|
|
|
expect(args).toEqual([3]);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should support locals when double-intercepted', inject(function($parse) {
|
|
|
|
|
var args1;
|
|
|
|
|
function int1(v) {
|
|
|
|
|
args1 = sliceArgs(arguments);
|
|
|
|
|
return v + 4;
|
|
|
|
|
}
|
|
|
|
|
var args2;
|
|
|
|
|
function int2(v) {
|
|
|
|
|
args2 = sliceArgs(arguments);
|
|
|
|
|
return v + 8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var exp = $parse($parse('a + b', int1), int2);
|
|
|
|
|
|
|
|
|
|
scope.a = 1;
|
|
|
|
|
expect(exp(scope, {b: 2})).toBe(15);
|
|
|
|
|
expect(args1).toEqual([3]);
|
|
|
|
|
expect(args2).toEqual([7]);
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should always be invoked if they are flagged as having $stateful',
|
|
|
|
|
inject(function($parse) {
|
|
|
|
|
var called = false;
|
|
|
|
|
function interceptor() {
|
|
|
|
|
called = true;
|
2017-05-11 21:29:54 -07:00
|
|
|
}
|
2017-05-24 00:20:31 -07:00
|
|
|
interceptor.$stateful = true;
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$watch($parse('a', interceptor));
|
|
|
|
|
scope.a = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(true);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
called = false;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(true);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.a++;
|
|
|
|
|
called = false;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(true);
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-25 23:43:34 -07:00
|
|
|
it('should always be invoked if flagged as $stateful when wrapping one-time',
|
|
|
|
|
inject(function($parse) {
|
|
|
|
|
|
|
|
|
|
var interceptorCalls = 0;
|
|
|
|
|
function interceptor() {
|
|
|
|
|
interceptorCalls++;
|
|
|
|
|
return 123;
|
|
|
|
|
}
|
|
|
|
|
interceptor.$stateful = true;
|
|
|
|
|
|
|
|
|
|
scope.$watch($parse('::a', interceptor));
|
|
|
|
|
|
|
|
|
|
interceptorCalls = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(interceptorCalls).not.toBe(0);
|
|
|
|
|
|
|
|
|
|
interceptorCalls = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(interceptorCalls).not.toBe(0);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should always be invoked if flagged as $stateful when wrapping one-time with inputs',
|
|
|
|
|
inject(function($parse) {
|
|
|
|
|
|
|
|
|
|
$filterProvider.register('identity', valueFn(identity));
|
|
|
|
|
|
|
|
|
|
var interceptorCalls = 0;
|
|
|
|
|
function interceptor() {
|
|
|
|
|
interceptorCalls++;
|
|
|
|
|
return 123;
|
|
|
|
|
}
|
|
|
|
|
interceptor.$stateful = true;
|
|
|
|
|
|
|
|
|
|
scope.$watch($parse('::a | identity', interceptor));
|
|
|
|
|
|
|
|
|
|
interceptorCalls = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(interceptorCalls).not.toBe(0);
|
|
|
|
|
|
|
|
|
|
interceptorCalls = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(interceptorCalls).not.toBe(0);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should always be invoked if flagged as $stateful when wrapping one-time literal',
|
|
|
|
|
inject(function($parse) {
|
|
|
|
|
|
|
|
|
|
var interceptorCalls = 0;
|
|
|
|
|
function interceptor() {
|
|
|
|
|
interceptorCalls++;
|
|
|
|
|
return 123;
|
|
|
|
|
}
|
|
|
|
|
interceptor.$stateful = true;
|
|
|
|
|
|
|
|
|
|
scope.$watch($parse('::[a]', interceptor));
|
|
|
|
|
|
|
|
|
|
interceptorCalls = 0;
|
2017-06-16 01:32:34 -07:00
|
|
|
scope.$digest();
|
2017-05-25 23:43:34 -07:00
|
|
|
expect(interceptorCalls).not.toBe(0);
|
|
|
|
|
|
|
|
|
|
interceptorCalls = 0;
|
2017-06-16 01:32:34 -07:00
|
|
|
scope.$digest();
|
2017-05-25 23:43:34 -07:00
|
|
|
expect(interceptorCalls).not.toBe(0);
|
|
|
|
|
}));
|
|
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should not be invoked unless the input changes', inject(function($parse) {
|
|
|
|
|
var called = false;
|
|
|
|
|
function interceptor(v) {
|
|
|
|
|
called = true;
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
scope.$watch($parse('a', interceptor));
|
|
|
|
|
scope.$watch($parse('a + b', interceptor));
|
|
|
|
|
scope.a = scope.b = 0;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(true);
|
|
|
|
|
|
|
|
|
|
called = false;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(false);
|
|
|
|
|
|
|
|
|
|
scope.a++;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(true);
|
2014-10-10 19:57:47 -03:00
|
|
|
}));
|
|
|
|
|
|
2017-05-26 00:28:34 -07:00
|
|
|
it('should always be invoked if inputs are non-primitive', inject(function($parse) {
|
|
|
|
|
var called = false;
|
|
|
|
|
function interceptor(v) {
|
|
|
|
|
called = true;
|
|
|
|
|
return v.sub;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scope.$watch($parse('[o]', interceptor));
|
|
|
|
|
scope.o = {sub: 1};
|
|
|
|
|
|
|
|
|
|
called = false;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(true);
|
|
|
|
|
|
|
|
|
|
called = false;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(true);
|
|
|
|
|
}));
|
|
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should not be invoked unless the input.valueOf() changes even if the instance changes', inject(function($parse) {
|
|
|
|
|
var called = false;
|
|
|
|
|
function interceptor(v) {
|
|
|
|
|
called = true;
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
scope.$watch($parse('a', interceptor));
|
|
|
|
|
scope.a = new Date();
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(true);
|
|
|
|
|
|
|
|
|
|
called = false;
|
|
|
|
|
scope.a = new Date(scope.a.valueOf());
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(false);
|
|
|
|
|
}));
|
2014-10-10 19:57:47 -03:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should be invoked if input.valueOf() changes even if the instance does not', inject(function($parse) {
|
|
|
|
|
var called = false;
|
|
|
|
|
function interceptor(v) {
|
|
|
|
|
called = true;
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
scope.$watch($parse('a', interceptor));
|
|
|
|
|
scope.a = new Date();
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(true);
|
|
|
|
|
|
|
|
|
|
called = false;
|
|
|
|
|
scope.a.setTime(scope.a.getTime() + 1);
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(true);
|
|
|
|
|
}));
|
2014-10-10 19:57:47 -03:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should be invoked when the expression is `undefined`', inject(function($parse) {
|
|
|
|
|
var called = false;
|
|
|
|
|
function interceptor(v) {
|
|
|
|
|
called = true;
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
scope.$watch($parse(undefined, interceptor));
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(called).toBe(true);
|
|
|
|
|
}));
|
2017-06-08 22:04:30 -07:00
|
|
|
|
|
|
|
|
it('should not affect when a one-time binding becomes stable', inject(function($parse) {
|
|
|
|
|
scope.$watch($parse('::x'));
|
|
|
|
|
scope.$watch($parse('::x', identity));
|
|
|
|
|
scope.$watch($parse('::x', function() { return 1; })); //interceptor that returns non-undefined
|
|
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(scope.$$watchersCount).toBe(3);
|
|
|
|
|
|
|
|
|
|
scope.x = 1;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(scope.$$watchersCount).toBe(0);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should not affect when a one-time literal binding becomes stable', inject(function($parse) {
|
|
|
|
|
scope.$watch($parse('::[x]'));
|
|
|
|
|
scope.$watch($parse('::[x]', identity));
|
|
|
|
|
scope.$watch($parse('::[x]', function() { return 1; })); //interceptor that returns non-literal
|
|
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(scope.$$watchersCount).toBe(3);
|
|
|
|
|
|
|
|
|
|
scope.x = 1;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(scope.$$watchersCount).toBe(0);
|
|
|
|
|
}));
|
2017-06-16 01:32:34 -07:00
|
|
|
|
|
|
|
|
it('should watch the intercepted value of one-time bindings', inject(function($parse, log) {
|
|
|
|
|
scope.$watch($parse('::{x:x, y:y}', function(lit) { return lit.x; }), log);
|
|
|
|
|
|
|
|
|
|
scope.$apply();
|
|
|
|
|
expect(log.empty()).toEqual([undefined]);
|
|
|
|
|
|
|
|
|
|
scope.$apply('x = 1');
|
|
|
|
|
expect(log.empty()).toEqual([1]);
|
|
|
|
|
|
|
|
|
|
scope.$apply('x = 2; y=1');
|
|
|
|
|
expect(log.empty()).toEqual([2]);
|
|
|
|
|
|
|
|
|
|
scope.$apply('x = 1; y=2');
|
|
|
|
|
expect(log.empty()).toEqual([]);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should watch the intercepted value of one-time bindings in nested interceptors', inject(function($parse, log) {
|
|
|
|
|
scope.$watch($parse($parse('::{x:x, y:y}', function(lit) { return lit.x; }), identity), log);
|
|
|
|
|
|
|
|
|
|
scope.$apply();
|
|
|
|
|
expect(log.empty()).toEqual([undefined]);
|
|
|
|
|
|
|
|
|
|
scope.$apply('x = 1');
|
|
|
|
|
expect(log.empty()).toEqual([1]);
|
|
|
|
|
|
|
|
|
|
scope.$apply('x = 2; y=1');
|
|
|
|
|
expect(log.empty()).toEqual([2]);
|
|
|
|
|
|
|
|
|
|
scope.$apply('x = 1; y=2');
|
|
|
|
|
expect(log.empty()).toEqual([]);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should nest interceptors around eachother, not around the intercepted', inject(function($parse) {
|
|
|
|
|
function origin() { return 0; }
|
|
|
|
|
|
|
|
|
|
var fn = origin;
|
|
|
|
|
function addOne(n) { return n + 1; }
|
|
|
|
|
|
|
|
|
|
fn = $parse(fn, addOne);
|
|
|
|
|
expect(fn.$$intercepted).toBe(origin);
|
|
|
|
|
expect(fn()).toBe(1);
|
|
|
|
|
|
|
|
|
|
fn = $parse(fn, addOne);
|
|
|
|
|
expect(fn.$$intercepted).toBe(origin);
|
|
|
|
|
expect(fn()).toBe(2);
|
|
|
|
|
|
|
|
|
|
fn = $parse(fn, addOne);
|
|
|
|
|
expect(fn.$$intercepted).toBe(origin);
|
|
|
|
|
expect(fn()).toBe(3);
|
|
|
|
|
}));
|
2017-05-25 23:43:34 -07:00
|
|
|
|
|
|
|
|
it('should not propogate $$watchDelegate to the interceptor wrapped expression', inject(function($parse) {
|
|
|
|
|
function getter(s) {
|
|
|
|
|
return s.x;
|
|
|
|
|
}
|
|
|
|
|
getter.$$watchDelegate = getter;
|
|
|
|
|
|
|
|
|
|
function doubler(v) {
|
|
|
|
|
return 2 * v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var lastValue;
|
|
|
|
|
function watcher(val) {
|
|
|
|
|
lastValue = val;
|
|
|
|
|
}
|
|
|
|
|
scope.$watch($parse(getter, doubler), watcher);
|
|
|
|
|
|
|
|
|
|
scope.$apply('x = 1');
|
|
|
|
|
expect(lastValue).toBe(2 * 1);
|
|
|
|
|
|
|
|
|
|
scope.$apply('x = 123');
|
|
|
|
|
expect(lastValue).toBe(2 * 123);
|
|
|
|
|
}));
|
2017-05-24 00:20:31 -07:00
|
|
|
});
|
2014-10-10 19:57:47 -03:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
describe('literals', function() {
|
2014-10-10 19:57:47 -03:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should support watching', inject(function($parse) {
|
|
|
|
|
var lastVal = NaN;
|
|
|
|
|
var callCount = 0;
|
|
|
|
|
var listener = function(val) { callCount++; lastVal = val; };
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$watch('{val: val}', listener);
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val = 1');
|
|
|
|
|
expect(callCount).toBe(1);
|
|
|
|
|
expect(lastVal).toEqual({val: 1});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val = []');
|
|
|
|
|
expect(callCount).toBe(2);
|
|
|
|
|
expect(lastVal).toEqual({val: []});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val = []');
|
|
|
|
|
expect(callCount).toBe(3);
|
|
|
|
|
expect(lastVal).toEqual({val: []});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val = {}');
|
|
|
|
|
expect(callCount).toBe(4);
|
|
|
|
|
expect(lastVal).toEqual({val: {}});
|
2014-09-09 22:03:10 -07:00
|
|
|
}));
|
|
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should only watch the direct inputs', inject(function($parse) {
|
|
|
|
|
var lastVal = NaN;
|
|
|
|
|
var callCount = 0;
|
|
|
|
|
var listener = function(val) { callCount++; lastVal = val; };
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$watch('{val: val}', listener);
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val = 1');
|
|
|
|
|
expect(callCount).toBe(1);
|
|
|
|
|
expect(lastVal).toEqual({val: 1});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val = [2]');
|
|
|
|
|
expect(callCount).toBe(2);
|
|
|
|
|
expect(lastVal).toEqual({val: [2]});
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val.push(3)');
|
|
|
|
|
expect(callCount).toBe(2);
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val.length = 0');
|
|
|
|
|
expect(callCount).toBe(2);
|
|
|
|
|
}));
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should only watch the direct inputs when nested', inject(function($parse) {
|
|
|
|
|
var lastVal = NaN;
|
|
|
|
|
var callCount = 0;
|
|
|
|
|
var listener = function(val) { callCount++; lastVal = val; };
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$watch('[{val: [val]}]', listener);
|
2014-09-09 22:03:10 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val = 1');
|
|
|
|
|
expect(callCount).toBe(1);
|
|
|
|
|
expect(lastVal).toEqual([{val: [1]}]);
|
2014-11-23 23:08:33 +01:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val = [2]');
|
|
|
|
|
expect(callCount).toBe(2);
|
|
|
|
|
expect(lastVal).toEqual([{val: [[2]]}]);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val.push(3)');
|
|
|
|
|
expect(callCount).toBe(2);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$apply('val.length = 0');
|
|
|
|
|
expect(callCount).toBe(2);
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
describe('with non-primative input', function() {
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
describe('that does NOT support valueOf()', function() {
|
|
|
|
|
it('should not be reevaluated', inject(function($parse) {
|
|
|
|
|
var obj = scope.obj = {};
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var parsed = $parse('[obj]');
|
|
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch(parsed, function(input) {
|
|
|
|
|
expect(input[0]).toBe(obj);
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
|
|
|
|
});
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
describe('that does support valueOf()', function() {
|
|
|
|
|
it('should not be reevaluated', inject(function($parse) {
|
|
|
|
|
var date = scope.date = new Date();
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var parsed = $parse('[date]');
|
|
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch(parsed, function(input) {
|
|
|
|
|
expect(input[0]).toBe(date);
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should be reevaluated even when valueOf() changes', inject(function($parse) {
|
|
|
|
|
var date = scope.date = new Date();
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var parsed = $parse('[date]');
|
|
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch(parsed, function(input) {
|
|
|
|
|
expect(input[0]).toBe(date);
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-05-11 21:29:54 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
date.setYear(1901);
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(2);
|
|
|
|
|
}));
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should not be reevaluated when the instance changes but valueOf() does not', inject(function($parse) {
|
|
|
|
|
scope.date = new Date(1234567890123);
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var parsed = $parse('[date]');
|
|
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch(parsed, function(input) {
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.date = new Date(1234567890123);
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
}));
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
it('should be reevaluated when the instance does not change but valueOf() does', inject(function($parse) {
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
scope.date = new Date(1234567890123);
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2017-05-24 00:20:31 -07:00
|
|
|
var parsed = $parse('[date]');
|
|
|
|
|
var watcherCalls = 0;
|
|
|
|
|
scope.$watch(parsed, function(input) {
|
|
|
|
|
watcherCalls++;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(1);
|
|
|
|
|
|
|
|
|
|
scope.date.setTime(scope.date.getTime() + 1);
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(watcherCalls).toBe(2);
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-03-31 00:17:55 -07:00
|
|
|
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should continue with the evaluation of the expression without invoking computed parts',
|
|
|
|
|
inject(function($parse) {
|
|
|
|
|
var value = 'foo';
|
|
|
|
|
var spy = jasmine.createSpy();
|
|
|
|
|
|
2016-03-13 13:20:30 +01:00
|
|
|
spy.and.callFake(function() { return value; });
|
2014-11-23 23:08:33 +01:00
|
|
|
scope.foo = spy;
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.$watch('foo() | uppercase');
|
2014-11-23 23:08:33 +01:00
|
|
|
scope.$digest();
|
2016-03-13 13:20:30 +01:00
|
|
|
expect(spy).toHaveBeenCalledTimes(2);
|
2014-11-23 23:08:33 +01:00
|
|
|
scope.$digest();
|
2016-03-13 13:20:30 +01:00
|
|
|
expect(spy).toHaveBeenCalledTimes(3);
|
2014-11-23 23:08:33 +01:00
|
|
|
value = 'bar';
|
|
|
|
|
scope.$digest();
|
2016-03-13 13:20:30 +01:00
|
|
|
expect(spy).toHaveBeenCalledTimes(5);
|
2014-11-23 23:08:33 +01:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should invoke all statements in multi-statement expressions', inject(function($parse) {
|
|
|
|
|
var lastVal = NaN;
|
|
|
|
|
var listener = function(val) { lastVal = val; };
|
|
|
|
|
|
|
|
|
|
scope.setBarToOne = false;
|
|
|
|
|
scope.bar = 0;
|
|
|
|
|
scope.two = 2;
|
|
|
|
|
scope.foo = function() { if (scope.setBarToOne) scope.bar = 1; };
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.$watch('foo(); bar + two', listener);
|
2014-11-23 23:08:33 +01:00
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(lastVal).toBe(2);
|
|
|
|
|
|
|
|
|
|
scope.bar = 2;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(lastVal).toBe(4);
|
|
|
|
|
|
|
|
|
|
scope.setBarToOne = true;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(lastVal).toBe(3);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should watch the left side of assignments', inject(function($parse) {
|
|
|
|
|
var lastVal = NaN;
|
|
|
|
|
var listener = function(val) { lastVal = val; };
|
|
|
|
|
|
|
|
|
|
var objA = {};
|
|
|
|
|
var objB = {};
|
|
|
|
|
|
2016-08-10 12:13:14 +02:00
|
|
|
scope.$watch('curObj.value = input', noop);
|
2014-11-23 23:08:33 +01:00
|
|
|
|
|
|
|
|
scope.curObj = objA;
|
|
|
|
|
scope.input = 1;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(objA.value).toBe(scope.input);
|
|
|
|
|
|
|
|
|
|
scope.curObj = objB;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(objB.value).toBe(scope.input);
|
|
|
|
|
|
|
|
|
|
scope.input = 2;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(objB.value).toBe(scope.input);
|
|
|
|
|
}));
|
2016-10-20 01:59:56 -07:00
|
|
|
|
2017-01-24 15:54:53 +08:00
|
|
|
it('should watch ES6 object computed property changes', function() {
|
|
|
|
|
var count = 0;
|
2017-07-16 22:15:49 -07:00
|
|
|
var lastValue;
|
2017-01-24 15:54:53 +08:00
|
|
|
|
|
|
|
|
scope.$watch('{[a]: true}', function(val) {
|
|
|
|
|
count++;
|
2017-07-16 22:15:49 -07:00
|
|
|
lastValue = val;
|
|
|
|
|
});
|
2017-01-24 15:54:53 +08:00
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(count).toBe(1);
|
2017-07-16 22:15:49 -07:00
|
|
|
expect(lastValue).toEqual({'undefined': true});
|
2017-01-24 15:54:53 +08:00
|
|
|
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(count).toBe(1);
|
2017-07-16 22:15:49 -07:00
|
|
|
expect(lastValue).toEqual({'undefined': true});
|
2017-01-24 15:54:53 +08:00
|
|
|
|
|
|
|
|
scope.a = true;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(count).toBe(2);
|
2017-07-16 22:15:49 -07:00
|
|
|
expect(lastValue).toEqual({'true': true});
|
2017-01-24 15:54:53 +08:00
|
|
|
|
|
|
|
|
scope.a = 'abc';
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(count).toBe(3);
|
2017-07-16 22:15:49 -07:00
|
|
|
expect(lastValue).toEqual({'abc': true});
|
2017-01-24 15:54:53 +08:00
|
|
|
|
|
|
|
|
scope.a = undefined;
|
|
|
|
|
scope.$digest();
|
|
|
|
|
expect(count).toBe(4);
|
2017-07-16 22:15:49 -07:00
|
|
|
expect(lastValue).toEqual({'undefined': true});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not shallow-watch ES6 object computed properties in case of stateful toString', function() {
|
|
|
|
|
var count = 0;
|
|
|
|
|
var lastValue;
|
|
|
|
|
|
|
|
|
|
scope.$watch('{[a]: true}', function(val) {
|
|
|
|
|
count++;
|
|
|
|
|
lastValue = val;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
scope.a = {toString: function() { return this.b; }};
|
|
|
|
|
scope.a.b = 1;
|
|
|
|
|
|
|
|
|
|
//TODO: would be great if it didn't throw!
|
|
|
|
|
expect(function() { scope.$apply(); }).toThrowMinErr('$rootScope', 'infdig');
|
|
|
|
|
expect(lastValue).toEqual({1: true});
|
|
|
|
|
|
|
|
|
|
expect(function() { scope.$apply('a.b = 2'); }).toThrowMinErr('$rootScope', 'infdig');
|
|
|
|
|
expect(lastValue).toEqual({2: true});
|
2017-01-24 15:54:53 +08:00
|
|
|
});
|
2014-09-09 22:03:10 -07:00
|
|
|
});
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
describe('locals', function() {
|
|
|
|
|
it('should expose local variables', inject(function($parse) {
|
|
|
|
|
expect($parse('a')({a: 0}, {a: 1})).toEqual(1);
|
|
|
|
|
expect($parse('add(a,b)')({b: 1, add: function(a, b) { return a + b; }}, {a: 2})).toEqual(3);
|
2013-10-07 09:58:37 -07:00
|
|
|
}));
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should expose traverse locals', inject(function($parse) {
|
|
|
|
|
expect($parse('a.b')({a: {b: 0}}, {a: {b:1}})).toEqual(1);
|
|
|
|
|
expect($parse('a.b')({a: null}, {a: {b:1}})).toEqual(1);
|
|
|
|
|
expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
|
|
|
|
|
expect($parse('a.b.c')({a: null}, {a: {b: {c: 1}}})).toEqual(1);
|
|
|
|
|
}));
|
2013-10-07 09:58:37 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should not use locals to resolve object properties', inject(function($parse) {
|
2014-10-24 19:42:48 -04:00
|
|
|
expect($parse('a[0].b')({a: [{b: 'scope'}]}, {b: 'locals'})).toBe('scope');
|
|
|
|
|
expect($parse('a[0]["b"]')({a: [{b: 'scope'}]}, {b: 'locals'})).toBe('scope');
|
2014-10-18 19:48:33 -04:00
|
|
|
expect($parse('a[0][0].b')({a: [[{b: 'scope'}]]}, {b: 'locals'})).toBe('scope');
|
2014-10-24 19:42:48 -04:00
|
|
|
expect($parse('a[0].b.c')({a: [{b: {c: 'scope'}}] }, {b: {c: 'locals'} })).toBe('scope');
|
2014-05-22 08:43:24 -07:00
|
|
|
}));
|
2014-11-23 23:08:33 +01:00
|
|
|
|
|
|
|
|
it('should assign directly to locals when the local property exists', inject(function($parse) {
|
|
|
|
|
var s = {}, l = {};
|
|
|
|
|
|
2016-08-10 12:13:14 +02:00
|
|
|
$parse('a = 1')(s, l);
|
2014-11-23 23:08:33 +01:00
|
|
|
expect(s.a).toBe(1);
|
|
|
|
|
expect(l.a).toBeUndefined();
|
|
|
|
|
|
|
|
|
|
l.a = 2;
|
2016-08-10 12:13:14 +02:00
|
|
|
$parse('a = 0')(s, l);
|
2014-11-23 23:08:33 +01:00
|
|
|
expect(s.a).toBe(1);
|
|
|
|
|
expect(l.a).toBe(0);
|
|
|
|
|
|
2016-08-10 12:13:14 +02:00
|
|
|
$parse('toString = 1')(s, l);
|
2014-11-23 23:08:33 +01:00
|
|
|
expect(isFunction(s.toString)).toBe(true);
|
|
|
|
|
expect(l.toString).toBe(1);
|
|
|
|
|
}));
|
2016-09-11 20:31:14 +02:00
|
|
|
|
|
|
|
|
it('should overwrite undefined / null scope properties when assigning', inject(function($parse) {
|
|
|
|
|
var scope;
|
|
|
|
|
|
|
|
|
|
scope = {};
|
|
|
|
|
$parse('a.b = 1')(scope);
|
|
|
|
|
$parse('c["d"] = 2')(scope);
|
|
|
|
|
expect(scope).toEqual({a: {b: 1}, c: {d: 2}});
|
|
|
|
|
|
|
|
|
|
scope = {a: {}};
|
|
|
|
|
$parse('a.b.c = 1')(scope);
|
|
|
|
|
$parse('a.c["d"] = 2')(scope);
|
|
|
|
|
expect(scope).toEqual({a: {b: {c: 1}, c: {d: 2}}});
|
|
|
|
|
|
|
|
|
|
scope = {a: undefined, c: undefined};
|
|
|
|
|
$parse('a.b = 1')(scope);
|
|
|
|
|
$parse('c["d"] = 2')(scope);
|
|
|
|
|
expect(scope).toEqual({a: {b: 1}, c: {d: 2}});
|
|
|
|
|
|
|
|
|
|
scope = {a: {b: undefined, c: undefined}};
|
|
|
|
|
$parse('a.b.c = 1')(scope);
|
|
|
|
|
$parse('a.c["d"] = 2')(scope);
|
|
|
|
|
expect(scope).toEqual({a: {b: {c: 1}, c: {d: 2}}});
|
|
|
|
|
|
|
|
|
|
scope = {a: null, c: null};
|
|
|
|
|
$parse('a.b = 1')(scope);
|
|
|
|
|
$parse('c["d"] = 2')(scope);
|
|
|
|
|
expect(scope).toEqual({a: {b: 1}, c: {d: 2}});
|
|
|
|
|
|
|
|
|
|
scope = {a: {b: null, c: null}};
|
|
|
|
|
$parse('a.b.c = 1')(scope);
|
|
|
|
|
$parse('a.c["d"] = 2')(scope);
|
|
|
|
|
expect(scope).toEqual({a: {b: {c: 1}, c: {d: 2}}});
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
they('should not overwrite $prop scope properties when assigning', [0, false, '', NaN],
|
|
|
|
|
function(falsyValue) {
|
|
|
|
|
inject(function($parse) {
|
|
|
|
|
var scope;
|
|
|
|
|
|
|
|
|
|
scope = {a: falsyValue, c: falsyValue};
|
|
|
|
|
tryParseAndIgnoreException('a.b = 1');
|
|
|
|
|
tryParseAndIgnoreException('c["d"] = 2');
|
|
|
|
|
expect(scope).toEqual({a: falsyValue, c: falsyValue});
|
|
|
|
|
|
|
|
|
|
scope = {a: {b: falsyValue, c: falsyValue}};
|
|
|
|
|
tryParseAndIgnoreException('a.b.c = 1');
|
|
|
|
|
tryParseAndIgnoreException('a.c["d"] = 2');
|
|
|
|
|
expect(scope).toEqual({a: {b: falsyValue, c: falsyValue}});
|
|
|
|
|
|
|
|
|
|
// Helpers
|
|
|
|
|
//
|
|
|
|
|
// Normally assigning property on a primitive should throw exception in strict mode
|
|
|
|
|
// and silently fail in non-strict mode, IE seems to always have the non-strict-mode behavior,
|
|
|
|
|
// so if we try to use 'expect(function() {$parse('a.b=1')({a:false});).toThrow()' for testing
|
|
|
|
|
// the test will fail in case of IE because it will not throw exception, and if we just use
|
|
|
|
|
// '$parse('a.b=1')({a:false})' the test will fail because it will throw exception in case of Chrome
|
|
|
|
|
// so we use tryParseAndIgnoreException helper to catch the exception silently for all cases.
|
|
|
|
|
//
|
|
|
|
|
function tryParseAndIgnoreException(expression) {
|
|
|
|
|
try {
|
|
|
|
|
$parse(expression)(scope);
|
|
|
|
|
} catch (error) {/* ignore exception */}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
2013-10-07 09:58:37 -07:00
|
|
|
});
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
describe('literal', function() {
|
|
|
|
|
it('should mark scalar value expressions as literal', inject(function($parse) {
|
|
|
|
|
expect($parse('0').literal).toBe(true);
|
|
|
|
|
expect($parse('"hello"').literal).toBe(true);
|
|
|
|
|
expect($parse('true').literal).toBe(true);
|
|
|
|
|
expect($parse('false').literal).toBe(true);
|
|
|
|
|
expect($parse('null').literal).toBe(true);
|
|
|
|
|
expect($parse('undefined').literal).toBe(true);
|
2013-10-07 09:58:37 -07:00
|
|
|
}));
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should mark array expressions as literal', inject(function($parse) {
|
|
|
|
|
expect($parse('[]').literal).toBe(true);
|
|
|
|
|
expect($parse('[1, 2, 3]').literal).toBe(true);
|
|
|
|
|
expect($parse('[1, identifier]').literal).toBe(true);
|
2012-04-14 10:39:24 -07:00
|
|
|
}));
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should mark object expressions as literal', inject(function($parse) {
|
|
|
|
|
expect($parse('{}').literal).toBe(true);
|
|
|
|
|
expect($parse('{x: 1}').literal).toBe(true);
|
|
|
|
|
expect($parse('{foo: bar}').literal).toBe(true);
|
|
|
|
|
}));
|
2013-10-07 09:58:37 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should not mark function calls or operator expressions as literal', inject(function($parse) {
|
|
|
|
|
expect($parse('1 + 1').literal).toBe(false);
|
|
|
|
|
expect($parse('call()').literal).toBe(false);
|
|
|
|
|
expect($parse('[].length').literal).toBe(false);
|
|
|
|
|
}));
|
|
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
describe('constant', function() {
|
2014-11-23 23:08:33 +01:00
|
|
|
it('should mark an empty expressions as constant', inject(function($parse) {
|
|
|
|
|
expect($parse('').constant).toBe(true);
|
|
|
|
|
expect($parse(' ').constant).toBe(true);
|
|
|
|
|
expect($parse('::').constant).toBe(true);
|
|
|
|
|
expect($parse(':: ').constant).toBe(true);
|
|
|
|
|
}));
|
|
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should mark scalar value expressions as constant', inject(function($parse) {
|
|
|
|
|
expect($parse('12.3').constant).toBe(true);
|
|
|
|
|
expect($parse('"string"').constant).toBe(true);
|
|
|
|
|
expect($parse('true').constant).toBe(true);
|
|
|
|
|
expect($parse('false').constant).toBe(true);
|
|
|
|
|
expect($parse('null').constant).toBe(true);
|
|
|
|
|
expect($parse('undefined').constant).toBe(true);
|
|
|
|
|
}));
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should mark arrays as constant if they only contain constant elements', inject(function($parse) {
|
|
|
|
|
expect($parse('[]').constant).toBe(true);
|
|
|
|
|
expect($parse('[1, 2, 3]').constant).toBe(true);
|
|
|
|
|
expect($parse('["string", null]').constant).toBe(true);
|
|
|
|
|
expect($parse('[[]]').constant).toBe(true);
|
|
|
|
|
expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true);
|
|
|
|
|
}));
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) {
|
|
|
|
|
expect($parse('[foo]').constant).toBe(false);
|
|
|
|
|
expect($parse('[x + 1]').constant).toBe(false);
|
|
|
|
|
expect($parse('[bar[0]]').constant).toBe(false);
|
|
|
|
|
}));
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should mark complex expressions involving constant values as constant', inject(function($parse) {
|
|
|
|
|
expect($parse('!true').constant).toBe(true);
|
|
|
|
|
expect($parse('-42').constant).toBe(true);
|
|
|
|
|
expect($parse('1 - 1').constant).toBe(true);
|
|
|
|
|
expect($parse('"foo" + "bar"').constant).toBe(true);
|
|
|
|
|
expect($parse('5 != null').constant).toBe(true);
|
|
|
|
|
expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true);
|
2016-04-10 12:19:56 +02:00
|
|
|
expect($parse('{[standard]: 4/3, wide: 16/9}').constant).toBe(false);
|
2017-07-16 22:13:44 -07:00
|
|
|
expect($parse('{["key"]: 1}').constant).toBe(true);
|
|
|
|
|
expect($parse('[0].length').constant).toBe(true);
|
|
|
|
|
expect($parse('[0][0]').constant).toBe(true);
|
|
|
|
|
expect($parse('{x: 1}.x').constant).toBe(true);
|
|
|
|
|
expect($parse('{x: 1}["x"]').constant).toBe(true);
|
2014-05-22 08:43:24 -07:00
|
|
|
}));
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should not mark any expression involving variables or function calls as constant', inject(function($parse) {
|
|
|
|
|
expect($parse('true.toString()').constant).toBe(false);
|
|
|
|
|
expect($parse('foo(1, 2, 3)').constant).toBe(false);
|
|
|
|
|
expect($parse('"name" + id').constant).toBe(false);
|
|
|
|
|
}));
|
|
|
|
|
});
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
describe('null/undefined in expressions', function() {
|
|
|
|
|
// simpleGetterFn1
|
|
|
|
|
it('should return null for `a` where `a` is null', inject(function($rootScope) {
|
|
|
|
|
$rootScope.a = null;
|
|
|
|
|
expect($rootScope.$eval('a')).toBe(null);
|
|
|
|
|
}));
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should return undefined for `a` where `a` is undefined', inject(function($rootScope) {
|
|
|
|
|
expect($rootScope.$eval('a')).toBeUndefined();
|
|
|
|
|
}));
|
2011-11-23 15:53:06 -08:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
// simpleGetterFn2
|
|
|
|
|
it('should return undefined for properties of `null` constant', inject(function($rootScope) {
|
|
|
|
|
expect($rootScope.$eval('null.a')).toBeUndefined();
|
|
|
|
|
}));
|
2012-04-27 15:20:54 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should return undefined for properties of `null` values', inject(function($rootScope) {
|
|
|
|
|
$rootScope.a = null;
|
|
|
|
|
expect($rootScope.$eval('a.b')).toBeUndefined();
|
|
|
|
|
}));
|
2012-04-27 15:20:54 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should return null for `a.b` where `b` is null', inject(function($rootScope) {
|
|
|
|
|
$rootScope.a = { b: null };
|
|
|
|
|
expect($rootScope.$eval('a.b')).toBe(null);
|
|
|
|
|
}));
|
2012-04-27 15:20:54 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
// cspSafeGetter && pathKeys.length < 6 || pathKeys.length > 2
|
|
|
|
|
it('should return null for `a.b.c.d.e` where `e` is null', inject(function($rootScope) {
|
|
|
|
|
$rootScope.a = { b: { c: { d: { e: null } } } };
|
|
|
|
|
expect($rootScope.$eval('a.b.c.d.e')).toBe(null);
|
|
|
|
|
}));
|
2012-04-27 15:20:54 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should return undefined for `a.b.c.d.e` where `d` is null', inject(function($rootScope) {
|
|
|
|
|
$rootScope.a = { b: { c: { d: null } } };
|
|
|
|
|
expect($rootScope.$eval('a.b.c.d.e')).toBeUndefined();
|
|
|
|
|
}));
|
2012-04-14 10:39:24 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
// cspSafeGetter || pathKeys.length > 6
|
|
|
|
|
it('should return null for `a.b.c.d.e.f.g` where `g` is null', inject(function($rootScope) {
|
|
|
|
|
$rootScope.a = { b: { c: { d: { e: { f: { g: null } } } } } };
|
|
|
|
|
expect($rootScope.$eval('a.b.c.d.e.f.g')).toBe(null);
|
|
|
|
|
}));
|
2012-04-27 15:20:54 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should return undefined for `a.b.c.d.e.f.g` where `f` is null', inject(function($rootScope) {
|
|
|
|
|
$rootScope.a = { b: { c: { d: { e: { f: null } } } } };
|
|
|
|
|
expect($rootScope.$eval('a.b.c.d.e.f.g')).toBeUndefined();
|
|
|
|
|
}));
|
2012-04-27 15:20:54 -07:00
|
|
|
|
2012-04-14 10:39:24 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should return undefined if the return value of a function invocation is undefined',
|
|
|
|
|
inject(function($rootScope) {
|
|
|
|
|
$rootScope.fn = function() {};
|
|
|
|
|
expect($rootScope.$eval('fn()')).toBeUndefined();
|
|
|
|
|
}));
|
2012-04-14 10:39:24 -07:00
|
|
|
|
2014-05-22 08:43:24 -07:00
|
|
|
it('should ignore undefined values when doing addition/concatenation',
|
|
|
|
|
inject(function($rootScope) {
|
|
|
|
|
$rootScope.fn = function() {};
|
|
|
|
|
expect($rootScope.$eval('foo + "bar" + fn()')).toBe('bar');
|
|
|
|
|
}));
|
2014-12-05 22:00:11 -08:00
|
|
|
|
|
|
|
|
it('should treat properties named null/undefined as normal properties', inject(function($rootScope) {
|
2016-08-10 12:13:14 +02:00
|
|
|
expect($rootScope.$eval('a.null.undefined.b', {a:{null:{undefined:{b: 1}}}})).toBe(1);
|
2014-12-05 22:00:11 -08:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should not allow overriding null/undefined keywords', inject(function($rootScope) {
|
|
|
|
|
expect($rootScope.$eval('null.a', {null: {a: 42}})).toBeUndefined();
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should allow accessing null/undefined properties on `this`', inject(function($rootScope) {
|
|
|
|
|
$rootScope.null = {a: 42};
|
|
|
|
|
expect($rootScope.$eval('this.null.a')).toBe(42);
|
|
|
|
|
}));
|
2015-12-06 17:03:15 +01:00
|
|
|
|
|
|
|
|
it('should allow accessing $locals', inject(function($rootScope) {
|
|
|
|
|
$rootScope.foo = 'foo';
|
|
|
|
|
$rootScope.bar = 'bar';
|
|
|
|
|
$rootScope.$locals = 'foo';
|
|
|
|
|
var locals = {foo: 42};
|
|
|
|
|
expect($rootScope.$eval('$locals')).toBeUndefined();
|
|
|
|
|
expect($rootScope.$eval('$locals.foo')).toBeUndefined();
|
|
|
|
|
expect($rootScope.$eval('this.$locals')).toBe('foo');
|
|
|
|
|
expect(function() {
|
|
|
|
|
$rootScope.$eval('$locals = {}');
|
|
|
|
|
}).toThrow();
|
|
|
|
|
expect(function() {
|
|
|
|
|
$rootScope.$eval('$locals.bar = 23');
|
|
|
|
|
}).toThrow();
|
|
|
|
|
expect($rootScope.$eval('$locals', locals)).toBe(locals);
|
|
|
|
|
expect($rootScope.$eval('$locals.foo', locals)).toBe(42);
|
|
|
|
|
expect($rootScope.$eval('this.$locals', locals)).toBe('foo');
|
|
|
|
|
expect(function() {
|
|
|
|
|
$rootScope.$eval('$locals = {}', locals);
|
|
|
|
|
}).toThrow();
|
|
|
|
|
expect($rootScope.$eval('$locals.bar = 23', locals)).toEqual(23);
|
|
|
|
|
expect(locals.bar).toBe(23);
|
|
|
|
|
}));
|
2011-11-23 15:53:06 -08:00
|
|
|
});
|
2012-04-27 15:20:54 -07:00
|
|
|
});
|
2012-02-06 21:56:05 -08:00
|
|
|
});
|
2016-07-22 12:46:02 +03:00
|
|
|
|
|
|
|
|
forEach([true, false], function(cspEnabled) {
|
|
|
|
|
describe('custom identifiers (csp: ' + cspEnabled + ')', function() {
|
|
|
|
|
var isIdentifierStartRe = /[#a-z]/;
|
2016-11-25 14:54:49 +00:00
|
|
|
var isIdentifierContinueRe = /[-a-z]/;
|
2016-07-22 12:46:02 +03:00
|
|
|
var isIdentifierStartFn;
|
|
|
|
|
var isIdentifierContinueFn;
|
|
|
|
|
var scope;
|
|
|
|
|
|
|
|
|
|
beforeEach(module(function($parseProvider) {
|
|
|
|
|
isIdentifierStartFn = jasmine.
|
|
|
|
|
createSpy('isIdentifierStart').
|
|
|
|
|
and.callFake(function(ch, cp) { return isIdentifierStartRe.test(ch); });
|
|
|
|
|
isIdentifierContinueFn = jasmine.
|
|
|
|
|
createSpy('isIdentifierContinue').
|
|
|
|
|
and.callFake(function(ch, cp) { return isIdentifierContinueRe.test(ch); });
|
|
|
|
|
|
|
|
|
|
$parseProvider.setIdentifierFns(isIdentifierStartFn, isIdentifierContinueFn);
|
|
|
|
|
csp().noUnsafeEval = cspEnabled;
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
beforeEach(inject(function($rootScope) {
|
|
|
|
|
scope = $rootScope;
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should allow specifying a custom `isIdentifierStart/Continue` functions', function() {
|
|
|
|
|
scope.x = {};
|
|
|
|
|
|
|
|
|
|
scope['#foo'] = 'foo';
|
|
|
|
|
scope.x['#foo'] = 'foo';
|
|
|
|
|
expect(scope.$eval('#foo')).toBe('foo');
|
|
|
|
|
expect(scope.$eval('x.#foo')).toBe('foo');
|
|
|
|
|
|
|
|
|
|
scope['bar--'] = 42;
|
|
|
|
|
scope.x['bar--'] = 42;
|
|
|
|
|
expect(scope.$eval('bar--')).toBe(42);
|
|
|
|
|
expect(scope.$eval('x.bar--')).toBe(42);
|
|
|
|
|
expect(scope['bar--']).toBe(42);
|
|
|
|
|
expect(scope.x['bar--']).toBe(42);
|
|
|
|
|
|
|
|
|
|
scope['#-'] = 'baz';
|
|
|
|
|
scope.x['#-'] = 'baz';
|
|
|
|
|
expect(scope.$eval('#-')).toBe('baz');
|
|
|
|
|
expect(scope.$eval('x.#-')).toBe('baz');
|
|
|
|
|
|
|
|
|
|
expect(function() { scope.$eval('##'); }).toThrow();
|
|
|
|
|
expect(function() { scope.$eval('x.##'); }).toThrow();
|
|
|
|
|
|
|
|
|
|
expect(function() { scope.$eval('--'); }).toThrow();
|
|
|
|
|
expect(function() { scope.$eval('x.--'); }).toThrow();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should pass the character and codepoint to the custom functions', function() {
|
|
|
|
|
scope.$eval('#-');
|
|
|
|
|
expect(isIdentifierStartFn).toHaveBeenCalledOnceWith('#', '#'.charCodeAt(0));
|
|
|
|
|
expect(isIdentifierContinueFn).toHaveBeenCalledOnceWith('-', '-'.charCodeAt(0));
|
|
|
|
|
|
|
|
|
|
isIdentifierStartFn.calls.reset();
|
|
|
|
|
isIdentifierContinueFn.calls.reset();
|
|
|
|
|
|
|
|
|
|
scope.$eval('#.foo.#-.bar-');
|
|
|
|
|
expect(isIdentifierStartFn).toHaveBeenCalledTimes(7);
|
|
|
|
|
expect(isIdentifierStartFn.calls.allArgs()).toEqual([
|
|
|
|
|
['#', '#'.charCodeAt(0)],
|
|
|
|
|
['.', '.'.charCodeAt(0)],
|
|
|
|
|
['f', 'f'.charCodeAt(0)],
|
|
|
|
|
['.', '.'.charCodeAt(0)],
|
|
|
|
|
['#', '#'.charCodeAt(0)],
|
|
|
|
|
['.', '.'.charCodeAt(0)],
|
|
|
|
|
['b', 'b'.charCodeAt(0)]
|
|
|
|
|
]);
|
|
|
|
|
expect(isIdentifierContinueFn).toHaveBeenCalledTimes(9);
|
|
|
|
|
expect(isIdentifierContinueFn.calls.allArgs()).toEqual([
|
|
|
|
|
['.', '.'.charCodeAt(0)],
|
|
|
|
|
['o', 'o'.charCodeAt(0)],
|
|
|
|
|
['o', 'o'.charCodeAt(0)],
|
|
|
|
|
['.', '.'.charCodeAt(0)],
|
|
|
|
|
['-', '-'.charCodeAt(0)],
|
|
|
|
|
['.', '.'.charCodeAt(0)],
|
|
|
|
|
['a', 'a'.charCodeAt(0)],
|
|
|
|
|
['r', 'r'.charCodeAt(0)],
|
|
|
|
|
['-', '-'.charCodeAt(0)]
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-10-07 18:27:20 +02:00
|
|
|
|
|
|
|
|
describe('hidden/unsupported features', function() {
|
|
|
|
|
describe('$$getAst()', function() {
|
|
|
|
|
it('should be a method exposed on the `$parse` service', inject(function($parse) {
|
|
|
|
|
expect(isFunction($parse.$$getAst)).toBeTruthy();
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should accept a string expression argument and return the corresponding AST', inject(function($parse) {
|
|
|
|
|
var ast = $parse.$$getAst('foo.bar');
|
|
|
|
|
expect(ast).toEqual({
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
property: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
computed: false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
it('should parse one time binding expressions', inject(function($parse) {
|
|
|
|
|
var ast = $parse.$$getAst('::foo.bar');
|
|
|
|
|
expect(ast).toEqual({
|
|
|
|
|
type: 'Program',
|
|
|
|
|
body: [
|
|
|
|
|
{
|
|
|
|
|
type: 'ExpressionStatement',
|
|
|
|
|
expression: {
|
|
|
|
|
type: 'MemberExpression',
|
|
|
|
|
object: { type: 'Identifier', name: 'foo' },
|
|
|
|
|
property: { type: 'Identifier', name: 'bar' },
|
|
|
|
|
computed: false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
});
|
2010-10-15 13:44:53 -07:00
|
|
|
});
|