mozjexl
Version:
Javascript Expression Language: Powerful context-based expression parser and evaluator
180 lines (174 loc) • 6.17 kB
JavaScript
/*
* Jexl
* Copyright (c) 2015 TechnologyAdvice
*/
var chai = require('chai'),
chaiAsPromised = require('chai-as-promised'),
should = require('chai').should(),
Lexer = require('../../lib/Lexer'),
Parser = require('../../lib/parser/Parser'),
Evaluator = require('../../lib/evaluator/Evaluator'),
grammar = require('../../lib/grammar').elements;
chai.use(chaiAsPromised);
var lexer = new Lexer(grammar);
function toTree(exp) {
var p = new Parser(grammar);
p.addTokens(lexer.tokenize(exp));
return p.complete();
}
describe('Evaluator', function() {
it('should evaluate an arithmetic expression', function() {
const e = new Evaluator(grammar);
return e.eval(toTree('(2 + 3) * 4')).should.become(20);
});
it('should evaluate a string concat', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('"Hello" + (4+4) + "Wo\\"rld"'))
.should.become('Hello8Wo"rld');
});
it('should evaluate a true comparison expression', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('2 > 1')).should.become(true);
});
it('should evaluate a false comparison expression', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('2 <= 1')).should.become(false);
});
it('should evaluate a complex expression', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('"foo" && 6 >= 6 && 0 + 1 && true'))
.should.become(true);
});
it('should evaluate an identifier chain', function() {
var context = {foo: {baz: {bar: 'tek'}}},
e = new Evaluator(grammar, null, context);
return e.eval(toTree('foo.baz.bar'))
.should.become(context.foo.baz.bar);
});
it('should apply transforms', function() {
var context = {foo: 10},
half = function(val) {
return val / 2;
},
e = new Evaluator(grammar, {half: half}, context);
return e.eval(toTree('foo|half + 3')).should.become(8);
});
it('should filter arrays', function() {
var context = {foo: {bar: [
{tek: 'hello'},
{tek: 'baz'},
{tok: 'baz'}
]}},
e = new Evaluator(grammar, null, context);
return e.eval(toTree('foo.bar[.tek == "baz"]'))
.should.eventually.deep.equal([{tek: 'baz'}]);
});
it('should assume array index 0 when traversing', function() {
var context = {foo: {bar: [
{tek: {hello: 'world'}},
{tek: {hello: 'universe'}}
]}},
e = new Evaluator(grammar, null, context);
return e.eval(toTree('foo.bar.tek.hello')).should.become('world');
});
it('should make array elements addressable by index', function() {
var context = {foo: {bar: [
{tek: 'tok'},
{tek: 'baz'},
{tek: 'foz'}
]}},
e = new Evaluator(grammar, null, context);
return e.eval(toTree('foo.bar[1].tek')).should.become('baz');
});
it('should allow filters to select object properties', function() {
var context = {foo: {baz: {bar: 'tek'}}},
e = new Evaluator(grammar, null, context);
return e.eval(toTree('foo["ba" + "z"].bar'))
.should.become(context.foo.baz.bar);
});
it('should allow simple filters on undefined objects', function() {
var context = {foo: {}},
e = new Evaluator(grammar, null, context);
return e.eval(toTree('foo.bar["baz"].tok'))
.should.become(undefined);
});
it('should allow complex filters on undefined objects', function() {
var context = {foo: {}},
e = new Evaluator(grammar, null, context);
return e.eval(toTree('foo.bar[.size > 1].baz'))
.should.become(undefined);
});
it('should throw when transform does not exist', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('"hello"|world')).should.reject;
});
it('should apply the DivFloor operator', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('7 // 2')).should.become(3);
});
it('should evaluate an object literal', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('{foo: {bar: "tek"}}'))
.should.eventually.deep.equal({foo: {bar: 'tek'}});
});
it('should evaluate an empty object literal', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('{}'))
.should.eventually.deep.equal({});
});
it('should evaluate a transform with multiple args', function() {
var e = new Evaluator(grammar, {
concat: function(val, a1, a2, a3) {
return val + ": " + a1 + a2 + a3;
}
});
return e.eval(toTree('"foo"|concat("baz", "bar", "tek")'))
.should.become('foo: bazbartek');
});
it('should evaluate dot notation for object literals', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('{foo: "bar"}.foo')).should.become('bar');
});
it('should allow access to literal properties', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('"foo".length')).should.become(3);
});
it('should evaluate array literals', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('["foo", 1+2]'))
.should.eventually.deep.equal(["foo", 3]);
});
it('should apply the "in" operator to strings', function() {
var e = new Evaluator(grammar);
return Promise.all([
e.eval(toTree('"bar" in "foobartek"')).should.become(true),
e.eval(toTree('"baz" in "foobartek"')).should.become(false)
]);
});
it('should apply the "in" operator to arrays', function() {
var e = new Evaluator(grammar);
return Promise.all([
e.eval(toTree('"bar" in ["foo","bar","tek"]')).should.become(true),
e.eval(toTree('"baz" in ["foo","bar","tek"]')).should.become(false)
]);
});
it('should evaluate a conditional expression', function() {
var e = new Evaluator(grammar);
return Promise.all([
e.eval(toTree('"foo" ? 1 : 2')).should.become(1),
e.eval(toTree('"" ? 1 : 2')).should.become(2)
]);
});
it('should allow missing consequent in ternary', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('"foo" ?: "bar"')).should.become("foo");
});
it('does not treat falsey properties as undefined', function() {
const e = new Evaluator(grammar);
return e.eval(toTree('"".length')).should.become(0);
});
it('should handle an expression with arbitrary whitespace', function() {
var e = new Evaluator(grammar);
return e.eval(toTree('(\t2\n+\n3) *\n4\n\r\n')).should.become(20);
});
});