From d2085744dcc7b40d211ee0a6d965841998c12dc9 Mon Sep 17 00:00:00 2001 From: Emiel Wit Date: Mon, 8 Apr 2024 14:16:45 +0200 Subject: [PATCH] feat: json stringify object and array values in expression version 1.2 --- __tests__/expression/1.2/index.test.js | 182 +++++++++++++++++++++++++ blocks/expression.json | 2 +- functions/expression/1.2/function.json | 55 ++++++++ functions/expression/1.2/index.js | 22 +++ 4 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 __tests__/expression/1.2/index.test.js create mode 100644 functions/expression/1.2/function.json create mode 100644 functions/expression/1.2/index.js diff --git a/__tests__/expression/1.2/index.test.js b/__tests__/expression/1.2/index.test.js new file mode 100644 index 00000000..05d553f5 --- /dev/null +++ b/__tests__/expression/1.2/index.test.js @@ -0,0 +1,182 @@ +import expression from '../../../functions/expression/1.2'; + +describe('Expression', () => { + const name = 'John Doe'; + const age = '17'; + test('It handles a expression with input variables', async () => { + const result = await expression({ + expression: '"{{name}}"', + variables: [{ key: 'name', value: name }], + }); + expect(result).toEqual({ result: name }); + }); + + test('It evaluate two string values', async () => { + const result = await expression({ + expression: "'{{first_name}}' + ' ' + '{{last_name}}'", + variables: [ + { key: 'first_name', value: 'John' }, + { key: 'last_name', value: 'Doe' }, + ], + }); + + expect(result).toEqual({ result: 'John Doe' }); + }); + + test('It evaluate a ternary expression', async () => { + const result = await expression({ + expression: '{{age}} > 17 ? true : false', + variables: [{ key: 'age', value: age }], + }); + + expect(result).toEqual({ result: false }); + }); + + test('It evaluate counting two numbers', async () => { + const result = await expression({ + expression: '{{number1}} + {{number2}}', + variables: [ + { key: 'number1', value: '1' }, + { key: 'number2', value: '1' }, + ], + }); + + expect(result).toEqual({ result: 2 }); + }); + + test('It respects pipelines', async () => { + const result = await expression({ + expression: '"hello || world"', + variables: [], + }); + + expect(result).toEqual({ result: 'hello || world' }); + }); + + test('It respects interpolating falsy values', async () => { + const result = await expression({ + expression: '{{count}} || 1', + variables: [{ key: 'count', value: 0 }], + }); + + expect(result).toEqual({ result: 1 }); + }); + + test('It can loop through arrays', async () => { + const result = await expression({ + expression: '"{{#names}}{{.}}, {{/names}}"', + variables: [{ key: 'names', value: ['John', 'Jane'] }], + }); + + expect(result).toEqual({ result: 'John, Jane, ' }); + }); + + test('It handles arrays', async () => { + const result = await expression({ + expression: '{{{names}}}', + variables: [{ key: 'names', value: ['John', 'Jane'] }], + }); + + expect(result).toEqual({ result: ['John', 'Jane'] }); + }); + + test('It maps through an array of objects', async () => { + const result = await expression({ + expression: '{{{names}}}.map((name) => name.value)', + variables: [ + { + key: 'names', + value: [ + { + id: 1, + label: 'Hello', + index: 1, + value: 'World', + }, + { + id: 2, + label: 'Foo', + index: 2, + value: 'Bar', + }, + { + id: 3, + label: 'Baz', + index: 3, + value: 'Qux', + }, + { + id: 4, + label: 'Quux', + index: 4, + value: 'Corge', + }, + { + id: 5, + label: 'Grault', + index: 5, + value: 'Garply', + }, + { + id: 6, + label: 'Waldo', + index: 6, + value: 'Fred', + }, + { + id: 7, + label: 'Plugh', + index: 7, + value: 'Xyzzy', + }, + { + id: 8, + label: 'Thud', + index: 8, + value: 'Blah', + }, + ], + }, + ], + }); + + expect(result).toEqual({ + result: [ + 'World', + 'Bar', + 'Qux', + 'Corge', + 'Garply', + 'Fred', + 'Xyzzy', + 'Blah', + ], + }); + }); + + test('It handles objects', async () => { + const result = await expression({ + expression: '{{{object}}}', + variables: [ + { + key: 'object', + value: { + id: 1, + label: 'Hello', + index: 1, + value: 'World', + }, + }, + ], + }); + + expect(result).toEqual({ + result: { + id: 1, + label: 'Hello', + index: 1, + value: 'World', + }, + }); + }); +}); diff --git a/blocks/expression.json b/blocks/expression.json index 0a5e4480..de54327e 100644 --- a/blocks/expression.json +++ b/blocks/expression.json @@ -1,5 +1,5 @@ { "dependencies": [], - "functions": ["expression 1.1"], + "functions": ["expression 1.2"], "includes": ["functions/utils"] } diff --git a/functions/expression/1.2/function.json b/functions/expression/1.2/function.json new file mode 100644 index 00000000..c97a73ab --- /dev/null +++ b/functions/expression/1.2/function.json @@ -0,0 +1,55 @@ +{ + "description": "Evaluate a javascript expression (use {{name_of_your_value}} to interpolate values).", + "label": "Expression", + "category": "Misc", + "icon": { + "name": "ExpressionIcon", + "color": "Orange" + }, + "options": [ + { + "meta": { + "type": "MultilineText", + "validations": { "required": true } + }, + "name": "expression", + "label": "Expression", + "configuration": { + "placeholder": "\"{{first_name}}\" + \" \" + \"{{last_name}}\"" + } + }, + { + "info": "Map the values that you want to use in your expression.", + "label": "Variables", + "meta": { + "type": "Map" + }, + "name": "variables" + }, + { + "info": "The result of the expression.", + "meta": { + "type": "Output", + "validations": { + "required": true + }, + "output": { + "anyOf": [ + { + "type": "Text" + }, + { + "type": "Boolean" + }, + { + "type": "Number" + } + ] + } + }, + "name": "result", + "label": "Result" + } + ], + "yields": "NONE" +} diff --git a/functions/expression/1.2/index.js b/functions/expression/1.2/index.js new file mode 100644 index 00000000..d10db093 --- /dev/null +++ b/functions/expression/1.2/index.js @@ -0,0 +1,22 @@ +import templayed from '../../utils/templayed'; + +const isObjectOrArray = (value) => typeof value === 'object' && value !== null; + +const expression = async ({ expression: expres, variables }) => { + const variableMap = variables.reduce( + (previousValue, currentValue) => ({ + ...previousValue, + [currentValue.key]: isObjectOrArray(currentValue.value) + ? JSON.stringify(currentValue.value) + : currentValue.value, + }), + {}, + ); + + return { + // eslint-disable-next-line no-new-func + result: new Function(`return ${templayed(expres)(variableMap)}`)(), + }; +}; + +export default expression;