diff --git a/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js b/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js index f9157d738..77a8074e6 100644 --- a/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js +++ b/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js @@ -3538,7 +3538,43 @@ describe('properties panel', function() { 'General': [ 'Key', 'Target value', - 'Compute on' + 'Compute on', + "Do not submit the expression's result with the form submission" + ], + 'Condition': [ + 'Deactivate if' + ], + 'Layout': [ + 'Columns' + ], + 'Custom properties': [] + }); + + }); + + }); + + + describe('js function field', function() { + + it('entries', function() { + + // given + const field = schema.components.find(({ type }) => type === 'script'); + + bootstrapPropertiesPanel({ + container, + field + }); + + // then + expectPanelStructure(container, { + 'General': [ + 'Key', + 'Function parameters', + 'Javascript code', + 'Compute on', + "Do not submit the function's result with the form submission", ], 'Condition': [ 'Deactivate if' diff --git a/packages/form-js-editor/test/spec/form.json b/packages/form-js-editor/test/spec/form.json index 62589717b..5fb0f3908 100644 --- a/packages/form-js-editor/test/spec/form.json +++ b/packages/form-js-editor/test/spec/form.json @@ -3,6 +3,15 @@ "id": "Form_1", "type": "default", "components": [ + { + "id": "Script_1", + "type": "script", + "key": "script", + "jsFunction": "return [\"reading\", \"swimming\", \"running\"];", + "functionParameters": "={}", + "computeOn": "interval", + "interval": 1000 + }, { "id": "ExpressionField_1", "type": "expression", diff --git a/packages/form-js-playground/test/spec/form.json b/packages/form-js-playground/test/spec/form.json index b507bed89..3daeef092 100644 --- a/packages/form-js-playground/test/spec/form.json +++ b/packages/form-js-playground/test/spec/form.json @@ -1,6 +1,14 @@ { "$schema": "../../../form-json-schema/resources/schema.json", "components": [ + { + "type": "script", + "key": "otherHobbies", + "jsFunction": "return [\"reading\", \"swimming\", \"running\"];", + "functionParameters": "={}", + "computeOn": "change", + "interval": 1000 + }, { "type": "expression", "key": "expressionResult", diff --git a/packages/form-js-viewer/test/spec/render/components/form-fields/JSFunctionField.spec.js b/packages/form-js-viewer/test/spec/render/components/form-fields/JSFunctionField.spec.js new file mode 100644 index 000000000..e15ca6398 --- /dev/null +++ b/packages/form-js-viewer/test/spec/render/components/form-fields/JSFunctionField.spec.js @@ -0,0 +1,163 @@ +import { + render +} from '@testing-library/preact/pure'; + +import { JSFunctionField } from '../../../../../src/render/components/form-fields/JSFunctionField'; + +import { MockFormContext } from '../helper'; + +import { act } from 'preact/test-utils'; + +import { + createFormContainer +} from '../../../../TestHelper'; + +let container; + +describe('JSFunctionField', function() { + + beforeEach(function() { + container = createFormContainer(); + }); + + + afterEach(function() { + container.remove(); + }); + + + it('should evaluate with setValue', async function() { + + // given + const onChangeSpy = sinon.spy(); + const field = defaultField; + const passedData = { value : 42 }; + + const services = { + expressionLanguage: { + isExpression: () => true, + evaluate: () => { + return passedData; + } + } + }; + + // when + act(() => { + createJSFunctionField({ field, onChange: onChangeSpy, services }); + }); + + // wait for the iframe to compute the expression and pass it back + await new Promise(r => setTimeout(r, 100)).then(() => { + + // then + expect(onChangeSpy).to.be.calledOnce; + expect(onChangeSpy).to.be.calledWith({ field, value: 42 }); + }); + + }); + + + it('should evaluate with return', async function() { + + // given + const onChangeSpy = sinon.spy(); + const field = { + ...defaultField, + jsFunction: 'return data.value' + }; + const passedData = { value : 42 }; + + const services = { + expressionLanguage: { + isExpression: () => true, + evaluate: () => { + return passedData; + } + } + }; + + // when + act(() => { + createJSFunctionField({ field, onChange: onChangeSpy, services }); + }); + + // wait for the iframe to compute the expression and pass it back + await new Promise(r => setTimeout(r, 100)).then(() => { + + // then + expect(onChangeSpy).to.be.calledOnce; + expect(onChangeSpy).to.be.calledWith({ field, value: 42 }); + }); + + }); + + + it('should evaluate multiple times when using interval', async function() { + + // given + const onChangeSpy = sinon.spy(); + const field = { + ...defaultField, + computeOn: 'interval', + interval: 100 + }; + const passedData = { value : 42 }; + + const services = { + expressionLanguage: { + isExpression: () => true, + evaluate: () => { + return passedData; + } + } + }; + + // when + act(() => { + createJSFunctionField({ field, onChange: onChangeSpy, services }); + }); + + // wait for the iframe to compute the expression and pass it back + await new Promise(r => setTimeout(r, 500)).then(() => { + + // then + + // deliberately underestimating the number of calls to account for potential timing issues + expect(onChangeSpy.callCount > 3).to.be.true; + expect(onChangeSpy).to.be.calledWith({ field, value: 42 }); + }); + + + }); + +}); + +// helpers ////////// + +const defaultField = { + type: 'script', + key: 'jsfunction', + jsFunction: 'setValue(data.value)', + computeOn: 'load' +}; + +function createJSFunctionField({ services, ...restOptions } = {}) { + const options = { + field: defaultField, + onChange: () => {}, + ...restOptions + }; + + return render( + + + , { + container: options.container || container.querySelector('.fjs-form') + } + ); +} \ No newline at end of file