diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Custom/CustomWidget_PropertyPane_Validation.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Custom/CustomWidget_PropertyPane_Validation.ts new file mode 100644 index 000000000000..060edac878e4 --- /dev/null +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Custom/CustomWidget_PropertyPane_Validation.ts @@ -0,0 +1,112 @@ +import { + agHelper, + assertHelper, + locators, + propPane, +} from "../../../../../support/Objects/ObjectsCore"; + +import EditorNavigation, { + EntityType, +} from "../../../../../support/Pages/EditorNavigation"; + +describe("Custom widget Tests", {}, function () { + before(() => { + agHelper.AddDsl("customWidgetWithSrc"); + }); + + const getIframeBody = () => { + // get the iframe > document > body + // and retry until the body element is not empty + return cy + .get(".t--widget-customwidget iframe") + .last() + .its("0.contentDocument.body") + .should("not.be.empty") + .then(cy.wrap); + }; + + it("should check that custom widget property pane details are coming up properly", () => { + const defaultModel = `{ + "tips": [ + "Pass data to this widget in the default model field", + "data in the javascript file using the appsmith.model variable", + "Create events in the widget and trigger them in the javascript file using appsmith.triggerEvent('eventName')", + "Access data in CSS as var(--appsmith-model-{property-name})" + ] + }`; + const custom2WidgetBoundary = + "//div[@data-widgetname-cy='Custom2']//div[contains(@class,'widget-component-boundary')]//div//div"; + + EditorNavigation.SelectEntityByName("Custom2", EntityType.Widget); + getIframeBody().find(".tip-container").should("exist"); + getIframeBody() + .find(".tip-container p") + .should( + "have.text", + "Pass data to this widget in the default model field", + ); + + agHelper.UpdateCodeInput(propPane._propDefaultModel, defaultModel); + + getIframeBody().find("button.primary").trigger("click"); + + getIframeBody() + .find(".tip-container p") + .should( + "have.text", + "data in the javascript file using the appsmith.model variable", + ); + + EditorNavigation.SelectEntityByName("Custom1", EntityType.Widget); + propPane.TogglePropertyState("Visible", "Off"); + agHelper.GetNClick(locators._previewModeToggle("edit")); + agHelper.AssertElementAbsence(locators._widgetByName("Custom1")); + agHelper.GetNClick(locators._previewModeToggle("preview")); + propPane.TogglePropertyState("Visible", "On"); + + EditorNavigation.SelectEntityByName("Custom2", EntityType.Widget); + + propPane.ToggleJSMode("onResetClick"); + propPane.EnterJSContext( + "onResetClick", + `{{showAlert('Reset Clicked', 'error');}}`, + ); + getIframeBody().find("button.reset").trigger("click"); + agHelper.ValidateToastMessage("Reset Clicked"); + + propPane.MoveToTab("Style"); + // Background color + agHelper.GetNClick(propPane._propertyControlColorPicker("backgroundcolor")); + agHelper.AssertElementVisibility(propPane._colorPickerV2Color); + propPane.ToggleJSMode("backgroundcolor", true); + propPane.UpdatePropertyFieldValue("Background color", "#eab308"); + agHelper.AssertCSS( + `(${locators._draggableCustomWidget}//div)[10]`, + "background-color", + "rgb(239, 117, 65)", + ); + propPane.ToggleJSMode("backgroundcolor", false); + + // Border Color + propPane.SelectColorFromColorPicker("bordercolor", 13); + assertHelper.AssertNetworkStatus("@updateLayout"); + + agHelper + .GetWidgetCSSFrAttribute(propPane._borderColorCursor, "background-color") + .then((color) => { + agHelper + .GetWidgetCSSFrAttribute(custom2WidgetBoundary, "border-color") + .then((bgcolor) => { + expect(color).to.eq(bgcolor); + }); + }); + + // Verify border width + propPane.UpdatePropertyFieldValue("Border width", "7"); + agHelper + .GetWidgetCSSFrAttribute(custom2WidgetBoundary, "border-width") + .then((width) => { + expect(width).to.eq("7px"); + }); + }); +}); diff --git a/app/client/cypress/fixtures/customWidgetWithSrc.json b/app/client/cypress/fixtures/customWidgetWithSrc.json new file mode 100644 index 000000000000..6e503437e5fa --- /dev/null +++ b/app/client/cypress/fixtures/customWidgetWithSrc.json @@ -0,0 +1,149 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 730, + "containerStyle": "none", + "snapRows": 124, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 91, + "minHeight": 1292, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "needsErrorInfo": false, + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "mobileBottomRow": 42, + "widgetName": "Custom1", + "borderColor": "#E0DEDE", + "srcDoc": { + "html": " \n
", + "css": ".table-container {\n padding: 20px;\n background-color: #f9f9f9;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.table {\n width: 100%;\n border-collapse: collapse;\n}\n\n.table th, .table td {\n padding: 12px;\n border-bottom: 1px solid #ddd;\n text-align: left;\n}\n\n.table th {\n background-color: #f1f1f1;\n font-weight: bold;\n}\n\n.table tr:hover {\n background-color: #f5f5f5;\n}", + "js": "import React from 'https://cdn.jsdelivr.net/npm/react@18.2.0/+esm';\nimport ReactDOM from 'https://cdn.jsdelivr.net/npm/react-dom@18.2.0/+esm';\nfunction TableWidget() {\n // Mock data for the table\n const [data, setData] = React.useState(appsmith.model.data || [{\n name: 'John Doe',\n age: 28,\n occupation: 'Engineer'\n }, {\n name: 'Jane Smith',\n age: 34,\n occupation: 'Designer'\n }, {\n name: 'Sam Green',\n age: 45,\n occupation: 'Manager'\n }]);\n React.useEffect(() => {\n appsmith.onModelChange((model, prevModel) => {\n if (JSON.stringify(model.data) !== JSON.stringify(prevModel?.data)) {\n setData(model.data);\n }\n });\n }, []);\n return /*#__PURE__*/React.createElement(\"div\", {\n className: \"table-container\"\n }, /*#__PURE__*/React.createElement(\"table\", {\n className: \"table\"\n }, /*#__PURE__*/React.createElement(\"thead\", null, /*#__PURE__*/React.createElement(\"tr\", null, /*#__PURE__*/React.createElement(\"th\", null, \"Name\"), /*#__PURE__*/React.createElement(\"th\", null, \"Age\"), /*#__PURE__*/React.createElement(\"th\", null, \"Occupation\"))), /*#__PURE__*/React.createElement(\"tbody\", null, data.map((row, index) => /*#__PURE__*/React.createElement(\"tr\", {\n key: index\n }, /*#__PURE__*/React.createElement(\"td\", null, row.name), /*#__PURE__*/React.createElement(\"td\", null, row.age), /*#__PURE__*/React.createElement(\"td\", null, row.occupation))))));\n}\nappsmith.onReady(() => {\n ReactDOM.render( /*#__PURE__*/React.createElement(TableWidget, null), document.getElementById(\"root\"));\n});" + }, + "isCanvas": false, + "topRow": 10, + "bottomRow": 40, + "parentRowSpace": 10, + "type": "CUSTOM_WIDGET", + "mobileRightColumn": 31, + "dynamicTriggerPathList": [ + { + "key": "onResetClick" + } + ], + "parentColumnSpace": 7.890625, + "dynamicBindingPathList": [ + { + "key": "theme" + }, + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "leftColumn": 8, + "defaultModel": "{\n \"tips\": [\n \"Pass data to this widget in the default model field\",\n \"Access data in the javascript file using the appsmith.model variable\",\n \"Create events in the widget and trigger them in the javascript file using appsmith.triggerEvent('eventName')\",\n \"Access data in CSS as var(--appsmith-model-{property-name})\"\n ]\n}", + "borderWidth": "1", + "theme": "{{appsmith.theme}}", + "onResetClick": "{{showAlert('Successfully reset!!', '');}}", + "events": ["onResetClick"], + "key": "podxgd85w9", + "backgroundColor": "#FFFFFF", + "rightColumn": 56, + "dynamicHeight": "FIXED", + "isSearchWildcard": true, + "widgetId": "zkhwsvct6f", + "isVisible": true, + "version": 1, + "uncompiledSrcDoc": { + "html": " \n
", + "css": ".table-container {\n padding: 20px;\n background-color: #f9f9f9;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n }\n .table {\n width: 100%;\n border-collapse: collapse;\n }\n .table th, .table td {\n padding: 12px;\n border-bottom: 1px solid #ddd;\n text-align: left;\n }\n .table th {\n background-color: #f1f1f1;\n font-weight: bold;\n }\n .table tr:hover {\n background-color: #f5f5f5;\n }", + "js": " import React from 'https://cdn.jsdelivr.net/npm/react@18.2.0/+esm';\n import ReactDOM from 'https://cdn.jsdelivr.net/npm/react-dom@18.2.0/+esm';\n function TableWidget() {\n // Mock data for the table\n const [data, setData] = React.useState(appsmith.model.data || [\n { name: 'John Doe', age: 28, occupation: 'Engineer' },\n { name: 'Jane Smith', age: 34, occupation: 'Designer' },\n { name: 'Sam Green', age: 45, occupation: 'Manager' }\n ]);\n React.useEffect(() => {\n appsmith.onModelChange((model, prevModel) => {\n if (JSON.stringify(model.data) !== JSON.stringify(prevModel?.data)) {\n setData(model.data);\n }\n });\n }, []);\n return (\n
\n \n \n \n \n \n \n \n \n \n {data.map((row, index) => (\n \n \n \n \n \n ))}\n \n
NameAgeOccupation
{row.name}{row.age}{row.occupation}
\n
\n );\n }\n appsmith.onReady(() => {\n ReactDOM.render(, document.getElementById(\"root\"));\n });" + }, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "mobileTopRow": 12, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "mobileLeftColumn": 8, + "maxDynamicHeight": 9000, + "minDynamicHeight": 4 + }, + { + "needsErrorInfo": false, + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "mobileBottomRow": 75, + "widgetName": "Custom2", + "borderColor": "#E0DEDE", + "srcDoc": { + "html": "\n
\n", + "css": ".app {\n\twidth: calc(var(--appsmith-ui-width) * 1px);\n\tjustify-content: center;\n\tborder-radius: 0px;\n\tborder: none;\n}\n\n.tip-container {\n margin-bottom: 20px;\n}\n\n.tip-container h2 {\n margin-bottom: 20px;\n\tfont-size: 16px;\n\tfont-weight: 700;\n}\n\n.tip-header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: baseline;\n}\n\n.tip-header div {\n\tcolor: #999;\n}\n\n.button-container {\n\ttext-align: right;\t\n}\n\n.button-container button {\n margin: 0 10px;\n\tborder-radius: var(--appsmith-theme-borderRadius) !important;\n}\n\n.button-container button.primary {\n\tbackground: var(--appsmith-theme-primaryColor) !important;\n}\n\n.button-container button.reset:not([disabled]) {\n\tcolor: var(--appsmith-theme-primaryColor) !important;\n\tborder-color: var(--appsmith-theme-primaryColor) !important;\n}", + "js": "import React from 'https://cdn.jsdelivr.net/npm/react@18.2.0/+esm';\nimport reactDom from 'https://cdn.jsdelivr.net/npm/react-dom@18.2.0/+esm';\nimport { Button, Card } from 'https://cdn.jsdelivr.net/npm/antd@5.11.1/+esm';\nimport Markdown from 'https://cdn.jsdelivr.net/npm/react-markdown@9.0.1/+esm';\n\nfunction App() {\n const [currentIndex, setCurrentIndex] = React.useState(0);\n const handleNext = () => {\n setCurrentIndex(prevIndex => (prevIndex + 1) % appsmith.model.tips.length);\n };\n const handleReset = () => {\n setCurrentIndex(0);\n appsmith.triggerEvent(\"onResetClick\");\n };\n return /*#__PURE__*/React.createElement(Card, {\n className: \"app\",\n }, /*#__PURE__*/React.createElement(\"div\", {\n className: \"tip-container\"\n }, /*#__PURE__*/React.createElement(\"div\", {\n className: \"tip-header\"\n }, /*#__PURE__*/React.createElement(\"h2\", null, \"Custom Widget\"), /*#__PURE__*/React.createElement(\"div\", null, currentIndex + 1, \" / \", appsmith.model.tips.length, \" \")), /*#__PURE__*/React.createElement(Markdown, null, appsmith.model.tips[currentIndex])), /*#__PURE__*/React.createElement(\"div\", {\n className: \"button-container\"\n }, /*#__PURE__*/React.createElement(Button, {\n className: \"primary\",\n onClick: handleNext,\n type: \"primary\"\n }, \"Next Tip\"), /*#__PURE__*/React.createElement(Button, {\n\tclassName: \"reset\",\n\tdisabled: currentIndex === 0,\n onClick: handleReset\n }, \"Reset\")));\n}\nappsmith.onReady(() => {\n reactDom.render( /*#__PURE__*/React.createElement(App, null), document.getElementById(\"root\"));\n});" + }, + "isCanvas": false, + "topRow": 43, + "bottomRow": 73, + "parentRowSpace": 10, + "type": "CUSTOM_WIDGET", + "mobileRightColumn": 41, + "dynamicTriggerPathList": [ + { + "key": "onResetClick" + } + ], + "parentColumnSpace": 14.21875, + "dynamicBindingPathList": [ + { + "key": "theme" + }, + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "leftColumn": 19, + "defaultModel": "{\n \"tips\": [\n \"Pass data to this widget in the default model field\",\n \"Access data in the javascript file using the appsmith.model variable\",\n \"Create events in the widget and trigger them in the javascript file using appsmith.triggerEvent('eventName')\",\n \"Access data in CSS as var(--appsmith-model-{property-name})\"\n ]\n}", + "borderWidth": "1", + "theme": "{{appsmith.theme}}", + "onResetClick": "{{showAlert('Successfully reset!!', '');}}", + "events": ["onResetClick"], + "key": "podxgd85w9", + "backgroundColor": "#FFFFFF", + "rightColumn": 42, + "dynamicHeight": "FIXED", + "isSearchWildcard": true, + "widgetId": "9iep04z42x", + "isVisible": true, + "version": 1, + "uncompiledSrcDoc": { + "html": "\n
\n", + "css": ".app {\n width: calc(1px * var(--appsmith-ui-width));\n justify-content: center;\n border-radius: 0px;\n border: none;\n \n .tip-container {\n margin-bottom: 20px;\n\n h2 {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 700;\n }\n\n .tip-header {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n\n div {\n color: #999;\n }\n }\n }\n\t\n\t.button-container {\n text-align: right;\n\n button {\n margin: 0 10px;\n border-radius: var(--appsmith-theme-borderRadius) !important;\n\n &.primary {\n background: var(--appsmith-theme-primaryColor) !important;\n }\n\n &.reset:not([disabled]) {\n color: var(--appsmith-theme-primaryColor) !important;\n border-color: var(--appsmith-theme-primaryColor) !important;\n }\n }\n }\n}\n", + "js": "import React from 'https://cdn.jsdelivr.net/npm/react@18.2.0/+esm'\nimport reactDom from 'https://cdn.jsdelivr.net/npm/react-dom@18.2.0/+esm'\nimport { Button, Card } from 'https://cdn.jsdelivr.net/npm/antd@5.11.1/+esm'\nimport Markdown from 'https://cdn.jsdelivr.net/npm/react-markdown@9.0.1/+esm';\n\nfunction App() {\n\tconst [currentIndex, setCurrentIndex] = React.useState(0);\n\n\tconst handleNext = () => {\n\t\tsetCurrentIndex((prevIndex) => (prevIndex + 1) % appsmith.model.tips.length);\n\t};\n\n\tconst handleReset = () => {\n\t\tsetCurrentIndex(0);\n\t\tappsmith.triggerEvent(\"onResetClick\");\n\t};\n\n\treturn (\n\t\t\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t

Custom Widget

\n\t\t\t\t\t
{currentIndex + 1} / {appsmith.model.tips.length}\t\t
\n\t\t\t\t
\n\t\t\t\t{appsmith.model.tips[currentIndex]}\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t
\n);\n}\n\nappsmith.onReady(() => {\n\t/*\n\t * This handler function will get called when parent application is ready.\n\t * Initialize your component here\n\t * more info - https://docs.appsmith.com/reference/widgets/custom#onready\n\t */\n\treactDom.render(, document.getElementById(\"root\"));\n});" + }, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "mobileTopRow": 45, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "mobileLeftColumn": 18, + "maxDynamicHeight": 9000, + "minDynamicHeight": 4 + } + ] + } +} diff --git a/app/client/cypress/limited-tests.txt b/app/client/cypress/limited-tests.txt index 00e32a4629d0..6d8a667b9444 100644 --- a/app/client/cypress/limited-tests.txt +++ b/app/client/cypress/limited-tests.txt @@ -1,7 +1,7 @@ # To run only limited tests - give the spec names in below format: -#cypress/e2e/Regression/ClientSide/VisualTests/JSEditorIndent_spec.js +cypress/e2e/Regression/ClientSide/Widgets/Custom/CustomWidget_PropertyPane_Validation.ts # For running all specs - uncomment below: #cypress/e2e/**/**/* -cypress/e2e/Regression/ClientSide/Anvil/Widgets/* +#cypress/e2e/Regression/ClientSide/Anvil/Widgets/* #ci-test-limit uses this file to run minimum of specs. Do not run entire suite with this command. diff --git a/app/client/cypress/support/Objects/CommonLocators.ts b/app/client/cypress/support/Objects/CommonLocators.ts index f10c896d3b45..9fab1960db69 100644 --- a/app/client/cypress/support/Objects/CommonLocators.ts +++ b/app/client/cypress/support/Objects/CommonLocators.ts @@ -355,4 +355,6 @@ export class CommonLocators { _dropdownActiveOption = ".rc-select-dropdown .rc-select-item-option-active"; _homeIcon = "[data-testid='t--default-home-icon']"; _widget = (widgetName: string) => `.t--widget-${widgetName}`; + _draggableCustomWidget = + "//div[contains(@class,'t--draggable-customwidget')]"; } diff --git a/app/client/cypress/support/Pages/PropertyPane.ts b/app/client/cypress/support/Pages/PropertyPane.ts index 7b97287dfbe6..4a17189e4c27 100644 --- a/app/client/cypress/support/Pages/PropertyPane.ts +++ b/app/client/cypress/support/Pages/PropertyPane.ts @@ -181,6 +181,7 @@ export class PropertyPane { _buttonWidget = "[data-widgetname-cy='Button1']"; _getActionCardSelector = (type: string) => `[data-testid='action-card-Show ${type}']`; + _propDefaultModel = ".t--property-pane-section-defaultmodel"; public OpenJsonFormFieldSettings(fieldName: string) { this.agHelper.GetNClick(this._jsonFieldEdit(fieldName));