├── .eslintignore
├── .eslintrc.js
├── .github
├── pull_request_template.md
└── workflows
│ ├── ci.yml
│ ├── deploy-package.yml
│ ├── sonarqube.yml
│ └── storybook.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .storybook
├── main.js
├── manager.js
└── preview.js
├── README.md
├── babel.config.json
├── cypress.config.js
├── favicon.ico
├── index.html
├── jest.config.js
├── package-lock.json
├── package.json
├── sonar-project.properties
├── src
├── App.vue
├── CountElements.js
├── DataProvider.js
├── ValidationsFactory.js
├── VariableDataTypeProperties.js
├── assets
│ ├── Shape.svg
│ ├── css
│ │ ├── custom.css
│ │ └── tabs.css
│ ├── icons
│ │ ├── Button.png
│ │ ├── Bypass.svg
│ │ ├── Checkbox.png
│ │ ├── Checkboxes.png
│ │ ├── Date.png
│ │ ├── Dropdown.png
│ │ ├── ErrorMessage.svg
│ │ ├── FileUpload.png
│ │ ├── HiddenField.png
│ │ ├── Image.png
│ │ ├── Label.png
│ │ ├── Link.png
│ │ ├── Location.png
│ │ ├── Panel.png
│ │ ├── QRCode.png
│ │ ├── RadioButton.png
│ │ ├── Signature.png
│ │ ├── Subform.png
│ │ ├── SubmitButton.png
│ │ ├── Subtitle.png
│ │ ├── SuggestField.png
│ │ ├── Table.png
│ │ ├── TextArea.png
│ │ ├── TextField.png
│ │ ├── Title.png
│ │ ├── Unbypass.svg
│ │ ├── UploadAudio.png
│ │ ├── UploadImage.png
│ │ ├── UploadVideo.png
│ │ ├── angle-double-right-solid.svg
│ │ ├── calendar-regular.svg
│ │ ├── caret-square-down-solid.svg
│ │ ├── check-square-solid.svg
│ │ ├── columns-solid.svg
│ │ ├── font-solid.svg
│ │ ├── list-ul-solid.svg
│ │ ├── paragraph-solid.svg
│ │ ├── share-square-solid.svg
│ │ ├── square-regular.svg
│ │ └── th-list-solid.svg
│ ├── logo.png
│ ├── pencil-square.svg
│ ├── priority-header.svg
│ ├── table_empty.svg
│ └── welcome_default.png
├── bootstrap.js
├── components
│ ├── ClipboardButton.vue
│ ├── CssIcon.vue
│ ├── ScreenTemplateCard.vue
│ ├── ScreenTemplates.vue
│ ├── ScreenToolbar.vue
│ ├── SelectUserGroup.vue
│ ├── SimpleErrorMessage.vue
│ ├── TabsBar.vue
│ ├── VariableNameGenerator.js
│ ├── accordions.js
│ ├── basic-search.vue
│ ├── computed-properties.vue
│ ├── custom-css-output.js
│ ├── custom-css.vue
│ ├── editor
│ │ ├── index.js
│ │ ├── loop.vue
│ │ ├── multi-column.vue
│ │ └── pagesDropdown.vue
│ ├── index.js
│ ├── inspector
│ │ ├── analytics-selector.vue
│ │ ├── collection-data-source.vue
│ │ ├── collection-designer-mode.vue
│ │ ├── collection-display-mode.vue
│ │ ├── collection-records-list.vue
│ │ ├── collection-select-list.vue
│ │ ├── color-select-modern.vue
│ │ ├── color-select.vue
│ │ ├── column-setup.vue
│ │ ├── container-columns.vue
│ │ ├── data-mapping.vue
│ │ ├── data-source-types.js
│ │ ├── default-value-editor.vue
│ │ ├── device-visibility.vue
│ │ ├── edit-option.vue
│ │ ├── encrypted-config.vue
│ │ ├── form-multiselect.vue
│ │ ├── image-upload.vue
│ │ ├── image-variable.vue
│ │ ├── index.js
│ │ ├── input-variable.vue
│ │ ├── label-submit-button.vue
│ │ ├── loading-submit-button.vue
│ │ ├── loop.vue
│ │ ├── mustache-helper.vue
│ │ ├── options-list.vue
│ │ ├── outbound-config.vue
│ │ ├── page-select.vue
│ │ ├── screen-selector.vue
│ │ ├── select-data-type-mask.vue
│ │ ├── tooltip.vue
│ │ └── validation-select.vue
│ ├── renderer
│ │ ├── add-loop-row.vue
│ │ ├── avatar-dropdown.vue
│ │ ├── card.vue
│ │ ├── file-download.vue
│ │ ├── file-upload.vue
│ │ ├── form-analytics-chart.vue
│ │ ├── form-avatar.vue
│ │ ├── form-button.vue
│ │ ├── form-collection-record-control.vue
│ │ ├── form-collection-view-control.vue
│ │ ├── form-empty-table.vue
│ │ ├── form-image.vue
│ │ ├── form-input-masked.vue
│ │ ├── form-list-table.vue
│ │ ├── form-loop.vue
│ │ ├── form-masked-input.vue
│ │ ├── form-multi-column.vue
│ │ ├── form-nested-screen.vue
│ │ ├── form-new-request.vue
│ │ ├── form-record-list-static.vue
│ │ ├── form-record-list.vue
│ │ ├── form-requests.vue
│ │ ├── form-tasks.vue
│ │ ├── form-text.vue
│ │ ├── index.js
│ │ ├── link-button.vue
│ │ ├── new-form-multi-column.vue
│ │ └── screen-renderer-error.vue
│ ├── screen-renderer.vue
│ ├── screen-variable-selector.vue
│ ├── sortable
│ │ ├── Sortable.vue
│ │ ├── sortable.scss
│ │ ├── sortableList
│ │ │ ├── SortableList.vue
│ │ │ └── sortableList.scss
│ │ └── tableStyles.scss
│ ├── task.vue
│ ├── utils
│ │ ├── default-loading-spinner.vue
│ │ ├── index.js
│ │ ├── multiple-uploads-checkbox.vue
│ │ └── required-checkbox.vue
│ ├── vue-form-builder.vue
│ ├── vue-form-renderer.vue
│ ├── watchers-form.vue
│ ├── watchers-list.vue
│ ├── watchers-popup.vue
│ └── watchers-synchronous.vue
├── currency.json
├── customLogs.js
├── factories
│ ├── CustomValidationRules.js
│ └── ValidatorFactory.js
├── form-builder-controls.js
├── form-control-common-properties.js
├── form-input-mask-config.js
├── global-properties.js
├── itemProcessingUtils.js
├── main.js
├── mixins
│ ├── Clipboard.js
│ ├── CurrentPageProperty.js
│ ├── DataReference.js
│ ├── DeviceDetector.js
│ ├── HasColorProperty.js
│ ├── Json2Vue.js
│ ├── ScreenBase.js
│ ├── ValidationRules.js
│ ├── VisibilityRule.js
│ ├── canOpenJsonFile.js
│ ├── computedFields.js
│ ├── datatable.js
│ ├── defaultValues.js
│ ├── extensions
│ │ ├── AccordionContainer.js
│ │ ├── ComputedFields.js
│ │ ├── CustomCss.js
│ │ ├── DataManager.js
│ │ ├── DefaultValues.js
│ │ ├── InputText.js
│ │ ├── LoadFieldComponents.js
│ │ ├── LoopContainer.js
│ │ ├── MultiColumn.js
│ │ ├── PageNavigate.js
│ │ ├── Submit.js
│ │ ├── ValidationRules.js
│ │ ├── VisibilityRule.js
│ │ ├── Watchers.js
│ │ └── index.js
│ ├── focusErrors.js
│ ├── formWatchers.js
│ ├── getValidPath.js
│ ├── index.js
│ ├── multiselectApi.js
│ ├── mustacheEvaluation.js
│ ├── shouldElementBeVisible.js
│ ├── testing.js
│ └── watchers.js
├── store
│ └── modules
│ │ ├── ClipboardManager.js
│ │ ├── clipboardModule.js
│ │ ├── globalErrorsModule.js
│ │ └── undoRedoModule.js
└── stories
│ ├── ClipboardButton.stories.js
│ ├── ColorSelect.stories.js
│ ├── Configure.mdx
│ ├── DropdownAndPages.stories.js
│ ├── PageTabs.stories.js
│ ├── PagesDropdown.stories.js
│ ├── ScreenToolbar.stories.js
│ └── Sortable.stories.js
├── tests
├── components
│ ├── RenderScreen.vue
│ ├── RenderScreen2.vue
│ ├── TaskAssigned.vue
│ ├── TaskMobile.vue
│ ├── TaskMultiInstance.vue
│ ├── TaskRedirect.vue
│ ├── TaskWebEntry.vue
│ ├── WebEntry.vue
│ └── index.js
├── e2e
│ ├── fixtures
│ │ ├── Expense_Report_Launcher_Main_13_screen.json
│ │ ├── Expense_Report_Launcher_Main_13_task.json
│ │ ├── FOUR-13453.json
│ │ ├── FOUR-13457.json
│ │ ├── FOUR-16958_watchers.json
│ │ ├── FOUR-4849.json
│ │ ├── FOUR-4853.json
│ │ ├── FOUR-5086.json
│ │ ├── FOUR-5139-calcError.json
│ │ ├── FOUR-5139.json
│ │ ├── FOUR-5161.json
│ │ ├── FOUR-6523.json
│ │ ├── FOUR-6788_screen_performance.json
│ │ ├── FOUR-6788_screen_performance_2.json
│ │ ├── FOUR-6910_Watcher_Checkbox.json
│ │ ├── FOUR-6910_Watcher_Radio.json
│ │ ├── FOUR-6990_Double_Loop.json
│ │ ├── FOUR-7587_LoopNestedLoop.json
│ │ ├── FPP_PFP_CHAIRS_SCREEN.json
│ │ ├── LoopFileUploadScreen.json
│ │ ├── MultiInstanceLoopContext.json
│ │ ├── Nested screen FOUR-7257 4.4.0RC2.json
│ │ ├── NestedScreenFOUR-7257.json
│ │ ├── ParentNestedScreen.json
│ │ ├── RAOS1.0.0AccountOpeningCustomerForm.json
│ │ ├── RAOS_1.0.0_-_People_2.json
│ │ ├── Screen _parent in default value.json
│ │ ├── Screen error nested calc.json
│ │ ├── Screen nested calc inside 2 loops.json
│ │ ├── Screen parent in record list.json
│ │ ├── TCP4-4444.json
│ │ ├── TCP4-4446.json
│ │ ├── TCP4-4452.json
│ │ ├── TCP4-4454.json
│ │ ├── Test Record List.json
│ │ ├── Test Validation with Nested side effects.json
│ │ ├── UUID_compatibility.json
│ │ ├── avatar.jpeg
│ │ ├── byPassFalse.json
│ │ ├── byPassTrue.json
│ │ ├── complex_screen.json
│ │ ├── computed_datetime.json
│ │ ├── default_values_with_computed_fields.json
│ │ ├── deviceVisibilityDynamic.json
│ │ ├── displayScreenNext.json
│ │ ├── file1.jpeg
│ │ ├── file1.png
│ │ ├── file2.jpeg
│ │ ├── file2.png
│ │ ├── file3.jpeg
│ │ ├── file3.png
│ │ ├── interstitial_screen.json
│ │ ├── large_screen_warning.json
│ │ ├── loop_select_list.json
│ │ ├── loops_validations_with_parent_rules.json
│ │ ├── multi_loop_validations.json
│ │ ├── multiple_file_download.json
│ │ ├── multiple_upload.json
│ │ ├── multiselect_with_string_value.json
│ │ ├── nested_calc_properties.json
│ │ ├── nested_calc_radio_freeze.json
│ │ ├── nested_screen_with_event_submit.json
│ │ ├── nested_validations.json
│ │ ├── nested_validations_with_hidden_rules.json
│ │ ├── parent_record_list_and_loop.json
│ │ ├── parent_variable.json
│ │ ├── recordListDeviceVisibility.json
│ │ ├── record_list.json
│ │ ├── record_list_date_input.json
│ │ ├── record_list_fileupload.json
│ │ ├── record_list_fileupload_loops.json
│ │ ├── record_list_fileupload_required.json
│ │ ├── record_list_invalid.json
│ │ ├── record_list_multicolumn_loop.json
│ │ ├── record_list_single_input.json
│ │ ├── refresh_nested_nested_screen.json
│ │ ├── refresh_nested_nested_screen_2.json
│ │ ├── refresh_nested_screen.json
│ │ ├── refresh_nested_screen_2.json
│ │ ├── required_if_with_checkbox.json
│ │ ├── required_unless_with_checkbox.json
│ │ ├── screen2TCP4-4446.json
│ │ ├── screen_parent_signature.json
│ │ ├── screen_with_readonly_fields.json
│ │ ├── select_list_array_data.json
│ │ ├── select_list_checkbox_collection.json
│ │ ├── select_list_collection.json
│ │ ├── select_list_datasource.json
│ │ ├── select_list_default_value.json
│ │ ├── select_list_dependent.json
│ │ ├── select_list_dependent_collection.json
│ │ ├── select_list_multiselect_collection.json
│ │ ├── select_list_mustache.json
│ │ ├── select_list_mustache_custom_key.json
│ │ ├── select_list_mustache_request_data.json
│ │ ├── select_list_radio_collection.json
│ │ ├── single_file_download.json
│ │ ├── single_line_input.json
│ │ ├── single_select_with_invalid_value.json
│ │ ├── subtotals_with_loops.json
│ │ ├── subtotals_with_loops_currency.json
│ │ ├── subtotals_with_loops_decimal.json
│ │ ├── subtotasl_calc_props.json
│ │ ├── subtotasl_calc_props_currency.json
│ │ ├── test_parent_in_validations.json
│ │ ├── validation rules loop.json
│ │ ├── validation_nested_variable.json
│ │ ├── validation_rules.json
│ │ ├── validation_rules_and_recordlist.json
│ │ ├── validation_rules_name_object.json
│ │ ├── watcher_inside_loop.json
│ │ ├── watcher_on_loop_inside_recordlist.json
│ │ ├── watcher_on_loop_new_array.json
│ │ ├── watcher_select_list.json
│ │ └── webentry.json
│ ├── specs
│ │ ├── Builder.spec.js
│ │ ├── CalcDragAndDrop.spec.js
│ │ ├── Clipboard.spec.js
│ │ ├── ClipboardBwCompatibility.spec.js
│ │ ├── ClipboardControl.spec.js
│ │ ├── ClipboardDragPaste.spec.js
│ │ ├── ClipboardFormTypeDisplay.spec.js
│ │ ├── ClipboardManager.spec.js
│ │ ├── ClipboardTabClearAll.spec.js
│ │ ├── ClipboardTabDuplicate.spec.js
│ │ ├── ClipboardTestCases.spec.js
│ │ ├── ComplexScreen.spec.js
│ │ ├── ComputedDateTime.spec.js
│ │ ├── ComputedFields.spec.js
│ │ ├── ComputedFieldsReadOnly.spec.js
│ │ ├── CustomCss.spec.js
│ │ ├── DatePicker.spec.js
│ │ ├── DatePickerTimezone.spec.js
│ │ ├── DefaulValueWithNestedAndRecordList.js
│ │ ├── DefaultValueFromCalculatedField.spec.js
│ │ ├── DefaultValues.spec.js
│ │ ├── DefaultValuesWithComputedFields.spec.js
│ │ ├── DeviceVisivilityInspector.spec.js
│ │ ├── DoubleLoop.spec.js
│ │ ├── EncryptedField.spec.js
│ │ ├── EndEventRedirection.spec.js
│ │ ├── ExpenseReport.spec.js
│ │ ├── FOUR-16958_watchers.spec.js
│ │ ├── FOUR-6721_RAOS_People2.spec.js
│ │ ├── FOUR-7027_RecordList_nested_calc.spec.js
│ │ ├── FOUR-7029_Parent_in_default_value.spec.js
│ │ ├── FOUR-7587_LoopNestedLoop.spec.js
│ │ ├── FOUR4849.spec.js
│ │ ├── FOUR6788_ScreenPerformanceTests.spec.js
│ │ ├── FileDownload.spec.js
│ │ ├── FileUpload.spec.js
│ │ ├── FormImage.spec.js
│ │ ├── FormInput.spec.js
│ │ ├── FormSelectList.spec.js
│ │ ├── FormTextArea.spec.js
│ │ ├── InterstitialRedirect.spec.js
│ │ ├── LoadComplexData.spec.js
│ │ ├── Loop.spec.js
│ │ ├── LoopFileUpload.spec.js
│ │ ├── LoopSelectList.spec.js
│ │ ├── MediaQuery.spec.js
│ │ ├── MultiColumn.spec.js
│ │ ├── MultiInstanceLoopContext.spec.js
│ │ ├── MultipleUpload.spec.js
│ │ ├── MultiselectWithStringValue.spec.js
│ │ ├── NestedCalcProperties.spec.js
│ │ ├── NestedCalcRadioFreeze.spec.js
│ │ ├── NestedScreen.spec.js
│ │ ├── NestedScreenFOUR-7257.spec.js
│ │ ├── NestedValidationRules.spec.js
│ │ ├── Pages.spec.js
│ │ ├── Pagination.spec.js
│ │ ├── ParentAccessTests.spec.js
│ │ ├── ParentVariable.spec.js
│ │ ├── RAOS1.0.0AccountOpeningCustomerForm.spec.js
│ │ ├── RecordList.spec.js
│ │ ├── RecordListMultiColumnLoop.spec.js
│ │ ├── RecordListWithLoops.js
│ │ ├── RefreshNestedScreen.spec.js
│ │ ├── ResponsivePreview.spec.js
│ │ ├── RichText.spec.js
│ │ ├── ScreenBuilder.spec.js
│ │ ├── ScreenErrorNestedCalc.spec.js
│ │ ├── ScreenTemplateSection.spec.js
│ │ ├── ScreenWarnings.spec.js
│ │ ├── SelectListCollection.spec.js
│ │ ├── SelectListDataSource.spec.js
│ │ ├── SelectListDefaultValues.spec.js
│ │ ├── SelectListDependent.spec.js
│ │ ├── SelectListDependentCollection.spec.js
│ │ ├── SelectListMustache.spec.js
│ │ ├── SelectListWatcher.spec.js
│ │ ├── SingleSelectWithInvalidValue.spec.js
│ │ ├── Subtotals.spec.js
│ │ ├── TCP4-4444VerifyAddingMultipleContentToClipboard.spec.js
│ │ ├── TCP4-4446VerifyMulticolumnControlsClipboard.spec.js
│ │ ├── TCP4-4447VerifyUpdateConfigurationClipboard.spec.js
│ │ ├── TCP4-4452VerifyReloadPageClipboard.spec.js
│ │ ├── TCP4-4454DragAndPasteClipboard.spec.js
│ │ ├── TCP4-4458ClearClipboard.spec.js
│ │ ├── TCP4-4462VerifyAddComponentsToClipboard.spec.js
│ │ ├── Task.spec.js
│ │ ├── TaskRedirect.spec.js
│ │ ├── TestValidationWithNestedSideEffects.spec.js
│ │ ├── UndoRedo.spec.js
│ │ ├── ValidationCalcsAndLoop.spec.js
│ │ ├── ValidationNestedVariable.spec.js
│ │ ├── ValidationRules.spec.js
│ │ ├── ValidationRulesAdvanced.spec.js
│ │ ├── ValidationRulesAndRecordlist.spec.js
│ │ ├── ValidationRulesNameObject.js
│ │ ├── ValidationShownOnSubmit.spec.js
│ │ ├── VariableNames.js
│ │ ├── VisibilityRule.spec.js
│ │ ├── WatcherInsideLoop.spec.js
│ │ ├── WatcherOnLoops.js
│ │ ├── Watchers.spec.js
│ │ ├── WatchersBypass.spec.js
│ │ ├── WatchersDragAndDrop.spec.js
│ │ ├── WebEntryEndEventRedirect.spec.js
│ │ ├── WrongConfigurationErrors.js
│ │ └── pagesDropdown.spec.js
│ ├── support
│ │ ├── commands.js
│ │ ├── constants.js
│ │ ├── e2e.js
│ │ ├── index.js
│ │ └── utils.js
│ └── tsconfig.json
└── unit
│ ├── .eslintrc.js
│ ├── ConditionalHide.spec.js
│ ├── CustomCss.spec.js
│ ├── DefaultValues.spec.js
│ ├── FormImage.spec.js
│ ├── OptionsList.spec.js
│ ├── ValidationFactory.spec.js
│ ├── VueFormBuilder.spec.js
│ └── fixtures
│ ├── VueFormRendererWrapper.vue
│ ├── defaults.json
│ └── screen_validation.json
├── vite.config.js
├── vue.config.js
└── webpack.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | **/.circleci
3 | **/.github
4 | **/.nyc_output
5 | **/coverage
6 | **/cypress
7 | **/dist
8 | **/public
9 | *.json
10 | *.svg
11 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2020: true,
5 | node: true
6 | },
7 |
8 | globals: {
9 | ProcessMaker: "readonly"
10 | },
11 |
12 | extends: [
13 | "plugin:vue/recommended",
14 | "airbnb-base",
15 | "plugin:prettier/recommended",
16 | "plugin:storybook/recommended"
17 | ],
18 |
19 | parserOptions: {
20 | ecmaVersion: 6,
21 | sourceType: "module",
22 | parser: "@babel/eslint-parser"
23 | },
24 |
25 | plugins: ["vue", "prettier"],
26 |
27 | rules: {
28 | "prettier/prettier": ["error", { trailingComma: "none" }],
29 | "no-unexpected-multiline": "error",
30 | "no-param-reassign": 1,
31 | eqeqeq: "error",
32 | "max-len": ["error", { code: 140, ignoreUrls: true }],
33 | "comma-dangle": ["error", "never"],
34 | quotes: [
35 | "error",
36 | "double",
37 | { avoidEscape: true, allowTemplateLiterals: true }
38 | ],
39 | "import/no-extraneous-dependencies": "warn",
40 | "consistent-return": "warn",
41 | "no-plusplus": 0,
42 | "no-underscore-dangle": 0,
43 | "no-restricted-syntax": "warn",
44 | "no-continue": "warn",
45 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
46 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
47 | },
48 |
49 | root: true,
50 |
51 | overrides: [
52 | {
53 | files: ["**/tests/**/*.{j,t}s?(x)", "**/tests/e2e/**/*.spec.{j,t}s?(x)"],
54 | plugins: ["cypress"],
55 | env: {
56 | mocha: true,
57 | "cypress/globals": true
58 | },
59 | rules: {
60 | strict: "off"
61 | }
62 | },
63 | {
64 | files: ["**/tests/unit/**/*.spec.{j,t}s?(x)"],
65 | env: {
66 | jest: true
67 | }
68 | }
69 | ]
70 | };
71 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Issue & Reproduction Steps
2 |
3 | Expected behavior:
4 |
5 | Actual behavior:
6 |
7 | ## Solution
8 | -
9 |
10 | ## How to Test
11 | Test the steps above
12 |
13 | ## Related Tickets & Packages
14 | -
15 |
16 | ## Code Review Checklist
17 | - [ ] I have pulled this code locally and tested it on my instance, along with any associated packages.
18 | - [ ] This code adheres to [ProcessMaker Coding Guidelines](https://github.com/ProcessMaker/processmaker/wiki/Coding-Guidelines).
19 | - [ ] This code includes a unit test or an E2E test that tests its functionality, or is covered by an existing test.
20 | - [ ] This solution fixes the bug reported in the original ticket.
21 | - [ ] This solution does not alter the expected output of a component in a way that would break existing Processes.
22 | - [ ] This solution does not implement any breaking changes that would invalidate documentation or cause existing Processes to fail.
23 | - [ ] This solution has been tested with enterprise packages that rely on its functionality and does not introduce bugs in those packages.
24 | - [ ] This code does not duplicate functionality that already exists in the framework or in ProcessMaker.
25 | - [ ] This ticket conforms to the PRD associated with this part of ProcessMaker.
26 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-package.yml:
--------------------------------------------------------------------------------
1 | name: Build Package Screen Builder
2 | on:
3 | pull_request:
4 | types: [opened, reopened, synchronize, edited, closed]
5 | workflow_dispatch:
6 | jobs:
7 | run_deploy:
8 | name: Run Build PM4-workflow
9 | uses: processmaker/processmaker/.github/workflows/deploy-pm4.yml@develop
10 | secrets: inherit
11 |
--------------------------------------------------------------------------------
/.github/workflows/sonarqube.yml:
--------------------------------------------------------------------------------
1 | name: SonarQube
2 |
3 | on:
4 | workflow_call:
5 |
6 | jobs:
7 | build:
8 | name: Scan
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | with:
13 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
14 | - uses: actions/download-artifact@v4
15 | with:
16 | name: code-coverage-report
17 | - name: Fix code coverage paths
18 | working-directory: ./coverage
19 | run: |
20 | sed -i 's@'$GITHUB_WORKSPACE'@/github/workspace/@g' lcov.info
21 | - uses: sonarsource/sonarqube-scan-action@master
22 | env:
23 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
24 | SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
25 | # If you wish to fail your job when the Quality Gate is red, uncomment the
26 | # following lines. This would typically be used to fail a deployment.
27 | # We do not recommend to use this in a pull request. Prefer using pull request
28 | # decoration instead.
29 | # - uses: sonarsource/sonarqube-quality-gate-action@master
30 | # timeout-minutes: 5
31 | # env:
32 | # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
33 |
--------------------------------------------------------------------------------
/.github/workflows/storybook.yml:
--------------------------------------------------------------------------------
1 | name: Storybook
2 | on:
3 | pull_request:
4 | types: [opened, reopened, synchronize, edited]
5 | jobs:
6 | test:
7 | timeout-minutes: 60
8 | runs-on: ubuntu-22.04
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: actions/setup-node@v3
12 | with:
13 | node-version: 20
14 | cache: 'npm'
15 | - name: Install dependencies
16 | run: npm install
17 |
18 | - name: Get installed Playwright version
19 | id: playwright-version
20 | run: echo "PLAYWRIGHT_VERSION=$(node -e "console.log(require('./package.json').devDependencies['@playwright/test'])")" >> $GITHUB_ENV
21 | - name: Cache playwright binaries
22 | uses: actions/cache@v3
23 | id: playwright-cache
24 | with:
25 | key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }}
26 | path: |
27 | ~/.cache/ms-playwright
28 | - name: Install Playwright Browsers
29 | run: npx playwright install --with-deps chromium
30 | if: steps.playwright-cache.outputs.cache-hit != 'true'
31 |
32 | - name: Build Storybook
33 | run: npm run build-storybook --quiet
34 | - name: Serve Storybook and run tests
35 | run: |
36 | npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
37 | "npx http-server storybook-static --port 6006 --silent" \
38 | "npx wait-on tcp:6006 && npm run test-storybook --coverage"
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 | .nyc_output/
5 | coverage/
6 | tests/e2e/screenshots/
7 | tests/e2e/videos/
8 | tests/e2e/downloads
9 |
10 | # local env files
11 | .env.local
12 | .env.*.local
13 |
14 | # Log files
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 | # Editor directories and files
20 | .idea
21 | .vscode
22 | *.code-workspace
23 | .editorconfig
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw*
29 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | **/dist
3 |
4 | # Ignore artifacts:
5 | build
6 | coverage
7 |
8 | # Ignore all HTML files:
9 | *.html
10 |
11 | # Ignore all fixtures
12 | tests/e2e/fixtures/
13 |
14 | # Ignore local env files
15 | .env.local
16 | .env.*.local
17 |
18 | # Ignore log files
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
23 | # Ignore editor directories and files
24 | .idea
25 | .vscode
26 | *.suo
27 | *.ntvs*
28 | *.njsproj
29 | *.sln
30 | *.sw*
31 |
32 | *.bpmn
33 | *.svg
34 |
35 | .circleci
36 | .github
37 | .nyc_output
38 | cypress
39 | public
40 | reports
41 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none"
3 | }
4 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | /** @type { import('@storybook/vue-vite').StorybookConfig } */
2 | const config = {
3 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
4 | addons: [
5 | "@storybook/addon-links",
6 | "@storybook/addon-essentials",
7 | "@storybook/addon-docs",
8 | "@storybook/addon-interactions"
9 | ],
10 | framework: {
11 | name: "@storybook/vue-vite",
12 | options: {}
13 | },
14 | docs: {
15 | autodocs: "tag"
16 | }
17 | };
18 | export default config;
19 |
--------------------------------------------------------------------------------
/.storybook/manager.js:
--------------------------------------------------------------------------------
1 | import { addons } from '@storybook/manager-api';
2 |
3 | addons.setConfig({
4 | // Place the controls panel at the bottom of the canvas
5 | panelPosition: 'bottom',
6 | });
7 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | /** @type { import('@storybook/vue').Preview } */
2 | const preview = {
3 | parameters: {
4 | actions: { argTypesRegex: "^on[A-Z].*" },
5 | controls: {
6 | matchers: {
7 | color: /(background|color)$/i,
8 | date: /Date$/i
9 | }
10 | }
11 | }
12 | };
13 |
14 | export default preview;
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ProcessMaker Screen Builder
2 |
3 | `@processmaker/screen-builder` is a VueJS powered Screen Builder that produces compatible JSON for our vue-form-renderer.
4 |
5 | - [ProcessMaker Screen Builder](#processmaker-screen-builder)
6 | - [Project setup](#project-setup)
7 | - [Testing](#testing)
8 |
9 | ## Project setup
10 |
11 | Clone the repository and `cd` into the `screen-builder` directory:
12 |
13 | ```bash
14 | git clone git@github.com:ProcessMaker/screen-builder.git
15 | cd screen-builder
16 | ```
17 |
18 | Install dependencies using NPM, then run the local development server:
19 |
20 | ```bash
21 | npm ci
22 | npm run dev
23 | ```
24 |
25 | ## Testing
26 |
27 | Unit tests are set up using jest and end-to-end tests are set up using Cypress. Tests can be run locally with the following commands:
28 |
29 | ```bash
30 | # Run the Jest unit test suite
31 | npm test
32 |
33 | # Open Cypress to run the end-to-end (e2e) test suite
34 | npm run open-cypress
35 | ```
36 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "node": "current"
8 | }
9 | }
10 | ]
11 | ],
12 | "plugins": ["istanbul"]
13 | }
14 |
--------------------------------------------------------------------------------
/cypress.config.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require("cypress");
2 | const { prepareAudit, lighthouse } = require("@cypress-audit/lighthouse");
3 | const { pa11y } = require("@cypress-audit/pa11y");
4 | const globby = require("globby");
5 |
6 | module.exports = defineConfig({
7 | projectId: "c4pb2f",
8 | viewportWidth: 1140,
9 | viewportHeight: 668,
10 | video: false,
11 | fixturesFolder: "tests/e2e/fixtures",
12 | screenshotsFolder: "tests/e2e/screenshots",
13 | videosFolder: "tests/e2e/videos",
14 | downloadsFolder: "tests/e2e/downloads",
15 | experimentalMemoryManagement: true,
16 | numTestsKeptInMemory: 20,
17 | e2e: {
18 | // We've imported your old cypress plugins here.
19 | // You may want to clean this up later by importing these.
20 | setupNodeEvents(on, config) {
21 | require("@cypress/code-coverage/task")(on, config);
22 | // include any other plugin code...
23 | on("before:browser:launch", (browser = {}, launchOptions) => {
24 | prepareAudit(launchOptions);
25 | });
26 | on("task", {
27 | lighthouse: lighthouse(),
28 | pa11y: pa11y(),
29 | // a task to find one file matching the given mask
30 | // returns just the first matching file
31 | async findFiles(mask) {
32 | if (!mask) {
33 | throw new Error("Missing a file mask to search");
34 | }
35 |
36 | console.log("searching for files %s", mask);
37 | const list = await globby(mask);
38 |
39 | if (!list.length) {
40 | console.log("found no files");
41 |
42 | return null;
43 | }
44 |
45 | console.log("found %d files, first one %s", list.length, list[0]);
46 |
47 | return list[0];
48 | }
49 | });
50 | // It's IMPORTANT to return the config object
51 | // with any changed environment variables
52 | return config;
53 | },
54 | testIsolation: false,
55 | baseUrl: "http://localhost:5173",
56 | specPattern: "tests/e2e/specs/**/*.{js,jsx,ts,tsx}",
57 | supportFile: "tests/e2e/support/index.js",
58 | waitForAnimations: true
59 | }
60 | });
61 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/favicon.ico
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-form-builder
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: [
3 | 'js',
4 | 'jsx',
5 | 'json',
6 | 'vue',
7 | ],
8 | transform: {
9 | '^.+\\.vue$': 'vue-jest',
10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
11 | '^.+\\.jsx?$': 'babel-jest',
12 | },
13 | moduleNameMapper: {
14 | '^@/(.*)$': '/src/$1',
15 | '\\.(css|less)$': 'identity-obj-proxy',
16 | },
17 | snapshotSerializers: [
18 | 'jest-serializer-vue',
19 | ],
20 | testMatch: [
21 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)',
22 | ],
23 | testURL: 'http://localhost/',
24 | transformIgnorePatterns: [
25 | 'node_modules/(?!(vuetable-2|vue-uniq-ids|@processmaker/vue-form-elements/src)/)',
26 | ],
27 | };
28 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar.projectKey=ProcessMaker_screen-builder_AYlHjxBBxYvY_isvKt7h
2 | sonar.javascript.lcov.reportPaths=./coverage/lcov.info
3 | sonar.exclusions=node_modules/**, dist/**, public/**, home/**, storybook-static/**
4 | sonar.sources=src
5 | sonar.tests=tests
6 | sonar.cpd.exclusions=src/stories/**
7 | sonar.coverage.exclusions=src/stories/**
8 |
--------------------------------------------------------------------------------
/src/VariableDataTypeProperties.js:
--------------------------------------------------------------------------------
1 | import SelectDataTypeMask from './components/inspector/select-data-type-mask';
2 |
3 | const string = { value: 'string', content: 'Text' };
4 | const int = { value: 'int', content: 'Integer' };
5 | const currency = { value: 'currency', content: 'Currency' };
6 | const password = { value: 'password', content: 'Password' };
7 | const percentage = { value: 'percentage', content: 'Percentage' };
8 | const float = { value: 'float', content: 'Decimal' };
9 | const datetime = { value: 'datetime', content: 'Datetime' };
10 | const date = { value: 'date', content: 'Date' };
11 | const boolean = { value: 'boolean', content: 'Boolean' };
12 |
13 | const allOptions = [string, int, currency, percentage, float, datetime, date, password];
14 | const allOptionsWithoutDate = [string, int, float];
15 |
16 | function dataTypeFactory(options) {
17 | return {
18 | type: 'FormMultiselect',
19 | field: 'dataFormat',
20 | config: {
21 | label: 'Data Type',
22 | name: 'Data Type',
23 | helper: 'The data type specifies what kind of data is stored in the variable.',
24 | validation: 'required',
25 | options,
26 | },
27 | };
28 | }
29 |
30 | function dataFormatFactory() {
31 |
32 | return {
33 | type: 'SelectDataTypeMask',
34 | field: 'dataMask',
35 | config: {
36 | label: 'Data Format',
37 | name: 'Data Format',
38 | helper: 'The data format for the selected type.',
39 | },
40 | };
41 | }
42 |
43 | export const DataTypeProperty = dataTypeFactory(allOptions);
44 | export const DataTypeWithoutDateProperty = dataTypeFactory(allOptionsWithoutDate);
45 | export const DataTypeBooleanProperty = dataTypeFactory([boolean]);
46 | export const DataTypeDateTimeProperty = dataTypeFactory([date, datetime]);
47 | export const DataFormatProperty = dataFormatFactory();
48 |
--------------------------------------------------------------------------------
/src/assets/Shape.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/css/custom.css:
--------------------------------------------------------------------------------
1 | .btn-platform {
2 | background-color: #ffff;
3 | color: #6a7888;
4 | padding: 8px 8px 2px 8px;
5 | }
6 | .btn-platform:hover {
7 | color: #6a7888;
8 | }
9 | .page-dropdown-menu {
10 | min-width: 333px;
11 | max-height: 26rem;
12 | overflow-y: auto;
13 | scrollbar-width: thin;
14 | }
--------------------------------------------------------------------------------
/src/assets/icons/Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Button.png
--------------------------------------------------------------------------------
/src/assets/icons/Bypass.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/Checkbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Checkbox.png
--------------------------------------------------------------------------------
/src/assets/icons/Checkboxes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Checkboxes.png
--------------------------------------------------------------------------------
/src/assets/icons/Date.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Date.png
--------------------------------------------------------------------------------
/src/assets/icons/Dropdown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Dropdown.png
--------------------------------------------------------------------------------
/src/assets/icons/FileUpload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/FileUpload.png
--------------------------------------------------------------------------------
/src/assets/icons/HiddenField.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/HiddenField.png
--------------------------------------------------------------------------------
/src/assets/icons/Image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Image.png
--------------------------------------------------------------------------------
/src/assets/icons/Label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Label.png
--------------------------------------------------------------------------------
/src/assets/icons/Link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Link.png
--------------------------------------------------------------------------------
/src/assets/icons/Location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Location.png
--------------------------------------------------------------------------------
/src/assets/icons/Panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Panel.png
--------------------------------------------------------------------------------
/src/assets/icons/QRCode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/QRCode.png
--------------------------------------------------------------------------------
/src/assets/icons/RadioButton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/RadioButton.png
--------------------------------------------------------------------------------
/src/assets/icons/Signature.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Signature.png
--------------------------------------------------------------------------------
/src/assets/icons/Subform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Subform.png
--------------------------------------------------------------------------------
/src/assets/icons/SubmitButton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/SubmitButton.png
--------------------------------------------------------------------------------
/src/assets/icons/Subtitle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Subtitle.png
--------------------------------------------------------------------------------
/src/assets/icons/SuggestField.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/SuggestField.png
--------------------------------------------------------------------------------
/src/assets/icons/Table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Table.png
--------------------------------------------------------------------------------
/src/assets/icons/TextArea.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/TextArea.png
--------------------------------------------------------------------------------
/src/assets/icons/TextField.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/TextField.png
--------------------------------------------------------------------------------
/src/assets/icons/Title.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/Title.png
--------------------------------------------------------------------------------
/src/assets/icons/Unbypass.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/UploadAudio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/UploadAudio.png
--------------------------------------------------------------------------------
/src/assets/icons/UploadImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/UploadImage.png
--------------------------------------------------------------------------------
/src/assets/icons/UploadVideo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/icons/UploadVideo.png
--------------------------------------------------------------------------------
/src/assets/icons/angle-double-right-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/calendar-regular.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/caret-square-down-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/check-square-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/columns-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/font-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/list-ul-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/paragraph-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/share-square-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/square-regular.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/th-list-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/pencil-square.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/priority-header.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/welcome_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/src/assets/welcome_default.png
--------------------------------------------------------------------------------
/src/components/ClipboardButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
22 |
23 |
24 |
64 |
65 |
78 |
--------------------------------------------------------------------------------
/src/components/CssIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/SimpleErrorMessage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ $t(title) }}
8 |
9 |
10 | {{ $t(message) }}
11 |
12 |
13 |
14 |
15 |
26 |
27 |
43 |
--------------------------------------------------------------------------------
/src/components/VariableNameGenerator.js:
--------------------------------------------------------------------------------
1 | export default class VariableNameGenerator {
2 | names = [];
3 |
4 | GetVariableNames(configuration) {
5 | configuration.forEach(item => {
6 |
7 | //If the element has containers
8 | if (Array.isArray(item)) {
9 | this.GetVariableNames(item);
10 | }
11 |
12 | //If the element has items
13 | if (item.items) {
14 | this.GetVariableNames(item.items);
15 | }
16 |
17 | //If the element has its variable name set
18 | if (item.config && item.config.name) {
19 | this.names.push(`${item.config.name}`);
20 | }
21 |
22 | });
23 |
24 | }
25 |
26 | generate(config, component) {
27 | this.names = [];
28 | this.GetVariableNames(config);
29 | let definitionId = this.generateNameId(this.snakeCase(component), 1);
30 | this.names.push(definitionId);
31 |
32 | return [this.names, definitionId];
33 | }
34 |
35 | snakeCase(name) {
36 | return name.replace(/\W+/g, ' ')
37 | .split(/ |\B(?=[A-Z])/)
38 | .map(word => word.toLowerCase())
39 | .join('_');
40 | }
41 |
42 | generateNameId = (name, id) => {
43 | let generated = name + '_' + id;
44 | if (this.names.indexOf(generated) !== -1) {
45 | id++;
46 | generated = this.generateNameId(name, id);
47 | }
48 | return generated;
49 | };
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/components/basic-search.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
53 |
54 |
92 |
--------------------------------------------------------------------------------
/src/components/custom-css-output.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'custom-css-output',
3 | render(createElement) {
4 | return createElement('style', this.$slots.default);
5 | },
6 | };
--------------------------------------------------------------------------------
/src/components/custom-css.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 | {{ $t("You can set CSS Selector names in the inspector. Use them here with [selector='my-selector']") }}
18 |
19 |
20 |
21 |
22 |
23 | {{ cssErrors }}
24 |
25 | {{ $t('Cancel') }}
26 | {{ $t('Save') }}
27 |
28 |
29 |
30 |
71 |
72 |
86 |
--------------------------------------------------------------------------------
/src/components/editor/index.js:
--------------------------------------------------------------------------------
1 | export { default as Loop } from "./loop.vue";
2 | export { default as MultiColumn } from "./multi-column.vue";
3 |
--------------------------------------------------------------------------------
/src/components/inspector/analytics-selector.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
17 |
18 |
59 |
--------------------------------------------------------------------------------
/src/components/inspector/collection-designer-mode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
74 |
--------------------------------------------------------------------------------
/src/components/inspector/collection-display-mode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 | {{ $t("Update collection on submit") }}
16 |
17 |
18 |
19 |
20 |
21 |
84 |
--------------------------------------------------------------------------------
/src/components/inspector/color-select.vue:
--------------------------------------------------------------------------------
1 |
2 |
34 |
35 |
36 |
86 |
87 |
96 |
--------------------------------------------------------------------------------
/src/components/inspector/data-source-types.js:
--------------------------------------------------------------------------------
1 | export const dataSources = [
2 | { value: 'provideData', text: 'Provide Values' },
3 | { value: 'dataObject', text: 'Request Data' },
4 | { value: 'dataConnector', text: 'Data Connector' },
5 | { value: 'collection', text: 'Collection' },
6 | ];
7 |
8 | export const dataSourceValues = dataSources.reduce((values, source) => {
9 | values[source.value] = source.value;
10 | return values;
11 | }, {});
12 |
--------------------------------------------------------------------------------
/src/components/inspector/device-visibility.vue:
--------------------------------------------------------------------------------
1 |
2 |
23 |
24 |
25 |
62 |
63 |
72 |
--------------------------------------------------------------------------------
/src/components/inspector/edit-option.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
10 |
11 |
12 |
13 |
{{ optionError }}
14 |
15 |
16 |
17 |
25 |
26 |
27 |
28 |
29 |
45 |
--------------------------------------------------------------------------------
/src/components/inspector/encrypted-config.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
24 |
25 |
26 |
27 |
76 |
77 |
79 |
--------------------------------------------------------------------------------
/src/components/inspector/form-multiselect.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 | {{ $t("No elements found. Consider changing the search query.") }}
16 |
17 |
18 | {{ $t("No Data Available") }}
19 |
20 |
21 |
22 |
26 |
27 | {{ error }}
28 |
29 |
{{ error }}
30 |
31 |
{{ helper }}
32 |
33 |
34 |
35 |
74 |
75 |
80 |
--------------------------------------------------------------------------------
/src/components/inspector/image-upload.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
52 |
53 |
62 |
--------------------------------------------------------------------------------
/src/components/inspector/image-variable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
63 |
--------------------------------------------------------------------------------
/src/components/inspector/index.js:
--------------------------------------------------------------------------------
1 | export { default as CollectionSelectList } from "./collection-select-list.vue";
2 | export { default as CollectionRecordsList } from "./collection-records-list.vue";
3 | export { default as collectionDataSource } from "./collection-data-source.vue";
4 | export { default as CollectionDisplayMode } from "./collection-display-mode.vue";
5 | export { default as CollectionDesignerMode } from "./collection-designer-mode.vue";
6 | export { default as ColorSelect } from "./color-select.vue";
7 | export { default as ColorSelectRecord } from "./color-select.vue";
8 | export { default as ColorSelectModern } from "./color-select-modern.vue";
9 | export { default as ColumnSetup } from "./column-setup.vue";
10 | export { default as ContainerColumns } from "./container-columns.vue";
11 | export { default as DataMapping } from "./data-mapping.vue";
12 | export { dataSources, dataSourceValues } from "./data-source-types";
13 | export { default as DefaultValueEditor } from "./default-value-editor.vue";
14 | export { default as DeviceVisibility } from "./device-visibility.vue";
15 | export { default as EditOption } from "./edit-option.vue";
16 | export { default as FormMultiselect } from "./form-multiselect.vue";
17 | export { default as ImageUpload } from "./image-upload.vue";
18 | export { default as ImageVariable } from "./image-variable.vue";
19 | export { default as InputVariable } from "./input-variable.vue";
20 | export { default as LoopInspector } from "./loop.vue";
21 | export { default as MustacheHelper } from "./mustache-helper.vue";
22 | export { default as OptionsList } from "./options-list.vue";
23 | export { default as OutboundConfig } from "./outbound-config.vue";
24 | export { default as PageSelect } from "./page-select.vue";
25 | export { default as ScreenSelector } from "./screen-selector.vue";
26 | export { default as SelectDataTypeMask } from "./select-data-type-mask.vue";
27 | export { default as Tooltip } from "./tooltip.vue";
28 | export { default as ValidationSelect } from "./validation-select.vue";
29 | export { default as LoadingSubmitButton } from "./loading-submit-button.vue";
30 | export { default as LabelSubmitButton } from "./label-submit-button.vue";
31 | export { default as AnalyticsSelector } from "./analytics-selector.vue";
32 | export { default as EncryptedConfig } from "./encrypted-config.vue";
33 |
--------------------------------------------------------------------------------
/src/components/inspector/label-submit-button.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
50 |
--------------------------------------------------------------------------------
/src/components/inspector/loading-submit-button.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
48 |
--------------------------------------------------------------------------------
/src/components/inspector/mustache-helper.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 | {}
8 |
9 |
10 |
11 |
16 |
17 |
26 |
--------------------------------------------------------------------------------
/src/components/inspector/page-select.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 | {{ $t('No elements found. Consider changing the search query.') }}
16 |
17 |
18 | {{ $t('No Data Available') }}
19 |
20 |
21 | {{ helper }}
22 |
23 |
24 |
25 |
45 |
--------------------------------------------------------------------------------
/src/components/inspector/select-data-type-mask.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 | {{ $t('No elements found. Consider changing the search query.') }}
15 | {{ $t('No Data Available') }}
16 |
17 |
18 |
19 |
{{ error }}
20 |
{{ error }}
21 |
22 |
{{ helper }}
23 |
24 |
25 |
26 |
51 |
--------------------------------------------------------------------------------
/src/components/renderer/add-loop-row.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ error }}
6 |
7 |
8 |
9 |
13 |
14 |
22 |
23 |
24 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
64 |
--------------------------------------------------------------------------------
/src/components/renderer/avatar-dropdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ label }}
4 |
5 |
6 |
7 |
16 |
--------------------------------------------------------------------------------
/src/components/renderer/form-analytics-chart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
58 |
59 |
83 |
--------------------------------------------------------------------------------
/src/components/renderer/form-avatar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
17 |
22 | {{ getInitials() }}
23 | PM
24 |
25 |
26 |
27 |
28 |
29 |
69 |
70 |
79 |
80 |
--------------------------------------------------------------------------------
/src/components/renderer/form-empty-table.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 | {{ $t(title) }}
6 |
7 |
8 | {{ $t(linkText) }}
9 |
10 |
11 |
12 |
13 |
29 |
30 |
45 |
--------------------------------------------------------------------------------
/src/components/renderer/form-image.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
28 |
29 |
37 |
--------------------------------------------------------------------------------
/src/components/renderer/form-input-masked.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
90 |
91 |
93 |
--------------------------------------------------------------------------------
/src/components/renderer/form-new-request.vue:
--------------------------------------------------------------------------------
1 |
2 |
23 |
24 |
25 |
84 |
--------------------------------------------------------------------------------
/src/components/renderer/form-record-list-static.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t(label) }}
4 |
5 |
6 |
7 |
8 | {{ field.content }}
9 | |
10 |
11 |
12 |
13 |
14 |
15 | {{ formatIfDate(mustache(field.value, val)) }}
16 | |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
40 |
--------------------------------------------------------------------------------
/src/components/renderer/form-text.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
45 |
--------------------------------------------------------------------------------
/src/components/renderer/index.js:
--------------------------------------------------------------------------------
1 | export { default as AddLoopRow } from "./add-loop-row.vue";
2 | export { default as FileDownload } from "./file-download.vue";
3 | export { default as FileUpload } from "./file-upload.vue";
4 | export { default as FormAvatar } from "./form-avatar.vue";
5 | export { default as FormButton } from "./form-button.vue";
6 | export { default as FormImage } from "./form-image.vue";
7 | export { default as FormInputMasked } from "./form-masked-input.vue";
8 | export { default as FormLoop } from "./form-loop.vue";
9 | export { default as FormMaskedInput } from "./form-masked-input.vue";
10 | // export { default as FormMultiColumn } from "./form-multi-column.vue";
11 | export { default as FormNestedScreen } from "./form-nested-screen.vue";
12 | export { default as FormRecordList } from "./form-record-list.vue";
13 | export { default as FormRecordListStatic } from "./form-record-list-static.vue";
14 | export { default as FormText } from "./form-text.vue";
15 | export { default as NewFormMultiColumn } from "./new-form-multi-column.vue";
16 | export { default as ScreenRendererError } from "./screen-renderer-error.vue";
17 | export { default as FormListTable } from "./form-list-table.vue";
18 | export { default as FormAnalyticsChart } from "./form-analytics-chart.vue";
19 | export { default as FormRequests } from "./form-requests.vue";
20 | export { default as FormTasks } from "./form-tasks.vue";
21 | export { default as LinkButton } from "./link-button.vue";
22 | export { default as FormCollectionRecordControl } from "./form-collection-record-control.vue";
23 | export { default as FormCollectionViewControl } from "./form-collection-view-control.vue";
24 |
--------------------------------------------------------------------------------
/src/components/renderer/link-button.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
26 |
31 |
--------------------------------------------------------------------------------
/src/components/renderer/new-form-multi-column.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/src/components/renderer/screen-renderer-error.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ value.error }}
5 |
6 |
7 |
8 |
9 |
10 |
17 |
--------------------------------------------------------------------------------
/src/components/sortable/sortable.scss:
--------------------------------------------------------------------------------
1 | .sortable {
2 | &-box {
3 | font-family: "Open Sans", sans-serif !important;
4 | overflow-x: auto;
5 | }
6 |
7 | &-search-box {
8 | display: flex;
9 | align-items: center;
10 | border-color: #cdddee !important;
11 |
12 | & > input.form-control:focus {
13 | border: 0 none !important;
14 | }
15 | }
16 |
17 | &-search-icon {
18 | margin: {
19 | left: 16px;
20 | right: 8px;
21 | }
22 | color: #6A7888;
23 | }
24 |
25 | &-btn-new {
26 | background: #1572C2;
27 | color: #ffffff;
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/src/components/sortable/tableStyles.scss:
--------------------------------------------------------------------------------
1 | $primary-bg-color: #f8fbff;
2 | $primary-border-color: #e6efff;
3 |
4 | $success-bg-color: #71d188;
5 | $success-border-color: #28a745;
6 |
7 | $warning-bg-color: #fff3cd;
8 | $warning-border-color: #ffc107;
9 |
10 | $secondary-bg-color: #fbfbfc;
11 | $secondary-border-color: #f3f5f7;
12 |
13 | @mixin record-list-table($bg-color, $border-color) {
14 | @extend .record-list-table-base;
15 |
16 | thead th {
17 | background-color: $bg-color;
18 | border-top-color: $border-color;
19 | border-bottom-color: $border-color;
20 | &:first-child {
21 | border-left-color: $border-color;
22 | }
23 | &:last-child {
24 | border-right-color: $border-color;
25 | }
26 | }
27 |
28 | tbody tr {
29 | td {
30 | border-color: $border-color;
31 | border-top: solid 0;
32 | }
33 |
34 | td:first-child {
35 | border-left-color: $border-color;
36 | }
37 |
38 | td:last-child {
39 | border-right-color: $border-color;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/utils/default-loading-spinner.vue:
--------------------------------------------------------------------------------
1 |
2 | Loading Data...
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/src/components/utils/index.js:
--------------------------------------------------------------------------------
1 | import DefaultLoadingSpinner from "./default-loading-spinner.vue";
2 | import MultipleUploadsCheckbox from "./multiple-uploads-checkbox.vue";
3 | import RequiredCheckbox from "./required-checkbox.vue";
4 |
5 | export { DefaultLoadingSpinner, MultipleUploadsCheckbox, RequiredCheckbox };
6 |
--------------------------------------------------------------------------------
/src/components/utils/multiple-uploads-checkbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $t('Upload multiple files') }}
4 |
5 |
6 |
7 |
35 |
--------------------------------------------------------------------------------
/src/components/utils/required-checkbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $t('Required') }}
4 |
5 |
6 |
7 |
35 |
--------------------------------------------------------------------------------
/src/customLogs.js:
--------------------------------------------------------------------------------
1 | export default class CustomLog {
2 | static logWithStyle(icon, type, name, status, message, color) {
3 | const baseColor = color === '255, 0, 0' ? 'red' : 'green';
4 | const style = `background-color: rgba(${color}, 0.1); color: ${baseColor}; padding: 2px 4px; border-radius: 3px;`;
5 | const transparentStyle = 'background-color: transparent';
6 |
7 | const logPrefix = `%c${icon} %c${type} "${name}" has`;
8 | const logStatus = `%c${status}`;
9 |
10 | if (status === 'RUN') {
11 | console.log(`${logPrefix} ${logStatus}`, style, transparentStyle, style);
12 | } else if (status === 'FAILED') {
13 | console.groupCollapsed(`${logPrefix} ${logStatus}`, style, transparentStyle, style);
14 | console.log(`%c${message}`, style);
15 | console.groupEnd();
16 | }
17 | }
18 |
19 | static success(type, name) {
20 | this.logWithStyle('\u2705', type, name, 'RUN', '', '0, 128, 0');
21 | }
22 |
23 | static error(type, name, message) {
24 | this.logWithStyle('\u274C', type, name, 'FAILED', message, '255, 0, 0');
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/form-input-mask-config.js:
--------------------------------------------------------------------------------
1 | import currencies from './currency.json';
2 |
3 | export default {
4 | defaultMask: {
5 | label: 'Data Format',
6 | options: [],
7 | config: {},
8 | },
9 | currency: {
10 | label: 'Currency Format',
11 | options: currencies,
12 | optionsLabel: 'code',
13 | config: currencies,
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/src/global-properties.js:
--------------------------------------------------------------------------------
1 | export const formTypes = {
2 | form: 'form',
3 | display: 'display',
4 | };
5 |
6 | export default [
7 | {
8 | inspector: [
9 | {
10 | type: 'FormInput',
11 | field: 'conditionalHide',
12 | config: {
13 | label: 'Visibility Rule',
14 | helper: 'This control is hidden until this expression is true',
15 | },
16 | },
17 | {
18 | type: 'DeviceVisibility',
19 | field: 'deviceVisibility',
20 | config: {
21 | label: 'Device Visibility',
22 | helper: 'This control is hidden until this expression is true',
23 | },
24 | },
25 | {
26 | type: 'FormInput',
27 | field: 'customFormatter',
28 | config: {
29 | label: 'Custom Format String',
30 | helper: 'Use the Mask Pattern format
Date ##/##/####
SSN ###-##-####
Phone (###) ###-####',
31 | validation: '',
32 | },
33 | },
34 | {
35 | type: 'FormInput',
36 | field: 'customCssSelector',
37 | config: {
38 | label: 'CSS Selector Name',
39 | helper: 'Use this in your custom css rules',
40 | validation: 'regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]',
41 | },
42 | },
43 | {
44 | type: 'FormInput',
45 | field: 'ariaLabel',
46 | config: {
47 | label: 'Aria Label',
48 | helper: 'Attribute designed to help assistive technology (e.g. screen readers) attach a label',
49 | },
50 | },
51 | {
52 | type: 'FormInput',
53 | field: 'tabindex',
54 | config: {
55 | label: 'Tab Order',
56 | helper: 'Order in which a user will move focus from one control to another by pressing the Tab key',
57 | validation: 'regex: [0-9]*',
58 | },
59 | },
60 | {
61 | type: 'EncryptedConfig',
62 | field: 'encryptedConfig',
63 | config: {
64 | label: 'Encrypted',
65 | helper: '',
66 | },
67 | },
68 | ],
69 | },
70 | ];
71 |
--------------------------------------------------------------------------------
/src/itemProcessingUtils.js:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 | import moment from 'moment-timezone';
3 |
4 | function processFormItem(item) {
5 | if (item.component !== 'FormMultiColumn') {
6 | return item;
7 | }
8 |
9 | return item.items.flatMap(processFormItem);
10 | }
11 |
12 | export function getItemsFromConfig(config) {
13 | return config
14 | .flatMap(page => page.items)
15 | .flatMap(processFormItem);
16 | }
17 |
18 | export function getDefaultValueForItem(item) {
19 | let defaultValue = null;
20 |
21 | if (['FormInput', 'FormTextArea', 'FormText'].includes(item.component)) {
22 | defaultValue = '';
23 | }
24 |
25 | if (item.component === 'FormCheckbox') {
26 | defaultValue = item.config.initiallyChecked || false;
27 | }
28 |
29 | if (item.component === 'FormRecordList') {
30 | defaultValue = [];
31 | }
32 |
33 | if (item.component === 'FormDatePicker') {
34 | defaultValue = generateNewDate(item.config.dataFormat);
35 | }
36 |
37 | if (item.component === 'FormButton' && item.config.event === 'script') {
38 | defaultValue = 0;
39 | }
40 |
41 | return defaultValue;
42 | }
43 |
44 | function generateNewDate(dataFormat) {
45 | let timezone = moment.tz.guess();
46 |
47 | if (typeof window.ProcessMaker !== 'undefined' && window.ProcessMaker.user && window.ProcessMaker.user.timezone) {
48 | timezone = window.ProcessMaker.user.timezone;
49 | }
50 |
51 | const date = moment.tz(timezone);
52 |
53 | if (dataFormat !== 'datetime') {
54 | date.startOf('day');
55 | }
56 |
57 | return date.toISOString();
58 | }
59 |
--------------------------------------------------------------------------------
/src/mixins/CurrentPageProperty.js:
--------------------------------------------------------------------------------
1 | export default {
2 | props: {
3 | currentPage: null,
4 | },
5 | methods: {
6 | updateCurrentPage() {
7 | if (this.currentPage!==undefined && this.currentPage!==null) {
8 | this.setCurrentPage(this.currentPage);
9 | }
10 | },
11 | },
12 | watch: {
13 | currentPage() {
14 | this.updateCurrentPage();
15 | },
16 | },
17 | mounted() {
18 | this.$nextTick(() => {
19 | this.updateCurrentPage();
20 | });
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/src/mixins/DeviceDetector.js:
--------------------------------------------------------------------------------
1 | export const MAX_MOBILE_WIDTH = 480;
2 | export const originalDevicePixelRatio = window.devicePixelRatio;
3 | export default {
4 | created() {
5 | window.addEventListener("resize", this.resizeHandler);
6 | },
7 | destroyed() {
8 | window.removeEventListener("resize", this.resizeHandler);
9 | },
10 | mounted() {
11 | this.$nextTick(() => {
12 | this.checkIfIsMobile();
13 | });
14 | },
15 | methods: {
16 | resizeHandler() {
17 | this.checkIfIsMobile();
18 | },
19 | checkIfIsMobile() {
20 | const renderer = document.getElementById("vue-form-renderer");
21 | const isModelerInspector = this.data && this.data.$type && this.data.$type.startsWith("bpmn:");
22 | if (this.definition && !isModelerInspector) {
23 | this.definition.isMobile =
24 | renderer &&
25 | renderer.offsetWidth <= MAX_MOBILE_WIDTH &&
26 | originalDevicePixelRatio === window.devicePixelRatio;
27 | }
28 | }
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/src/mixins/HasColorProperty.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | methods: {
4 | elementCssClass(element) {
5 | const css = [];
6 | element.config.bgcolor ? css.push(element.config.bgcolor) : null;
7 | element.config.color ? css.push(element.config.color) : null;
8 | element.config.bgcolormodern ? css.push(element.config.bgcolormodern) : null;
9 | return css.join(' ');
10 | },
11 | elementCssClassModern(element) {
12 | const css = [];
13 | element.config.bgcolormodern ? css.push(element.config.bgcolormodern) : null;
14 | return css.join(' ');
15 | }
16 | },
17 | };
18 |
19 |
--------------------------------------------------------------------------------
/src/mixins/VisibilityRule.js:
--------------------------------------------------------------------------------
1 | import { Parser } from 'expr-eval';
2 |
3 | export default {
4 | methods: {
5 | visibilityRuleIsVisible(rule, name, deviceVisibility) {
6 | const visibility = deviceVisibility || { showForDesktop: true, showForMobile: true, isMobile: false };
7 | const visibleInDevice =
8 | (visibility.isMobile && visibility.showForMobile) || (!visibility.isMobile && visibility.showForDesktop);
9 |
10 | try {
11 | if (rule && rule.trim().length > 0) {
12 | const dataWithParent = this.getDataReference();
13 | const isVisible = Boolean(Parser.evaluate(rule, dataWithParent));
14 |
15 | return isVisible && visibleInDevice;
16 | }
17 |
18 | return visibleInDevice;
19 | } catch (e) {
20 | // empty.
21 | }
22 |
23 | return false;
24 | }
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/src/mixins/canOpenJsonFile.js:
--------------------------------------------------------------------------------
1 | import FileUpload from 'vue-upload-component';
2 |
3 | const reader = new FileReader();
4 |
5 | export default {
6 | components: { FileUpload },
7 | data() {
8 | return {
9 | uploadedJson: null,
10 | jsonFiles: [],
11 | };
12 | },
13 | watch: {
14 | jsonFiles([fileObject]) {
15 | if (fileObject) {
16 | reader.readAsText(fileObject.file);
17 | }
18 | },
19 | },
20 | methods: {
21 | loadScreenPackage() {
22 | const json = JSON.parse(this.uploadedJson);
23 | let screen;
24 | if (json instanceof Array) {
25 | screen = { config:json, computed: [], customCSS: null };
26 | } else if (json && json.screens instanceof Array) {
27 | screen = json.screens[0];
28 | if (window.exampleScreens instanceof Array) {
29 | window.exampleScreens = json.screens;
30 | }
31 | } else if (json && json.screens && json.screens.config) {
32 | screen = json.screens;
33 | }
34 | this.$refs.builder.config.splice(0, Infinity, ...screen.config);
35 | this.$refs.builder.migrateConfig();
36 | this.computed.splice(0, Infinity, ...screen.computed);
37 | this.customCSS = screen.customCSS;
38 | },
39 | clearUpload() {
40 | this.uploadedJson = null;
41 | this.jsonFiles = [];
42 | },
43 | setUploadedJson(event) {
44 | this.uploadedJson = event.target.result;
45 | },
46 | },
47 | created() {
48 | reader.onload = this.setUploadedJson;
49 | },
50 | };
51 |
--------------------------------------------------------------------------------
/src/mixins/computedFields.js:
--------------------------------------------------------------------------------
1 | import { Parser } from "expr-eval";
2 | import CustomLog from '../customLogs';
3 |
4 | export default {
5 | methods: {
6 | evaluateExpression(expression, type) {
7 | let value = null;
8 |
9 | try {
10 | // Monitor if variable belongs to data (defined variables) or
11 | // vdata (external variables)in this way the event is not
12 | // executed again when the variable is update
13 | const data = this.getDataReference(null, () => {
14 | throw new Error(
15 | "You are not allowed to set properties from inside an expression"
16 | );
17 | });
18 |
19 | if (type === "expression") {
20 | value = Parser.evaluate(expression, data);
21 | } else {
22 | // Create a new function with the expression and bind the data context
23 | // eslint-disable-next-line no-new-func
24 | value = new Function(expression).bind(data);
25 | return { result: value(), error: null };
26 | }
27 |
28 | if (value instanceof Date) {
29 | value = value.toISOString();
30 | }
31 | return { result: value, error: null };
32 | } catch (error) {
33 | // Catch any errors and return them
34 | return { result: null, error };
35 | }
36 | },
37 | /**
38 | * Logs an error message with a custom format.
39 | * @param {string} name - The name of the calculation.
40 | * @param {string} message - The error message.
41 | */
42 | customErrorLog(name, message) {
43 | CustomLog.error('Calc', name, message);
44 | },
45 |
46 | /**
47 | * Logs a success message with a custom format.
48 | * @param {string} name - The name of the calculation.
49 | */
50 | customSuccessLog(name) {
51 | CustomLog.success('Calc', name);
52 | },
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/src/mixins/extensions/AccordionContainer.js:
--------------------------------------------------------------------------------
1 | export default {
2 | mounted() {
3 | this.extensions.push({
4 | onloadproperties({ componentName, properties, element }) {
5 | if (componentName === 'FormAccordion') {
6 | properties[':value'] = this.byRef(element.items);
7 | }
8 | },
9 | });
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/src/mixins/extensions/ComputedFields.js:
--------------------------------------------------------------------------------
1 | export default {
2 | methods: {
3 | /**
4 | * Implements computed fields like this:
5 | *
6 | * calcProperty() {
7 | * let value = this.evaluateExpression('return formula();', 'javascript');
8 | * value = this.addNonDefinedComputedAttributes(value);
9 | * this.setValueAsync("calcProperty", value, this.vdata);
10 | * }
11 | */
12 | computedFields(screen, definition, logsEnabled = true) {
13 | // For each computed field defined
14 | definition.computed.forEach((computed) => {
15 | if (computed.byPass) {
16 | // If the computed field has bypass set to true, skip it
17 | return;
18 | }
19 | const formula = JSON.stringify(computed.formula);
20 | const type = JSON.stringify(computed.type);
21 | const name = JSON.stringify(computed.property);
22 | const safeDotName = this.safeDotName(computed.property);
23 | const code = `
24 | const evaluatedExpression = this.evaluateExpression(${formula}, ${type});
25 | // Handle errors if any
26 | if (evaluatedExpression.error) {
27 | if (${logsEnabled}) {
28 | this.customErrorLog(${name}, evaluatedExpression.error);
29 | }
30 | } else {
31 | // Add non-defined computed attributes
32 | const value = this.addNonDefinedComputedAttributes(evaluatedExpression.result);
33 | // Set the value
34 | this.setValue(${name}, value, this.vdata);
35 |
36 | // Log the successful calculation if logging is enabled
37 | if (${logsEnabled}) {
38 | this.customSuccessLog(${name});
39 | }
40 |
41 | // Return the result
42 | return value;
43 | }
44 | `;
45 | this.addComputed(screen, safeDotName, code, "");
46 | // required to enable reactivity of computed field
47 | this.addWatch(screen, safeDotName, "");
48 | });
49 | }
50 | },
51 | mounted() {
52 | this.extensions.push({
53 | onbuild({ screen, definition }) {
54 | if (definition.computed) {
55 | this.computedFields(screen, definition);
56 | }
57 | }
58 | });
59 | }
60 | };
61 |
--------------------------------------------------------------------------------
/src/mixins/extensions/CustomCss.js:
--------------------------------------------------------------------------------
1 | export default {
2 | mounted() {
3 | this.extensions.push({
4 | onloaditems({ element, wrapper }) {
5 | if (element.config.customCssSelector) {
6 | wrapper.setAttribute('selector', element.config.customCssSelector);
7 | }
8 | },
9 | });
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/src/mixins/extensions/DataManager.js:
--------------------------------------------------------------------------------
1 | export default {
2 | methods: {
3 | dataFields(screen, definition) {
4 | const localVariables = this.variables.filter(
5 | (v) => !this.isComputedVariable(v.name, definition)
6 | );
7 | localVariables.forEach((v) => {
8 | const { component } = v.element;
9 | const dataFormat = v.config.dataFormat || null;
10 | const safeDotName = this.safeDotName(v.name);
11 | this.addData(
12 | screen,
13 | safeDotName,
14 | `
15 | this.getValue(${JSON.stringify(v.name)}, this.vdata) ||
16 | this.getValue(${JSON.stringify(v.name)}, data) ||
17 | this.initialValue(
18 | '${component}',
19 | '${dataFormat}',
20 | ${JSON.stringify(v.config)})
21 | `,
22 | v.name
23 | );
24 | this.addWatch(
25 | screen,
26 | `vdata.${v.name}`,
27 | `if (this.canUpdate("${safeDotName}")) {
28 | this.${safeDotName} = value;
29 | }`
30 | );
31 | });
32 | this.addProp(screen, "vdata", null);
33 | },
34 | /**
35 | * Replace `.` by `_DOT_` in a variable name
36 | * @param {string} name
37 | * @returns {string}
38 | */
39 | safeDotName(name) {
40 | // if starts with _parent returns as is
41 | if (name.startsWith("_parent")) {
42 | return name;
43 | }
44 | return name.replace(/\./g, "_DOT_");
45 | }
46 | },
47 | mounted() {
48 | this.extensions.push({
49 | onbuild({ screen, definition }) {
50 | this.dataFields(screen, definition);
51 | }
52 | });
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/src/mixins/extensions/InputText.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | mounted() {
4 | this.alias['FormInput'] = 'form-masked-input';
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/src/mixins/extensions/MultiColumn.js:
--------------------------------------------------------------------------------
1 | export default {
2 | methods: {
3 | loadMultiColumnProperties({ properties, element }) {
4 | properties.class = this.elementCssClass(element);
5 | properties.ref = 'container';
6 | properties.selected = 'selected';
7 | //properties['v-model'] = "element.items";
8 | //@submit="submit"
9 | properties[':config'] = JSON.stringify(element.config);
10 | //:ancestor-screens="ancestorScreens"
11 | properties.name = element.config && element.config.name !== undefined ? element.config.name : null;
12 | //@pageNavigate="pageNavigate"
13 | //v-bind="element.config"
14 | //:is="element.component"
15 | properties[':form-config'] = '$parent && $parent.definition.config';
16 | //:mode="mode"
17 | },
18 | loadMultiColumnItems({ element, node, screen, definition, formIndex }) {
19 | element.items.forEach((col, index) => {
20 | const column = this.createComponent('div', {
21 | class: `col-sm-${element.config.options[index].content}`,
22 | });
23 | this.loadItems(col, column, screen, definition, formIndex);
24 | node.appendChild(column);
25 | });
26 | },
27 | },
28 | mounted() {
29 | this.extensions.push({
30 | onloadproperties(params) {
31 | if (params.element.container && params.componentName === 'FormMultiColumn') {
32 | this.loadMultiColumnProperties(params);
33 | }
34 | },
35 | onloaditems(params) {
36 | if (params.element.container && params.componentName === 'FormMultiColumn') {
37 | this.loadMultiColumnItems(params);
38 | }
39 | },
40 | });
41 | this.alias['FormMultiColumn'] = 'NewFormMultiColumn';
42 | this.alias['MultiColumn'] = 'NewFormMultiColumn';
43 | },
44 | };
45 |
--------------------------------------------------------------------------------
/src/mixins/extensions/PageNavigate.js:
--------------------------------------------------------------------------------
1 | export default {
2 | methods: {
3 | /* istanbul ignore next */
4 | pageNavigate() {},
5 | pageNavigationProperties({ properties }) {
6 | properties['@page-navigate'] = 'pageNavigate';
7 | properties[':validate'] = '$v';
8 | },
9 | pageNavigationBuild(screen) {
10 | this.addData(screen, 'currentPage__', 'this._initialPage');
11 | screen.methods.pageNavigate = function(page) {
12 | // Skip navigate button if page is not defined
13 | if (!this.$parent.definition.config[page]) {
14 | return;
15 | }
16 | this.$parent.$emit("updatePage");
17 | this.currentPage__ = page;
18 | };
19 | },
20 | },
21 | mounted() {
22 | this.extensions.push({
23 | onloadproperties(params) {
24 | if (params.element.component === 'FormButton' && params.element.config.event === 'pageNavigate') {
25 | this.pageNavigationProperties(params);
26 | }
27 | },
28 | onbuild({ screen }) {
29 | this.pageNavigationBuild(screen);
30 | },
31 | });
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/src/mixins/extensions/Submit.js:
--------------------------------------------------------------------------------
1 | export default {
2 | mounted() {
3 | this.extensions.push({
4 | onloadproperties({ element, properties }) {
5 | if (element.component === 'FormButton' && element.config.event === 'submit') {
6 | properties[':validate'] = '$v';
7 | }
8 | },
9 | });
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/src/mixins/extensions/VisibilityRule.js:
--------------------------------------------------------------------------------
1 | import VisibilityRule from '../VisibilityRule';
2 |
3 | export default {
4 | mounted() {
5 | this.extensions.push({
6 | onloaditems({ element, wrapper, definition }) {
7 | const visibility = element.config.deviceVisibility || { showForDesktop: true, showForMobile: true }
8 | const restrictDeviceVisibility = !visibility.showForDesktop || !visibility.showForMobile;
9 |
10 |
11 | element.visibleInDevice =
12 | (definition.isMobile && visibility.showForMobile) ||
13 | (!definition.isMobile && visibility.showForDesktop);
14 |
15 | if (element.config.conditionalHide || restrictDeviceVisibility) {
16 | const deviceVisibility = JSON.stringify( { ...visibility, isMobile: definition.isMobile } );
17 | wrapper.setAttribute(
18 | 'v-show',
19 | `visibilityRuleIsVisible(${JSON.stringify(element.config.conditionalHide)},
20 | ${JSON.stringify(element.config.name)}, ${deviceVisibility})`
21 | );
22 | }
23 | },
24 | onbuild({ screen }) {
25 | screen.mixins.push(VisibilityRule);
26 | },
27 | });
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/src/mixins/extensions/Watchers.js:
--------------------------------------------------------------------------------
1 | import watchersMixin from '../../mixins/watchers';
2 |
3 | export default {
4 | methods: {
5 | filterWatchers(watcher) {
6 | const inContext = !watcher.watching || this.variables.find(({name}) => name === watcher.watching);
7 | return inContext;
8 | },
9 | addWatcherVariables(definition) {
10 | if (definition.watchers) {
11 | definition.watchers.filter(this.filterWatchers).forEach((watcher) => {
12 | const inContext = !watcher.watching || this.variables.find(({name}) => name === watcher.watching);
13 | if (inContext) {
14 | this.registerVariable(watcher.output_variable, {});
15 | }
16 | });
17 | }
18 | },
19 | watchers(screen, definition) {
20 | if (definition.watchers) {
21 | screen.mixins.push(watchersMixin);
22 | definition.watchers.filter(this.filterWatchers).forEach((watcher) => {
23 | if (watcher?.byPass) {
24 | // If the watcher has bypass set to true, skip it
25 | return;
26 | }
27 | this.addMounted(screen, `
28 | this.$nextTick(() => this.$watch('vdata.${watcher.watching}', (newValue) => {
29 | if (typeof newValue !== 'undefined') {
30 | this.queueWatcher(${JSON.stringify(watcher)});
31 | }
32 | }));
33 | `);
34 |
35 | if (watcher.run_onload) {
36 | this.addMounted(screen, `
37 | this.queueWatcher(${JSON.stringify(watcher)});
38 | `);
39 | }
40 | });
41 | }
42 | },
43 | },
44 | mounted() {
45 | this.extensions.push({
46 | onbuild({ screen, definition }) {
47 | this.watchers(screen, definition);
48 | },
49 | onparse({ definition }) {
50 | this.addWatcherVariables(definition);
51 | },
52 | });
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/src/mixins/extensions/index.js:
--------------------------------------------------------------------------------
1 | const mixins = [];
2 | const modules = import.meta.glob("./*.js", { eager: true });
3 |
4 | Object.entries(modules).forEach(([path, m]) => {
5 | mixins.push(m.default);
6 | });
7 |
8 | export default mixins;
9 |
--------------------------------------------------------------------------------
/src/mixins/getValidPath.js:
--------------------------------------------------------------------------------
1 | export default {
2 | methods: {
3 | getValidPath(objectPath) {
4 | return this.objectPathHasError(objectPath)
5 | ? `["${objectPath}"]`
6 | : objectPath;
7 | },
8 | objectPathHasError(objectPath) {
9 | try {
10 | this.$vueSet({}, objectPath);
11 | return false;
12 | } catch (error) {
13 | return true;
14 | }
15 | },
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/src/mixins/index.js:
--------------------------------------------------------------------------------
1 | export { default as canOpenJsonFile } from "./canOpenJsonFile";
2 | export { default as computedFields } from "./computedFields";
3 | export { default as CurrentPageProperty } from "./CurrentPageProperty";
4 | export { default as DataReference } from "./DataReference";
5 | export { default as datatable } from "./datatable";
6 | export { default as defaultValues } from "./defaultValues";
7 | export { default as DeviceDetector } from "./DeviceDetector";
8 | export { default as focusErrors } from "./focusErrors";
9 | export { default as formWatchers } from "./formWatchers";
10 | export { default as getValidPath } from "./getValidPath";
11 | export { default as HasColorProperty } from "./HasColorProperty";
12 | export { default as Json2Vue } from "./Json2Vue";
13 | export { default as multiselectApi } from "./multiselectApi";
14 | export { default as mustacheEvaluation } from "./mustacheEvaluation";
15 | export { default as ScreenBase } from "./ScreenBase";
16 | export { default as shouldElementBeVisible } from "./shouldElementBeVisible";
17 | export { default as testing } from "./testing";
18 | export { ValidationMsg, validators } from "./ValidationRules";
19 | export { default as VisibilityRule } from "./VisibilityRule";
20 | export { default as watchers } from "./watchers";
21 | export { default as Clipboard } from "./Clipboard";
--------------------------------------------------------------------------------
/src/mixins/multiselectApi.js:
--------------------------------------------------------------------------------
1 | import { get } from 'lodash';
2 | import debounce from 'lodash/debounce';
3 |
4 | export default {
5 | props: {
6 | value: null,
7 | placeholder: String,
8 | trackBy: {
9 | type: String,
10 | default: 'id',
11 | },
12 | label: {
13 | type: String,
14 | default: 'name',
15 | },
16 | api: {
17 | type: String,
18 | default: 'process',
19 | },
20 | multiple: {
21 | type: Boolean,
22 | default: false,
23 | },
24 | storeId: {
25 | type: Boolean,
26 | default: true,
27 | },
28 | },
29 | data() {
30 | return {
31 | pmql: null,
32 | options: [],
33 | selectedOption: null,
34 | fields: null,
35 | };
36 | },
37 | watch: {
38 | value: {
39 | immediate: true,
40 | handler(value) {
41 | this.selectedOption = this.storeId
42 | ? this.options.find(option => get(option, this.trackBy) == value)
43 | : value;
44 | value && !this.selectedOption ? this.loadSelected(value) : null;
45 | },
46 | },
47 | },
48 | methods: {
49 | change(value) {
50 | this.$emit('input', this.storeId ? get(value, this.trackBy) : value);
51 | },
52 | loadOptions(filter) {
53 | const pmql = this.pmql;
54 | const fields = this.fields || undefined;
55 | window.ProcessMaker.apiClient
56 | .get(this.api, { params: { filter, pmql, fields } })
57 | .then(response => {
58 | this.options = response.data.data || [];
59 | });
60 | },
61 | loadSelected(value) {
62 | window.ProcessMaker.apiClient
63 | .get(`${this.api}/${value}`)
64 | .then(response => {
65 | this.selectedOption = response.data;
66 | });
67 | },
68 | },
69 | mounted() {
70 | this.loadOptions = debounce(this.loadOptions, 1000);
71 | },
72 | };
73 |
--------------------------------------------------------------------------------
/src/mixins/mustacheEvaluation.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Mustache from 'mustache';
3 |
4 | export default {
5 | methods: {
6 | mustache(expression, data) {
7 | const value = _.get(data, expression);
8 | try {
9 | return expression.indexOf('{{') > -1 ? Mustache.render(expression, data) : value;
10 | } catch (error) {
11 | return value;
12 | }
13 | },
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/src/mixins/shouldElementBeVisible.js:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 | import { Parser } from 'expr-eval';
3 |
4 | export default {
5 | methods: {
6 | shouldElementBeVisible(element) {
7 | const { conditionalHide } = element.config;
8 |
9 | if (!conditionalHide) {
10 | return true;
11 | }
12 |
13 | try {
14 | return !!Parser.evaluate(conditionalHide, this.transientData);
15 | } catch (error) {
16 | return false;
17 | }
18 | },
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/src/mixins/testing.js:
--------------------------------------------------------------------------------
1 | import { get } from 'lodash';
2 |
3 | // TESTING HELPERS
4 | let mounted;
5 | window.testing = {
6 | builder: null,
7 | onready: new Promise(resolve => mounted = resolve),
8 | addControlByType(type, target = null) {
9 | const control = this.builder.controls.find(control => control.component === type);
10 | this.builder.testingAddControl(control, target);
11 | },
12 | addControlByLabel(label, target = null) {
13 | const control = this.builder.controls.find(control => control.label === label);
14 | this.builder.testingAddControl(control, target);
15 | },
16 | removeControl(index) {
17 | this.builder.testingDeleteItem(index);
18 | },
19 | showControlsTypes() {
20 | const types = this.builder.controls.map(control => control.component);
21 | // eslint-disable-next-line no-console
22 | console.log(types);
23 | },
24 | showControlsLabels() {
25 | const labels = this.builder.controls.map(control => control.label);
26 | // eslint-disable-next-line no-console
27 | console.log(labels);
28 | },
29 | };
30 |
31 | export default {
32 | data() {
33 | window.testing.builder = this;
34 | return {};
35 | },
36 | methods: {
37 | testingAddControl(control, target) {
38 | const clone = this.cloneControl(control);
39 | if (!target) {
40 | this.config[this.currentPage].items.push(clone);
41 | } else {
42 | const items = get(this, target);
43 | items.push(clone);
44 | }
45 | this.updateState();
46 | this.inspect(clone);
47 | },
48 | testingDeleteItem(index) {
49 | this.config[this.currentPage].items.splice(index, 1);
50 | this.updateState();
51 | },
52 | },
53 | mounted() {
54 | this.$nextTick(() => mounted());
55 | },
56 | };
57 |
--------------------------------------------------------------------------------
/src/store/modules/undoRedoModule.js:
--------------------------------------------------------------------------------
1 | const undoRedoModule = {
2 | namespaced: true,
3 | state() {
4 | return {
5 | stack: [],
6 | position: null
7 | };
8 | },
9 | getters: {
10 | canUndo(state) {
11 | return state.position > 0;
12 | },
13 | canRedo(state) {
14 | return state.position < state.stack.length - 1;
15 | },
16 | currentState(state, getters) {
17 | const currentState = state.stack[state.position];
18 |
19 | if (getters.nextState && getters.nextState.deletedPage) {
20 | currentState.currentPage = getters.nextState.currentPage;
21 | }
22 |
23 | return currentState;
24 | },
25 | nextState(state, getters) {
26 | if (getters.canRedo) {
27 | return state.stack[state.position + 1];
28 | }
29 |
30 | return false;
31 | }
32 | },
33 | mutations: {
34 | setPosition(state, position) {
35 | state.position = position;
36 | },
37 | setState(state, newState) {
38 | state.stack.push(newState);
39 | }
40 | },
41 | methods: {
42 | emitChanges() {
43 | window.ProcessMaker.EventBus.$emit("screen-change");
44 | }
45 | },
46 | actions: {
47 | pushState({ state, getters, commit }, newState) {
48 | if (newState.config === getters.currentState) {
49 | return;
50 | }
51 |
52 | commit("setState", newState);
53 | commit("setPosition", state.stack.length - 1);
54 | undoRedoModule.methods.emitChanges();
55 | },
56 | undo({ state, getters, commit }) {
57 | if (!getters.canUndo) {
58 | return;
59 | }
60 |
61 | commit("setPosition", state.position - 1);
62 | undoRedoModule.methods.emitChanges();
63 | },
64 | redo({ state, getters, commit }) {
65 | if (!getters.canRedo) {
66 | return;
67 | }
68 |
69 | commit("setPosition", state.position + 1);
70 | undoRedoModule.methods.emitChanges();
71 | }
72 | }
73 | };
74 |
75 | export default undoRedoModule;
76 |
--------------------------------------------------------------------------------
/src/stories/ClipboardButton.stories.js:
--------------------------------------------------------------------------------
1 | import ClipboardButton from '../components/ClipboardButton.vue';
2 | import { within, userEvent, expect } from "@storybook/test";
3 |
4 | export default {
5 | title: 'Components/ClipboardButton',
6 | tags: ["autodocs"],
7 | component: ClipboardButton,
8 | argTypes: {
9 | isInClipboard: { control: 'boolean' },
10 | },
11 | };
12 |
13 | const Template = (args) => ({
14 | components: { ClipboardButton },
15 | setup() {
16 | return { args };
17 | },
18 | template: '',
19 | });
20 |
21 | // Story for when the item is in the clipboard
22 | export const InClipboard = Template.bind({});
23 | InClipboard.args = {
24 | isInClipboard: true,
25 | index: 1,
26 | addTitle: 'Add to clipboard',
27 | removeTitle: 'Remove from clipboard',
28 | addToClipboard: () => {},
29 | removeFromClipboard: () => {},
30 | };
31 | InClipboard.play = async ({ canvasElement }) => {
32 | const canvas = within(canvasElement);
33 |
34 | // Expect the "Remove from clipboard" button to be rendered
35 | const removeButton = await canvas.getByRole('button', { name: /Remove from clipboard/i });
36 | expect(removeButton).toBeInTheDocument();
37 |
38 | // Simulate a click on the remove button
39 | await userEvent.click(removeButton);
40 |
41 | // Ensure that the button remains visible after interaction
42 | expect(removeButton).toBeVisible();
43 | };
44 |
45 | // Story for when the item is not in the clipboard
46 | export const NotInClipboard = Template.bind({});
47 | NotInClipboard.args = {
48 | isInClipboard: false,
49 | index: 1,
50 | addToClipboard: () => {},
51 | removeFromClipboard: () => {},
52 | };
53 |
54 | NotInClipboard.play = async ({ canvasElement }) => {
55 | const canvas = within(canvasElement);
56 |
57 | // Expect the "Add to clipboard" button to be rendered
58 | const addButton = await canvas.getByRole('button', { name: /Add to clipboard/i });
59 | expect(addButton).toBeInTheDocument();
60 |
61 | // Simulate a click on the add button
62 | await userEvent.click(addButton);
63 |
64 | // Ensure that the button remains visible after interaction
65 | expect(addButton).toBeVisible();
66 | };
67 |
--------------------------------------------------------------------------------
/tests/components/RenderScreen.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
23 |
--------------------------------------------------------------------------------
/tests/components/RenderScreen2.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
23 |
--------------------------------------------------------------------------------
/tests/components/WebEntry.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
87 |
88 |
94 |
--------------------------------------------------------------------------------
/tests/components/index.js:
--------------------------------------------------------------------------------
1 | const components = {};
2 | const modules = import.meta.glob("./*.vue");
3 | Object.entries(modules).forEach(([path, m]) => {
4 | components[path.substr(2, path.length - 6)] = m;
5 | });
6 |
7 | export default components;
8 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/avatar.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/tests/e2e/fixtures/avatar.jpeg
--------------------------------------------------------------------------------
/tests/e2e/fixtures/file1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/tests/e2e/fixtures/file1.jpeg
--------------------------------------------------------------------------------
/tests/e2e/fixtures/file1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/tests/e2e/fixtures/file1.png
--------------------------------------------------------------------------------
/tests/e2e/fixtures/file2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/tests/e2e/fixtures/file2.jpeg
--------------------------------------------------------------------------------
/tests/e2e/fixtures/file2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/tests/e2e/fixtures/file2.png
--------------------------------------------------------------------------------
/tests/e2e/fixtures/file3.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/tests/e2e/fixtures/file3.jpeg
--------------------------------------------------------------------------------
/tests/e2e/fixtures/file3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProcessMaker/screen-builder/5035e14771d97d4189c2ec777725b4f38855f1e6/tests/e2e/fixtures/file3.png
--------------------------------------------------------------------------------
/tests/e2e/specs/ClipboardBwCompatibility.spec.js:
--------------------------------------------------------------------------------
1 | import ClipboardManager from "../../../src/store/modules/ClipboardManager";
2 |
3 | describe("Clipboard backward compatibility", () => {
4 | beforeEach(() => {
5 | cy.visit("/");
6 | });
7 |
8 | it("FOUR-19630: Check clipboard with a screen from a previous version", () => {
9 | ClipboardManager.saveToLocalStorage([]);
10 | cy.loadFromJson("UUID_compatibility.json", 1);
11 |
12 | // for each control, check if it can be added to the clipboard
13 | cy.get('.control-item').each(($el, index) => {
14 | cy.wrap($el).click({ force: true });
15 | cy.get('[data-cy="addToClipboard"]').click({ force: true });
16 | cy.get('[data-cy="addToClipboard"]').should("not.exist").then(() => {
17 | // Verify it was added to the clipboard storage
18 | const content = ClipboardManager.loadFromLocalStorage();
19 | expect(content.length).to.equal(index + 1);
20 | });
21 | });
22 |
23 | // Go to second page
24 | cy.get('[data-test="page-dropdown"] button').click({force: true});
25 | cy.get('[data-test="page-popup"]').click({force: true});
26 | cy.wait(1000);
27 | const elementsAddedFromMainPage = 17;
28 |
29 | // check if each control in the second page can be added to the clipboard
30 | cy.get('.control-item').each(($el, index) => {
31 | cy.wrap($el).click({ force: true });
32 | cy.get('[data-cy="addToClipboard"]').click({ force: true });
33 | cy.get('[data-cy="addToClipboard"]').should("not.exist").then(() => {
34 | // Verify it was added to the clipboard storage
35 | const content = ClipboardManager.loadFromLocalStorage();
36 | expect(content.length).to.equal(elementsAddedFromMainPage + index + 1);
37 | });
38 | });
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/tests/e2e/specs/ClipboardControl.spec.js:
--------------------------------------------------------------------------------
1 | describe("Add Clipboard Section to Control Menu", () => {
2 |
3 | /**
4 | * Test to verify that the Clipboard section is present in the control menu.
5 | */
6 | it('should display the "Clipboard" section in the control menu', () => {
7 | // Step 1: Navigate to the homepage
8 | cy.visit("/");
9 |
10 | // Step 2: Open the clipboard acordeon
11 | cy.openAcordeonByLabel("Clipboard");
12 |
13 | // Step 3: Verify that the Clipboard section is visible in the control menu
14 | cy.contains("Clipboard").should("be.visible");
15 | });
16 |
17 | /**
18 | * Test to verify that the Clipboard accordion expands and shows the "Drag to Paste" control.
19 | */
20 | it('should expand the Clipboard accordion and display the "Drag to Paste" control', () => {
21 | // Step 1: Navigate to the homepage
22 | cy.visit("/");
23 |
24 | // Step 2: Open the clipboard acordeon
25 | cy.openAcordeonByLabel("Clipboard");
26 |
27 | // Step 3: Verify that the Clipboard accordion exists and is visible
28 | cy.get('[data-cy="controls-Clipboard"]').should("exist").should("be.visible");
29 | });
30 |
31 | /**
32 | * Test to verify the visibility of the popover when hovering over the "Drag to Paste" control.
33 | */
34 | it('should display the popover when hovering over the "Drag to Paste" control', () => {
35 | // Step 1: Navigate to the homepage
36 | cy.visit("/");
37 |
38 | // Step 2: Open the clipboard acordeon
39 | cy.openAcordeonByLabel("Clipboard");
40 |
41 | // Step 3: Click to open the Clipboard accordion
42 | cy.get('[data-cy="controls-Clipboard"]').click();
43 |
44 | // Step 4: Verify that the popover associated with the "Drag to Paste" control is visible
45 | cy.get('.custom-popover').should("be.visible");
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/tests/e2e/specs/ClipboardFormTypeDisplay.spec.js:
--------------------------------------------------------------------------------
1 | import ClipboardManager from "../../../src/store/modules/ClipboardManager";
2 |
3 | describe("Clipboard Form Type Display", () => {
4 | before(() => {
5 | // Visit the home page and load the JSON data before the tests
6 | cy.visit('/');
7 | cy.loadFromJson('displayScreenNext.json', 0, 'display');
8 | });
9 |
10 | const checkAddToClipboardNotExist = (childIndex) => {
11 | cy.get(`:nth-child(${childIndex}) > [data-cy="screen-element-container"]`)
12 | .click({ force: true });
13 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
14 | };
15 |
16 | it("Verify add clipboard button does not exist in display mode", () => {
17 | // Verify that the button does not exist for specific child elements
18 | checkAddToClipboardNotExist(2);
19 | checkAddToClipboardNotExist(3);
20 | });
21 | });
--------------------------------------------------------------------------------
/tests/e2e/specs/ComputedDateTime.spec.js:
--------------------------------------------------------------------------------
1 | describe("Computed datetime", () => {
2 | it("", () => {
3 | cy.visit("/", {
4 | onBeforeLoad(win) {
5 | cy.stub(win.console, "log").as("consoleLog");
6 | cy.stub(win.console, "error").as("consoleError");
7 | }
8 | });
9 | cy.loadFromJson("computed_datetime.json", 0);
10 |
11 | // Enter preview mode
12 | cy.get("[data-cy=mode-preview]").click();
13 |
14 | // verify that no console errors are registerd when using the computed
15 | // property
16 | cy.get("@consoleError").should("not.to.be.called");
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/tests/e2e/specs/DatePickerTimezone.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 |
3 | describe("Date Picker", () => {
4 | beforeEach(() => {
5 | // Set date to midnight January 1, 2024.
6 | // The timezone is UTC. We set it to UTC in package.json `TZ=UTC`
7 | cy.clock(new Date(2024, 0, 0, 0, 0, 0), ['Date']);
8 | cy.visit("/");
9 | cy.openAcordeon("collapse-2");
10 |
11 | // Set the User's timezone to Los Angeles. This is what DatePicker will use
12 | cy.window().then((win) => {
13 | win.ProcessMaker.user.timezone = "America/Los_Angeles";
14 | });
15 | });
16 |
17 | it("should convert the date from ther users timezone to UTC", () => {
18 | cy.get("[data-cy=controls-FormDatePicker]").drag(
19 | "[data-cy=screen-drop-zone]",
20 | "bottom"
21 | );
22 | cy.get("[data-cy=screen-element-container]").click();
23 | cy.setMultiselect("[data-cy=inspector-dataFormat]", "Datetime");
24 | cy.get("[data-cy=mode-preview]").click();
25 | cy.get(
26 | "[data-cy=preview-content] [data-cy='screen-field-form_date_picker_1'] input"
27 | ).click();
28 | // Choose today. In Los Angeles, that would be December 31, 2023
29 | cy.get(
30 | "[data-cy=preview-content] [data-cy='screen-field-form_date_picker_1'] .selectable.today"
31 | ).click();
32 | // Set time to 8:15 PM Los Angeles time
33 | cy.get(
34 | "[data-cy=preview-content] [data-cy='screen-field-form_date_picker_1'] .vdpHoursInput"
35 | ).type("8");
36 | cy.get(
37 | "[data-cy=preview-content] [data-cy='screen-field-form_date_picker_1'] .vdpMinutesInput"
38 | ).type("{moveToEnd}15");
39 | cy.get(
40 | "[data-cy=preview-content] [data-cy='screen-field-form_date_picker_1'] .vdp12HourToggleBtn"
41 | ).then((toggle) => {
42 | // Change to AM/PM - Time is now 8:15 PM, Los Angeles time
43 | if (toggle.is(".vdp12HourToggleBtn")) {
44 | cy.get(toggle).click();
45 | }
46 | });
47 |
48 | cy.assertPreviewData({
49 | // 8:15 PM on December 31, 2023 in Los Angeles is 04:15 (4:15am) the next day (January 1, 2024) in UTC
50 | form_date_picker_1: "2024-01-01T04:15:00.000Z"
51 | });
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/tests/e2e/specs/DefaulValueWithNestedAndRecordList.js:
--------------------------------------------------------------------------------
1 | describe("Validation Default value", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | it("Verify with mustache in nested and record list", () => {
7 | cy.loadFromJson("parent_record_list_and_loop.json", 0);
8 | cy.get("[data-cy=mode-preview]").click();
9 |
10 | fillInputText("screen-field-parentValue", 0, 130);
11 |
12 | cy.assertPreviewData({
13 | parentValue: "130",
14 | loop_1: [
15 | {
16 | form_record_list_1: null
17 | }
18 | ],
19 | loop_2: [
20 | {
21 | form_input_1: "130"
22 | }
23 | ]
24 | });
25 |
26 | cy.get("[data-cy=add-row]").click();
27 | cy.get(
28 | "[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] [name=form_input_1]"
29 | ).should("have.value", "130");
30 | });
31 | });
32 |
33 | function fillInputText(dataCy, index = null, value = "test") {
34 | if (index === null) {
35 | cy.get(`[data-cy=preview-content] [data-cy="${dataCy}"]`)
36 | .clear()
37 | .type(value);
38 | } else {
39 | cy.get(`[data-cy=preview-content] [data-cy="${dataCy}"]`)
40 | .eq(index)
41 | .clear()
42 | .type(value);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/e2e/specs/DefaultValueFromCalculatedField.spec.js:
--------------------------------------------------------------------------------
1 | describe("Computed field and default values", () => {
2 | it("Test default values with computed fields", () => {
3 | cy.visit("/");
4 |
5 | cy.loadFromJson("FOUR-6523.json", 0);
6 |
7 | // Preview
8 | cy.get("[data-cy=mode-preview]").click();
9 |
10 | cy.assertPreviewData({
11 | c: null,
12 | a: "0",
13 | b: 0,
14 | total: 0
15 | });
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/tests/e2e/specs/DefaultValuesWithComputedFields.spec.js:
--------------------------------------------------------------------------------
1 | describe("Computed field and default values", () => {
2 | it("Test default values with computed fields", () => {
3 | cy.visit("/");
4 |
5 | cy.loadFromJson("default_values_with_computed_fields.json", 0);
6 |
7 | // Preview
8 | cy.get("[data-cy=mode-preview]").click();
9 |
10 | // Assertion: Calculated properties are correct:
11 | // - varA = 1
12 | // - res = ok
13 | // Control with default value is correct:
14 | // - uno = {{varA}} = "1"
15 | cy.assertPreviewData({
16 | uno: "1",
17 | varA: 1,
18 | res: "ok"
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/tests/e2e/specs/DoubleLoop.spec.js:
--------------------------------------------------------------------------------
1 | describe("Double loop tests", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | it("Verify access to _parent._parent inside a double loop", () => {
7 | cy.loadFromJson("FOUR-6990_Double_Loop.json", 0);
8 | // set init screen test data
9 | cy.setPreviewDataInput({
10 | accounts: [
11 | {
12 | accountUid: "one",
13 | accountLabel: "one"
14 | },
15 | {
16 | accountUid: "two",
17 | accountLabel: "two"
18 | }
19 | ]
20 | });
21 | cy.get("[data-cy=mode-preview]").click();
22 |
23 | // Select option "one" on select inside loop 2
24 | cy.get(
25 | "[data-cy=preview-content] [data-cy=screen-field-form_select_list_1]"
26 | ).selectOption("one");
27 |
28 | // Select option "one" on select inside loop 3
29 | cy.get(
30 | "[data-cy=preview-content] [data-cy=screen-field-form_select_list_2]"
31 | ).selectOption("two");
32 |
33 | // Check the data of the screen
34 | cy.assertPreviewData({
35 | accounts: [
36 | {
37 | accountUid: "one",
38 | accountLabel: "one"
39 | },
40 | {
41 | accountUid: "two",
42 | accountLabel: "two"
43 | }
44 | ],
45 | loop_1: [
46 | {
47 | form_select_list_1: "one",
48 | loop_3: [
49 | {
50 | form_select_list_2: "two"
51 | }
52 | ]
53 | }
54 | ]
55 | });
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/tests/e2e/specs/FOUR-16958_watchers.spec.js:
--------------------------------------------------------------------------------
1 | describe("FOUR-16958_watchers ", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | it("Verify that watcher clean data from the watcher works", () => {
7 | // Mock script response
8 |
9 | cy.intercept(
10 | "POST",
11 | "/api/1.0/scripts/execute/2272",
12 | JSON.stringify({
13 | output: {
14 | "input1": "",
15 | "input2": "",
16 | "q1comment": "",
17 | "q2comment": "",
18 | "form_text_area_1": ""
19 | }
20 | })
21 | );
22 | cy.loadFromJson("FOUR-16958_watchers.json", 0);
23 |
24 | cy.get("[data-cy=mode-preview]").click();
25 |
26 | cy.get('[data-cy="screen-field-nyc.q1comment"]').type("1");
27 | cy.get('[data-cy="screen-field-nyc.form_text_area_1"]').type("2");
28 | cy.get('[data-cy="screen-field-nyc.input1"]').type("3");
29 | cy.get('[data-cy="screen-field-nyc.input2"]').type("4");
30 | cy.get("[name=resetfields]").click();
31 | cy.wait(800);
32 | cy.get('[data-cy="screen-field-nyc.input2"]').type("5");
33 | cy.get('[data-cy="screen-field-nyc.q1comment"]').should('be.empty');
34 | cy.get('[data-cy="screen-field-nyc.form_text_area_1"]').should('be.empty');
35 | cy.get('[data-cy="screen-field-nyc.input1"]').should('be.empty');
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/tests/e2e/specs/FOUR-7027_RecordList_nested_calc.spec.js:
--------------------------------------------------------------------------------
1 | describe("screen error nested calc", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | it("Verify calc can access to _parent inside Record List with nested screen", () => {
7 | // @link https://processmaker.atlassian.net/browse/FOUR-7027
8 | cy.loadFromJson("Screen parent in record list.json", 0);
9 | // set init screen test data
10 | cy.get("[data-cy=mode-preview]").click();
11 |
12 | cy.get("[data-cy=preview-content] [name='parentInput']")
13 | .clear()
14 | .type("new value to parent");
15 |
16 | // Click ADD record in Record List
17 | cy.get(
18 | "[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=add-row]"
19 | ).click();
20 |
21 | // Click OK button to insert the row
22 | cy.get(
23 | "[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] button.btn-primary"
24 | ).click();
25 |
26 | // Check the data of the screen
27 | cy.assertPreviewData({
28 | result: "new value to parent",
29 | parentInput: "new value to parent",
30 | form_record_list_1: [
31 | {
32 | result: "new value to parent"
33 | }
34 | ]
35 | });
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/tests/e2e/specs/FOUR-7029_Parent_in_default_value.spec.js:
--------------------------------------------------------------------------------
1 | describe("access to _parent variable in default value", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | it("Test access to _parent variable in default value inside a nested inside a loop", () => {
7 | cy.loadFromJson("Screen _parent in default value.json", 0);
8 | // set init screen test data
9 | cy.get("[data-cy=mode-preview]").click();
10 |
11 | cy.get("[data-cy=preview-content] [name='parentInput']")
12 | .clear()
13 | .type("new value to parent");
14 |
15 | cy.assertPreviewData({
16 | parentInput: "new value to parent",
17 | loop_1: [
18 | {
19 | inputDefault: "new value to parent"
20 | }
21 | ]
22 | });
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/tests/e2e/specs/FOUR-7587_LoopNestedLoop.spec.js:
--------------------------------------------------------------------------------
1 | describe("FOUR-7587 Loop Nested Loop", () => {
2 | // initial data to test the screen
3 | const initialData = {
4 | businesses: [
5 | {
6 | legalStructure: {
7 | id: 5,
8 | coreCode: "170",
9 | isActive: true,
10 | bizChexCode: "05",
11 | businessType: "Corporation",
12 | requiredDocs: [
13 | {
14 | file: null,
15 | document: {
16 | id: 15,
17 | label:
18 | "Recent Statement of Information (Domestic Stock Corporation) filed with Secretary of State",
19 | coreCode: "recentStatementInfo"
20 | },
21 | required: true
22 | },
23 | {
24 | file: null,
25 | document: {
26 | id: 16,
27 | label: "Certificate of Status (if available)",
28 | coreCode: "statusCertificate"
29 | },
30 | required: false
31 | },
32 | {
33 | file: null,
34 | document: {
35 | id: 17,
36 | label: "Corporate Resolution",
37 | coreCode: "corporateResolution"
38 | },
39 | required: true
40 | }
41 | ]
42 | }
43 | }
44 | ]
45 | };
46 |
47 | beforeEach(() => {
48 | cy.visit("/", {
49 | onBeforeLoad(win) {
50 | cy.stub(win.console, "log").as("consoleLog");
51 | cy.stub(win.console, "error").as("consoleError");
52 | }
53 | });
54 | });
55 |
56 | it("Test render of screen with Loop>Nested>Loop", () => {
57 | cy.loadFromJson("FOUR-7587_LoopNestedLoop.json", 1);
58 |
59 | // set initial data to test the screen
60 | cy.setPreviewDataInput(initialData);
61 | cy.get("[data-cy=mode-preview]").click();
62 | // Check no errors in console
63 | cy.get("@consoleError").should("not.to.be.called");
64 | });
65 | });
66 |
--------------------------------------------------------------------------------
/tests/e2e/specs/FOUR6788_ScreenPerformanceTests.spec.js:
--------------------------------------------------------------------------------
1 | describe("FOUR-6788 screen performance", () => {
2 | // @todo Improve the boot-time of the stand alone app (general and within cypress)
3 | const avgBootTime = 22000;
4 | const minimumPerformanceScore = 12;
5 | const accessibility = 50;
6 |
7 | // This test includes a Loop with 6 iterations, multi-column, select lists, rich texts and text areas
8 | it("Verify FOUR-6788 screen performance: select list, rich text", () => {
9 | const maximumScreenRenderTime = 6000;
10 |
11 | cy.loadFromJson("FOUR-6788_screen_performance.json");
12 | cy.visit("/?scenario=RenderScreen");
13 | const customThresholds = {
14 | performance: minimumPerformanceScore,
15 | accessibility,
16 | interactive: avgBootTime + maximumScreenRenderTime
17 | };
18 |
19 | const desktopConfig = {
20 | formFactor: "desktop",
21 | screenEmulation: { disabled: true }
22 | };
23 |
24 | cy.lighthouse(customThresholds, desktopConfig);
25 | });
26 |
27 | // This test includes a Loop with 6 iterations, multi-column, select lists, rich texts,
28 | // text areas, input texts, validations rules, visibility rules and a submit button
29 | it("Verify FOUR-6788 screen performance: input text, validations, visibility rules", () => {
30 | const maximumScreenRenderTime = 6000;
31 |
32 | cy.loadFromJson("FOUR-6788_screen_performance_2.json");
33 | cy.visit("/?scenario=RenderScreen2");
34 | const customThresholds = {
35 | performance: minimumPerformanceScore,
36 | accessibility,
37 | interactive: avgBootTime + maximumScreenRenderTime
38 | };
39 |
40 | const desktopConfig = {
41 | formFactor: "desktop",
42 | screenEmulation: { disabled: true }
43 | };
44 |
45 | cy.lighthouse(customThresholds, desktopConfig);
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/tests/e2e/specs/FileUpload.spec.js:
--------------------------------------------------------------------------------
1 | describe("File Upload", () => {
2 | it("Automatically sets a variable name", () => {
3 | cy.visit("/");
4 | cy.openAcordeon("collapse-6");
5 | cy.get("[data-cy=controls-FileUpload]").drag("[data-cy=screen-drop-zone]", {
6 | position: "bottom"
7 | });
8 | cy.get("[data-cy=screen-element-container]").click();
9 |
10 | cy.get('[data-cy="screen-element-container"] .card-body').then((div) => {
11 | const data = div[0].__vue__.name;
12 | expect(data).to.eql("file_upload_1");
13 | });
14 |
15 | cy.get("[data-cy=mode-preview]").click();
16 | cy.get("[data-cy=file-upload-button]").should("not.have.attr", "disabled");
17 | });
18 |
19 | it("Disables when task is self service", () => {
20 | cy.visit("/");
21 | cy.openAcordeon("collapse-6");
22 | cy.window().then((win) => {
23 | win.ProcessMaker.isSelfService = true;
24 | });
25 | cy.get("[data-cy=controls-FileUpload]").drag("[data-cy=screen-drop-zone]", {
26 | position: "bottom"
27 | });
28 | cy.get("[data-cy=mode-preview]").click();
29 | cy.get("[data-cy=file-upload-button]").should("have.attr", "disabled");
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/tests/e2e/specs/MediaQuery.spec.js:
--------------------------------------------------------------------------------
1 | const sizes = [
2 | [700, 800],
3 | [1000, 800],
4 | [1200, 768]
5 | ];
6 | const backgroundColor = ["rgb(0, 0, 255)", "rgb(255, 0, 0)", "rgb(0, 128, 0)"];
7 |
8 | describe("Media Query CSS", () => {
9 | before(() => {
10 | // run these tests as if in a desktop
11 | cy.visit("/");
12 | cy.openAcordeon("collapse-2");
13 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
14 | position: "bottom"
15 | });
16 | cy.get("[data-cy=screen-element-container]").click();
17 | cy.get("[data-cy=accordion-Advanced]").click();
18 | cy.get("[data-cy=inspector-customCssSelector]").type("new_input_css");
19 | cy.get("[data-cy=topbar-css]").click();
20 | cy.wait(500);
21 | // write the media query in the custom css panel
22 | cy.get("#custom-css").type(
23 | `@media (max-width: 480px) {{}{del}
24 | div[selector="new_input_css"] {{}{del}
25 | background-color: blue;
26 | }
27 | }
28 |
29 | @media (min-width: 481px) and (max-width: 810px) {{}{del}
30 | div[selector="new_input_css"] {{}{del}
31 | background-color: red;
32 | }
33 | }
34 |
35 | @media (min-width: 811px) {{}{del}
36 | div[selector="new_input_css"] {{}{del}
37 | background-color: green;
38 | }
39 | }
40 | `,
41 | { parseSpecialCharSequences: true }
42 | );
43 |
44 | cy.wait(500);
45 | cy.get("[data-cy=save-button]").click();
46 | // preview
47 | cy.get("[data-cy=mode-preview]").click();
48 | });
49 |
50 | sizes.forEach((size, index) => {
51 | // make assertions on the screen using
52 | // an array of different viewports
53 | it(`Should display screen on ${size} screen`, () => {
54 | if (Cypress._.isArray(size)) {
55 | cy.viewport(size[0], size[1]);
56 | } else {
57 | cy.viewport(size);
58 | }
59 |
60 | cy.wait(300);
61 | cy.get("[selector=new_input_css]")
62 | .should("have.css", "background-color")
63 | .and("eq", backgroundColor[index]);
64 | });
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/tests/e2e/specs/MultiselectWithStringValue.spec.js:
--------------------------------------------------------------------------------
1 | describe("multiselect with string value", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | it("Multiselect checkbox with string value without user interaction keep its value", () => {
7 | cy.loadFromJson("multiselect_with_string_value.json", 0);
8 |
9 | // init screen data
10 | cy.setPreviewDataInput({ person: "foo" });
11 | cy.get("[data-cy=mode-preview]").click();
12 |
13 | // Check the data of the screen
14 | cy.assertPreviewData({
15 | person: "foo"
16 | });
17 | });
18 |
19 | it("Multiselect checkbox with string value after user interaction change to array", () => {
20 | cy.loadFromJson("multiselect_with_string_value.json", 0);
21 |
22 | // init screen data
23 | cy.setPreviewDataInput({ person: "foo" });
24 | cy.get("[data-cy=mode-preview]").click();
25 |
26 | cy.get("[data-cy=preview-content] [name=person]").eq(0).click();
27 | cy.get("[data-cy=preview-content] [name=person]").eq(1).click();
28 | cy.get("[data-cy=preview-content] [name=person]").eq(0).click();
29 |
30 | // Check the data of the screen
31 | cy.assertPreviewData({
32 | person: [
33 | {
34 | content: "two",
35 | value: "two"
36 | }
37 | ]
38 | });
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/tests/e2e/specs/NestedCalcProperties.spec.js:
--------------------------------------------------------------------------------
1 | describe("nested calculated properties", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | // @link: https://processmaker.atlassian.net/browse/FOUR-4873
7 | it("Verify nested calculated properties", () => {
8 | cy.loadFromJson("nested_calc_properties.json", 1);
9 | cy.get("[data-cy=mode-preview]").click();
10 |
11 | cy.get('[data-cy=preview] input[name="varA"]').type(10);
12 | cy.get('[data-cy=preview] input[name="varB"]').type(5);
13 | cy.get(
14 | '[data-cy=preview-content] [data-cy="screen-field-operation"]'
15 | ).selectOption("Addition");
16 |
17 | // Wait until you load the screen
18 | cy.wait(500);
19 |
20 | // Check the data of the screen
21 | cy.assertPreviewData({
22 | equal: "=",
23 | operation: 1,
24 | varA: 10,
25 | varB: 5,
26 | varR: 15
27 | });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/tests/e2e/specs/NestedCalcRadioFreeze.spec.js:
--------------------------------------------------------------------------------
1 | describe("nested calc radio freeze", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | // @link: https://processmaker.atlassian.net/browse/FOUR-4873
7 | it("Verify nested calc radio do not freeze", () => {
8 | cy.loadFromJson("nested_calc_radio_freeze.json", 0);
9 | cy.get("[data-cy=mode-preview]").click();
10 |
11 | cy.get("[data-cy=preview-content] button:contains(Continue)").click();
12 | // Wait until you load the screen
13 | cy.wait(500);
14 |
15 | // Check the data of the screen
16 | cy.assertPreviewData({
17 | jointOwner: [
18 | {
19 | varb: null,
20 | form_input_1: null,
21 | selectLIst1: null,
22 | form_select_list_1: [],
23 | selectIsArray: true
24 | }
25 | ]
26 | });
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/tests/e2e/specs/Pages.spec.js:
--------------------------------------------------------------------------------
1 | describe("Pages and navigations", () => {
2 | it("Basic default value", () => {
3 | cy.visit("/");
4 | cy.openAllAcordeon();
5 | cy.get("[data-test=page-dropdown]").click();
6 | cy.get("[data-test=add-page]").click({ force: true });
7 | // Define Page 2
8 | cy.get("[data-cy=add-page-name]").clear().type("Page 2");
9 | cy.get("[data-cy=add-page-modal] button.btn").eq(1).click();
10 | cy.wait(500);
11 | cy.get('[data-cy=controls-FormButton]:contains("Page")').drag(
12 | "[data-cy=screen-drop-zone]",
13 | { position: "bottom" }
14 | );
15 | cy.get("[data-cy=screen-element-container]").click();
16 | cy.get("[data-cy=inspector-label]").clear().type("Go to Page 1");
17 | cy.get("[data-cy=accordion-Configuration]").click();
18 | cy.setMultiselect("[data-cy=inspector-eventData]", "Default");
19 | // Define Page 1
20 | cy.get("[data-test=page-dropdown]").click();
21 | cy.get("[data-cy=page-0]").click({ force: true });
22 | cy.wait(500);
23 | cy.get('[data-cy=controls-FormButton]:contains("Page")').drag(
24 | "[data-cy=screen-drop-zone]",
25 | { position: "bottom" }
26 | );
27 | cy.get("[data-cy=screen-element-container]").click();
28 | cy.get("[data-cy=inspector-label]").clear().type("Go to Page 2");
29 | cy.get("[data-cy=accordion-Configuration]").click();
30 | cy.setMultiselect("[data-cy=inspector-eventData]", "Page 2");
31 | // Preview
32 | cy.get("[data-cy=mode-preview]").click();
33 | cy.get("[data-cy=preview-content]").should("contain.text", "Go to Page 2");
34 | cy.get("[data-cy=preview-content] button").click();
35 | cy.get("[data-cy=preview-content]").should("contain.text", "Go to Page 1");
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/tests/e2e/specs/ParentAccessTests.spec.js:
--------------------------------------------------------------------------------
1 | describe("Test access to _parent", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | it("Verify calc can access to _parent inside Record List with nested screen and Loops", () => {
7 | cy.loadFromJson("ParentNestedScreen.json", 1);
8 | // set init screen test data
9 | cy.get("[data-cy=mode-preview]").click();
10 |
11 | cy.get("[data-cy=preview-content] [name='parentInput']")
12 | .eq(0)
13 | .clear()
14 | .type("value in parent")
15 | .blur();
16 |
17 | cy.get("[data-cy=preview-content] [name='form_input_1']")
18 | .eq(0)
19 | .type(":value in loop");
20 |
21 | // Click ADD record in Record List
22 | cy.get(
23 | "[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=add-row]"
24 | ).click();
25 |
26 | // Type in form_input_1 inside record list
27 | cy.get(
28 | "[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] [name=form_input_1]"
29 | )
30 | .type(":value in record list + loop")
31 | .blur();
32 |
33 | // Click OK button to insert the row
34 | cy.get(
35 | "[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] button.btn-primary"
36 | ).click();
37 |
38 | // Check the data of the screen
39 | cy.assertPreviewData({
40 | parentInput: "value in parent",
41 | loop_1: [
42 | {
43 | parentInput: "value in parent",
44 | loop_1: [
45 | {
46 | form_input_1: "value in parent:value in loop"
47 | }
48 | ]
49 | }
50 | ],
51 | form_record_list_1: [
52 | {
53 | parentInput: "value in parent",
54 | loop_1: [
55 | {
56 | form_input_1: "value in parent:value in record list + loop"
57 | }
58 | ]
59 | }
60 | ]
61 | });
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/tests/e2e/specs/ParentVariable.spec.js:
--------------------------------------------------------------------------------
1 | describe("_parent variable", () => {
2 | beforeEach(() => {});
3 |
4 | it("Test _parent in loop>nested", () => {
5 | cy.visit("/");
6 | cy.loadFromJson("parent_variable.json", 1);
7 | cy.get("[data-cy=mode-preview]").click();
8 |
9 | cy.get(
10 | '[data-cy=preview-content] [data-cy="screen-field-select_padre"]'
11 | ).selectOption("aaaa");
12 |
13 | cy.get(
14 | '[data-cy=preview-content] [data-cy="screen-field-select_hijo"]'
15 | ).selectOption("aaaa");
16 | cy.get('[data-cy=preview-content] [data-cy="screen-field-input_hijo"]')
17 | .clear()
18 | .type("test");
19 |
20 | cy.assertPreviewData({
21 | select_padre: [
22 | {
23 | value: "a",
24 | content: "aaaa"
25 | }
26 | ],
27 | loop_1: [
28 | {
29 | input_hijo: "test",
30 | select_hijo: "a"
31 | }
32 | ],
33 | form_record_list_1: null
34 | });
35 | });
36 |
37 | it("Test _parent in record list", () => {
38 | cy.visit("/");
39 | cy.loadFromJson("parent_variable.json", 1);
40 | cy.get("[data-cy=mode-preview]").click();
41 |
42 | cy.get(
43 | '[data-cy=preview-content] [data-cy="screen-field-select_padre"]'
44 | ).selectOption("aaaa");
45 |
46 | cy.get(
47 | "[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=add-row]"
48 | ).click();
49 | cy.get(
50 | '[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] [data-cy="screen-field-form_select_list_1"]'
51 | ).selectOption("aaaa");
52 | cy.get(
53 | "[data-cy=preview-content] [data-cy=screen-field-form_record_list_1] [data-cy=modal-add] button.btn-primary"
54 | ).click();
55 |
56 | cy.assertPreviewData({
57 | select_padre: [
58 | {
59 | value: "a",
60 | content: "aaaa"
61 | }
62 | ],
63 | loop_1: [
64 | {
65 | input_hijo: "",
66 | select_hijo: null
67 | }
68 | ],
69 | form_record_list_1: [
70 | {
71 | form_select_list_1: "a"
72 | }
73 | ]
74 | });
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/tests/e2e/specs/RAOS1.0.0AccountOpeningCustomerForm.spec.js:
--------------------------------------------------------------------------------
1 | describe("RAOS 1.0.0 Account Opening Customer Form", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | it("Verify Calcs with visibility rules update immediately on the same screen", () => {
7 | cy.loadFromJson("RAOS1.0.0AccountOpeningCustomerForm.json", 1);
8 |
9 | cy.get("[data-cy=mode-preview]").click();
10 |
11 | // add a new account
12 | cy.get(
13 | "[data-cy=preview-content] [data-cy='loop-additionalAccounts-add']"
14 | ).click();
15 | // select the first account type: "Checking Account"
16 | cy.get(
17 | "[data-cy=preview-content] [data-cy='screen-field-productType']"
18 | ).selectOption("Checking Account");
19 | // click on continue
20 | cy.get("[data-cy=preview-content] [aria-label='Continue']").eq(0).click();
21 | // choose funding method
22 | cy.get("[data-cy=preview-content] [data-cy='screen-field-fundingMethod']")
23 | .eq(0)
24 | .click();
25 | // "initialDepositOther" field must be visible
26 | cy.get("[data-cy=preview-content] [name='initialDepositOther']")
27 | .eq(0)
28 | .should("be.visible");
29 |
30 | // check that ONLY requireInitialDepositOther property was initialized in accounts variable
31 | cy.assertPreviewData({
32 | accounts: [
33 | {
34 | product: "Checking",
35 | initialDepositOther: 0,
36 | requireInitialDepositOther: "Yes"
37 | }
38 | ],
39 | showFundingAccountLoop: true,
40 | additionalAccounts: [
41 | {
42 | productType: "Checking"
43 | }
44 | ],
45 | fundingMethod: "Transfer from Existing Quaint Oak Account",
46 | externalBank: "",
47 | externalAccountType: null,
48 | externalAccountName: "",
49 | externalRoutingNumber: "",
50 | externalAccountNumber: "",
51 | internalAccountType: null,
52 | internalAccountNumber: ""
53 | });
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/tests/e2e/specs/RichText.spec.js:
--------------------------------------------------------------------------------
1 | describe("Rich Text control", () => {
2 | it("Rich text with mustache", () => {
3 | cy.visit("/");
4 | cy.openAcordeon("collapse-3");
5 | cy.get("[data-cy=controls-FormHtmlViewer]").drag(
6 | "[data-cy=screen-drop-zone]",
7 | { position: "bottom" }
8 | );
9 | cy.get("[data-cy=screen-element-container]").click();
10 | cy.get("[data-cy=inspector-content]")
11 | .focus()
12 | .clear()
13 | .type("Hello {{ name }}
", { parseSpecialCharSequences: false });
14 | cy.get("[data-cy=mode-preview]").click();
15 | cy.setPreviewDataInput('{"name":"World"}');
16 | cy.get("[data-cy=preview-content]").should(
17 | "contain.html",
18 | "Hello World
"
19 | );
20 | });
21 |
22 | it("Rich text render HTML from a Variable", () => {
23 | cy.visit("/");
24 | cy.openAcordeon("collapse-3");
25 | cy.get("[data-cy=controls-FormHtmlViewer]").drag(
26 | "[data-cy=screen-drop-zone]",
27 | { position: "bottom" }
28 | );
29 | cy.get("[data-cy=screen-element-container]").click();
30 | cy.get("[data-cy=inspector-content]")
31 | .focus()
32 | .clear()
33 | .type("{{ name }}", { parseSpecialCharSequences: false });
34 | cy.get("[data-cy=inspector-renderVarHtml]").click();
35 | cy.get("[data-cy=mode-preview]").click();
36 | cy.setPreviewDataInput('{"name":"Hello World
"}');
37 | cy.get("[data-cy=preview-content]").should(
38 | "contain.html",
39 | "Hello World
"
40 | );
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/tests/e2e/specs/ScreenBuilder.spec.js:
--------------------------------------------------------------------------------
1 | describe("ScreenBuilder", () => {
2 | it("Visits the app root url and renders form builder", () => {
3 | cy.visit("/");
4 | cy.contains("Place your controls here.").should("be.visible");
5 | });
6 | });
7 |
--------------------------------------------------------------------------------
/tests/e2e/specs/ScreenWarnings.spec.js:
--------------------------------------------------------------------------------
1 | describe("Screen Warnings", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | it("Large screen warning", () => {
7 | cy.loadFromJson("large_screen_warning.json", 1);
8 |
9 | cy.get('[data-cy="open-console"] .badge-warning').should(
10 | "contain.text",
11 | "1"
12 | );
13 | cy.get('[data-cy="open-console"]').click();
14 | cy.get('[data-cy="validation-panel"]').should(
15 | "contain.text",
16 | "We recommend using fewer than 25 form elements in your screen for optimal performance."
17 | );
18 | });
19 |
20 | it("Use script warning", () => {
21 | cy.openAcordeon("collapse-2");
22 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
23 | position: "bottom"
24 | });
25 | cy.get('[data-cy="topbar-watchers"]').click();
26 | cy.get('[data-cy="watchers-add-watcher"]').click();
27 | cy.get('[data-cy="watchers-watcher-name"]').type("test");
28 | cy.get('[data-cy="watchers-watcher-variable"]').selectOption(
29 | "form_input_1"
30 | );
31 | cy.get('[data-cy="watchers-accordion-source"]').click();
32 | cy.get("#watcherSource").should(
33 | "not.contain.text",
34 | "Using watchers with Scripts can slow the performance of your screen"
35 | );
36 | cy.get('[data-cy="watchers-watcher-source"]').selectOption("Test Script");
37 | cy.get("#watcherSource").should(
38 | "contain.text",
39 | "Using watchers with Scripts can slow the performance of your screen"
40 | );
41 | cy.get('[data-cy="watchers-button-save"]').click();
42 | cy.get('[data-cy="watchers-modal"] .close').click();
43 | cy.get('[data-cy="open-console"] .badge-warning').should(
44 | "contain.text",
45 | "1"
46 | );
47 | cy.get('[data-cy="open-console"]').click();
48 | cy.get('[data-cy="validation-panel"]').should(
49 | "contain.text",
50 | "Using watchers with Scripts can slow the performance of your screen."
51 | );
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/tests/e2e/specs/SelectListDefaultValues.spec.js:
--------------------------------------------------------------------------------
1 | describe("Select List default Value", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | it("Verify default value not existing in option list set value to null", () => {
7 | cy.loadFromJson("select_list_default_value.json", 0);
8 |
9 | // Configure a non existing default value in option list
10 | cy.get("[data-cy=screen-element-container]").click();
11 | cy.get("[data-cy=accordion-Advanced]").click();
12 | cy.get("[data-cy=inspector-defaultValue-basicValue]").clear().type("c");
13 |
14 | cy.get("[data-cy=mode-preview]").click();
15 |
16 | // Assert value was configured in null
17 | cy.assertPreviewData({
18 | form_select_list_1: null
19 | });
20 |
21 | // Select a valid option
22 | cy.get('[data-cy="screen-field-form_select_list_1"]').selectOption("a");
23 |
24 | // Assert value is set correctly
25 | cy.assertPreviewData({
26 | form_select_list_1: "a"
27 | });
28 |
29 | cy.get("[data-cy=mode-editor]").click();
30 |
31 | // Configure an existing default value in option list
32 | cy.get("[data-cy=screen-element-container]").click();
33 | cy.get("[data-cy=accordion-Advanced]").click();
34 | cy.get("[data-cy=inspector-defaultValue-basicValue]").clear().type("b");
35 |
36 | cy.get("[data-cy=mode-preview]").click();
37 |
38 | // Assert default valid value is set correctly
39 | cy.assertPreviewData({
40 | form_select_list_1: "b"
41 | });
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/tests/e2e/specs/SingleSelectWithInvalidValue.spec.js:
--------------------------------------------------------------------------------
1 | describe("single select with invalid initial value", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 |
6 | it("Single select radio with array value without user interaction keep its value", () => {
7 | cy.loadFromJson("single_select_with_invalid_value.json", 0);
8 |
9 | // init screen data
10 | cy.setPreviewDataInput({ person: [] });
11 | cy.get("[data-cy=mode-preview]").click();
12 |
13 | // Check the data of the screen
14 | cy.assertPreviewData({
15 | person: []
16 | });
17 | });
18 |
19 | it("Single select radio with array value after user interaction change to the selected option", () => {
20 | cy.loadFromJson("single_select_with_invalid_value.json", 0);
21 |
22 | // init screen data
23 | cy.setPreviewDataInput({ person: [] });
24 | cy.get("[data-cy=mode-preview]").click();
25 |
26 | cy.get("[data-cy=preview-content] [name=person]").eq(0).click();
27 | cy.get("[data-cy=preview-content] [name=person]").eq(1).click();
28 | cy.get("[data-cy=preview-content] [name=person]").eq(0).click();
29 |
30 | // Check the data of the screen
31 | cy.assertPreviewData({
32 | person: {
33 | content: "one",
34 | value: "one"
35 | }
36 | });
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/tests/e2e/specs/TCP4-4444VerifyAddingMultipleContentToClipboard.spec.js:
--------------------------------------------------------------------------------
1 | describe("Verify Adding Multiple Content To The Clipboard", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | cy.showValidationOnLoad();
5 | cy.clearLocalStorage();
6 | });
7 |
8 | it("TCP4-4446: Content selected should remain flagged as selected for pasting", () => {
9 | // Step 1: Load the initial JSON data and check screen content
10 | cy.loadFromJson("TCP4-4446.json", 0);
11 | cy.get("[data-cy=screen-drop-zone]").should("not.contain.text", "Place your controls here.");
12 |
13 | cy.get(':nth-child(1) > [data-cy="screen-element-container"]').click({ force: true });
14 | cy.get('[data-cy="addToClipboard"]').should("be.visible").click();
15 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
16 |
17 | // Step 2: Load a new screen and repeat the process for adding to clipboard
18 | cy.loadFromJson("screen2TCP4-4446.json", 0);
19 | cy.wait(100);
20 | cy.get(':nth-child(1) > [data-cy="screen-element-container"]').click({ force: true });
21 | cy.get('[data-cy="addToClipboard"]').should("be.visible").click();
22 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
23 |
24 | // Step 3: Reload the first screen and verify that the element is flagged as selected
25 | cy.loadFromJson("TCP4-4446.json", 0);
26 | cy.wait(100);
27 | cy.get(':nth-child(1) > [data-cy="screen-element-container"]').click({ force: true });
28 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
29 |
30 | // Step 4: Reload the second screen and verify clipboard status remains unchanged
31 | cy.loadFromJson("screen2TCP4-4446.json", 0);
32 | cy.wait(100);
33 | cy.get(':nth-child(1) > [data-cy="screen-element-container"]').click({ force: true });
34 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/tests/e2e/specs/TCP4-4452VerifyReloadPageClipboard.spec.js:
--------------------------------------------------------------------------------
1 | describe("TCP4-4452 Verify reload page, remove button still present in the copied elements", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | cy.showValidationOnLoad();
5 | cy.clearLocalStorage();
6 | });
7 |
8 | it("Verify that after reloading the page, all controls within each page are displayed with (-) button", () => {
9 | // Step 1: Load the initial JSON data and check screen content
10 | cy.loadFromJson("TCP4-4452.json", 0);
11 | cy.get(':nth-child(1) > :nth-child(1) > :nth-child(1) > .m-2 > .form-group').click({ force: true });
12 | cy.get('[data-cy="addToClipboard"]').should("be.visible").click();
13 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
14 |
15 | cy.get(':nth-child(1) > :nth-child(2) > :nth-child(1) > .m-2').click({ force: true });
16 | cy.get('[data-cy="addToClipboard"]').should("be.visible").click();
17 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
18 |
19 |
20 | cy.get('#form_record_list_1 > .m-2').click({ force: true });
21 | cy.get('[data-cy="addToClipboard"]').should("be.visible").click({ force: true });
22 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
23 | // reload page
24 | cy.loadFromJson("TCP4-4452.json", 0);
25 | cy.get(':nth-child(1) > :nth-child(1) > :nth-child(1) > .m-2 > .form-group').click({ force: true });
26 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
27 |
28 | cy.get(':nth-child(1) > :nth-child(2) > :nth-child(1) > .m-2').click({ force: true });
29 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
30 |
31 | cy.get('#form_record_list_1 > .m-2').click({ force: true });
32 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/tests/e2e/specs/TCP4-4458ClearClipboard.spec.js:
--------------------------------------------------------------------------------
1 | describe("TCP4-4458 Verify clear all components in clipboard", () => {
2 | beforeEach(() => {
3 | // Step 1: Navigate to the homepage, show validation, and clear local storage
4 | cy.visit("/");
5 | cy.showValidationOnLoad();
6 | cy.clearLocalStorage();
7 | });
8 |
9 | it("Verify that after clicking Clear All, all controls are deleted.", () => {
10 | // Step 2: Load the JSON data (TCP4-4454)
11 | cy.loadFromJson("TCP4-4454.json", 0);
12 |
13 | // Step 3: Function to add a screen element to the clipboard
14 | const addToClipboard = (index) => {
15 | // Select screen element based on index, click it, then click the add to clipboard button
16 | cy.get(`:nth-child(${index}) > [data-cy="screen-element-container"]`).click({ force: true });
17 | cy.get('[data-cy="addToClipboard"]').should("be.visible").click();
18 | cy.get('[data-cy="addToClipboard"]').should("not.exist");
19 | };
20 |
21 | // Step 4: Loop through elements (1-11) and add each to the clipboard
22 | for (let i = 1; i <= 11; i++) {
23 | addToClipboard(i);
24 | }
25 |
26 | // Step 5: Open the clipboard menu
27 | cy.get("[data-test=page-dropdown]").click();
28 | cy.get("[data-test=clipboard]").should("exist").click({ force: true });
29 |
30 | // Step 6: Verify that the Clipboard tab is active
31 | cy.get('a[role="tab"]')
32 | .contains('Clipboard')
33 | .should('have.class', 'active')
34 | .and('be.visible');
35 |
36 | // Step 7: Click 'Clear All' button in the clipboard
37 | cy.get('[data-test="tab-content"] > .btn-link').click();
38 |
39 | // Step 8: Confirm the action to clear the clipboard
40 | cy.contains('button', 'Confirm').click();
41 |
42 | // Step 9: Verify that all clipboard components have been deleted
43 | cy.get('[data-cy="editor-content"]').children().should('have.length', 0);
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/tests/e2e/specs/TCP4-4462VerifyAddComponentsToClipboard.spec.js:
--------------------------------------------------------------------------------
1 | describe("TCP4-4462 Verify clear all components in clipboard", () => {
2 | beforeEach(() => {
3 | // Step 1: Navigate to the homepage, show validation, and clear local storage
4 | cy.visit("/");
5 | cy.showValidationOnLoad();
6 | cy.clearLocalStorage();
7 | });
8 |
9 | it("Verify that after clicking Clear All, all controls are deleted.", () => {
10 |
11 |
12 | cy.get("[data-test=page-dropdown]").click();
13 | cy.get("[data-test=clipboard]").should("exist").click({ force: true });
14 |
15 | cy.get('a[role="tab"]')
16 | .contains('Clipboard')
17 | .should('have.class', 'active')
18 | .and('be.visible');
19 |
20 | cy.get("[data-cy=controls-FormSelectList]").drag("[data-cy=editor-content]", { position: "top", force: true });
21 | cy.get("[data-cy=controls-FormButton]").drag("[data-cy=screen-element-container]", { position: "top", force: true });
22 | cy.get("[data-cy=controls-FormTextArea]").drag("[data-cy=screen-element-container]", { position: "top", force: true });
23 | cy.get("[data-cy=controls-FormDatePicker]").drag("[data-cy=screen-element-container]", { position: "top", force: true });
24 | cy.get("[data-cy=controls-FormCheckbox]").drag("[data-cy=screen-element-container]", { position: "top", force: true });
25 |
26 | cy.get('[data-cy="screen-element-container"]')
27 | .children()
28 | .should('have.length', 5);
29 | cy.visit("/");
30 | cy.openAcordeonByLabel("Content Fields");
31 |
32 | cy.get("[data-cy=controls-FormHtmlViewer]").drag("[data-cy=screen-drop-zone]", { position: "bottom" });
33 | cy.get("[data-cy=controls-FormImage]").drag("[data-cy=screen-element-container]", { position: "top" });
34 | cy.get("[data-cy=controls-FormRecordList]").drag("[data-cy=screen-element-container]", { position: "top" });
35 | cy.get("[data-cy=controls-FormLoop]").drag("[data-cy=screen-element-container]", { position: "top" });
36 | cy.get("[data-cy=controls-FormNestedScreen]").drag("[data-cy=screen-element-container]", { position: "top" });
37 | cy.get("[data-cy=controls-FormMultiColumn]").drag("[data-cy=screen-element-container]", { position: "top" });
38 | cy.get('[data-cy="screen-element-container"]')
39 | .children()
40 | .should('have.length', 6);
41 |
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/tests/e2e/specs/TestValidationWithNestedSideEffects.spec.js:
--------------------------------------------------------------------------------
1 | describe("test validation with nested side effects", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | cy.showValidationOnLoad();
5 | });
6 |
7 | it("Verify test validation with nested side effects", () => {
8 | cy.loadFromJson("Test Validation with Nested side effects.json", 1);
9 | // click on preview
10 | cy.get("[data-cy=mode-preview]").click();
11 | // click on New Submit button
12 | cy.get("[data-cy=preview-content] button[aria-label='Submit']").click();
13 | // In editor: ensure standard required field displays error while readonly required field does not
14 | cy.get("[data-cy=preview-content] [name='form_input_2']")
15 | .parent()
16 | .find(".invalid-feedback")
17 | .should("be.visible");
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/tests/e2e/specs/UndoRedo.spec.js:
--------------------------------------------------------------------------------
1 | describe("Undo and Redo", () => {
2 | it("Can Undo", () => {
3 | cy.visit("/");
4 | cy.openAcordeon("collapse-2");
5 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
6 | position: "bottom"
7 | });
8 | cy.get("[data-cy=toolbar-undo]").click();
9 | cy.get("[data-cy=screen-drop-zone]").should(
10 | "contain.text",
11 | "Place your controls here."
12 | );
13 | });
14 |
15 | it("Can Redo", () => {
16 | cy.visit("/");
17 | cy.openAcordeon("collapse-2");
18 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
19 | position: "bottom"
20 | });
21 | cy.get("[data-cy=toolbar-undo]").click();
22 | cy.get("[data-cy=toolbar-redo]").click();
23 | // Check that New Input control was restored
24 | cy.get("[data-cy=screen-element-container]").should(
25 | "contain.text",
26 | "New Input"
27 | );
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/tests/e2e/specs/ValidationRulesNameObject.js:
--------------------------------------------------------------------------------
1 | describe("Validation Rules", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | cy.showValidationOnLoad();
5 | });
6 |
7 | it("Variable name object and rules", () => {
8 | cy.loadFromJson("validation_rules_name_object.json", 0);
9 | cy.get("[data-cy=mode-preview]").click();
10 |
11 | // ACCEPTED INPUT
12 | cy.get(
13 | '[data-cy=preview-content] [data-cy="screen-field-form_input_2.res"]'
14 | )
15 | .parent()
16 | .should("contain.text", "Field is required");
17 | cy.get("[data-cy=preview-content] [name=form_input_1]").type("12");
18 | cy.get(
19 | '[data-cy=preview-content] [data-cy="screen-field-form_input_2.res"]'
20 | )
21 | .parent()
22 | .should("not.contain.text", "Field is required");
23 |
24 | // ACCEPTED INPUT
25 | cy.get('[data-cy=preview-content] [data-cy="screen-field-form_input_21"]')
26 | .parent()
27 | .should("contain.text", "Field is required");
28 | cy.get("[data-cy=preview-content] [name=form_input_11]").type("13");
29 | cy.get('[data-cy=preview-content] [data-cy="screen-field-form_input_21"]')
30 | .parent()
31 | .should("not.contain.text", "Field is required");
32 |
33 | // REJECTED INPUT
34 | cy.get(
35 | '[data-cy=preview-content] [data-cy="screen-field-form_input_4.res"]'
36 | )
37 | .parent()
38 | .should("not.contain.text", "Must be same as form_input_3");
39 | cy.get("[data-cy=preview-content] [name=form_input_3]").type("13");
40 | cy.get(
41 | '[data-cy=preview-content] [data-cy="screen-field-form_input_4.res"]'
42 | )
43 | .parent()
44 | .should("contain.text", "Must be same as form_input_3");
45 |
46 | // REJECTED INPUT
47 | cy.get(
48 | '[data-cy=preview-content] [data-cy="screen-field-form_input_6.res"]'
49 | )
50 | .parent()
51 | .should("not.contain.text", "Field is required");
52 | cy.get("[data-cy=preview-content] [name=form_input_5]").type("14");
53 | cy.get(
54 | '[data-cy=preview-content] [data-cy="screen-field-form_input_6.res"]'
55 | )
56 | .parent()
57 | .should("contain.text", "Field is required");
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/tests/e2e/specs/ValidationShownOnSubmit.spec.js:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 |
3 | describe("Validation Rules", () => {
4 | beforeEach(() => {
5 | cy.visit("/");
6 | });
7 |
8 | it("Invalid default values", () => {
9 | cy.loadFromJson("validation_rules.json", 0);
10 | cy.get("[data-cy=mode-preview]").click();
11 |
12 | cy.shouldHaveValidationErrors("screen-field-form_checkbox_1");
13 | cy.shouldHaveValidationErrors("screen-field-form_input_1");
14 | cy.shouldHaveValidationErrors("screen-field-form_input_2");
15 | cy.shouldNotHaveValidationErrors("screen-field-form_input_3");
16 | cy.shouldHaveValidationErrors("screen-field-form_input_4");
17 | cy.shouldNotHaveValidationErrors("screen-field-form_input_5");
18 | cy.shouldNotHaveValidationErrors("screen-field-form_input_6");
19 |
20 | cy.get("[data-cy=preview-content] .page button").click();
21 |
22 | cy.shouldHaveValidationErrors("screen-field-form_input_1");
23 |
24 | cy.get('[data-cy=preview-content] [data-cy="screen-field-form_input_1"]')
25 | .clear()
26 | .type("on");
27 |
28 | cy.shouldHaveValidationErrors("screen-field-form_checkbox_1");
29 | cy.shouldNotHaveValidationErrors("screen-field-form_input_1");
30 | cy.shouldHaveValidationErrors("screen-field-form_input_2");
31 | cy.shouldNotHaveValidationErrors("screen-field-form_input_3");
32 | cy.shouldHaveValidationErrors("screen-field-form_input_4");
33 | cy.shouldNotHaveValidationErrors("screen-field-form_input_5");
34 | cy.shouldNotHaveValidationErrors("screen-field-form_input_6");
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/tests/e2e/specs/VariableNames.js:
--------------------------------------------------------------------------------
1 | describe("Default values", () => {
2 | it("Variable names with dots 2 levels", () => {
3 | cy.visit("/");
4 | cy.openAcordeon("collapse-2");
5 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
6 | position: "bottom"
7 | });
8 | cy.get("[data-cy=screen-element-container]").click();
9 | cy.get("[data-cy=inspector-name]").clear().type("user.address");
10 | cy.get("[data-cy=mode-preview]").click();
11 | cy.assertPreviewData({
12 | user: {
13 | address: ""
14 | }
15 | });
16 | });
17 | it("Variable names with dots 3 levels", () => {
18 | cy.visit("/");
19 | cy.openAcordeon("collapse-2");
20 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
21 | position: "bottom"
22 | });
23 | cy.get("[data-cy=screen-element-container]").click();
24 | cy.get("[data-cy=inspector-name]").clear().type("user.address.city");
25 | cy.get("[data-cy=mode-preview]").click();
26 | cy.get('[data-cy=preview-content] [name="user.address.city"]').type(
27 | "La Paz"
28 | );
29 | cy.assertPreviewData({
30 | user: {
31 | address: {
32 | city: "La Paz"
33 | }
34 | }
35 | });
36 | });
37 | it("Variable names with dots and one attribute same as the name", () => {
38 | cy.visit("/");
39 | cy.openAcordeon("collapse-2");
40 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
41 | position: "bottom"
42 | });
43 | cy.get("[data-cy=screen-element-container]").click();
44 | cy.get("[data-cy=inspector-name]").clear().type("address.address.city");
45 | cy.get("[data-cy=mode-preview]").click();
46 | cy.get('[data-cy=preview-content] [name="address.address.city"]').type(
47 | "La Paz"
48 | );
49 | cy.assertPreviewData({
50 | address: {
51 | address: {
52 | city: "La Paz"
53 | }
54 | }
55 | });
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/tests/e2e/specs/VisibilityRule.spec.js:
--------------------------------------------------------------------------------
1 | describe("Default values", () => {
2 | it("Check visible", () => {
3 | cy.visit("/");
4 | cy.setPreviewDataInput({ name: "world" });
5 | cy.openAcordeon("collapse-2");
6 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
7 | position: "bottom"
8 | });
9 | cy.get("[data-cy=screen-element-container]").click();
10 | cy.get("[data-cy=accordion-Advanced]").click();
11 | cy.get("[data-cy=inspector-conditionalHide]").clear().type("name");
12 | cy.get("[data-cy=mode-preview]").click();
13 | cy.get("[data-cy=preview-content] [name=form_input_1]").should(
14 | "be.visible"
15 | );
16 | });
17 | it("Check hidden", () => {
18 | cy.visit("/");
19 | cy.setPreviewDataInput({ name: "" });
20 | cy.openAcordeon("collapse-2");
21 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
22 | position: "bottom"
23 | });
24 | cy.get("[data-cy=screen-element-container]").click();
25 | cy.get("[data-cy=accordion-Advanced]").click();
26 | cy.get("[data-cy=inspector-conditionalHide]").clear().type("name");
27 | cy.get("[data-cy=mode-preview]").click();
28 | cy.get("[data-cy=preview-content] [name=form_input_1]").should(
29 | "not.be.visible"
30 | );
31 | });
32 | it("Check dynamic visibility rule", () => {
33 | cy.visit("/");
34 | cy.setPreviewDataInput({ name: "" });
35 | cy.openAcordeon("collapse-2");
36 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
37 | position: "bottom"
38 | });
39 | // Add a second input field
40 | cy.get("[data-cy=controls-FormInput]").drag(
41 | "[data-cy=screen-element-container]",
42 | { position: "bottom" }
43 | );
44 | cy.get("[data-cy=screen-element-container]").first().click();
45 | cy.get("[data-cy=accordion-Advanced]").click();
46 | cy.get("[data-cy=inspector-conditionalHide]").clear().type("form_input_2");
47 | cy.get("[data-cy=mode-preview]").click();
48 | cy.get("[data-cy=preview-content] [name=form_input_1]").should(
49 | "not.be.visible"
50 | );
51 | cy.get("[data-cy=preview-content] [name=form_input_2]")
52 | .clear()
53 | .type("show next");
54 | cy.get("[data-cy=preview-content] [name=form_input_1]").should(
55 | "be.visible"
56 | );
57 | cy.get("[data-cy=preview-content] [name=form_input_1]")
58 | .clear()
59 | .type("visible");
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/tests/e2e/specs/WebEntryEndEventRedirect.spec.js:
--------------------------------------------------------------------------------
1 | import SingleScreen from "../fixtures/single_line_input.json";
2 |
3 | function initializeTaskAndScreenIntercepts(method, url, response) {
4 | cy.intercept(method, url.replace(",screen,", ",").replace(",nested,", ","), response).as('getTask');
5 | cy.intercept(method, url.replace(/\?.*$/, "/screen?include=screen,nested"), response.screen).as('getScreen');
6 | }
7 |
8 | describe("End Event Redirect (Process completed)", () => {
9 | beforeEach(() => {
10 | // Reset the intercepts and the state before each test
11 | cy.visit("/?scenario=TaskWebEntry", {});
12 | });
13 |
14 | it("Element destination type is summaryScreen and process was opened from webEntry", () => {
15 | const taskUrl = "http://localhost:5173/api/1.1/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination";
16 |
17 | initializeTaskAndScreenIntercepts("GET", taskUrl, {
18 | id: 1,
19 | advanceStatus: "open",
20 | component: "task-screen",
21 | allow_interstitial: false,
22 | elementDestination: {
23 | type: "taskList",
24 | value: "http://localhost:5173/tasks",
25 | },
26 | user: {
27 | id: 1,
28 | },
29 | screen: SingleScreen.screens[0],
30 | process_request: {
31 | id: 1,
32 | status: "ACTIVE",
33 | },
34 | });
35 |
36 | // Interact with the UI
37 | cy.get('.form-group > .btn').click();
38 |
39 | // Emit socket event and verify the URL change
40 | cy.socketEventNext("ProcessMaker\\Events\\RedirectTo", {
41 | params: [
42 | {
43 | endEventDestination: {
44 | type: "summaryScreen",
45 | value: null,
46 | },
47 | },
48 | ],
49 | method: "processCompletedRedirect",
50 | })
51 | cy.wait(1000);
52 | // This code will execute after the socket event is finished
53 | cy.url().should("eq", "http://localhost:5173/?scenario=TaskWebEntry");
54 |
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/tests/e2e/specs/WrongConfigurationErrors.js:
--------------------------------------------------------------------------------
1 | describe.skip("Wrong Configuration Errors", () => {
2 | it("Screen with rendering problem in a component should show a warning", () => {
3 | cy.visit("/", {
4 | onBeforeLoad(win) {
5 | cy.stub(win.console, "warn").as("consoleWarn");
6 | cy.stub(win.console, "error").as("consoleError");
7 | }
8 | });
9 | cy.openAcordeon("collapse-2");
10 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
11 | position: "bottom"
12 | });
13 | cy.get("[data-cy=screen-element-container]").click();
14 | cy.get("[data-cy=inspector-name]").clear().type("s.1");
15 | cy.get("[data-cy=mode-preview]").click();
16 | cy.get("[data-cy=mode-editor]").click();
17 | cy.get("@consoleWarn").should(
18 | "be.calledWith",
19 | "There was a problem rendering the screen"
20 | );
21 | });
22 |
23 | it("Screen with rendering problem in a component should not show a warning after the problematic component is deleted", () => {
24 | cy.visit("/", {
25 | onBeforeLoad(win) {
26 | cy.stub(win.console, "warn").as("consoleWarn");
27 | cy.stub(win.console, "error").as("consoleError");
28 | }
29 | });
30 | cy.openAcordeon("collapse-2");
31 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
32 | position: "bottom"
33 | });
34 | cy.get("[data-cy=screen-element-container]").click();
35 | cy.get("[data-cy=inspector-name]").clear().type("s.1");
36 | cy.get("[data-cy=mode-preview]").click();
37 | cy.get("[data-cy=mode-editor]").click();
38 | cy.get("@consoleWarn").should(
39 | "be.calledWith",
40 | "There was a problem rendering the screen"
41 | );
42 | cy.get("[data-cy=screen-element-container] .ml-auto > .btn-danger").click();
43 | cy.get("[data-cy=controls-FormInput]").drag("[data-cy=screen-drop-zone]", {
44 | position: "bottom"
45 | });
46 | cy.get("[data-cy=mode-preview]").click();
47 | cy.get("[data-cy=mode-editor]").click();
48 | cy.get("@consoleError").should("not.to.be.called");
49 | cy.get("@consoleWarn").should("not.to.be.calledTwice");
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/tests/e2e/specs/pagesDropdown.spec.js:
--------------------------------------------------------------------------------
1 | describe("Page Dropdown", () => {
2 | beforeEach(() => {
3 | cy.visit("/");
4 | });
5 | it("Basic default value", () => {
6 | cy.openAllAcordeon();
7 | cy.get("[data-test=page-dropdown]").click();
8 | cy.get("[data-test=add-page]").click({ force: true });
9 | // Define Page 2
10 | cy.get("[data-cy=add-page-name]").clear().type("Page_2");
11 | cy.get("[data-cy=add-page-modal] button.btn").eq(1).click();
12 | cy.wait(300);
13 | cy.get('[data-cy=controls-FormButton]:contains("Page")').drag(
14 | "[data-cy=screen-drop-zone]",
15 | { position: "bottom" }
16 | );
17 | cy.get("[data-cy=screen-element-container]").click();
18 | cy.get("[data-cy=inspector-label]").clear().type("Go to Page 1");
19 | cy.get("[data-cy=accordion-Configuration]").click();
20 | cy.setMultiselect("[data-cy=inspector-eventData]", "Default");
21 | // Define Page 1
22 | cy.get("[data-test=page-dropdown]").click();
23 | cy.get("[data-test=page-Default]").click({ force: true });
24 | cy.get('[data-cy=controls-FormButton]:contains("Page")').drag(
25 | "[data-cy=screen-drop-zone]",
26 | { position: "bottom" }
27 | );
28 | cy.get("[data-cy=screen-element-container]").click();
29 | cy.get("[data-cy=inspector-label]").clear().type("Go to Page 2");
30 | cy.get("[data-cy=accordion-Configuration]").click();
31 | cy.setMultiselect("[data-cy=inspector-eventData]", "Page_2");
32 | // Navigate Default
33 | cy.get("[data-test=page-dropdown]").click();
34 | cy.get("[data-test=page-Default]").click({ force: true });
35 | cy.get("[data-cy=editor-content]").should("contain.text", "Go to Page 2");
36 | // Navigate Page 2
37 | cy.get("[data-test=page-dropdown]").click();
38 | cy.get("[data-test=page-Page_2]").click({ force: true });
39 | cy.get("[data-cy=editor-content]").should("contain.text", "Go to Page 1");
40 | // Preview
41 | cy.get("[data-cy=mode-preview]").click();
42 | cy.get("[data-cy=preview-content]").should("contain.text", "Go to Page 2");
43 | cy.get("[data-cy=preview-content] button").click();
44 | cy.get("[data-cy=preview-content]").should("contain.text", "Go to Page 1");
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/tests/e2e/support/constants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Node types are controls of the screen
3 | */
4 | export const nodeControls = {
5 | checkbox: "[data-cy=controls-FormCheckbox]",
6 | datePicker: "[data-cy=controls-FormDatePicker]",
7 | fileDownload: "[data-cy=controls-FileDownload]",
8 | fileUpload: "[data-cy=controls-FileUpload]",
9 | formImage: "[data-cy=controls-FormImage]",
10 | formInput: "[data-cy=controls-FormInput]",
11 | formLoop: "[data-cy=controls-FormLoop]",
12 | formMultiColumn: "[data-cy=controls-FormMultiColumn]",
13 | formNestedScreen: "[data-cy=controls-FormNestedScreen]",
14 | formRecordList: "[data-cy=controls-FormRecordList]",
15 | formHtmlViewer: "[data-cy=controls-FormHtmlViewer]",
16 | formSelectList: "[data-cy=controls-FormSelectList]",
17 | formSubmit: "[data-cy=controls-FormButton]",
18 | formTextArea: "[data-cy=controls-FormTextArea]",
19 | clipboard: "[data-cy=controls-Clipboard]"
20 | };
21 |
--------------------------------------------------------------------------------
/tests/e2e/support/e2e.js:
--------------------------------------------------------------------------------
1 | import "@cypress/code-coverage/support";
2 |
--------------------------------------------------------------------------------
/tests/e2e/support/index.js:
--------------------------------------------------------------------------------
1 | import "./commands";
2 | import "./e2e";
3 |
--------------------------------------------------------------------------------
/tests/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "baseUrl": "../../node_modules",
5 | "types": [
6 | "cypress"
7 | ]
8 | },
9 | "include": [
10 | "**/*.*"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/tests/unit/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['jest'] ,
3 | env: {
4 | 'jest/globals': true,
5 | node: true,
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/tests/unit/CustomCss.spec.js:
--------------------------------------------------------------------------------
1 | import { createLocalVue, mount } from '@vue/test-utils';
2 | import Renderer from '../../src/components/vue-form-renderer';
3 | import BootstrapVue from 'bootstrap-vue';
4 |
5 | const localVue = createLocalVue();
6 | localVue.use(BootstrapVue);
7 |
8 | describe('Test custom css', () => {
9 |
10 | // Now mount the component and you have the wrapper
11 | const wrapper = mount(Renderer, {
12 | localVue,
13 | mocks: {
14 | $t: text => text,
15 | },
16 | sync: false,
17 | propsData: {
18 | config: [{
19 | items: [
20 | {
21 | 'config': {
22 | 'label': 'Field test 1',
23 | 'name': 'field1',
24 | 'placeholder': '',
25 | 'validation': '',
26 | 'helper': null,
27 | 'type': 'text',
28 | },
29 | 'label': 'Line Input',
30 | },
31 | {
32 | 'config': {
33 | 'label': 'Field test 2',
34 | 'name': 'field2',
35 | 'placeholder': '',
36 | 'validation': '',
37 | 'helper': null,
38 | 'type': 'text',
39 | },
40 | 'label': 'Line Input',
41 | }],
42 | }],
43 | data: {},
44 | page: 0,
45 | computed: [],
46 | customCss: 'div {background: green;} .form-control {color: gray;}',
47 | },
48 | });
49 |
50 | it('Test custom CSS inclusion', () => {
51 | // Test if the rendered form contains the custom CSS
52 | expect(wrapper.html()).toMatch(/