UNPKG

@networkteam/eel

Version:

Embedded expression language, a parser and compiler for a safe subset of JavaScript for dynamic evaluation in JavaScript.

312 lines (267 loc) 11.2 kB
import compile, { parse } from '../src/compile.js'; import assert from 'assert'; describe('eel', function() { describe('number literals', function() { describe('integer without sign', function() { it('should be parsed to literal value', function() { const code = parse('42'); assert.strictEqual(code, '42'); }); it('should parse and evaluate to literal value', function() { const fn = compile('42'); const ctx = {}; assert.strictEqual(fn(ctx), 42); }); }); describe('negative integer', function() { it('should be parsed to literal value', function() { const code = parse('-1'); assert.strictEqual(code, '-1'); }); it('should parse and evaluate to literal value', function() { const fn = compile('-1'); const ctx = {}; assert.strictEqual(fn(ctx), -1); }); }); }); describe('string literals', function() { describe('double quoted', function() { it('should be parsed to literal value', function() { const code = parse(`"foo"`); assert.strictEqual(code, `"foo"`); }); it('should parse and evaluate to literal value', function() { const fn = compile(`"foo"`); const ctx = {}; assert.strictEqual(fn(ctx), `foo`); }); describe('with escapes', function() { it('should parse and evaluate to literal value with escape sequences', function() { const fn = compile(`"foo\\"next'thingy'"`); const ctx = {}; assert.strictEqual(fn(ctx), `foo"next'thingy'`); }); it('should parse and evaluate newlines', function() { const fn = compile(`"multi\\nline\\ntext"`); const ctx = {}; assert.strictEqual(fn(ctx), `multi\nline\ntext`); }); }) }); describe('single quoted', function() { it('should be parsed to literal value', function() { const code = parse(`'foo'`); assert.strictEqual(code, `'foo'`); }); it('should parse and evaluate to literal value', function() { const fn = compile(`'foo'`); const ctx = {}; assert.strictEqual(fn(ctx), `foo`); }); describe('with escapes', function() { it('should parse and evaluate to literal value with escape sequences', function() { const fn = compile(`'foo\\'next"thingy"'`); const ctx = {}; assert.strictEqual(fn(ctx), `foo'next"thingy"`); }); it('should parse and evaluate newlines', function() { const fn = compile(`'multi\\nline\\ntext'`); const ctx = {}; assert.strictEqual(fn(ctx), `multi\nline\ntext`); }); }) }); }); describe('variables', function() { it('should be parsed to helper call', function() { const code = parse('myVar'); assert.strictEqual(code, `helper.val("myVar")(ctx)`); }); it('should parse and evaluate to variable in context', function() { const fn = compile('myVar'); const ctx = {myVar: 'foo'}; assert.strictEqual(fn(ctx), ctx.myVar); }); }); describe('property access', function() { it('should be parsed to helper call', function() { const code = parse('myObj.myProp'); assert.strictEqual(code, `helper.val("myProp")(helper.val("myObj")(ctx))`); }); it('should parse and evaluate to property of variable in context', function() { const fn = compile('myObj.myProp'); const ctx = {myObj: {myProp: 'foo'}}; assert.strictEqual(fn(ctx), ctx.myObj.myProp); }); it('should ignore undefined properties in path', function() { const fn = compile('myObj.noKey.otherProp'); const ctx = {myObj: {myProp: 'foo'}}; assert.strictEqual(fn(ctx), undefined); }); }); describe('offset access with string', function() { it('should be parsed to helper call', function() { const code = parse('myObj["myProp"]'); assert.strictEqual(code, `helper.val("myProp")(helper.val("myObj")(ctx))`); }); it('should parse and evaluate to property of variable in context', function() { const fn = compile('myObj["myProp"]'); const ctx = {myObj: {myProp: 'foo'}}; assert.strictEqual(fn(ctx), ctx.myObj.myProp); }); it('should ignore undefined properties in path', function() { const fn = compile('myObj["noKey"]["otherProp"]'); const ctx = {myObj: {myProp: 'foo'}}; assert.strictEqual(fn(ctx), undefined); }); }); describe('boolean operators', function() { describe('conjunction', function() { it('should be parsed to helper call', function() { const code = parse('varA &&varB'); assert.strictEqual(code, `helper.val("varA")(ctx)&&helper.val("varB")(ctx)`); }); it('should parse and short circuit evaluate to operand', function() { const fn = compile('varA &&varB'); assert.strictEqual(fn({varA: 'x', varB: 0}), 0); assert.strictEqual(fn({varA: 'x', varB: 1}), 1); assert.strictEqual(fn({varA: 0, varB: 'foo'}), 0); }); it('should support multiple conjunctions', function() { const fn = compile('varA &&varB&& varC'); assert.strictEqual(fn({varA: 'x', varB: 0, varC: 1}), 0); assert.strictEqual(fn({varA: 'x', varB: 42, varC: false}), false); assert.strictEqual(fn({varA: 'x', varB: 42, varC: true}), true); }); }); describe('disjunction', function() { it('should be parsed to helper call', function() { const code = parse('varA ||varB'); assert.strictEqual(code, `helper.val("varA")(ctx)||helper.val("varB")(ctx)`); }); it('should parse and short circuit evaluate to operand', function() { const fn = compile('varA ||varB'); assert.strictEqual(fn({varA: 'x', varB: 0}), 'x'); assert.strictEqual(fn({varA: 'x', varB: 1}), 'x'); assert.strictEqual(fn({varA: 0, varB: 'foo'}), 'foo'); }); it('should support multiple disjunctions', function() { const fn = compile('varA ||varB|| varC'); assert.strictEqual(fn({varA: 'x', varB: 0, varC: 1}), 'x'); assert.strictEqual(fn({varA: 42, varB: 42, varC: false}), 42); assert.strictEqual(fn({varA: false, varB: 42, varC: true}), 42); assert.strictEqual(fn({varA: false, varB: 0, varC: ''}), ''); }); }); describe('negation', function() { describe('with variable', function() { it('should parse and evaluate', function() { const fn = compile('!varA'); assert.strictEqual(fn({varA: 'x'}), false); }); }); describe('double negation', function() { it('should parse and evaluate', function() { const fn = compile('!!varA'); assert.strictEqual(fn({varA: 'x'}), true); }); }); }); describe('mixed boolean operators', function() { describe('with disjunction and conjunction 1', function() { it('should parse and evaluate with operator precedence', function() { const fn = compile('!varA || varB && varC'); assert.strictEqual(fn({varA: 'x', varB: 42, varC: 0}), 0); assert.strictEqual(fn({varA: 0, varB: 42, varC: false}), true); }); }); }); describe('comparison', function() { describe('equality', function() { it('should parse and evaluate', function() { const fn = compile('varA == 42'); assert.strictEqual(fn({varA: 42}), true); assert.strictEqual(fn({varA: 23}), false); }); }); describe('inequality', function() { it('should parse and evaluate', function() { const fn = compile('varA != 42'); assert.strictEqual(fn({varA: 42}), false); assert.strictEqual(fn({varA: 23}), true); }); }); }); describe('comparison and boolean operators', function() { it('should parse and evaluate', function() { const fn = compile('(varA == 42 || varA < 10) && !varB'); assert.strictEqual(fn({varA: 42, varB: 'x'}), false); assert.strictEqual(fn({varA: 42, varB: 0}), true); assert.strictEqual(fn({varA: 33, varB: 0}), false); assert.strictEqual(fn({varA: 5, varB: 0}), true); }); it('should parse and evaluate with operator precedence', function() { const fn = compile('varA == 42 || varA < 10 && !varB'); assert.strictEqual(fn({varA: 42, varB: 'x'}), true); assert.strictEqual(fn({varA: 42, varB: 0}), true); assert.strictEqual(fn({varA: 33, varB: 0}), false); assert.strictEqual(fn({varA: 5, varB: 0}), true); }); }); }); describe('conditionals', function() { it('should be parsed to helper call', function() { const code = parse('x > 1 ? (x - 1) : 0'); assert.strictEqual(code, `helper.val("x")(ctx)>1?(helper.val("x")(ctx)-1):0`); }); it('should evaluate conditional', function() { const fn = compile('x > 1 ? (x - 1) * 2 : 0'); assert.strictEqual(fn({x: 42}), 82); assert.strictEqual(fn({x: 0}), 0); assert.strictEqual(fn({}), 0); }); }); describe('function calls', function() { it('should be parsed to helper call', function() { const code = parse('myFunc(varA)'); assert.strictEqual(code, `helper.call("myFunc", [helper.val("varA")(ctx)])(ctx)`); }); it('should parse and evaluate', function() { const fn = compile('myFunc(varA)'); assert.strictEqual(fn({myFunc: x => x * 2, varA: 21}), 42); }); it('should allow whitespace after identifier', function() { const fn = compile('myFunc (varA)'); assert.strictEqual(fn({myFunc: x => x * 2, varA: 21}), 42); }); it('should support empty arguments', function() { const fn = compile('myFunc()'); assert.strictEqual(fn({myFunc: () => 42}), 42); }); it('should support multiple arguments', function() { const fn = compile('myFunc(2, 3)'); assert.strictEqual(fn({myFunc: (x, y) => x * y}), 6); }); it('should support many arguments', function() { const fn = compile('myFunc(1, 2, 3, 4)'); assert.deepEqual(fn({myFunc: (...args) => args}), [1, 2, 3, 4]); }); describe('on property access', function() { it('should be parsed to helper call', function() { const code = parse('myObj.myFunc(varA)'); assert.strictEqual(code, `helper.call("myFunc", [helper.val("varA")(ctx)])(helper.val("myObj")(ctx))`); }); }); describe('on offset access with property access', function() { it('should be parsed to helper call', function() { const code = parse('myObj["x"].myFunc(varA).foo'); assert.strictEqual(code, `helper.val("foo")(helper.call("myFunc", [helper.val("varA")(ctx)])(helper.val("x")(helper.val("myObj")(ctx))))`); }); it('should parse and evaluate', function() { const fn = compile('myObj["x"].myFunc(varA).foo'); assert.strictEqual(fn({myObj: {x: { myFunc: x => ({foo: x*2})}}, varA: 21}), 42); }); }); }); });