diff --git a/package-lock.json b/package-lock.json index eeecb606d..3c64c8854 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@chantouchsek/validatorjs": "1.2.3", "@storybook/addon-docs": "^7.6.13", "axios-extensions": "^3.1.6", + "flatted": "^3.3.3", "lodash": "^4.17.21", "lru-cache": "^10.0.1", "moment": "^2.30.1", @@ -13581,10 +13582,9 @@ } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" }, "node_modules/flow-parser": { "version": "0.228.0", diff --git a/package.json b/package.json index 3494651db..9cfc07af8 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@chantouchsek/validatorjs": "1.2.3", "@storybook/addon-docs": "^7.6.13", "axios-extensions": "^3.1.6", + "flatted": "^3.3.3", "lodash": "^4.17.21", "lru-cache": "^10.0.1", "moment": "^2.30.1", diff --git a/src/components/editor/dynamic-panel-editor.vue b/src/components/editor/dynamic-panel-editor.vue new file mode 100644 index 000000000..cb05ba90f --- /dev/null +++ b/src/components/editor/dynamic-panel-editor.vue @@ -0,0 +1,377 @@ + + + + + diff --git a/src/components/editor/index.js b/src/components/editor/index.js index 2b2a453da..94e39bd1f 100644 --- a/src/components/editor/index.js +++ b/src/components/editor/index.js @@ -1,2 +1,3 @@ export { default as Loop } from "./loop.vue"; export { default as MultiColumn } from "./multi-column.vue"; +export { default as DynamicPanel } from "./dynamic-panel-editor.vue"; diff --git a/src/components/index.js b/src/components/index.js index 316be33da..e001acd71 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -8,8 +8,10 @@ import * as inspector from "./inspector"; import FormBuilderControls from "../form-builder-controls"; import Task from "./task.vue"; import Loop from "./editor/loop.vue"; +import DynamicPanel from "./editor/dynamic-panel-editor.vue"; import MultiColumn from "./editor/multi-column.vue"; import FormLoop from "./renderer/form-loop.vue"; +import FormDynamicPanel from "./renderer/form-dynamic-panel.vue"; import NewFormMultiColumn from "./renderer/new-form-multi-column.vue"; import FormNestedScreen from "./renderer/form-nested-screen.vue"; import ScreenRenderer from "./screen-renderer.vue"; @@ -145,10 +147,12 @@ export default { Vue.component("FormImage", FormImage); Vue.component("FormAvatar", FormAvatar); Vue.component("FormLoop", FormLoop); + Vue.component("FormDynamicPanel", FormDynamicPanel); Vue.component("FormMultiColumn", FormMultiColumn); Vue.component("FormNestedScreen", FormNestedScreen); Vue.component("FormRecordList", FormRecordList); Vue.component("Loop", Loop); + Vue.component("DynamicPanel", DynamicPanel); Vue.component("MultiColumn", MultiColumn); Vue.component("NewFormMultiColumn", NewFormMultiColumn); Vue.component("ScreenRenderer", ScreenRenderer); diff --git a/src/components/inspector/dynamic-panel.vue b/src/components/inspector/dynamic-panel.vue new file mode 100644 index 000000000..d5f6e8656 --- /dev/null +++ b/src/components/inspector/dynamic-panel.vue @@ -0,0 +1,61 @@ + + + + diff --git a/src/components/inspector/index.js b/src/components/inspector/index.js index 3df8d995a..210bfd7c9 100644 --- a/src/components/inspector/index.js +++ b/src/components/inspector/index.js @@ -18,6 +18,7 @@ export { default as ImageUpload } from "./image-upload.vue"; export { default as ImageVariable } from "./image-variable.vue"; export { default as InputVariable } from "./input-variable.vue"; export { default as LoopInspector } from "./loop.vue"; +export { default as DynamicPanelInspector } from "./dynamic-panel.vue"; export { default as MustacheHelper } from "./mustache-helper.vue"; export { default as OptionsList } from "./options-list.vue"; export { default as OutboundConfig } from "./outbound-config.vue"; diff --git a/src/components/renderer/form-button.vue b/src/components/renderer/form-button.vue index aa3116351..9d09e717c 100644 --- a/src/components/renderer/form-button.vue +++ b/src/components/renderer/form-button.vue @@ -10,7 +10,7 @@ :disabled="showSpinner" > - {{ showSpinner ? (!loadingLabel ? 'Loading...': loadingLabel) : label }} + {{ showSpinner ? (!loadingLabel ? "Loading..." : loadingLabel) : label }} @@ -19,17 +19,33 @@ import Mustache from 'mustache'; import { mapActions, mapState } from "vuex"; import { getValidPath } from '@/mixins'; +import Worker from "@/workers/worker.js?worker&inline"; +import { findRootScreen } from "@/mixins/DataReference"; +import { stringify } from 'flatted'; export default { mixins: [getValidPath], - props: ['variant', 'label', 'event', 'eventData', 'name', 'fieldValue', 'value', 'tooltip', 'transientData', 'loading', 'loadingLabel', 'handler'], + props: [ + "variant", + "label", + "event", + "eventData", + "name", + "fieldValue", + "value", + "tooltip", + "transientData", + "loading", + "loadingLabel", + "handler" + ], data() { return { showSpinner: false }; }, computed: { - ...mapState('globalErrorsModule', ['valid']), + ...mapState("globalErrorsModule", ["valid"]), classList() { let variant = this.variant || 'primary'; return { @@ -98,24 +114,50 @@ export default { }); return; } + if (this.event === 'pageNavigate') { + // Run handler for page navigate + await this.runHandler(); + } this.$emit(this.event, this.eventData); if (this.event === 'pageNavigate') { this.$emit('page-navigate', this.eventData); } }, - async runHandler() { + runHandler() { if (this.handler) { - try { - const data = this.getScreenDataReference(null, (screen, name, value) => { - // Enable the data reference to be updated by the handler - screen.$set(screen.vdata, name, value); - }); - await new Function(['toRaw'], this.handler).apply(data, [(item) => { - return item[Symbol.for('__v_raw')]; - }]); - } catch (error) { - console.error('❌ There is an error in the button handler', error); - } + return new Promise((resolve, reject) => { + try { + const rootScreen = findRootScreen(this); + const data = rootScreen.vdata; + const scope = this.transientData; + + const worker = new Worker(); + // Send the handler code to the worker + worker.postMessage({ + fn: this.handler, + dataRefs: stringify({data, scope}), + }); + + // Listen for the result from the worker + worker.onmessage = (e) => { + if (e.data.error) { + reject(e.data.error); + } else if (e.data.result) { + // Update the data with the result + Object.keys(e.data.result).forEach(key => { + if (key === '_root') { + Object.assign(data, e.data.result[key]); + } else { + scope[key] = e.data.result[key]; + } + }); + resolve(); + } + }; + } catch (error) { + console.error("❌ There is an error in the button handler", error); + } + }); } } }, diff --git a/src/components/renderer/form-dynamic-panel.vue b/src/components/renderer/form-dynamic-panel.vue new file mode 100644 index 000000000..a2a394567 --- /dev/null +++ b/src/components/renderer/form-dynamic-panel.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/components/renderer/index.js b/src/components/renderer/index.js index aca6deff0..0145ec328 100755 --- a/src/components/renderer/index.js +++ b/src/components/renderer/index.js @@ -21,4 +21,5 @@ export { default as FormTasks } from "./form-tasks.vue"; export { default as LinkButton } from "./link-button.vue"; export { default as FormCollectionRecordControl } from "./form-collection-record-control.vue"; export { default as FormCollectionViewControl } from "./form-collection-view-control.vue"; +export { default as FormDynamicPanel } from "./form-dynamic-panel.vue"; export { default as CaseProgressBar } from "./case-progress-bar.vue"; diff --git a/src/components/vue-form-builder.vue b/src/components/vue-form-builder.vue index 60702854f..a94fcefc8 100644 --- a/src/components/vue-form-builder.vue +++ b/src/components/vue-form-builder.vue @@ -1521,7 +1521,8 @@ export default { if ( _.findIndex(control.inspector, keyNamePropertyToFind) !== -1 || - control.component === "FormLoop" + control.component === "FormLoop" || + control.component === "FormDynamicPanel" ) { [this.variables, copy.config.name] = this.generator.generate( this.config, diff --git a/src/form-builder-controls.js b/src/form-builder-controls.js index 243fe5574..31d763480 100755 --- a/src/form-builder-controls.js +++ b/src/form-builder-controls.js @@ -3,6 +3,7 @@ import FormAvatar from './components/renderer/form-avatar'; import FormButton from './components/renderer/form-button'; import FormMultiColumn from './components/renderer/form-multi-column'; import FormLoop from './components/renderer/form-loop'; +import FormDynamicPanel from './components/renderer/form-dynamic-panel.vue'; import FormRecordList from './components/renderer/form-record-list'; import FormImage from './components/renderer/form-image'; import FormMaskedInput from './components/renderer/form-masked-input'; @@ -464,6 +465,42 @@ export default [ ], }, }, + { + editorComponent: FormDynamicPanel, + editorBinding: 'FormDynamicPanel', + rendererComponent: FormDynamicPanel, + rendererBinding: 'FormDynamicPanel', + control: { + popoverContent: "Add a dynamic panel component", + order: 6.0, + group: 'Content Fields', + label: 'Dynamic Panel', + component: 'FormDynamicPanel', + 'editor-component': 'DynamicPanel', + 'editor-control': 'DynamicPanel', + container: true, + // Default items container + items: [], + config: { + name: '', + icon: 'fas fa-th-large', + settings: { + type: 'new', + varname: 'dynamic_panel', + indexName: '', + add: false, + }, + }, + inspector: [ + { + type: 'DynamicPanelInspector', + field: 'settings', + config: { + }, + }, + ], + }, + }, { editorComponent: FormText, editorBinding: 'FormText', diff --git a/src/mixins/extensions/FormDynamicPanel.js b/src/mixins/extensions/FormDynamicPanel.js new file mode 100644 index 000000000..a940fbf6f --- /dev/null +++ b/src/mixins/extensions/FormDynamicPanel.js @@ -0,0 +1,62 @@ +export default { + props: { + configRef: null, + loopContext: null + }, + data() { + return { + }; + }, + methods: { + loadFormDynamicPanelProperties({ properties, element }) { + const variableName = element.config.settings.varname; + const index = element.config.settings.indexName; + + // Add itemData to the properties of FormDynamicPanel + properties[':itemData'] = `${variableName} && ${variableName}[${index}]`; + this.registerVariable(element.config.settings.varname, element); + }, + loadFormDynamicPanelItems({ element, node, definition }) { + const nested = { + config: [ + { + items: element.items, + } + ], + watchers: [], + isMobile: false + }; + + + const variableName = element.config.settings.varname; + const index = element.config.settings.indexName; + + // Add nested component inside dynamic panel + const child = this.createComponent("ScreenRenderer", { + ":definition": this.byRef(nested), + ":value": `${variableName} && ${variableName}[${index}]`, + ":loop-context": `'${variableName} && ${variableName}[${index}]'`, + ":_parent": "getValidationData()", + ":components": this.byRef(this.components), + ":config-ref": this.byRef(this.configRef || definition.config), + "@submit": "submitForm" + }); + node.appendChild(child); + } + }, + mounted() { + // Convert the FormDynamicPanel to a div + this.extensions.push({ + onloadproperties(params) { + if (params.element.container && params.componentName === "FormDynamicPanel") { + this.loadFormDynamicPanelProperties(params); + } + }, + onloaditems(params) { + if (params.element.container && params.componentName === "FormDynamicPanel") { + this.loadFormDynamicPanelItems(params); + } + } + }); + } +}; diff --git a/src/workers/worker.js b/src/workers/worker.js new file mode 100644 index 000000000..df13a0e9d --- /dev/null +++ b/src/workers/worker.js @@ -0,0 +1,66 @@ +// worker.js +import { parse } from 'flatted'; + +self.onmessage = async function (e) { + const { fn, dataRefs } = e.data; + const { data, scope, parent } = parse(dataRefs); + + try { + // Validate inputs + if (!fn || typeof fn !== 'string') { + throw new Error('Function code must be a string'); + } + + // Check if the code is asynchronous + const isAsync = detectAsyncCode(fn); + + // If the code contains await, wrap it in an async function + const functionBody = isAsync + ? `return (async () => { ${fn} })();` + : fn; + + // Use Function constructor with explicit parameter and body + // eslint-disable-next-line no-new-func + const userFunc = new Function('data', 'parent', functionBody); + const result = isAsync ? await userFunc.apply(scope, [data, parent]) : userFunc.apply(scope, [data, parent]); + + self.postMessage({ result }); + } catch (error) { + console.error('❌ Error executing handler:', error); + + self.postMessage({ + error: error.message || error.toString(), + stack: error.stack + }); + } +}; + +function detectAsyncCode(code) { + // Remove comments and strings to avoid false positives + const cleanCode = code + .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments + .replace(/\/\/.*$/gm, '') // Remove line comments + .replace(/"[^"]*"/g, '""') // Replace string content + .replace(/'[^']*'/g, "''") // Replace string content + .replace(/`[^`]*`/g, '``'); // Replace template literals + + // Check for async patterns + const asyncPatterns = [ + /\bawait\b/, // await keyword + /\bPromise\b/, // Promise constructor + /\bfetch\b/, // fetch API + /\bsetTimeout\b/, // setTimeout + /\bsetInterval\b/, // setInterval + /\brequestAnimationFrame\b/, // requestAnimationFrame + /\brequestIdleCallback\b/, // requestIdleCallback + /\bnew\s+Promise/, // new Promise + /\b\.then\s*\(/, // .then() method + /\b\.catch\s*\(/, // .catch() method + /\b\.finally\s*\(/, // .finally() method + /\bPromise\./, // Promise static methods + /\basync\b/, // async keyword (in case it's used) + ]; + + // Check if any async pattern is found + return asyncPatterns.some((pattern) => pattern.test(cleanCode)); +} diff --git a/tests/e2e/fixtures/button_click_handler.json b/tests/e2e/fixtures/button_click_handler.json index 504cac5a5..6bb22c4a4 100644 --- a/tests/e2e/fixtures/button_click_handler.json +++ b/tests/e2e/fixtures/button_click_handler.json @@ -690,7 +690,7 @@ "name": "button_with_handler", "fieldValue": null, "tooltip": {}, - "handler": "this.form_input_2=\"value changed by handler\";" + "handler": "return {\n form_input_2: \"value changed by handler\",\n}" }, "inspector": [ { @@ -1392,7 +1392,7 @@ "name": "record_list_button_with_handler", "fieldValue": null, "tooltip": {}, - "handler": "this.form_input_1=\"value changed by handler\";console.log('handler executed')" + "handler": "console.log('handler executed');\nreturn {\n form_input_1: \"value changed by handler\",\n}" }, "inspector": [ { @@ -1818,4 +1818,4 @@ ], "screen_categories": [], "scripts": [] -} \ No newline at end of file +} diff --git a/tests/e2e/fixtures/button_click_handler_worker.json b/tests/e2e/fixtures/button_click_handler_worker.json new file mode 100644 index 000000000..0f8062287 --- /dev/null +++ b/tests/e2e/fixtures/button_click_handler_worker.json @@ -0,0 +1,2626 @@ +{ + "type": "screen_package", + "version": "2", + "screens": [ + { + "id": 1, + "screen_category_id": "1", + "title": "ClickHandler", + "description": "ClickHandler", + "type": "FORM", + "config": [ + { + "name": "Default", + "items": [ + { + "uuid": "305baccb-7167-4930-acaf-e296100dff7b", + "config": { + "name": "form_record_list_1", + "icon": "fas fa-th-list", + "label": "New Record List", + "editable": true, + "fields": { + "dataSource": "provideData", + "jsonData": "[]", + "optionsList": [], + "showOptionCard": false, + "showRemoveWarning": false, + "showJsonEditor": false, + "editIndex": null, + "removeIndex": null + }, + "form": "1", + "source": { + "collectionFields": [], + "collectionFieldsColumns": [], + "pmql": null, + "sourceOptions": "Variable", + "variableStore": null, + "dataSelectionOptions": "no-selection", + "singleField": null + } + }, + "inspector": [ + { + "type": "FormInput", + "field": "name", + "config": { + "label": "Variable Name", + "name": "Variable Name", + "validation": "dot_notation|required|not_in:null,break,case,catch,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with,class,const,enum,export,extends,import,super,true,false", + "helper": "A variable name is a symbolic name to reference information." + } + }, + { + "type": "FormInput", + "field": "label", + "config": { + "label": "List Label", + "helper": "The label describes this record list" + } + }, + { + "type": "collectionDataSource", + "field": "source", + "config": { + "label": "Source of Record List", + "helper": "A record list can display the data of a defined variable or a collection" + } + }, + { + "type": "FormCheckbox", + "field": "editable", + "config": { + "label": "Editable?", + "helper": "Should records be editable/removable and can new records be added" + }, + "if": "hideControl" + }, + { + "type": "ColumnSetup", + "field": "fields", + "config": { + "label": "Columns", + "helper": "List of columns to display in the record list" + } + }, + { + "type": "FormMultiselect", + "field": "paginationOption", + "config": { + "icon": "fas", + "label": "Pagination", + "options": [ + { + "content": "No Pagination (show all)", + "value": 0 + }, + { + "content": "5 items per page", + "value": 5 + }, + { + "content": "10 items per page", + "value": 10 + }, + { + "content": "15 items per page", + "value": 15 + }, + { + "content": "25 items per page", + "value": 25 + }, + { + "content": "50 items per page", + "value": 50 + } + ], + "helper": "" + } + }, + { + "type": "PageSelect", + "field": "form", + "config": { + "label": "Record Form", + "helper": "The form to use for adding/editing records" + }, + "if": "hideControl" + }, + { + "type": "collectionDesignerMode", + "field": "designerMode", + "config": { + "label": "Table Style", + "helper": "" + } + }, + { + "type": "ColorSelectRecord", + "field": "color", + "config": { + "label": "Text Color", + "helper": "Set the element's text color", + "options": [ + { + "value": "text-primary", + "content": "primary" + }, + { + "value": "text-secondary", + "content": "secondary" + }, + { + "value": "text-success", + "content": "success" + }, + { + "value": "text-danger", + "content": "danger" + }, + { + "value": "text-warning", + "content": "warning" + }, + { + "value": "text-info", + "content": "info" + }, + { + "value": "text-light", + "content": "light" + }, + { + "value": "text-dark", + "content": "dark" + } + ] + } + }, + { + "type": "ColorSelectRecord", + "field": "bgcolor", + "config": { + "label": "Background Color", + "helper": "Set the element's background color", + "options": [ + { + "value": "alert alert-primary", + "content": "primary" + }, + { + "value": "alert alert-secondary", + "content": "secondary" + }, + { + "value": "alert alert-success", + "content": "success" + }, + { + "value": "alert alert-danger", + "content": "danger" + }, + { + "value": "alert alert-warning", + "content": "warning" + }, + { + "value": "alert alert-info", + "content": "info" + }, + { + "value": "alert alert-light", + "content": "light" + }, + { + "value": "alert alert-dark", + "content": "dark" + } + ] + } + }, + { + "type": "ColorSelectModern", + "field": "bgcolormodern", + "config": { + "label": "", + "helper": "", + "options": [ + { + "value": "alert alert-primary", + "content": "primary" + }, + { + "value": "alert alert-success", + "content": "success" + }, + { + "value": "alert alert-warning", + "content": "warning" + }, + { + "value": "alert alert-secondary", + "content": "secondary" + } + ] + } + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormRecordList", + "editor-component": "FormText", + "editor-control": "FormText", + "label": "Record List" + }, + { + "uuid": "e4447496-0239-4d6f-9d83-26c3053d54ec", + "config": { + "name": "form_record_list_1", + "icon": "fas fa-redo", + "settings": { + "type": "existing", + "varname": "form_record_list_1", + "times": "3", + "add": true + }, + "label": "" + }, + "inspector": [ + { + "type": "LoopInspector", + "field": "settings", + "config": { + "label": "", + "helper": "" + } + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormLoop", + "editor-component": "Loop", + "editor-control": "Loop", + "label": "Loop", + "items": [ + { + "uuid": "7e7670f3-12e2-4a9e-b683-fa4c2abe2b84", + "config": { + "icon": "fas fa-share-square", + "label": "x Delete row", + "variant": "danger", + "event": "submit", + "loading": false, + "loadingLabel": "Loading...", + "defaultSubmit": true, + "name": "delete_row", + "fieldValue": null, + "tooltip": {}, + "handler": "const form_record_list_1 = data.form_record_list_1;\nconst index = form_record_list_1.indexOf(this);\nform_record_list_1.splice(index, 1);\n\nreturn {\n name: \"deleted\",\n _root: {\n form_record_list_1\n }\n}" + }, + "inspector": [ + { + "type": "FormInput", + "field": "label", + "config": { + "label": "Label", + "helper": "The label describes the button's text" + } + }, + { + "type": "FormInput", + "field": "name", + "config": { + "label": "Variable Name", + "name": "Variable Name", + "helper": "A variable name is a symbolic name to reference information.", + "validation": "regex:/^(?:[A-Za-z])(?:[0-9A-Z_.a-z])*(? Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormButton", + "editor-component": "FormButton", + "editor-control": "FormSubmit", + "label": "Submit Button" + }, + { + "uuid": "693b4380-e319-4c5f-a1a5-18eea510d067", + "config": { + "icon": "fas fa-share-square", + "label": "Error", + "variant": "warning", + "event": "submit", + "loading": false, + "loadingLabel": "Loading...", + "defaultSubmit": true, + "name": "handle_error", + "fieldValue": null, + "tooltip": { + "variant": "primary" + }, + "handler": "throw new Error(\"Testing error\")" + }, + "inspector": [ + { + "type": "FormInput", + "field": "label", + "config": { + "label": "Label", + "helper": "The label describes the button's text" + } + }, + { + "type": "FormInput", + "field": "name", + "config": { + "label": "Variable Name", + "name": "Variable Name", + "helper": "A variable name is a symbolic name to reference information.", + "validation": "regex:/^(?:[A-Za-z])(?:[0-9A-Z_.a-z])*(? Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormButton", + "editor-component": "FormButton", + "editor-control": "FormSubmit", + "label": "Submit Button" + }, + { + "uuid": "3a02685d-598b-4aad-a0e6-cd6919d09cab", + "config": { + "icon": "far fa-square", + "label": "name", + "name": "name", + "placeholder": "", + "validation": [], + "helper": null, + "type": "text", + "dataFormat": "string", + "readonly": false + }, + "inspector": [ + { + "type": "FormInput", + "field": "name", + "config": { + "label": "Variable Name", + "name": "Variable Name", + "validation": "dot_notation|required|not_in:null,break,case,catch,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with,class,const,enum,export,extends,import,super,true,false", + "helper": "A variable name is a symbolic name to reference information." + } + }, + { + "type": "FormInput", + "field": "label", + "config": { + "label": "Label", + "helper": "The label describes the field's name" + } + }, + { + "type": "FormMultiselect", + "field": "dataFormat", + "config": { + "label": "Data Type", + "name": "Data Type", + "helper": "The data type specifies what kind of data is stored in the variable.", + "validation": "required", + "options": [ + { + "value": "string", + "content": "Text" + }, + { + "value": "int", + "content": "Integer" + }, + { + "value": "currency", + "content": "Currency" + }, + { + "value": "percentage", + "content": "Percentage" + }, + { + "value": "float", + "content": "Decimal" + }, + { + "value": "datetime", + "content": "Datetime" + }, + { + "value": "date", + "content": "Date" + }, + { + "value": "password", + "content": "Password" + } + ] + } + }, + { + "type": "SelectDataTypeMask", + "field": "dataMask", + "config": { + "label": "Data Format", + "name": "Data Format", + "helper": "The data format for the selected type." + } + }, + { + "type": "ValidationSelect", + "field": "validation", + "config": { + "label": "Validation Rules", + "helper": "The validation rules needed for this field" + } + }, + { + "type": "FormInput", + "field": "placeholder", + "config": { + "label": "Placeholder Text", + "helper": "The placeholder is what is shown in the field when no value is provided yet" + } + }, + { + "type": "FormInput", + "field": "helper", + "config": { + "label": "Helper Text", + "helper": "Help text is meant to provide additional guidance on the field's value" + } + }, + { + "type": "FormCheckbox", + "field": "readonly", + "config": { + "label": "Read Only", + "helper": "" + } + }, + { + "type": "ColorSelect", + "field": "color", + "config": { + "label": "Text Color", + "helper": "Set the element's text color", + "options": [ + { + "value": "text-primary", + "content": "primary" + }, + { + "value": "text-secondary", + "content": "secondary" + }, + { + "value": "text-success", + "content": "success" + }, + { + "value": "text-danger", + "content": "danger" + }, + { + "value": "text-warning", + "content": "warning" + }, + { + "value": "text-info", + "content": "info" + }, + { + "value": "text-light", + "content": "light" + }, + { + "value": "text-dark", + "content": "dark" + } + ] + } + }, + { + "type": "ColorSelect", + "field": "bgcolor", + "config": { + "label": "Background Color", + "helper": "Set the element's background color", + "options": [ + { + "value": "alert alert-primary", + "content": "primary" + }, + { + "value": "alert alert-secondary", + "content": "secondary" + }, + { + "value": "alert alert-success", + "content": "success" + }, + { + "value": "alert alert-danger", + "content": "danger" + }, + { + "value": "alert alert-warning", + "content": "warning" + }, + { + "value": "alert alert-info", + "content": "info" + }, + { + "value": "alert alert-light", + "content": "light" + }, + { + "value": "alert alert-dark", + "content": "dark" + } + ] + } + }, + { + "type": "default-value-editor", + "field": "defaultValue", + "config": { + "label": "Default Value", + "helper": "The default value is pre populated using the existing request data. This feature will allow you to modify the value displayed on screen load if needed." + } + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormInput", + "editor-component": "FormInput", + "editor-control": "FormInput", + "label": "Line Input" + }, + { + "uuid": "fa288d55-b472-4c23-a228-c4342cb6002d", + "config": { + "icon": "fas fa-share-square", + "label": "change and submit", + "variant": "primary", + "event": "submit", + "loading": false, + "loadingLabel": "Loading...", + "defaultSubmit": true, + "name": "change_and_submit", + "fieldValue": null, + "tooltip": {}, + "handler": "return {\n name: \"last change and submit\"\n}" + }, + "inspector": [ + { + "type": "FormInput", + "field": "label", + "config": { + "label": "Label", + "helper": "The label describes the button's text" + } + }, + { + "type": "FormInput", + "field": "name", + "config": { + "label": "Variable Name", + "name": "Variable Name", + "helper": "A variable name is a symbolic name to reference information.", + "validation": "regex:/^(?:[A-Za-z])(?:[0-9A-Z_.a-z])*(? Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormButton", + "editor-component": "FormButton", + "editor-control": "FormSubmit", + "label": "Submit Button" + } + ], + "container": true + } + ], + "order": 1 + }, + { + "name": "test", + "order": 2, + "items": [ + { + "uuid": "f539505a-65cc-40d2-8257-666bf44970cc", + "config": { + "icon": "far fa-square", + "label": "name", + "name": "name", + "placeholder": "", + "validation": [], + "helper": null, + "type": "text", + "dataFormat": "string", + "readonly": false + }, + "inspector": [ + { + "type": "FormInput", + "field": "name", + "config": { + "label": "Variable Name", + "name": "Variable Name", + "validation": "dot_notation|required|not_in:null,break,case,catch,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with,class,const,enum,export,extends,import,super,true,false", + "helper": "A variable name is a symbolic name to reference information." + } + }, + { + "type": "FormInput", + "field": "label", + "config": { + "label": "Label", + "helper": "The label describes the field's name" + } + }, + { + "type": "FormMultiselect", + "field": "dataFormat", + "config": { + "label": "Data Type", + "name": "Data Type", + "helper": "The data type specifies what kind of data is stored in the variable.", + "validation": "required", + "options": [ + { + "value": "string", + "content": "Text" + }, + { + "value": "int", + "content": "Integer" + }, + { + "value": "currency", + "content": "Currency" + }, + { + "value": "percentage", + "content": "Percentage" + }, + { + "value": "float", + "content": "Decimal" + }, + { + "value": "datetime", + "content": "Datetime" + }, + { + "value": "date", + "content": "Date" + }, + { + "value": "password", + "content": "Password" + } + ] + } + }, + { + "type": "SelectDataTypeMask", + "field": "dataMask", + "config": { + "label": "Data Format", + "name": "Data Format", + "helper": "The data format for the selected type." + } + }, + { + "type": "ValidationSelect", + "field": "validation", + "config": { + "label": "Validation Rules", + "helper": "The validation rules needed for this field" + } + }, + { + "type": "FormInput", + "field": "placeholder", + "config": { + "label": "Placeholder Text", + "helper": "The placeholder is what is shown in the field when no value is provided yet" + } + }, + { + "type": "FormInput", + "field": "helper", + "config": { + "label": "Helper Text", + "helper": "Help text is meant to provide additional guidance on the field's value" + } + }, + { + "type": "FormCheckbox", + "field": "readonly", + "config": { + "label": "Read Only", + "helper": "" + } + }, + { + "type": "ColorSelect", + "field": "color", + "config": { + "label": "Text Color", + "helper": "Set the element's text color", + "options": [ + { + "value": "text-primary", + "content": "primary" + }, + { + "value": "text-secondary", + "content": "secondary" + }, + { + "value": "text-success", + "content": "success" + }, + { + "value": "text-danger", + "content": "danger" + }, + { + "value": "text-warning", + "content": "warning" + }, + { + "value": "text-info", + "content": "info" + }, + { + "value": "text-light", + "content": "light" + }, + { + "value": "text-dark", + "content": "dark" + } + ] + } + }, + { + "type": "ColorSelect", + "field": "bgcolor", + "config": { + "label": "Background Color", + "helper": "Set the element's background color", + "options": [ + { + "value": "alert alert-primary", + "content": "primary" + }, + { + "value": "alert alert-secondary", + "content": "secondary" + }, + { + "value": "alert alert-success", + "content": "success" + }, + { + "value": "alert alert-danger", + "content": "danger" + }, + { + "value": "alert alert-warning", + "content": "warning" + }, + { + "value": "alert alert-info", + "content": "info" + }, + { + "value": "alert alert-light", + "content": "light" + }, + { + "value": "alert alert-dark", + "content": "dark" + } + ] + } + }, + { + "type": "default-value-editor", + "field": "defaultValue", + "config": { + "label": "Default Value", + "helper": "The default value is pre populated using the existing request data. This feature will allow you to modify the value displayed on screen load if needed." + } + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormInput", + "editor-component": "FormInput", + "editor-control": "FormInput", + "label": "Line Input" + }, + { + "uuid": "a50d6f9b-5798-49ab-8d0e-103930cdf000", + "config": { + "icon": "fas fa-share-square", + "label": "New Submit", + "variant": "secondary", + "event": "submit", + "loading": false, + "loadingLabel": "Loading...", + "defaultSubmit": true, + "name": "record_list_button_with_handler", + "fieldValue": null, + "tooltip": {}, + "handler": "return {\n name: \"value changed by handler\"\n}" + }, + "inspector": [ + { + "type": "FormInput", + "field": "label", + "config": { + "label": "Label", + "helper": "The label describes the button's text" + } + }, + { + "type": "FormInput", + "field": "name", + "config": { + "label": "Variable Name", + "name": "Variable Name", + "helper": "A variable name is a symbolic name to reference information.", + "validation": "regex:/^(?:[A-Za-z])(?:[0-9A-Z_.a-z])*(? Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormButton", + "editor-component": "FormButton", + "editor-control": "FormSubmit", + "label": "Submit Button" + } + ] + } + ], + "computed": [ + { + "id": 1, + "name": "padre is a circular reference", + "type": "javascript", + "formula": "return this._parent;", + "property": "padre" + } + ], + "custom_css": null, + "created_at": "2020-06-17T22:30:48+00:00", + "updated_at": "2020-08-03T21:11:40+00:00", + "status": "ACTIVE", + "key": null, + "watchers": [], + "categories": [ + { + "id": 1, + "name": "Uncategorized", + "status": "ACTIVE", + "is_system": 0, + "created_at": "2020-07-01T20:06:18+00:00", + "updated_at": "2020-07-01T20:06:18+00:00", + "pivot": { + "assignable_id": 5, + "category_id": 1, + "category_type": "ProcessMaker\\Models\\ScreenCategory" + } + } + ] + } + ], + "screen_categories": [], + "scripts": [] +} \ No newline at end of file diff --git a/tests/e2e/fixtures/dynamic_panels_screen.json b/tests/e2e/fixtures/dynamic_panels_screen.json new file mode 100644 index 000000000..6960ec6de --- /dev/null +++ b/tests/e2e/fixtures/dynamic_panels_screen.json @@ -0,0 +1,1198 @@ +{ + "screens": [ + { + "id": "dynamic-panels-test", + "config": [ + { + "name": "Default", + "items": [ + { + "uuid": "b3cbc4c3-234d-4a56-b880-f9a84617a673", + "config": { + "icon": "far fa-square", + "label": "Index Number", + "name": "index", + "placeholder": "", + "validation": [], + "helper": null, + "type": "text", + "dataFormat": "string", + "readonly": false + }, + "inspector": [ + { + "type": "FormInput", + "field": "name", + "config": { + "label": "Variable Name", + "name": "Variable Name", + "validation": "dot_notation|required|not_in:null,break,case,catch,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with,class,const,enum,export,extends,import,super,true,false", + "helper": "A variable name is a symbolic name to reference information." + } + }, + { + "type": "FormInput", + "field": "label", + "config": { + "label": "Label", + "helper": "The label describes the field's name" + } + }, + { + "type": "FormMultiselect", + "field": "dataFormat", + "config": { + "label": "Data Type", + "name": "Data Type", + "helper": "The data type specifies what kind of data is stored in the variable.", + "validation": "required", + "options": [ + { "value": "string", "content": "Text" }, + { "value": "int", "content": "Integer" }, + { "value": "currency", "content": "Currency" }, + { "value": "percentage", "content": "Percentage" }, + { "value": "float", "content": "Decimal" }, + { "value": "datetime", "content": "Datetime" }, + { "value": "date", "content": "Date" }, + { "value": "password", "content": "Password" } + ] + } + }, + { + "type": "SelectDataTypeMask", + "field": "dataMask", + "config": { + "label": "Data Format", + "name": "Data Format", + "helper": "The data format for the selected type." + } + }, + { + "type": "ValidationSelect", + "field": "validation", + "config": { + "label": "Validation Rules", + "helper": "The validation rules needed for this field" + } + }, + { + "type": "FormInput", + "field": "placeholder", + "config": { + "label": "Placeholder Text", + "helper": "The placeholder is what is shown in the field when no value is provided yet" + } + }, + { + "type": "FormInput", + "field": "helper", + "config": { + "label": "Helper Text", + "helper": "Help text is meant to provide additional guidance on the field's value" + } + }, + { + "type": "FormCheckbox", + "field": "readonly", + "config": { + "label": "Read Only", + "helper": "" + } + }, + { + "type": "ColorSelect", + "field": "color", + "config": { + "label": "Text Color", + "helper": "Set the element's text color", + "options": [ + { "value": "text-primary", "content": "primary" }, + { "value": "text-secondary", "content": "secondary" }, + { "value": "text-success", "content": "success" }, + { "value": "text-danger", "content": "danger" }, + { "value": "text-warning", "content": "warning" }, + { "value": "text-info", "content": "info" }, + { "value": "text-light", "content": "light" }, + { "value": "text-dark", "content": "dark" } + ] + } + }, + { + "type": "ColorSelect", + "field": "bgcolor", + "config": { + "label": "Background Color", + "helper": "Set the element's background color", + "options": [ + { "value": "alert alert-primary", "content": "primary" }, + { "value": "alert alert-secondary", "content": "secondary" }, + { "value": "alert alert-success", "content": "success" }, + { "value": "alert alert-danger", "content": "danger" }, + { "value": "alert alert-warning", "content": "warning" }, + { "value": "alert alert-info", "content": "info" }, + { "value": "alert alert-light", "content": "light" }, + { "value": "alert alert-dark", "content": "dark" } + ] + } + }, + { + "type": "default-value-editor", + "field": "defaultValue", + "config": { + "label": "Default Value", + "helper": "The default value is pre populated using the existing request data. This feature will allow you to modify the value displayed on screen load if needed." + } + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormInput", + "editor-component": "FormInput", + "editor-control": "FormInput", + "label": "Line Input" + }, + { + "uuid": "2ac890c4-6515-420a-9932-bf3ec8c1d1bc", + "config": { + "icon": "fas fa-table", + "options": [ + { "value": "1", "content": "6" }, + { "value": "2", "content": "6" } + ], + "label": "" + }, + "inspector": [ + { + "type": "ContainerColumns", + "field": "options", + "config": { + "label": "Column Width", + "validation": "columns-adds-to-12", + "helper": "" + } + }, + { + "type": "ColorSelect", + "field": "color", + "config": { + "label": "Text Color", + "helper": "Set the element's text color", + "options": [ + { "value": "text-primary", "content": "primary" }, + { "value": "text-secondary", "content": "secondary" }, + { "value": "text-success", "content": "success" }, + { "value": "text-danger", "content": "danger" }, + { "value": "text-warning", "content": "warning" }, + { "value": "text-info", "content": "info" }, + { "value": "text-light", "content": "light" }, + { "value": "text-dark", "content": "dark" } + ] + } + }, + { + "type": "ColorSelect", + "field": "bgcolor", + "config": { + "label": "Background Color", + "helper": "Set the element's background color", + "options": [ + { "value": "alert alert-primary", "content": "primary" }, + { "value": "alert alert-secondary", "content": "secondary" }, + { "value": "alert alert-success", "content": "success" }, + { "value": "alert alert-danger", "content": "danger" }, + { "value": "alert alert-warning", "content": "warning" }, + { "value": "alert alert-info", "content": "info" }, + { "value": "alert alert-light", "content": "light" }, + { "value": "alert alert-dark", "content": "dark" } + ] + } + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormMultiColumn", + "editor-component": "MultiColumn", + "editor-control": "FormMultiColumn", + "label": "Multicolumn / Table", + "items": [ + [ + { + "uuid": "80fa28e6-6c3b-418d-8bd9-32aed7dbb497", + "config": { + "name": "loop_1", + "icon": "fas fa-redo", + "settings": { + "type": "new", + "varname": "loop_1", + "times": "3", + "add": false + }, + "label": "" + }, + "inspector": [ + { + "type": "LoopInspector", + "field": "settings", + "config": {} + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormLoop", + "editor-component": "Loop", + "editor-control": "Loop", + "label": "Loop", + "items": [ + { + "uuid": "1eba26ad-a6c8-4295-b096-9de8c34c5851", + "config": { + "icon": "far fa-square", + "label": "Name", + "name": "name", + "placeholder": "", + "validation": [], + "helper": null, + "type": "text", + "dataFormat": "string", + "readonly": false + }, + "inspector": [ + { + "type": "FormInput", + "field": "name", + "config": { + "label": "Variable Name", + "name": "Variable Name", + "validation": "dot_notation|required|not_in:null,break,case,catch,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with,class,const,enum,export,extends,import,super,true,false", + "helper": "A variable name is a symbolic name to reference information." + } + }, + { + "type": "FormInput", + "field": "label", + "config": { + "label": "Label", + "helper": "The label describes the field's name" + } + }, + { + "type": "FormMultiselect", + "field": "dataFormat", + "config": { + "label": "Data Type", + "name": "Data Type", + "helper": "The data type specifies what kind of data is stored in the variable.", + "validation": "required", + "options": [ + { "value": "string", "content": "Text" }, + { "value": "int", "content": "Integer" }, + { "value": "currency", "content": "Currency" }, + { "value": "percentage", "content": "Percentage" }, + { "value": "float", "content": "Decimal" }, + { "value": "datetime", "content": "Datetime" }, + { "value": "date", "content": "Date" }, + { "value": "password", "content": "Password" } + ] + } + }, + { + "type": "SelectDataTypeMask", + "field": "dataMask", + "config": { + "label": "Data Format", + "name": "Data Format", + "helper": "The data format for the selected type." + } + }, + { + "type": "ValidationSelect", + "field": "validation", + "config": { + "label": "Validation Rules", + "helper": "The validation rules needed for this field" + } + }, + { + "type": "FormInput", + "field": "placeholder", + "config": { + "label": "Placeholder Text", + "helper": "The placeholder is what is shown in the field when no value is provided yet" + } + }, + { + "type": "FormInput", + "field": "helper", + "config": { + "label": "Helper Text", + "helper": "Help text is meant to provide additional guidance on the field's value" + } + }, + { + "type": "FormCheckbox", + "field": "readonly", + "config": { + "label": "Read Only", + "helper": "" + } + }, + { + "type": "ColorSelect", + "field": "color", + "config": { + "label": "Text Color", + "helper": "Set the element's text color", + "options": [ + { "value": "text-primary", "content": "primary" }, + { "value": "text-secondary", "content": "secondary" }, + { "value": "text-success", "content": "success" }, + { "value": "text-danger", "content": "danger" }, + { "value": "text-warning", "content": "warning" }, + { "value": "text-info", "content": "info" }, + { "value": "text-light", "content": "light" }, + { "value": "text-dark", "content": "dark" } + ] + } + }, + { + "type": "ColorSelect", + "field": "bgcolor", + "config": { + "label": "Background Color", + "helper": "Set the element's background color", + "options": [ + { "value": "alert alert-primary", "content": "primary" }, + { "value": "alert alert-secondary", "content": "secondary" }, + { "value": "alert alert-success", "content": "success" }, + { "value": "alert alert-danger", "content": "danger" }, + { "value": "alert alert-warning", "content": "warning" }, + { "value": "alert alert-info", "content": "info" }, + { "value": "alert alert-light", "content": "light" }, + { "value": "alert alert-dark", "content": "dark" } + ] + } + }, + { + "type": "default-value-editor", + "field": "defaultValue", + "config": { + "label": "Default Value", + "helper": "The default value is pre populated using the existing request data. This feature will allow you to modify the value displayed on screen load if needed." + } + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormInput", + "editor-component": "FormInput", + "editor-control": "FormInput", + "label": "Line Input" + } + ], + "container": true + } + ], + [ + { + "uuid": "ac634f39-d75e-461e-ac8c-0719a3991910", + "config": { + "name": "loop_1", + "icon": "fas fa-th-large", + "settings": { + "type": "new", + "varname": "loop_1", + "indexOf": "index", + "add": false, + "indexName": "index" + }, + "label": "" + }, + "inspector": [ + { + "type": "DynamicPanelInspector", + "field": "settings", + "config": { + "label": "", + "helper": "" + } + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormDynamicPanel", + "editor-component": "DynamicPanel", + "editor-control": "DynamicPanel", + "label": "Dynamic Panel", + "items": [ + { + "uuid": "4447b61f-b12c-49ee-8648-51f2c7ddff83", + "config": { + "icon": "far fa-square", + "label": "Selected Name", + "name": "name", + "placeholder": "", + "validation": [], + "helper": null, + "type": "text", + "dataFormat": "string", + "readonly": false + }, + "inspector": [ + { + "type": "FormInput", + "field": "name", + "config": { + "label": "Variable Name", + "name": "Variable Name", + "validation": "dot_notation|required|not_in:null,break,case,catch,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with,class,const,enum,export,extends,import,super,true,false", + "helper": "A variable name is a symbolic name to reference information." + } + }, + { + "type": "FormInput", + "field": "label", + "config": { + "label": "Label", + "helper": "The label describes the field's name" + } + }, + { + "type": "FormMultiselect", + "field": "dataFormat", + "config": { + "label": "Data Type", + "name": "Data Type", + "helper": "The data type specifies what kind of data is stored in the variable.", + "validation": "required", + "options": [ + { "value": "string", "content": "Text" }, + { "value": "int", "content": "Integer" }, + { "value": "currency", "content": "Currency" }, + { "value": "percentage", "content": "Percentage" }, + { "value": "float", "content": "Decimal" }, + { "value": "datetime", "content": "Datetime" }, + { "value": "date", "content": "Date" }, + { "value": "password", "content": "Password" } + ] + } + }, + { + "type": "SelectDataTypeMask", + "field": "dataMask", + "config": { + "label": "Data Format", + "name": "Data Format", + "helper": "The data format for the selected type." + } + }, + { + "type": "ValidationSelect", + "field": "validation", + "config": { + "label": "Validation Rules", + "helper": "The validation rules needed for this field" + } + }, + { + "type": "FormInput", + "field": "placeholder", + "config": { + "label": "Placeholder Text", + "helper": "The placeholder is what is shown in the field when no value is provided yet" + } + }, + { + "type": "FormInput", + "field": "helper", + "config": { + "label": "Helper Text", + "helper": "Help text is meant to provide additional guidance on the field's value" + } + }, + { + "type": "FormCheckbox", + "field": "readonly", + "config": { + "label": "Read Only", + "helper": "" + } + }, + { + "type": "ColorSelect", + "field": "color", + "config": { + "label": "Text Color", + "helper": "Set the element's text color", + "options": [ + { "value": "text-primary", "content": "primary" }, + { "value": "text-secondary", "content": "secondary" }, + { "value": "text-success", "content": "success" }, + { "value": "text-danger", "content": "danger" }, + { "value": "text-warning", "content": "warning" }, + { "value": "text-info", "content": "info" }, + { "value": "text-light", "content": "light" }, + { "value": "text-dark", "content": "dark" } + ] + } + }, + { + "type": "ColorSelect", + "field": "bgcolor", + "config": { + "label": "Background Color", + "helper": "Set the element's background color", + "options": [ + { "value": "alert alert-primary", "content": "primary" }, + { "value": "alert alert-secondary", "content": "secondary" }, + { "value": "alert alert-success", "content": "success" }, + { "value": "alert alert-danger", "content": "danger" }, + { "value": "alert alert-warning", "content": "warning" }, + { "value": "alert alert-info", "content": "info" }, + { "value": "alert alert-light", "content": "light" }, + { "value": "alert alert-dark", "content": "dark" } + ] + } + }, + { + "type": "default-value-editor", + "field": "defaultValue", + "config": { + "label": "Default Value", + "helper": "The default value is pre populated using the existing request data. This feature will allow you to modify the value displayed on screen load if needed." + } + }, + { + "type": "FormInput", + "field": "conditionalHide", + "config": { + "label": "Visibility Rule", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "DeviceVisibility", + "field": "deviceVisibility", + "config": { + "label": "Device Visibility", + "helper": "This control is hidden until this expression is true" + } + }, + { + "type": "FormInput", + "field": "customFormatter", + "config": { + "label": "Custom Format String", + "helper": "Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormInput", + "editor-component": "FormInput", + "editor-control": "FormInput", + "label": "Line Input" + } + ], + "container": true + } + ] + ], + "container": true + }, + { + "uuid": "48606936-107b-4bcf-ac86-a726cd717c57", + "config": { + "icon": "fas fa-share-square", + "label": "New Submit", + "variant": "primary", + "event": "submit", + "loading": false, + "loadingLabel": "Loading...", + "defaultSubmit": true, + "name": null, + "fieldValue": null, + "tooltip": {}, + "handler": "" + }, + "inspector": [ + { + "type": "FormInput", + "field": "label", + "config": { + "label": "Label", + "helper": "The label describes the button's text" + } + }, + { + "type": "FormInput", + "field": "name", + "config": { + "label": "Variable Name", + "name": "Variable Name", + "helper": "A variable name is a symbolic name to reference information.", + "validation": "regex:/^(?:[A-Za-z])(?:[0-9A-Z_.a-z])*(? Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": "" + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + }, + { + "type": "EncryptedConfig", + "field": "encryptedConfig", + "config": { + "label": "Encrypted", + "helper": "" + } + } + ], + "component": "FormButton", + "editor-component": "FormButton", + "editor-control": "FormSubmit", + "label": "Submit Button" + } + ], + "order": 1 + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/e2e/specs/ButtonClickHandlerWorker.spec.js b/tests/e2e/specs/ButtonClickHandlerWorker.spec.js new file mode 100644 index 000000000..5be1b182d --- /dev/null +++ b/tests/e2e/specs/ButtonClickHandlerWorker.spec.js @@ -0,0 +1,62 @@ +describe("Button click handler", () => { + + before(() => { + cy.visit("/", { + onBeforeLoad(win) { + // stub console.error + cy.stub(win.console, 'error').as('consoleError') + } + }); + }); + + it("Test circular reference and click handlers", () => { + cy.loadFromJson("button_click_handler_worker.json", 0); + cy.wait(1000); + + cy.get("[data-cy=mode-preview]").click(); + // Add new row in record list (data-cy="add-row") + cy.get("[data-cy=preview-content] [data-cy=add-row]").click(); + // Fill the first input + cy.get("[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] [name=name]").clear().type("12345678"); + cy.get("[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] [name=name]").should( + "have.value", + "12345678" + ); + // Click on the first button + cy.get("[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] [name=record_list_button_with_handler]").click(); + cy.get("[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] [name=name]").should( + "have.value", + "value changed by handler" + ); + // Click on OK + cy.get("[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] button.btn-primary").click(); + + // Add new row (data-cy="loop-form_record_list_1-add") + cy.get("[data-cy=preview-content] [data-cy=loop-form_record_list_1-add]").click(); + + // Click on first dete row + cy.get("[data-cy=preview-content] [name=delete_row]").eq(0).click(); + + // Click on first handle error + cy.get("[data-cy=preview-content] [name=handle_error]").eq(0).click(); + // Check the error message in console + cy.get('@consoleError') + .should('have.been.called') + .and('have.been.calledWith', 'Testing error'); + + // Click on first change and submit + cy.get("[data-cy=preview-content] [name=change_and_submit]").eq(0).click(); + + // Check the data of the screen + cy.assertPreviewData({ + "form_record_list_1": [ + { + "delete_row": null, + "handle_error": null, + "name": "last change and submit", + "change_and_submit": null + } + ] + }); + }); +}); diff --git a/tests/e2e/specs/DynamicPanelsSimplified.spec.js b/tests/e2e/specs/DynamicPanelsSimplified.spec.js new file mode 100644 index 000000000..5a2ed715b --- /dev/null +++ b/tests/e2e/specs/DynamicPanelsSimplified.spec.js @@ -0,0 +1,128 @@ +describe("Dynamic Panels - Simplified", () => { + beforeEach(() => { + cy.visit("/"); + // Load the screen configuration from fixture + cy.loadFromJson("dynamic_panels_screen.json", 0, "form"); + }); + + it("should render the screen with dynamic panels correctly", () => { + // Switch to preview mode + cy.get("[data-cy=mode-preview]").click(); + + // Verify the Index Number input is present + cy.get("[data-cy=preview-content] [name=index]").should("be.visible"); + cy.get("[data-cy=preview-content] label").contains("Index Number").should("be.visible"); + + // Verify the multicolumn structure is present + cy.get("[data-cy=preview-content] .col-sm-6").should("have.length", 2); + + // Verify the loop inputs are present (should be 3 based on the configuration) + cy.get("[data-cy=preview-content] [name=name]").should("have.length", 3); + + // Verify the dynamic panel is present + cy.get("[data-cy=preview-content] [name=name]").should("be.visible"); + + // Verify the submit button is present + cy.get("[data-cy=preview-content] button").contains("New Submit").should("be.visible"); + }); + + it("should test dynamic panel functionality with loop data", () => { + // Switch to preview mode + cy.get("[data-cy=mode-preview]").click(); + + // Set preview data with loop items + const testData = { + index: "0", + loop_1: [ + { name: "John Doe" }, + { name: "Jane Smith" }, + { name: "Bob Johnson" } + ] + }; + + cy.setPreviewDataInput(testData); + + // Verify the Index Number field has the correct value + cy.get("[data-cy=preview-content] [name=index]").should("have.value", "0"); + + // Verify the loop inputs are populated correctly + cy.get("[data-cy=preview-content] [name=name]").eq(0).should("have.value", "John Doe"); + cy.get("[data-cy=preview-content] [name=name]").eq(1).should("have.value", "Jane Smith"); + cy.get("[data-cy=preview-content] [name=name]").eq(2).should("have.value", "Bob Johnson"); + + // Test dynamic panel functionality by changing the index + cy.get("[data-cy=preview-content] [name=index]").clear().type("1"); + + // Find the input field that has the "Selected Name" label and check its value + // The dynamic panel should show the selected name based on the index + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "Jane Smith"); + + // Test with index 0 + cy.get("[data-cy=preview-content] [name=index]").clear().type("0"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "John Doe"); + + // Test with index 2 + cy.get("[data-cy=preview-content] [name=index]").clear().type("2"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "Bob Johnson"); + + // Test with invalid index - the dynamic panel should not exist or be hidden + cy.get("[data-cy=preview-content] [name=index]").clear().type("5"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").should("not.exist"); + + // Test with negative index - the dynamic panel should not exist + cy.get("[data-cy=preview-content] [name=index]").clear().type("-1"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").should("not.exist"); + + // Test with non-numeric index - the dynamic panel should not exist + cy.get("[data-cy=preview-content] [name=index]").clear().type("abc"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").should("not.exist"); + }); + + // test update loop items and see if the dynamic panel updates + it("should test update loop items and see if the dynamic panel updates", () => { + // Switch to preview mode + cy.get("[data-cy=mode-preview]").click(); + cy.get("[data-cy=preview-content] [name=index]").clear().type("1"); + + // Update the loop items + cy.get("[data-cy=preview-content] [name=name]").eq(0).clear().type("Alice Cooper"); + cy.get("[data-cy=preview-content] [name=name]").eq(1).clear().type("Bob Dylan"); + cy.get("[data-cy=preview-content] [name=name]").eq(2).clear().type("Charlie Brown"); + + // Verify the dynamic panel shows the correct selected name + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "Bob Dylan"); + + // Update Bob Dylan + cy.get("[data-cy=preview-content] [name=name]").eq(1).clear().type("Bob Dylan Updated"); + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "Bob Dylan Updated"); + + }); + + it("should test form submission with dynamic panels", () => { + // Switch to preview mode + cy.get("[data-cy=mode-preview]").click(); + + // Fill in the form data + cy.get("[data-cy=preview-content] [name=index]").clear().type("1"); + cy.get("[data-cy=preview-content] [name=name]").eq(0).clear().type("Alice Cooper"); + cy.get("[data-cy=preview-content] [name=name]").eq(1).clear().type("Bob Dylan"); + cy.get("[data-cy=preview-content] [name=name]").eq(2).clear().type("Charlie Brown"); + + // Verify the dynamic panel shows the correct selected name + cy.get("[data-cy=preview-content] label").contains("Selected Name").parent().find("input").should("have.value", "Bob Dylan"); + + // Wait for the submit button to be visible and click it + cy.get('[data-cy=preview-content]').should('be.visible'); + cy.get('[data-cy=preview-content] button').contains('New Submit').click(); + + // Verify the final data structure + cy.assertPreviewData({ + index: "1", + loop_1: [ + { name: "Alice Cooper" }, + { name: "Bob Dylan" }, + { name: "Charlie Brown" } + ] + }); + }); +}); \ No newline at end of file diff --git a/tests/e2e/specs/FOUR6788_ScreenPerformanceTests.spec.js b/tests/e2e/specs/FOUR6788_ScreenPerformanceTests.spec.js index b678833e2..294f5c815 100644 --- a/tests/e2e/specs/FOUR6788_ScreenPerformanceTests.spec.js +++ b/tests/e2e/specs/FOUR6788_ScreenPerformanceTests.spec.js @@ -6,7 +6,7 @@ describe("FOUR-6788 screen performance", () => { // This test includes a Loop with 6 iterations, multi-column, select lists, rich texts and text areas it("Verify FOUR-6788 screen performance: select list, rich text", () => { - const maximumScreenRenderTime = 6000; + const maximumScreenRenderTime = 6500; cy.loadFromJson("FOUR-6788_screen_performance.json"); cy.visit("/?scenario=RenderScreen"); @@ -27,7 +27,7 @@ describe("FOUR-6788 screen performance", () => { // This test includes a Loop with 6 iterations, multi-column, select lists, rich texts, // text areas, input texts, validations rules, visibility rules and a submit button it("Verify FOUR-6788 screen performance: input text, validations, visibility rules", () => { - const maximumScreenRenderTime = 6000; + const maximumScreenRenderTime = 6500; cy.loadFromJson("FOUR-6788_screen_performance_2.json"); cy.visit("/?scenario=RenderScreen2"); diff --git a/tests/e2e/support/commands.js b/tests/e2e/support/commands.js index 9c2dd4c53..405890c32 100644 --- a/tests/e2e/support/commands.js +++ b/tests/e2e/support/commands.js @@ -251,11 +251,23 @@ Cypress.Commands.add( (subject, option) => { cy.get(subject).click(); cy.get(subject).find("input").clear().type(option); - cy.get(subject) - .find( - `span:not(.multiselect__option--disabled) span:contains("${option}"):first` - ) - .click(); + + // Wait for options to be available and then try to find the target option + cy.get(subject).should('have.class', 'multiselect--active'); + + // Try to find the option with retry logic + cy.get(subject).then(($el) => { + const optionSelector = `span:not(.multiselect__option--disabled) span:contains("${option}"):first`; + + // Check if the option exists + if ($el.find(optionSelector).length > 0) { + cy.get(subject).find(optionSelector).click(); + } else { + // If option not found, try to wait a bit more and retry + cy.wait(500); + cy.get(subject).find(optionSelector).should('exist').click(); + } + }); } );