Files
owl/tests/compiler/inline_expressions.test.ts
T
2021-11-15 09:35:13 +01:00

215 lines
8.4 KiB
TypeScript

import { compileExpr, tokenize } from "../../src/compiler/inline_expressions";
describe("tokenizer", () => {
test("simple tokens", () => {
expect(tokenize("1.3")).toEqual([{ type: "VALUE", value: "1.3" }]);
expect(tokenize("{}")).toEqual([
{ type: "LEFT_BRACE", value: "{" },
{ type: "RIGHT_BRACE", value: "}" },
]);
expect(tokenize("{ }}")).toEqual([
{ type: "LEFT_BRACE", value: "{" },
{ type: "RIGHT_BRACE", value: "}" },
{ type: "RIGHT_BRACE", value: "}" },
]);
expect(tokenize("a")).toEqual([{ type: "SYMBOL", value: "a" }]);
expect(tokenize("true")).toEqual([{ type: "SYMBOL", value: "true" }]);
expect(tokenize("abcde")).toEqual([{ type: "SYMBOL", value: "abcde" }]);
expect(tokenize("_ab2")).toEqual([{ type: "SYMBOL", value: "_ab2" }]);
expect(tokenize("$ab2")).toEqual([{ type: "SYMBOL", value: "$ab2" }]);
expect(tokenize("ABC")).toEqual([{ type: "SYMBOL", value: "ABC" }]);
expect(tokenize("{a: 2}")).toEqual([
{ type: "LEFT_BRACE", value: "{" },
{ type: "SYMBOL", value: "a" },
{ type: "COLON", value: ":" },
{ type: "VALUE", value: "2" },
{ type: "RIGHT_BRACE", value: "}" },
]);
expect(tokenize("a,")).toEqual([
{ type: "SYMBOL", value: "a" },
{ type: "COMMA", value: "," },
]);
expect(tokenize("][")).toEqual([
{ type: "RIGHT_BRACKET", value: "]" },
{ type: "LEFT_BRACKET", value: "[" },
]);
});
test("various operators", () => {
expect(tokenize(">= <= < > !== !=")).toEqual([
{ type: "OPERATOR", value: ">=" },
{ type: "OPERATOR", value: "<=" },
{ type: "OPERATOR", value: "<" },
{ type: "OPERATOR", value: ">" },
{ type: "OPERATOR", value: "!==" },
{ type: "OPERATOR", value: "!=" },
]);
expect(tokenize("typeof a")).toEqual([
{ type: "OPERATOR", value: "typeof " },
{ type: "SYMBOL", value: "a" },
]);
expect(tokenize("a...1")).toEqual([
{ type: "SYMBOL", value: "a" },
{ type: "OPERATOR", value: "..." },
{ type: "VALUE", value: "1" },
]);
expect(tokenize("a in b")).toEqual([
{ type: "SYMBOL", value: "a" },
{ type: "OPERATOR", value: "in " },
{ type: "SYMBOL", value: "b" },
]);
});
test("strings", () => {
expect(() => tokenize("'")).toThrow("Invalid expression");
expect(() => tokenize("'\\")).toThrow("Invalid expression");
expect(() => tokenize("'\\'")).toThrow("Invalid expression");
expect(tokenize("'hello ged'")).toEqual([{ type: "VALUE", value: "'hello ged'" }]);
expect(tokenize("'hello \\'ged\\''")).toEqual([{ type: "VALUE", value: "'hello \\'ged\\''" }]);
expect(() => tokenize('"')).toThrow("Invalid expression");
expect(() => tokenize('"\\"')).toThrow("Invalid expression");
expect(tokenize('"hello ged"')).toEqual([{ type: "VALUE", value: '"hello ged"' }]);
expect(tokenize('"hello ged"}')).toEqual([
{ type: "VALUE", value: '"hello ged"' },
{ type: "RIGHT_BRACE", value: "}" },
]);
expect(tokenize('"hello \\"ged\\""')).toEqual([{ type: "VALUE", value: '"hello \\"ged\\""' }]);
});
});
describe("expression evaluation", () => {
test("simple static values", () => {
expect(compileExpr("1")).toBe("1");
expect(compileExpr("1 ")).toBe("1");
expect(compileExpr("'some string#/, {' ")).toBe("'some string#/, {'");
expect(compileExpr("{ } ")).toBe("{}");
expect(compileExpr("{a: 1} ")).toBe("{a:1}");
expect(compileExpr("{a: 1, b: 2 } ")).toBe("{a:1,b:2}");
expect(compileExpr("[] ")).toBe("[]");
expect(compileExpr("[1] ")).toBe("[1]");
expect(compileExpr("['1', '2'] ")).toBe("['1','2']");
expect(compileExpr("['1', \"2\"] ")).toBe("['1',\"2\"]");
});
test("various types of 'words'", () => {
expect(compileExpr("true")).toBe("true");
expect(compileExpr("false")).toBe("false");
expect(compileExpr("debugger")).toBe("debugger");
});
test("parenthesis", () => {
expect(compileExpr("(1)")).toBe("(1)");
expect(compileExpr("a*(1 +3)")).toBe("ctx['a']*(1+3)");
});
test("objects and sub objects", () => {
expect(compileExpr("{a:{b:1}} ")).toBe("{a:{b:1}}");
});
test("arrays and objects", () => {
expect(compileExpr("[{b:1}] ")).toBe("[{b:1}]");
expect(compileExpr("{a: []} ")).toBe("{a:[]}");
expect(compileExpr("[{b:1, c: [1, {d: {e: 3}} ]}] ")).toBe("[{b:1,c:[1,{d:{e:3}}]}]");
});
test("dot operator", () => {
expect(compileExpr("a.b")).toBe("ctx['a'].b");
expect(compileExpr("a.b.c")).toBe("ctx['a'].b.c");
});
test("various unary operators", () => {
expect(compileExpr("!flag")).toBe("!ctx['flag']");
expect(compileExpr("-3")).toBe("-3");
expect(compileExpr("-a")).toBe("-ctx['a']");
expect(compileExpr("typeof a")).toBe("typeof ctx['a']");
});
test("various binary operators", () => {
expect(compileExpr("color == 'black'")).toBe("ctx['color']=='black'");
expect(compileExpr("a || b")).toBe("ctx['a']||ctx['b']");
expect(compileExpr("color === 'black'")).toBe("ctx['color']==='black'");
expect(compileExpr("'li_'+item")).toBe("'li_'+ctx['item']");
expect(compileExpr("state.val > 1")).toBe("ctx['state'].val>1");
expect(compileExpr("a in b")).toBe("ctx['a']in ctx['b']");
});
test("boolean operations", () => {
expect(compileExpr("a && b")).toBe("ctx['a']&&ctx['b']");
});
test("ternary operators", () => {
expect(compileExpr("a ? b: '2'")).toBe("ctx['a']?ctx['b']:'2'");
expect(compileExpr("a ? b: (c or '2') ")).toBe("ctx['a']?ctx['b']:(ctx['c']||'2')");
expect(compileExpr("a ? {test:c}: [1,u]")).toBe("ctx['a']?{test:ctx['c']}:[1,ctx['u']]");
});
test("word replacement", () => {
expect(compileExpr("a or b")).toBe("ctx['a']||ctx['b']");
expect(compileExpr("a and b")).toBe("ctx['a']&&ctx['b']");
});
test("function calls", () => {
expect(compileExpr("a()")).toBe("ctx['a']()");
expect(compileExpr("a(1)")).toBe("ctx['a'](1)");
expect(compileExpr("a(1,2)")).toBe("ctx['a'](1,2)");
expect(compileExpr("a(1,2,{a:[a]})")).toBe("ctx['a'](1,2,{a:[ctx['a']]})");
expect(compileExpr("'x'.toUpperCase()")).toBe("'x'.toUpperCase()");
expect(compileExpr("'x'.toUpperCase({a: 3})")).toBe("'x'.toUpperCase({a:3})");
});
test("arrow functions", () => {
expect(compileExpr("list.map(e => e.val)")).toBe("ctx['list'].map(e=>e.val)");
expect(compileExpr("list.map(e => a + e)")).toBe("ctx['list'].map(e=>ctx['a']+e)");
expect(compileExpr("list.map((e) => e)")).toBe("ctx['list'].map((e)=>e)");
expect(compileExpr("list.map((elem, index) => elem + index)")).toBe(
"ctx['list'].map((elem,index)=>elem+index)"
);
expect(compileExpr("(ev => ev)(e)")).toBe("(ev=>ev)(ctx['e'])");
});
test.skip("arrow functions: not yet supported", () => {
// e is added to localvars in inline_expression but not removed after the arrow func body
expect(compileExpr("(e => e)(e)")).toBe("(e=>e)(ctx['e'])");
});
test("assignation", () => {
expect(compileExpr("a = b")).toBe("ctx['a']=ctx['b']");
expect(compileExpr("a += b")).toBe("ctx['a']+=ctx['b']");
expect(compileExpr("a -= b")).toBe("ctx['a']-=ctx['b']");
expect(compileExpr("a.b = !a.b")).toBe("ctx['a'].b=!ctx['a'].b");
});
test("spread operator", () => {
expect(compileExpr("[...state.list]")).toBe("[...ctx['state'].list]");
expect(compileExpr("f(...state.list)")).toBe("ctx['f'](...ctx['state'].list)");
expect(compileExpr("f([...list])")).toBe("ctx['f']([...ctx['list']])");
});
test("works with builtin properties", () => {
expect(compileExpr("state.constructor.name")).toBe("ctx['state'].constructor.name");
});
test("works with shortcut object key description", () => {
expect(compileExpr("{a}")).toBe("{a:ctx['a']}");
expect(compileExpr("{a,b}")).toBe("{a:ctx['a'],b:ctx['b']}");
expect(compileExpr("{a,b:3,c}")).toBe("{a:ctx['a'],b:3,c:ctx['c']}");
});
test("template strings", () => {
expect(compileExpr("`hey`")).toBe("`hey`");
expect(compileExpr("`hey ${you}`")).toBe("`hey ${ctx['you']}`");
expect(compileExpr("`hey ${1 + 2}`")).toBe("`hey ${1+2}`");
});
test("works with short object description and lists ", () => {
expect(compileExpr("[a, b]")).toBe("[ctx['a'],ctx['b']]");
expect(compileExpr("[a, b, c]")).toBe("[ctx['a'],ctx['b'],ctx['c']]");
expect(compileExpr("[a, {b, c},d]")).toBe("[ctx['a'],{b:ctx['b'],c:ctx['c']},ctx['d']]");
expect(compileExpr("{a:[b, {c, d: e}]}")).toBe("{a:[ctx['b'],{c:ctx['c'],d:ctx['e']}]}");
});
});