diff --git a/app/client/cypress/fixtures/CameraDsl.json b/app/client/cypress/fixtures/CameraDsl.json index 7cf44388e5..44bb9ab489 100644 --- a/app/client/cypress/fixtures/CameraDsl.json +++ b/app/client/cypress/fixtures/CameraDsl.json @@ -19,6 +19,22 @@ "dynamicBindingPathList": [], "leftColumn": 0, "children": [ + { + "isVisible": true, + "text": "Label", + "textAlign": "LEFT", + "widgetName": "Text1", + "type": "TEXT_WIDGET", + "isLoading": false, + "parentColumnSpace": 14.015625, + "parentRowSpace": 10, + "leftColumn": 1, + "rightColumn": 7, + "topRow": 1, + "bottomRow": 4, + "parentId": "bxekwxgc1i", + "widgetId": "9xcfqahpw2" + }, { "isMirrored": true, "widgetName": "Camera1", diff --git a/app/client/cypress/fixtures/SwitchGroupWidgetDsl.json b/app/client/cypress/fixtures/SwitchGroupWidgetDsl.json index acc93d0818..00bc7c4c28 100644 --- a/app/client/cypress/fixtures/SwitchGroupWidgetDsl.json +++ b/app/client/cypress/fixtures/SwitchGroupWidgetDsl.json @@ -59,6 +59,22 @@ "renderMode": "CANVAS", "isLoading": false, "isInline": true + }, + { + "isVisible": true, + "text": "Label", + "textAlign": "LEFT", + "widgetName": "Text1", + "type": "TEXT_WIDGET", + "isLoading": false, + "parentColumnSpace": 18.66171875, + "parentRowSpace": 10, + "leftColumn": 3, + "rightColumn": 10, + "topRow": 5, + "bottomRow": 9, + "parentId": "bxekwxgc1i", + "widgetId": "9xcfqahpw2" } ] } diff --git a/app/client/cypress/fixtures/TreeSelectDsl.json b/app/client/cypress/fixtures/TreeSelectDsl.json index 1a0dafdcf8..35ba148a30 100644 --- a/app/client/cypress/fixtures/TreeSelectDsl.json +++ b/app/client/cypress/fixtures/TreeSelectDsl.json @@ -122,6 +122,22 @@ "renderMode":"CANVAS", "isLoading":false, "allowClear":false + }, + { + "isVisible": true, + "text": "Label", + "textAlign": "LEFT", + "widgetName": "Text1", + "type": "TEXT_WIDGET", + "isLoading": false, + "parentColumnSpace": 15.974999999999998, + "parentRowSpace": 10, + "leftColumn": 3, + "rightColumn": 15, + "topRow": 10, + "bottomRow": 12, + "parentId": "bxekwxgc1i", + "widgetId": "9xcfqahpw2" } ] } diff --git a/app/client/cypress/fixtures/formHasChangesDsl.json b/app/client/cypress/fixtures/formHasChangesDsl.json new file mode 100644 index 0000000000..918195f33c --- /dev/null +++ b/app/client/cypress/fixtures/formHasChangesDsl.json @@ -0,0 +1,481 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 909, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1290, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 52, + "minHeight": 690, + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "widgetName": "Form1", + "backgroundColor": "white", + "rightColumn": 62, + "isCanvas": true, + "displayName": "Form", + "iconSVG": "/static/media/icon.ea3e08d1.svg", + "widgetId": "ui8kenblnq", + "topRow": 5, + "bottomRow": 62, + "parentRowSpace": 10, + "isVisible": true, + "type": "FORM_WIDGET", + "hideCard": false, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "animateLoading": true, + "parentColumnSpace": 14.015625, + "leftColumn": 2, + "children": [ + { + "widgetName": "Canvas1", + "rightColumn": 336.375, + "detachFromLayout": true, + "displayName": "Canvas", + "widgetId": "rgnqy2hpcb", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 570, + "parentRowSpace": 1, + "isVisible": true, + "type": "CANVAS_WIDGET", + "canExtend": false, + "version": 1, + "hideCard": true, + "parentId": "ui8kenblnq", + "minHeight": 400, + "renderMode": "CANVAS", + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "children": [ + { + "widgetName": "Text1", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b52.svg", + "topRow": 1, + "bottomRow": 5, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "dynamicTriggerPathList": [], + "leftColumn": 1.5, + "dynamicBindingPathList": [], + "shouldTruncate": false, + "truncateButtonColor": "#FFC13D", + "text": "TopLevelForm", + "key": "r71qxtucag", + "rightColumn": 25.5, + "textAlign": "LEFT", + "widgetId": "az2x2gnx8i", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "shouldScroll": false, + "version": 1, + "parentId": "rgnqy2hpcb", + "renderMode": "CANVAS", + "isLoading": false, + "fontSize": "HEADING1" + }, + { + "boxShadow": "NONE", + "widgetName": "Container1", + "borderColor": "transparent", + "isCanvas": true, + "displayName": "Container", + "iconSVG": "/static/media/icon.1977dca3.svg", + "topRow": 7, + "bottomRow": 55, + "parentRowSpace": 10, + "type": "CONTAINER_WIDGET", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 12.8271484375, + "leftColumn": 1, + "children": [ + { + "widgetName": "Canvas2", + "rightColumn": 307.8515625, + "detachFromLayout": true, + "displayName": "Canvas", + "widgetId": "o16xur9huq", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 490, + "parentRowSpace": 1, + "isVisible": true, + "type": "CANVAS_WIDGET", + "canExtend": false, + "version": 1, + "hideCard": true, + "parentId": "c3ib5l2wce", + "minHeight": 400, + "renderMode": "CANVAS", + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "children": [ + { + "widgetName": "Form2", + "backgroundColor": "white", + "rightColumn": 64, + "isCanvas": true, + "displayName": "Form", + "iconSVG": "/static/media/icon.ea3e08d1.svg", + "widgetId": "r0vj63apg4", + "topRow": 1, + "bottomRow": 47, + "parentRowSpace": 10, + "isVisible": true, + "type": "FORM_WIDGET", + "hideCard": false, + "parentId": "o16xur9huq", + "renderMode": "CANVAS", + "isLoading": false, + "animateLoading": true, + "parentColumnSpace": 12.314224243164062, + "leftColumn": 0, + "children": [ + { + "widgetName": "Canvas3", + "rightColumn": 295.5413818359375, + "detachFromLayout": true, + "displayName": "Canvas", + "widgetId": "hyxstzafwr", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 460, + "parentRowSpace": 1, + "isVisible": true, + "type": "CANVAS_WIDGET", + "canExtend": false, + "version": 1, + "hideCard": true, + "parentId": "r0vj63apg4", + "minHeight": 400, + "renderMode": "CANVAS", + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "children": [ + { + "widgetName": "Text2", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b52.svg", + "topRow": 1, + "bottomRow": 5, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "dynamicTriggerPathList": [], + "leftColumn": 1.5, + "dynamicBindingPathList": [], + "shouldTruncate": false, + "truncateButtonColor": "#FFC13D", + "text": "NestedForm1", + "key": "r71qxtucag", + "rightColumn": 25.5, + "textAlign": "LEFT", + "widgetId": "9x1tljetyn", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "shouldScroll": false, + "version": 1, + "parentId": "hyxstzafwr", + "renderMode": "CANVAS", + "isLoading": false, + "fontSize": "HEADING1" + }, + { + "boxShadow": "NONE", + "widgetName": "Container2", + "borderColor": "transparent", + "isCanvas": true, + "displayName": "Container", + "iconSVG": "/static/media/icon.1977dca3.svg", + "topRow": 6, + "bottomRow": 44, + "parentRowSpace": 10, + "type": "CONTAINER_WIDGET", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 12.001724243164062, + "leftColumn": 0, + "children": [ + { + "widgetName": "Canvas4", + "rightColumn": 288.0413818359375, + "detachFromLayout": true, + "displayName": "Canvas", + "widgetId": "bezg0vuivd", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 390, + "parentRowSpace": 1, + "isVisible": true, + "type": "CANVAS_WIDGET", + "canExtend": false, + "version": 1, + "hideCard": true, + "parentId": "d3bbrwipbo", + "minHeight": 400, + "renderMode": "CANVAS", + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "children": [ + { + "widgetName": "Form3", + "backgroundColor": "white", + "rightColumn": 62, + "isCanvas": true, + "displayName": "Form", + "iconSVG": "/static/media/icon.ea3e08d1.svg", + "widgetId": "e9qj527b6g", + "topRow": 0, + "bottomRow": 36, + "parentRowSpace": 10, + "isVisible": true, + "type": "FORM_WIDGET", + "hideCard": false, + "parentId": "bezg0vuivd", + "renderMode": "CANVAS", + "isLoading": false, + "animateLoading": true, + "parentColumnSpace": 12.001724243164062, + "leftColumn": 0, + "children": [ + { + "widgetName": "Canvas5", + "rightColumn": 288.0413818359375, + "detachFromLayout": true, + "displayName": "Canvas", + "widgetId": "ju0erkgavw", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 390, + "parentRowSpace": 1, + "isVisible": true, + "type": "CANVAS_WIDGET", + "canExtend": false, + "version": 1, + "hideCard": true, + "parentId": "e9qj527b6g", + "minHeight": 400, + "renderMode": "CANVAS", + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "children": [ + { + "widgetName": "Text3", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b52.svg", + "topRow": 1, + "bottomRow": 5, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "dynamicTriggerPathList": [], + "leftColumn": 1.5, + "dynamicBindingPathList": [], + "shouldTruncate": false, + "truncateButtonColor": "#FFC13D", + "text": "NestedForm2", + "key": "r71qxtucag", + "rightColumn": 25.5, + "textAlign": "LEFT", + "widgetId": "kx3kje0esv", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "shouldScroll": false, + "version": 1, + "parentId": "ju0erkgavw", + "renderMode": "CANVAS", + "isLoading": false, + "fontSize": "HEADING1" + }, + { + "resetFormOnClick": true, + "widgetName": "FormButton1", + "buttonColor": "#03B365", + "displayName": "FormButton", + "iconSVG": "/static/media/icon.c8f649ed.svg", + "topRow": 29, + "bottomRow": 33, + "type": "FORM_BUTTON_WIDGET", + "hideCard": true, + "animateLoading": true, + "leftColumn": 47, + "text": "Submit", + "key": "6rckarmxa8", + "rightColumn": 63, + "isDefaultClickDisabled": true, + "widgetId": "71jwl9rqyl", + "isVisible": true, + "recaptchaType": "V3", + "version": 1, + "parentId": "ju0erkgavw", + "renderMode": "CANVAS", + "isLoading": false, + "disabledWhenInvalid": true, + "buttonVariant": "PRIMARY" + }, + { + "resetFormOnClick": true, + "widgetName": "FormButton2", + "buttonColor": "#03B365", + "displayName": "FormButton", + "iconSVG": "/static/media/icon.c8f649ed.svg", + "topRow": 29, + "bottomRow": 33, + "type": "FORM_BUTTON_WIDGET", + "hideCard": true, + "animateLoading": true, + "leftColumn": 30, + "text": "Reset", + "key": "6rckarmxa8", + "rightColumn": 46, + "isDefaultClickDisabled": true, + "widgetId": "385vwj1sak", + "isVisible": true, + "recaptchaType": "V3", + "version": 1, + "parentId": "ju0erkgavw", + "renderMode": "CANVAS", + "isLoading": false, + "disabledWhenInvalid": false, + "buttonVariant": "SECONDARY" + }, + { + "widgetName": "Checkbox1", + "displayName": "Checkbox", + "iconSVG": "/static/media/icon.aaab032b.svg", + "topRow": 7, + "bottomRow": 11, + "parentRowSpace": 10, + "type": "CHECKBOX_WIDGET", + "alignWidget": "LEFT", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 11.011435985565186, + "leftColumn": 1, + "isDisabled": false, + "key": "kn37pa6063", + "isRequired": false, + "rightColumn": 21, + "widgetId": "xcs2djteir", + "isVisible": true, + "label": "Label", + "version": 1, + "parentId": "ju0erkgavw", + "renderMode": "CANVAS", + "isLoading": false, + "defaultCheckedState": true + } + ], + "key": "invgby4psp" + } + ], + "key": "1yxjunjfwb" + } + ], + "key": "invgby4psp" + } + ], + "borderWidth": "0", + "key": "tpj0tcwdhg", + "backgroundColor": "#FFFFFF", + "rightColumn": 64, + "widgetId": "d3bbrwipbo", + "containerStyle": "card", + "isVisible": true, + "version": 1, + "parentId": "hyxstzafwr", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "0" + } + ], + "key": "invgby4psp" + } + ], + "key": "1yxjunjfwb" + } + ], + "key": "invgby4psp" + } + ], + "borderWidth": "0", + "key": "tpj0tcwdhg", + "backgroundColor": "#FFFFFF", + "rightColumn": 64, + "widgetId": "c3ib5l2wce", + "containerStyle": "card", + "isVisible": true, + "version": 1, + "parentId": "rgnqy2hpcb", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "0" + }, + { + "widgetName": "Text4", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b52.svg", + "topRow": 1, + "bottomRow": 5, + "parentRowSpace": 10, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 12.8271484375, + "dynamicTriggerPathList": [], + "leftColumn": 47, + "dynamicBindingPathList": [ + { + "key": "text" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "#FFC13D", + "text": "{{Form1.hasChanges}}", + "key": "r71qxtucag", + "rightColumn": 63, + "textAlign": "LEFT", + "widgetId": "6lwl9odtd7", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "shouldScroll": false, + "version": 1, + "parentId": "rgnqy2hpcb", + "renderMode": "CANVAS", + "isLoading": false, + "fontSize": "PARAGRAPH" + } + ], + "key": "invgby4psp" + } + ], + "key": "1yxjunjfwb" + } + ] +} +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/formdsl1.json b/app/client/cypress/fixtures/formdsl1.json index 22d7d0acc4..513eeae362 100644 --- a/app/client/cypress/fixtures/formdsl1.json +++ b/app/client/cypress/fixtures/formdsl1.json @@ -105,6 +105,22 @@ "bottomRow": 7, "parentId": "7tkpo9s22m", "widgetId": "6h8j08u7ea" + }, + { + "isVisible": true, + "text": "Label", + "textAlign": "LEFT", + "widgetName": "Text1", + "type": "TEXT_WIDGET", + "isLoading": false, + "parentColumnSpace": 71.75, + "parentRowSpace": 38, + "leftColumn": 3, + "rightColumn": 5, + "topRow": 10, + "bottomRow": 12, + "parentId": "bxekwxgc1i", + "widgetId": "9xcfqahpw2" } ], "widgetId": "7tkpo9s22m", diff --git a/app/client/cypress/fixtures/multiSelectDsl.json b/app/client/cypress/fixtures/multiSelectDsl.json index a58625d801..598b0027f6 100644 --- a/app/client/cypress/fixtures/multiSelectDsl.json +++ b/app/client/cypress/fixtures/multiSelectDsl.json @@ -61,6 +61,25 @@ "leftColumn": 0, "dynamicBindingPathList": [], "children": [ + { + "widgetName": "Text1", + "rightColumn": 10, + "widgetId": "4d8d2eh4xg", + "dynamicPropertyPathList": [], + "topRow": 0, + "bottomRow": 3, + "parentRowSpace": 38, + "isVisible": true, + "type": "TEXT_WIDGET", + "dynamicBindingPathList": [], + "shouldScroll": true, + "parentId": "hjiybwqair", + "isLoading": false, + "parentColumnSpace": 71.75, + "leftColumn": 1, + "text": "Test text", + "textStyle": "HEADING" + }, { "isRequired": false, "widgetName": "Input1", diff --git a/app/client/cypress/fixtures/newFormDsl.json b/app/client/cypress/fixtures/newFormDsl.json index 5729138e1c..7af0b0cf49 100644 --- a/app/client/cypress/fixtures/newFormDsl.json +++ b/app/client/cypress/fixtures/newFormDsl.json @@ -52,6 +52,25 @@ "snapColumns": 16, "orientation": "VERTICAL", "children": [ + { + "widgetName": "Text1", + "rightColumn": 3, + "widgetId": "4d8d2eh4xg", + "dynamicPropertyPathList": [], + "topRow": 0, + "bottomRow": 1, + "parentRowSpace": 38, + "isVisible": true, + "type": "TEXT_WIDGET", + "dynamicBindingPathList": [], + "shouldScroll": true, + "parentId": "hjiybwqair", + "isLoading": false, + "parentColumnSpace": 71.75, + "leftColumn": 1, + "text": "Test text", + "textStyle": "HEADING" + }, { "isVisible": true, "inputType": "TEXT", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js index c3fffd9d7b..ddf868ae8b 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js @@ -59,9 +59,10 @@ describe("Entity explorer Drag and Drop widgets testcases", function() { cy.hoverAndClickParticularIndex(1); cy.selectAction("Show Bindings"); cy.get(apiwidget.propertyList).then(function($lis) { - expect($lis).to.have.length(2); + expect($lis).to.have.length(3); expect($lis.eq(0)).to.contain("{{FormTest.isVisible}}"); expect($lis.eq(1)).to.contain("{{FormTest.data}}"); + expect($lis.eq(2)).to.contain("{{FormTest.hasChanges}}"); }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Delete_Undo_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Delete_Undo_spec.js index 7dba62e0ec..32cf172641 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Delete_Undo_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Delete_Undo_spec.js @@ -35,9 +35,10 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() { cy.hoverAndClickParticularIndex(1); cy.selectAction("Show Bindings"); cy.get(apiwidget.propertyList).then(function($lis) { - expect($lis).to.have.length(2); + expect($lis).to.have.length(3); expect($lis.eq(0)).to.contain("{{FormTest.isVisible}}"); expect($lis.eq(1)).to.contain("{{FormTest.data}}"); + expect($lis.eq(2)).to.contain("{{FormTest.hasChanges}}"); }); cy.get(".t--entity-name") .contains("FormTest") @@ -68,9 +69,10 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() { cy.hoverAndClickParticularIndex(1); cy.selectAction("Show Bindings"); cy.get(apiwidget.propertyList).then(function($lis) { - expect($lis).to.have.length(2); + expect($lis).to.have.length(3); expect($lis.eq(0)).to.contain("{{FormTest.isVisible}}"); expect($lis.eq(1)).to.contain("{{FormTest.data}}"); + expect($lis.eq(2)).to.contain("{{FormTest.hasChanges}}"); }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js index 1694445d80..018238fa6a 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js @@ -46,9 +46,10 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() { ee.expandCollapseEntity("FormTest"); ee.ActionContextMenuByEntityName("FormTestCopy", "Show Bindings"); cy.get(apiwidget.propertyList).then(function($lis) { - expect($lis).to.have.length(2); + expect($lis).to.have.length(3); expect($lis.eq(0)).to.contain("{{FormTestCopy.isVisible}}"); expect($lis.eq(1)).to.contain("{{FormTestCopy.data}}"); + expect($lis.eq(2)).to.contain("{{FormTestCopy.hasChanges}}"); cy.contains("FormTestCopy"); cy.get($lis.eq(1)) .contains("{{FormTestCopy.data}}") diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/AudioRecorder_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/AudioRecorder_spec.js new file mode 100644 index 0000000000..68d7829268 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/AudioRecorder_spec.js @@ -0,0 +1,40 @@ +const explorer = require("../../../../locators/explorerlocators.json"); + +const widgetName = "audiorecorderwidget"; + +describe("AudioRecorder Widget", () => { + it("Drag & drop AudioRecorder and Text widgets", () => { + cy.get(explorer.addWidget).click(); + cy.dragAndDropToCanvas(widgetName, { x: 300, y: 300 }); + cy.get(`.t--widget-${widgetName}`).should("exist"); + cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 }); + cy.openPropertyPane("textwidget"); + cy.updateCodeInput( + ".t--property-control-text", + `{{AudioRecorder1.isDirty}}`, + ); + }); + + it("Check isDirty meta property", () => { + // Check if isDirty is false for the first time + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(`.t--widget-${widgetName} button`) + .first() + .click(); + cy.get(`.t--widget-${widgetName} .status`) + .should("have.text", "Press to start recording") + .should("exist"); + // Start recording and recorder for 3 seconds + cy.get(`.t--widget-${widgetName} button`) + .first() + .click(); + cy.wait(3000); + // Stop recording + cy.get(`.t--widget-${widgetName} button span.bp3-icon-symbol-square`) + .first() + .click(); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Camera_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Camera_spec.js index 8e1042924f..1f37304c1f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Camera_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Camera_spec.js @@ -14,12 +14,16 @@ describe("Camera Widget", () => { cy.goToEditFromPublish(); }); - it("Property: onImageSave, show modal", () => { + it("Check isDirty, onImageSave", () => { const modalName = `modal`; const mainControlSelector = "//div[contains(@class, 't--widget-camerawidget')]//button"; cy.createModal(modalName); + cy.openPropertyPane("textwidget"); + cy.updateCodeInput(".t--property-control-text", "{{Camera1.isDirty}}"); + // Initial value of isDirty should be false + cy.get(".t--widget-textwidget").should("contain", "false"); // Take photo cy.xpath(mainControlSelector) .eq(2) @@ -32,5 +36,7 @@ describe("Camera Widget", () => { // Assert: should trigger onImageSave action - modal popup cy.get(modalWidgetPage.modelTextField).should("have.text", modalName); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckBox_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckBox_spec.js index 166d31f5b5..2bfc34e429 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckBox_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckBox_spec.js @@ -5,6 +5,7 @@ const publish = require("../../../../locators/publishWidgetspage.json"); const dsl = require("../../../../fixtures/newFormDsl.json"); const formWidgetDsl = require("../../../../fixtures/formWidgetdsl.json"); const pages = require("../../../../locators/Pages.json"); +const explorer = require("../../../../locators/explorerlocators.json"); describe("Checkbox Widget Functionality", function() { before(() => { @@ -72,6 +73,26 @@ describe("Checkbox Widget Functionality", function() { cy.get(publish.checkboxWidget + " " + "input").should("be.checked"); cy.get(publish.backToEditor).click(); }); + + it("Check isDirty meta property", function() { + cy.openPropertyPane("textwidget"); + cy.updateCodeInput(".t--property-control-text", `{{checker.isDirty}}`); + // Check if initial value of isDirty is false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(`${formWidgetsPage.checkboxWidget} label`) + .first() + .click(); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Change defaultCheckedState property + cy.openPropertyPane("checkboxwidget"); + cy.get(".t--property-control-defaultselected label") + .last() + .click(); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); }); afterEach(() => { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckboxGroup_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckboxGroup_spec.js index 85812fe538..704e829098 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckboxGroup_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckboxGroup_spec.js @@ -1,6 +1,7 @@ const commonlocators = require("../../../../locators/commonlocators.json"); const formWidgetsPage = require("../../../../locators/FormWidgets.json"); const publish = require("../../../../locators/publishWidgetspage.json"); +const explorer = require("../../../../locators/explorerlocators.json"); const dsl = require("../../../../fixtures/checkboxgroupDsl.json"); describe("Checkbox Group Widget Functionality", function() { @@ -129,6 +130,27 @@ describe("Checkbox Group Widget Functionality", function() { ".t--draggable-checkboxgroupwidget div[data-cy^='checkbox-group-container']", ).should("have.css", "justify-content", "flex-start"); }); + + it("Check isDirty meta property", function() { + cy.get(explorer.addWidget).click(); + cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 }); + cy.openPropertyPane("textwidget"); + cy.updateCodeInput( + ".t--property-control-text", + `{{checkboxgrouptest.isDirty}}`, + ); + // Change defaultSelectedValues + cy.openPropertyPane("checkboxgroupwidget"); + cy.updateCodeInput(".t--property-control-defaultselectedvalues", "GREEN"); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(formWidgetsPage.labelCheckboxGroup) + .first() + .click(); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + }); }); afterEach(() => { // put your clean up code if any diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CurrencyInput_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CurrencyInput_spec.js index 7077fb45b7..0843eabfe8 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CurrencyInput_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CurrencyInput_spec.js @@ -248,4 +248,28 @@ describe("Currency widget - ", () => { enterAndTest(d[0], d[1]); }); }); + + it("Check isDirty meta property", function() { + cy.openPropertyPane("textwidget"); + cy.updateCodeInput( + ".t--property-control-text", + `{{CurrencyInput1.isDirty}}`, + ); + // Init isDirty + cy.openPropertyPane(widgetName); + cy.updateCodeInput(".t--property-control-defaulttext", "1"); + cy.closePropertyPane(); + // Check if initial value of isDirty is false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(widgetInput).clear(); + cy.wait(300); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Change defaultText + cy.openPropertyPane(widgetName); + cy.updateCodeInput(".t--property-control-defaulttext", "5"); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_2_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_2_spec.js index 5c768dbed5..d23df97d9f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_2_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_2_spec.js @@ -140,6 +140,49 @@ describe("DatePicker Widget Property pane tests with js bindings", function() { .should("have.text", "May 4, 2021 6:25 AM"); }); + it("Check isDirty meta property", function() { + cy.openPropertyPane("textwidget"); + cy.updateCodeInput(".t--property-control-text", `{{DatePicker1.isDirty}}`); + // Init isDirty + cy.openPropertyPane("datepickerwidget2"); + cy.get(formWidgetsPage.toggleJsDefaultDate).click(); + cy.get(".t--property-control-defaultdate .bp3-input").clear(); + cy.get(formWidgetsPage.toggleJsDefaultDate).click(); + cy.testJsontext( + "defaultdate", + '{{moment("04/05/2021 05:25", "DD/MM/YYYY HH:mm").toISOString()}}', + ); + cy.closePropertyPane(); + // Check if initial value of isDirty is false + cy.get(".t--widget-textwidget") + .first() + .should("contain", "false"); + // Interact with UI + cy.get(".t--draggable-datepickerwidget2 .bp3-input") + .clear({ + force: true, + }) + .type("04/05/2021 06:25"); + cy.wait("@updateLayout"); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget") + .first() + .should("contain", "true"); + // Change defaultDate + cy.openPropertyPane("datepickerwidget2"); + cy.get(formWidgetsPage.toggleJsDefaultDate).click(); + cy.get(".t--property-control-defaultdate .bp3-input").clear(); + cy.get(formWidgetsPage.toggleJsDefaultDate).click(); + cy.testJsontext( + "defaultdate", + '{{moment("07/05/2021 05:25", "DD/MM/YYYY HH:mm").toISOString()}}', + ); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget") + .first() + .should("contain", "false"); + }); + it("Datepicker default date validation with js binding", function() { cy.PublishtheApp(); // eslint-disable-next-line cypress/no-unnecessary-waiting diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FilePickerV2_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FilePickerV2_spec.js new file mode 100644 index 0000000000..8642061780 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FilePickerV2_spec.js @@ -0,0 +1,30 @@ +const explorer = require("../../../../locators/explorerlocators.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +const widgetName = "filepickerwidgetv2"; + +describe("File picker widget v2", () => { + it("1. Drag & drop FilePicker/Text widgets", () => { + cy.get(explorer.addWidget).click(); + cy.dragAndDropToCanvas(widgetName, { x: 300, y: 300 }); + cy.get(`.t--widget-${widgetName}`).should("exist"); + cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 }); + cy.openPropertyPane("textwidget"); + cy.updateCodeInput(".t--property-control-text", `{{FilePicker1.isDirty}}`); + }); + + it("Check isDirty meta property", function() { + // Check if initial value of isDirty is false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Upload a new file + cy.get(`.t--widget-${widgetName}`).click(); + cy.get(commonlocators.filePickerInput) + .first() + .attachFile("testFile.mov"); + cy.get(commonlocators.filePickerUploadButton).click(); + //eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(500); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_Nested_HasChanges_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_Nested_HasChanges_spec.js new file mode 100644 index 0000000000..535092b86e --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_Nested_HasChanges_spec.js @@ -0,0 +1,19 @@ +const dsl = require("../../../../fixtures/formHasChangesDsl.json"); + +describe("Form Widget", () => { + before(() => { + cy.addDsl(dsl); + }); + + it("Check hasChanges meta property", () => { + // Check if isDirty is false for the first time + cy.contains(".t--widget-textwidget", "false").should("exist"); + // Interact with UI + cy.get(`.t--widget-checkboxwidget label`) + .first() + .click(); + // Check if isDirty is set to true + cy.contains(".t--widget-textwidget", "false").should("not.exist"); + cy.contains(".t--widget-textwidget", "true").should("exist"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Inputv2_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Inputv2_spec.js index 12134110ea..29f0a71e37 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Inputv2_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Inputv2_spec.js @@ -422,6 +422,29 @@ describe("Input widget V2 - ", () => { cy.get(".t--widget-textwidget").should("contain", "1.0001:1.0001:true"); }); + it("Check isDirty meta property", function() { + cy.openPropertyPane("textwidget"); + cy.updateCodeInput(".t--property-control-text", `{{Input1.isDirty}}`); + // Init isDirty + cy.openPropertyPane(widgetName); + cy.selectDropdownValue(".t--property-control-datatype", "Text"); + cy.updateCodeInput(".t--property-control-defaulttext", "a"); + // Check if initial value of isDirty is false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(widgetInput).clear(); + cy.wait(300); + cy.get(widgetInput).type("b"); + cy.wait(300); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Change defaultText + cy.openPropertyPane(widgetName); + cy.updateCodeInput(".t--property-control-defaulttext", "c"); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); + function enterAndTest(text, expected) { cy.get(`.t--widget-${widgetName} input`).clear(); cy.wait(300); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/MultiSelect_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/MultiSelect_spec.js index 3d68652ee9..1253f5dcbc 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/MultiSelect_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/MultiSelect_spec.js @@ -90,6 +90,30 @@ describe("MultiSelect Widget Functionality", function() { .eq(1) .should("have.text", "Option 2"); }); + + it("Check isDirty meta property", function() { + cy.openPropertyPane("textwidget"); + cy.updateCodeInput(".t--property-control-text", `{{MultiSelect2.isDirty}}`); + // Init isDirty by changing defaultOptionValue + cy.openPropertyPane("multiselectwidgetv2"); + cy.updateCodeInput( + ".t--property-control-defaultvalue", + '[\n {\n "label": "Option 1",\n "value": "1"\n }\n]', + ); + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(".rc-select-selector").click({ force: true }); + cy.dropdownMultiSelectDynamic("Option 2"); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Reset isDirty by changing defaultOptionValue + cy.updateCodeInput( + ".t--property-control-defaultvalue", + '[\n {\n "label": "Option 2",\n "value": "2"\n }\n]', + ); + // Check if isDirty is set to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); }); afterEach(() => { // put your clean up code if any diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Multi_Select_Tree_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Multi_Select_Tree_spec.js index 5564fbeffa..1c0a8b9dad 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Multi_Select_Tree_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Multi_Select_Tree_spec.js @@ -2,6 +2,7 @@ const dsl = require("../../../../fixtures/TreeSelectDsl.json"); const formWidgetsPage = require("../../../../locators/FormWidgets.json"); const publish = require("../../../../locators/publishWidgetspage.json"); const commonlocators = require("../../../../locators/commonlocators.json"); +const explorer = require("../../../../locators/explorerlocators.json"); describe("MultiSelectTree Widget Functionality", function() { before(() => { @@ -41,6 +42,32 @@ describe("MultiSelectTree Widget Functionality", function() { ).should("be.visible"); cy.get(publish.backToEditor).click(); }); + + it("Check isDirty meta property", function() { + cy.get(explorer.addWidget).click(); + cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 }); + cy.openPropertyPane("textwidget"); + cy.updateCodeInput( + ".t--property-control-text", + `{{MultiSelectTree1.isDirty}}`, + ); + // Change defaultValue + cy.openPropertyPane("multiselecttreewidget"); + cy.testJsontext("defaultvalue", "GREEN\n"); + // Check if isDirty is set to false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(formWidgetsPage.treeSelectInput) + .first() + .click({ force: true }); + cy.treeMultiSelectDropdown("Red"); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Reset isDirty by changing defaultValue + cy.testJsontext("defaultvalue", "BLUE\n"); + // Check if isDirty is set to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); }); afterEach(() => { // put your clean up code if any diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Phone_input_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Phone_input_spec.js index e2877c5e94..1adc977f5d 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Phone_input_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Phone_input_spec.js @@ -105,4 +105,27 @@ describe("Phone input widget - ", () => { cy.get(widgetInput).should("contain.value", ""); cy.get(".t--widget-textwidget").should("contain", ":undefined"); }); + + it("Check isDirty meta property", function() { + cy.openPropertyPane("textwidget"); + cy.updateCodeInput(".t--property-control-text", `{{PhoneInput1.isDirty}}`); + // Change defaultText + cy.openPropertyPane(widgetName); + cy.updateCodeInput(".t--property-control-defaulttext", "1"); + cy.closePropertyPane(); + // Check if isDirty is set to false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(widgetInput).clear(); + cy.wait(300); + cy.get(widgetInput).type("2"); + cy.wait(300); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Reset isDirty by changing defaultText + cy.openPropertyPane(widgetName); + cy.updateCodeInput(".t--property-control-defaulttext", "3"); + // Check if isDirty is set to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/RadioGroup_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/RadioGroup_spec.js new file mode 100644 index 0000000000..d6202324e2 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/RadioGroup_spec.js @@ -0,0 +1,30 @@ +const explorer = require("../../../../locators/explorerlocators.json"); + +const widgetName = "radiogroupwidget"; + +describe("Radio Group Widget", () => { + it("Drag & drop Radio group & Text widgets", () => { + cy.get(explorer.addWidget).click(); + cy.dragAndDropToCanvas(widgetName, { x: 300, y: 300 }); + cy.get(`.t--widget-${widgetName}`).should("exist"); + cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 }); + cy.openPropertyPane("textwidget"); + cy.updateCodeInput(".t--property-control-text", `{{RadioGroup1.isDirty}}`); + }); + + it("Check isDirty meta property", function() { + // Check if initial value of isDirty is false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(".t--widget-radiogroupwidget .bp3-radio") + .last() + .click(); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Change defaultOptionValue + cy.openPropertyPane(widgetName); + cy.updateCodeInput(".t--property-control-defaultselectedvalue", "N"); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/RichTextEditor_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/RichTextEditor_spec.js index 9635b69c6b..dddcbb27b2 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/RichTextEditor_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/RichTextEditor_spec.js @@ -128,6 +128,28 @@ describe("RichTextEditor Widget Functionality", function() { ); }); + it("Check isDirty meta property", function() { + cy.openPropertyPane("textwidget"); + cy.updateCodeInput( + ".t--property-control-text", + `{{RichtextEditor.isDirty}}`, + ); + // Change defaultText + cy.openPropertyPane("richtexteditorwidget"); + cy.testJsontext("defaulttext", "a"); + // Check if isDirty has been changed into false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.setTinyMceContent("rte-6h8j08u7ea", "abc"); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Change defaultText + cy.openPropertyPane("richtexteditorwidget"); + cy.testJsontext("defaulttext", "b"); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); + afterEach(() => { cy.goToEditFromPublish(); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Select_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Select_spec.js new file mode 100644 index 0000000000..d3e373dedb --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Select_spec.js @@ -0,0 +1,34 @@ +const explorer = require("../../../../locators/explorerlocators.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); +const formWidgetsPage = require("../../../../locators/FormWidgets.json"); +const widgetLocators = require("../../../../locators/Widgets.json"); + +const widgetName = "selectwidget"; + +describe("Select widget", () => { + it("1. Drag and drop Select/Text widgets", () => { + cy.get(explorer.addWidget).click(); + cy.dragAndDropToCanvas(widgetName, { x: 300, y: 300 }); + cy.get(`.t--widget-${widgetName}`).should("exist"); + cy.dragAndDropToCanvas("textwidget", { x: 300, y: 500 }); + }); + it("2. Check isDirty meta property", () => { + cy.openPropertyPane("textwidget"); + cy.updateCodeInput(".t--property-control-text", `{{Select1.isDirty}}`); + // Check if initial value of isDirty is false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(formWidgetsPage.selectWidget) + .find(widgetLocators.dropdownSingleSelect) + .click({ force: true }); + cy.get(commonlocators.singleSelectWidgetMenuItem) + .contains("Blue") + .click({ force: true }); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Change defaultOptionValue property + cy.updateCodeInput(".t--property-control-defaultvalue", "RED"); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Single_Select_Tree_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Single_Select_Tree_spec.js index d4d42a9438..a4de78b0db 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Single_Select_Tree_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Single_Select_Tree_spec.js @@ -41,6 +41,35 @@ describe("Single Select Widget Functionality", function() { ).should("be.visible"); cy.get(publish.backToEditor).click(); }); + + it("Check isDirty meta property", function() { + cy.openPropertyPane("textwidget"); + cy.updateCodeInput( + ".t--property-control-text", + `{{SingleSelectTree1.isDirty}}`, + ); + // Change defaultText + cy.openPropertyPane("singleselecttreewidget"); + cy.updateCodeInput(".t--property-control-defaultvalue", "GREEN"); + cy.closePropertyPane(); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(formWidgetsPage.treeSelectInput) + .last() + .click({ force: true }); + cy.get(formWidgetsPage.treeSelectFilterInput) + .click() + .type("light"); + cy.treeSelectDropdown("Light Blue"); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Change defaultText + cy.openPropertyPane("singleselecttreewidget"); + cy.updateCodeInput(".t--property-control-defaultvalue", "RED"); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); }); afterEach(() => { // put your clean up code if any diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/SwitchGroup_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/SwitchGroup_spec.js index 497326be87..2dbffea652 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/SwitchGroup_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/SwitchGroup_spec.js @@ -100,4 +100,34 @@ describe("Switch Group Widget Functionality", function() { this.data.ModalName, ); }); + + it("Check isDirty meta property", function() { + cy.openPropertyPane("textwidget"); + cy.updateCodeInput( + ".t--property-control-text", + `{{switchgrouptest.isDirty}}`, + ); + // Change defaultSelectedValues + cy.openPropertyPane("switchgroupwidget"); + cy.updateCodeInput( + ".t--property-control-defaultselectedvalues", + `[\n"BLUE"\n]`, + ); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(formWidgetsPage.labelSwitchGroup) + .first() + .click(); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Change defaultSelectedValues + cy.openPropertyPane("switchgroupwidget"); + cy.updateCodeInput( + ".t--property-control-defaultselectedvalues", + `[\n"GREEN"\n]`, + ); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Switch_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Switch_spec.js index 74294c26ee..e91b1bac2b 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Switch_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Switch_spec.js @@ -86,6 +86,31 @@ describe("Switch Widget Functionality", function() { cy.get(publish.switchwidget + " " + ".bp3-align-left").should("not.exist"); cy.get(publish.backToEditor).click(); }); + + it("Check isDirty meta property", function() { + cy.openPropertyPane("textwidget"); + cy.updateCodeInput(".t--property-control-text", `{{Toggler.isDirty}}`); + // Change defaultSwitchState property + cy.openPropertyPane("switchwidget"); + cy.get(".t--property-control-defaultselected label") + .last() + .click(); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + // Interact with UI + cy.get(`${formWidgetsPage.switchWidget} label`) + .first() + .click(); + // Check if isDirty is set to true + cy.get(".t--widget-textwidget").should("contain", "true"); + // Change defaultSwitchState property + cy.openPropertyPane("switchwidget"); + cy.get(".t--property-control-defaultselected label") + .last() + .click(); + // Check if isDirty is reset to false + cy.get(".t--widget-textwidget").should("contain", "false"); + }); }); afterEach(() => { // put your clean up code if any diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts index ba4168911c..703373a7c4 100644 --- a/app/client/src/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts @@ -311,6 +311,7 @@ export const entityDefinitions: Record = { "!url": "https://docs.appsmith.com/widget-reference/form", isVisible: isVisible, data: generateTypeDef(widget.data), + hasChanges: "bool", }), FORM_BUTTON_WIDGET: { "!doc": diff --git a/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx b/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx index bd62f65107..8b5c50ef57 100644 --- a/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx +++ b/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx @@ -17,6 +17,7 @@ export interface AudioRecorderWidgetProps extends WidgetProps { onRecordingStart?: string; onRecordingComplete?: string; blobURL?: string; + isDirty: boolean; } class AudioRecorderWidget extends BaseWidget< @@ -117,6 +118,7 @@ class AudioRecorderWidget extends BaseWidget< blobURL: undefined, dataURL: undefined, rawBinary: undefined, + isDirty: false, }; } @@ -125,6 +127,10 @@ class AudioRecorderWidget extends BaseWidget< } handleRecordingStart = () => { + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + if (this.props.blobURL) { URL.revokeObjectURL(this.props.blobURL); } diff --git a/app/client/src/widgets/CameraWidget/component/index.tsx b/app/client/src/widgets/CameraWidget/component/index.tsx index e42e762aae..1a7d862b08 100644 --- a/app/client/src/widgets/CameraWidget/component/index.tsx +++ b/app/client/src/widgets/CameraWidget/component/index.tsx @@ -906,7 +906,9 @@ function CameraComponent(props: CameraComponentProps) { }, [isAudioMuted, isVideoMuted]); useEffect(() => { - setIsReadyPlayerTimer(false); + // Clean up + resetMedia(); + if (mode === CameraModeTypes.CAMERA) { setMediaCaptureStatus(MediaCaptureStatusTypes.IMAGE_DEFAULT); return; diff --git a/app/client/src/widgets/CameraWidget/widget/index.tsx b/app/client/src/widgets/CameraWidget/widget/index.tsx index cb87cba644..6fd50aed62 100644 --- a/app/client/src/widgets/CameraWidget/widget/index.tsx +++ b/app/client/src/widgets/CameraWidget/widget/index.tsx @@ -160,10 +160,10 @@ class CameraWidget extends BaseWidget { imageDataURL: undefined, imageRawBinary: undefined, mediaCaptureStatus: MediaCaptureStatusTypes.IMAGE_DEFAULT, - timer: undefined, videoBlobURL: undefined, videoDataURL: undefined, videoRawBinary: undefined, + isDirty: false, }; } @@ -215,6 +215,11 @@ class CameraWidget extends BaseWidget { this.props.updateWidgetMetaProperty("imageRawBinary", undefined); return; } + // Set isDirty to true when an image is caputured + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + const base64Data = image.split(",")[1]; const imageBlob = base64ToBlob(base64Data, "image/webp"); const blobURL = URL.createObjectURL(imageBlob); @@ -251,6 +256,10 @@ class CameraWidget extends BaseWidget { }; handleRecordingStart = () => { + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + if (this.props.onRecordingStart) { super.executeAction({ triggerPropertyName: "onRecordingStart", @@ -319,6 +328,7 @@ export interface CameraWidgetProps extends WidgetProps { onRecordingStop?: string; onVideoSave?: string; videoBlobURL?: string; + isDirty: boolean; } export default CameraWidget; diff --git a/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx b/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx index 71897d22c3..5af25bb95d 100644 --- a/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { compact } from "lodash"; +import { compact, xor } from "lodash"; import { ValidationResponse, @@ -267,6 +267,7 @@ class CheckboxGroupWidget extends BaseWidget< static getMetaPropertiesMap(): Record { return { selectedValues: undefined, + isDirty: false, }; } @@ -308,6 +309,14 @@ class CheckboxGroupWidget extends BaseWidget< }, }); } + // Reset isDirty to false whenever defaultSelectedValues changes + if ( + xor(this.props.defaultSelectedValues, prevProps.defaultSelectedValues) + .length > 0 && + this.props.isDirty + ) { + this.props.updateWidgetMetaProperty("isDirty", false); + } } getPageView() { @@ -346,6 +355,11 @@ class CheckboxGroupWidget extends BaseWidget< ); } + // Update isDirty to true whenever value changes + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty("selectedValues", selectedValues, { triggerPropertyName: "onSelectionChange", dynamicString: this.props.onSelectionChange, @@ -370,6 +384,10 @@ class CheckboxGroupWidget extends BaseWidget< break; } + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty("selectedValues", selectedValues, { triggerPropertyName: "onSelectionChange", dynamicString: this.props.onSelectionChange, diff --git a/app/client/src/widgets/CheckboxWidget/widget/index.tsx b/app/client/src/widgets/CheckboxWidget/widget/index.tsx index de7210b360..cd5d6b2676 100644 --- a/app/client/src/widgets/CheckboxWidget/widget/index.tsx +++ b/app/client/src/widgets/CheckboxWidget/widget/index.tsx @@ -127,9 +127,19 @@ class CheckboxWidget extends BaseWidget { static getMetaPropertiesMap(): Record { return { isChecked: undefined, + isDirty: false, }; } + componentDidUpdate(prevProps: CheckboxWidgetProps) { + if ( + this.props.defaultCheckedState !== prevProps.defaultCheckedState && + this.props.isDirty + ) { + this.props.updateWidgetMetaProperty("isDirty", false); + } + } + getPageView() { return ( { } onCheckChange = (isChecked: boolean) => { + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty("isChecked", isChecked, { triggerPropertyName: "onCheckChange", dynamicString: this.props.onCheckChange, diff --git a/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx b/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx index 3ab7898d64..02447b52df 100644 --- a/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx +++ b/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx @@ -204,6 +204,13 @@ class CurrencyInputWidget extends BaseInputWidget< ) { this.formatText(); } + // If defaultText property has changed, reset isDirty to false + if ( + this.props.defaultText !== prevProps.defaultText && + this.props.isDirty + ) { + this.props.updateWidgetMetaProperty("isDirty", false); + } } formatText() { @@ -303,6 +310,10 @@ class CurrencyInputWidget extends BaseInputWidget< this.props.decimals, String(value), ); + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty("text", String(formattedValue), { triggerPropertyName: "onTextChanged", dynamicString: this.props.onTextChanged, diff --git a/app/client/src/widgets/DatePickerWidget2/widget/index.tsx b/app/client/src/widgets/DatePickerWidget2/widget/index.tsx index bc9f990d49..81e2c78f89 100644 --- a/app/client/src/widgets/DatePickerWidget2/widget/index.tsx +++ b/app/client/src/widgets/DatePickerWidget2/widget/index.tsx @@ -230,9 +230,19 @@ class DatePickerWidget extends BaseWidget { static getMetaPropertiesMap(): Record { return { value: undefined, + isDirty: false, }; } + componentDidUpdate(prevProps: DatePickerWidget2Props): void { + if ( + this.props.defaultDate !== prevProps.defaultDate && + this.props.isDirty + ) { + this.props.updateWidgetMetaProperty("isDirty", false); + } + } + getPageView() { return ( { } onDateSelected = (selectedDate: string) => { + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty("value", selectedDate, { triggerPropertyName: "onDateSelected", dynamicString: this.props.onDateSelected, diff --git a/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx b/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx index 58670e262b..bda82711d4 100644 --- a/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx @@ -223,6 +223,7 @@ class FilePickerWidget extends BaseWidget< return { selectedFiles: [], uploadedFileData: {}, + isDirty: false, }; } @@ -383,6 +384,10 @@ class FilePickerWidget extends BaseWidget< }); Promise.all(fileReaderPromises).then((files) => { + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty( "selectedFiles", dslFiles.concat(files), diff --git a/app/client/src/widgets/FormWidget/widget/index.tsx b/app/client/src/widgets/FormWidget/widget/index.tsx index 996fa71c73..9f8c508f84 100644 --- a/app/client/src/widgets/FormWidget/widget/index.tsx +++ b/app/client/src/widgets/FormWidget/widget/index.tsx @@ -27,11 +27,45 @@ class FormWidget extends ContainerWidget { componentDidMount() { super.componentDidMount(); this.updateFormData(); + + // Check if the form is dirty + const hasChanges = this.checkFormValueChanges( + get(this.props, "children[0]"), + ); + + if (hasChanges !== this.props.hasChanges) { + this.props.updateWidgetMetaProperty("hasChanges", hasChanges); + } } componentDidUpdate(prevProps: ContainerWidgetProps) { super.componentDidUpdate(prevProps); this.updateFormData(); + // Check if the form is dirty + const hasChanges = this.checkFormValueChanges( + get(this.props, "children[0]"), + ); + + if (hasChanges !== this.props.hasChanges) { + this.props.updateWidgetMetaProperty("hasChanges", hasChanges); + } + } + + checkFormValueChanges( + containerWidget: ContainerWidgetProps, + ): boolean { + const childWidgets = containerWidget.children || []; + + const hasChanges = childWidgets.some((child) => child.isDirty); + if (!hasChanges) { + return childWidgets.some( + (child) => + child.children && + this.checkFormValueChanges(get(child, "children[0]")), + ); + } + + return hasChanges; } updateFormData() { @@ -75,6 +109,7 @@ class FormWidget extends ContainerWidget { export interface FormWidgetProps extends ContainerComponentProps { name: string; data: Record; + hasChanges: boolean; } export default FormWidget; diff --git a/app/client/src/widgets/InputWidgetV2/widget/index.tsx b/app/client/src/widgets/InputWidgetV2/widget/index.tsx index 15b5a0cc1d..8f4ebe1bdd 100644 --- a/app/client/src/widgets/InputWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/InputWidgetV2/widget/index.tsx @@ -379,6 +379,13 @@ class InputWidget extends BaseInputWidget { getParsedText(this.props.inputText, this.props.inputType), ); } + // If defaultText property has changed, reset isDirty to false + if ( + this.props.defaultText !== prevProps.defaultText && + this.props.isDirty + ) { + this.props.updateWidgetMetaProperty("isDirty", false); + } }; onValueChange = (value: string) => { diff --git a/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx index c060d23438..ef9a3f5691 100644 --- a/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx @@ -2,7 +2,7 @@ import React, { ReactNode } from "react"; import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget"; import { TextSize, WidgetType } from "constants/WidgetConstants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import { isArray, findIndex } from "lodash"; +import { isArray, findIndex, xor } from "lodash"; import { ValidationResponse, ValidationTypes, @@ -354,8 +354,20 @@ class MultiSelectTreeWidget extends BaseWidget< return { selectedOptionValueArr: undefined, selectedLabel: [], + isDirty: false, }; } + + componentDidUpdate(prevProps: MultiSelectTreeWidgetProps): void { + if ( + xor(this.props.defaultOptionValue, prevProps.defaultOptionValue).length > + 0 && + this.props.isDirty + ) { + this.props.updateWidgetMetaProperty("isDirty", false); + } + } + getPageView() { const options = isArray(this.props.options) && @@ -407,6 +419,9 @@ class MultiSelectTreeWidget extends BaseWidget< } onOptionChange = (value?: DefaultValueType, labelList?: ReactNode[]) => { + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } this.props.updateWidgetMetaProperty("selectedOptionValueArr", value); this.props.updateWidgetMetaProperty("selectedLabel", labelList, { triggerPropertyName: "onOptionChange", @@ -472,7 +487,7 @@ export interface MultiSelectTreeWidgetProps extends WidgetProps { labelTextColor?: string; labelTextSize?: TextSize; labelStyle?: string; - isDirty?: boolean; + isDirty: boolean; } export default MultiSelectTreeWidget; diff --git a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx index cb05cce6c7..4c8625d24e 100644 --- a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx @@ -2,7 +2,15 @@ import React from "react"; import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import { isArray, isString, isNumber, LoDashStatic } from "lodash"; +import { + isArray, + isEqual, + isFinite, + isString, + isNumber, + LoDashStatic, + xorWith, +} from "lodash"; import { ValidationResponse, ValidationTypes, @@ -208,7 +216,7 @@ class MultiSelectWidget extends BaseWidget< fn: defaultOptionValueValidation, expected: { type: "Array of values", - example: `['option1', 'option2'] | [{ "label": "label1", "value": "value1" }]`, + example: ` "option1, option2" | ['option1', 'option2'] | [{ "label": "label1", "value": "value1" }]`, autocompleteDataType: AutocompleteDataType.ARRAY, }, }, @@ -431,9 +439,38 @@ class MultiSelectWidget extends BaseWidget< return { selectedOptions: undefined, filterText: "", + isDirty: false, }; } + componentDidUpdate(prevProps: MultiSelectWidgetProps): void { + // Check if defaultOptionValue is string + let isStringArray = false; + if ( + this.props.defaultOptionValue.some( + (value: any) => isString(value) || isFinite(value), + ) + ) { + isStringArray = true; + } + + const hasChanges = isStringArray + ? xorWith( + this.props.defaultOptionValue as string[], + prevProps.defaultOptionValue as string[], + isEqual, + ).length > 0 + : xorWith( + this.props.defaultOptionValue as OptionValue[], + prevProps.defaultOptionValue as OptionValue[], + isEqual, + ).length > 0; + + if (hasChanges && this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", false); + } + } + getPageView() { const options = isArray(this.props.options) ? this.props.options : []; const dropDownWidth = MinimumPopupRows * this.props.parentColumnSpace; @@ -527,7 +564,7 @@ export interface MultiSelectWidgetProps extends WidgetProps { options?: DropdownOption[]; onOptionChange: string; onFilterChange: string; - defaultOptionValue: string | string[] | OptionValue[]; + defaultOptionValue: string[] | OptionValue[]; isRequired: boolean; isLoading: boolean; selectedOptions: LabelValueType[]; diff --git a/app/client/src/widgets/PhoneInputWidget/widget/index.tsx b/app/client/src/widgets/PhoneInputWidget/widget/index.tsx index 2365f26c43..a73eaa565a 100644 --- a/app/client/src/widgets/PhoneInputWidget/widget/index.tsx +++ b/app/client/src/widgets/PhoneInputWidget/widget/index.tsx @@ -206,6 +206,13 @@ class PhoneInputWidget extends BaseInputWidget< ); this.props.updateWidgetMetaProperty("text", formattedValue); } + + // If defaultText property has changed, reset isDirty to false + if (this.props.defaultText !== prevProps.defaultText) { + if (this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", false); + } + } } onISDCodeChange = (dialCode?: string) => { diff --git a/app/client/src/widgets/RadioGroupWidget/widget/index.tsx b/app/client/src/widgets/RadioGroupWidget/widget/index.tsx index d3bac8d5b0..033f7f0b52 100644 --- a/app/client/src/widgets/RadioGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/RadioGroupWidget/widget/index.tsx @@ -272,9 +272,19 @@ class RadioGroupWidget extends BaseWidget { static getMetaPropertiesMap(): Record { return { selectedOptionValue: undefined, + isDirty: false, }; } + componentDidUpdate(prevProps: RadioGroupWidgetProps): void { + if ( + this.props.defaultOptionValue !== prevProps.defaultOptionValue && + this.props.isDirty + ) { + this.props.updateWidgetMetaProperty("isDirty", false); + } + } + getPageView() { return ( { } else { newVal = updatedValue; } + // Set isDirty to true when the selection changes + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty("selectedOptionValue", newVal, { triggerPropertyName: "onSelectionChange", dynamicString: this.props.onSelectionChange, @@ -318,6 +333,7 @@ export interface RadioGroupWidgetProps extends WidgetProps { onSelectionChange: string; defaultOptionValue: string; isRequired?: boolean; + isDirty: boolean; } export default RadioGroupWidget; diff --git a/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx b/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx index fc08263eed..57cfc7e5eb 100644 --- a/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx +++ b/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx @@ -130,6 +130,8 @@ class RichTextEditorWidget extends BaseWidget< static getMetaPropertiesMap(): Record { return { text: undefined, + shouldReset: false, + isDirty: false, }; } @@ -146,7 +148,30 @@ class RichTextEditorWidget extends BaseWidget< }; } + componentDidMount(): void { + if (this.props.defaultText) { + this.props.updateWidgetMetaProperty("shouldReset", true); + } + } + + componentDidUpdate(prevProps: RichTextEditorWidgetProps): void { + if (this.props.defaultText !== prevProps.defaultText) { + if (this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", false); + } + if (this.props.defaultText) { + this.props.updateWidgetMetaProperty("shouldReset", true); + } + } + } + onValueChange = (text: string) => { + if (this.props.shouldReset) { + this.props.updateWidgetMetaProperty("shouldReset", false); + } else if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty("text", text, { triggerPropertyName: "onTextChange", dynamicString: this.props.onTextChange, @@ -196,6 +221,7 @@ export interface RichTextEditorWidgetProps extends WidgetProps { isVisible?: boolean; isRequired?: boolean; isToolbarHidden?: boolean; + isDirty: boolean; } export default RichTextEditorWidget; diff --git a/app/client/src/widgets/SelectWidget/widget/index.tsx b/app/client/src/widgets/SelectWidget/widget/index.tsx index a06e690aad..c0cef0d98e 100644 --- a/app/client/src/widgets/SelectWidget/widget/index.tsx +++ b/app/client/src/widgets/SelectWidget/widget/index.tsx @@ -11,7 +11,14 @@ import { import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { MinimumPopupRows, GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; import { AutocompleteDataType } from "utils/autocomplete/TernServer"; -import { findIndex, isArray, isNumber, isString, LoDashStatic } from "lodash"; +import { + findIndex, + isArray, + isEqual, + isNumber, + isString, + LoDashStatic, +} from "lodash"; export function defaultOptionValueValidation( value: unknown, @@ -335,6 +342,7 @@ class SelectWidget extends BaseWidget { value: undefined, label: undefined, filterText: "", + isDirty: false, }; } @@ -351,6 +359,16 @@ class SelectWidget extends BaseWidget { this.changeSelectedOption(); } + componentDidUpdate(prevProps: SelectWidgetProps): void { + // Reset isDirty to false if defaultOptionValue changes + if ( + !isEqual(this.props.defaultOptionValue, prevProps.defaultOptionValue) && + this.props.isDirty + ) { + this.props.updateWidgetMetaProperty("isDirty", false); + } + } + isStringOrNumber = (value: any): value is string | number => isString(value) || isNumber(value); @@ -408,6 +426,10 @@ class SelectWidget extends BaseWidget { isChanged = !(this.props.selectedOptionValue === selectedOption.value); } if (isChanged) { + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty("label", selectedOption.label ?? ""); this.props.updateWidgetMetaProperty("value", selectedOption.value ?? "", { diff --git a/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx index f5615c5954..04aa9f0e91 100644 --- a/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx +++ b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx @@ -319,9 +319,19 @@ class SingleSelectTreeWidget extends BaseWidget< selectedOption: undefined, selectedOptionValueArr: undefined, selectedLabel: [], + isDirty: false, }; } + componentDidUpdate(prevProps: SingleSelectTreeWidgetProps): void { + if ( + this.props.defaultOptionValue !== prevProps.defaultOptionValue && + this.props.isDirty + ) { + this.props.updateWidgetMetaProperty("isDirty", false); + } + } + getPageView() { const options = isArray(this.props.options) && @@ -372,6 +382,10 @@ class SingleSelectTreeWidget extends BaseWidget< } onOptionChange = (value?: DefaultValueType, labelList?: ReactNode[]) => { + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty("selectedOption", value); this.props.updateWidgetMetaProperty("selectedLabel", labelList, { triggerPropertyName: "onOptionChange", diff --git a/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx index a4500b6df8..ecd472d890 100644 --- a/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx @@ -1,5 +1,6 @@ import React from "react"; import { Alignment } from "@blueprintjs/core"; +import { xor } from "lodash"; import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; @@ -178,6 +179,7 @@ class SwitchGroupWidget extends BaseWidget< static getMetaPropertiesMap(): Record { return { selectedValuesArray: undefined, + isDirty: false, }; } @@ -196,6 +198,22 @@ class SwitchGroupWidget extends BaseWidget< return "SWITCH_GROUP_WIDGET"; } + componentDidUpdate(prevProps: SwitchGroupWidgetProps): void { + if ( + xor(this.props.defaultSelectedValues, prevProps.defaultSelectedValues) + .length > 0 + ) { + if (this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", false); + } + + this.props.updateWidgetMetaProperty( + "selectedValuesArray", + this.props.defaultSelectedValues, + ); + } + } + getPageView() { const { alignment, @@ -205,7 +223,7 @@ class SwitchGroupWidget extends BaseWidget< isValid, options, parentRowSpace, - selectedValues, + selectedValuesArray, } = this.props; return ( @@ -217,7 +235,7 @@ class SwitchGroupWidget extends BaseWidget< options={options} required={isRequired} rowSpace={parentRowSpace} - selected={selectedValues} + selected={selectedValuesArray} valid={isValid} /> ); @@ -235,6 +253,10 @@ class SwitchGroupWidget extends BaseWidget< ); } + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty( "selectedValuesArray", selectedValuesArray, @@ -253,6 +275,7 @@ class SwitchGroupWidget extends BaseWidget< export interface SwitchGroupWidgetProps extends WidgetProps { options: OptionProps[]; defaultSelectedValues: string[]; + selectedValuesArray: string[]; isInline: boolean; isRequired?: boolean; isValid?: boolean; diff --git a/app/client/src/widgets/SwitchWidget/widget/index.tsx b/app/client/src/widgets/SwitchWidget/widget/index.tsx index 7a080e2c14..2d883e59c8 100644 --- a/app/client/src/widgets/SwitchWidget/widget/index.tsx +++ b/app/client/src/widgets/SwitchWidget/widget/index.tsx @@ -131,6 +131,7 @@ class SwitchWidget extends BaseWidget { static getMetaPropertiesMap(): Record { return { isSwitchedOn: undefined, + isDirty: false, }; } @@ -140,7 +141,20 @@ class SwitchWidget extends BaseWidget { }; } + componentDidUpdate(prevProps: SwitchWidgetProps): void { + if ( + this.props.defaultSwitchState !== prevProps.defaultSwitchState && + this.props.isDirty + ) { + this.props.updateWidgetMetaProperty("isDirty", false); + } + } + onChange = (isSwitchedOn: boolean) => { + if (!this.props.isDirty) { + this.props.updateWidgetMetaProperty("isDirty", true); + } + this.props.updateWidgetMetaProperty("isSwitchedOn", isSwitchedOn, { triggerPropertyName: "onChange", dynamicString: this.props.onChange, @@ -156,6 +170,7 @@ export interface SwitchWidgetProps extends WidgetProps { defaultSwitchState: boolean; alignWidget: AlignWidget; label: string; + isDirty: boolean; } export default SwitchWidget;