diff --git a/app/client/cypress/fixtures/tableWithTextWidgetDsl.json b/app/client/cypress/fixtures/tableWithTextWidgetDsl.json new file mode 100644 index 0000000000..b6a218dd32 --- /dev/null +++ b/app/client/cypress/fixtures/tableWithTextWidgetDsl.json @@ -0,0 +1,208 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 744, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1120, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 46, + "minHeight": 1100, + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "widgetName": "Text1", + "dynamicPropertyPathList": [], + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b52.svg", + "topRow": 23, + "bottomRow": 41, + "parentRowSpace": 10, + "type": "TEXT_WIDGET", + "hideCard": false, + "parentColumnSpace": 26.59375, + "dynamicTriggerPathList": [], + "leftColumn": 6, + "dynamicBindingPathList": [ + { + "key": "text" + } + ], + "text": "{{Table1.selectedRowIndices}}", + "key": "zzkmsgz6bl", + "rightColumn": 30, + "backgroundColor": "", + "textAlign": "LEFT", + "widgetId": "vmo0ewamhx", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "fontSize": "PARAGRAPH" + }, + { + "multiRowSelection": false, + "widgetName": "Table1", + "defaultPageSize": 0, + "columnOrder": [ + "step", + "task", + "status", + "action" + ], + "isVisibleDownload": true, + "dynamicPropertyPathList": [], + "displayName": "Table", + "iconSVG": "/static/media/icon.db8a9cbd.svg", + "topRow": 41, + "bottomRow": 69, + "isSortable": true, + "parentRowSpace": 10, + "type": "TABLE_WIDGET", + "defaultSelectedRow": "0", + "hideCard": false, + "parentColumnSpace": 25.6875, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [ + { + "key": "primaryColumns.step.computedValue" + }, + { + "key": "primaryColumns.task.computedValue" + }, + { + "key": "primaryColumns.status.computedValue" + }, + { + "key": "primaryColumns.action.computedValue" + } + ], + "leftColumn": 19, + "primaryColumns": { + "step": { + "index": 0, + "width": 150, + "id": "step", + "horizontalAlignment": "LEFT", + "verticalAlignment": "CENTER", + "columnType": "text", + "textSize": "PARAGRAPH", + "enableFilter": true, + "enableSort": true, + "isVisible": true, + "isCellVisible": true, + "isDerived": false, + "label": "step", + "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.step))}}" + }, + "task": { + "index": 1, + "width": 150, + "id": "task", + "horizontalAlignment": "LEFT", + "verticalAlignment": "CENTER", + "columnType": "text", + "textSize": "PARAGRAPH", + "enableFilter": true, + "enableSort": true, + "isVisible": true, + "isCellVisible": true, + "isDerived": false, + "label": "task", + "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.task))}}" + }, + "status": { + "index": 2, + "width": 150, + "id": "status", + "horizontalAlignment": "LEFT", + "verticalAlignment": "CENTER", + "columnType": "text", + "textSize": "PARAGRAPH", + "enableFilter": true, + "enableSort": true, + "isVisible": true, + "isCellVisible": true, + "isDerived": false, + "label": "status", + "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.status))}}" + }, + "action": { + "index": 3, + "width": 150, + "id": "action", + "horizontalAlignment": "LEFT", + "verticalAlignment": "CENTER", + "columnType": "button", + "textSize": "PARAGRAPH", + "enableFilter": true, + "enableSort": true, + "isVisible": true, + "isCellVisible": true, + "isDisabled": false, + "isDerived": false, + "label": "action", + "onClick": "{{currentRow.step === '#1' ? showAlert('Done', 'success') : currentRow.step === '#2' ? navigateTo('https://docs.appsmith.com/core-concepts/connecting-to-data-sources/querying-a-database',undefined,'NEW_WINDOW') : navigateTo('https://docs.appsmith.com/core-concepts/displaying-data-read/display-data-tables',undefined,'NEW_WINDOW')}}", + "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.action))}}" + } + }, + "delimiter": ",", + "key": "fzi9jh5j7j", + "derivedColumns": {}, + "rightColumn": 44, + "textSize": "PARAGRAPH", + "widgetId": "2tk8bgzwaz", + "isVisibleFilters": true, + "tableData": [ + { + "step": "#1", + "task": "Drop a table", + "status": "✅", + "action": "" + }, + { + "step": "#2", + "task": "Create a query fetch_users with the Mock DB", + "status": "--", + "action": "" + }, + { + "step": "#3", + "task": "Bind the query using => fetch_users.data", + "status": "--", + "action": "" + } + ], + "isVisible": true, + "label": "Data", + "searchKey": "", + "version": 3, + "totalRecordsCount": 0, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "horizontalAlignment": "LEFT", + "isVisibleSearch": true, + "isVisiblePagination": true, + "verticalAlignment": "CENTER", + "columnSizeMap": { + "task": 245, + "step": 62, + "status": 75 + } + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js index 3f72787fe5..d1f9992ee1 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js @@ -37,7 +37,7 @@ describe("Test Suite to validate copy/paste table Widget", function() { .last() .click(); cy.get(apiwidget.propertyList).then(function($lis) { - expect($lis).to.have.length(11); + expect($lis).to.have.length(12); expect($lis.eq(0)).to.contain("{{Table1Copy.selectedRow}}"); expect($lis.eq(1)).to.contain("{{Table1Copy.selectedRows}}"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/table_with_text_no_2dArray_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/table_with_text_no_2dArray_spec.js new file mode 100644 index 0000000000..3c83477302 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/table_with_text_no_2dArray_spec.js @@ -0,0 +1,52 @@ +const dsl = require("../../../../fixtures/tableWithTextWidgetDsl.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); + +describe("Table widget edge case scenario testing", function() { + before(() => { + cy.addDsl(dsl); + }); + + it("Check if the selectedRowIndices does not contain 2d array", function() { + cy.openPropertyPane("tablewidget"); + + //Enable Multi row select + cy.get(widgetsPage.toggleEnableMultirowselection) + .first() + .click({ force: true }); + + //Change the value of default selected row + cy.updateCodeInput(".t--property-control-defaultselectedrow", "1"); + + //Disable Multi row select + cy.get(widgetsPage.toggleEnableMultirowselection) + .first() + .click({ force: true }); + + cy.get(`${widgetsPage.textWidget} .bp3-ui-text`).should("have.text", "[]"); + + //Enable Multi row select + cy.get(widgetsPage.toggleEnableMultirowselection) + .first() + .click({ force: true }); + + cy.get(`${widgetsPage.textWidget} .bp3-ui-text`).should( + "have.text", + "[ 1]", + ); + + //Disable Multi row select + cy.get(widgetsPage.toggleEnableMultirowselection) + .first() + .click({ force: true }); + + //Enable Multi row select + cy.get(widgetsPage.toggleEnableMultirowselection) + .first() + .click({ force: true }); + + cy.get(`${widgetsPage.textWidget} .bp3-ui-text`).should( + "have.text", + "[ 1]", + ); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/table_with_text_selRowIndices_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/table_with_text_selRowIndices_spec.js new file mode 100644 index 0000000000..ed6a76695d --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/table_with_text_selRowIndices_spec.js @@ -0,0 +1,44 @@ +const dsl = require("../../../../fixtures/tableWithTextWidgetDsl.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +describe("Table widget edge case scenario testing", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Check if the selectedRowIndices does not contain -1", function() { + cy.openPropertyPane("tablewidget"); + + //Update the property default selected row to blank + cy.updateCodeInput(".t--property-control-defaultselectedrow", ""); + + //Check if the evaluated value is undefined + cy.get(commonlocators.evaluatedCurrentValue) + .first() + .should("be.visible") + .should("have.text", "undefined"); + + //Check the value present in the textfield which is selectedRowIndices is blank + cy.get(`${widgetsPage.textWidget} .bp3-ui-text`).should("have.text", ""); + + //Enable the "Enable Multi Row selection" + cy.get(widgetsPage.toggleEnableMultirowselection) + .first() + .click({ force: true }); + + //Check the value present in the textfield which is selectedRowIndices is [] + cy.get(`${widgetsPage.textWidget} .bp3-ui-text`).should("have.text", "[]"); + + //Select the 1st, 2nd and 3rd row + cy.isSelectRow("0"); + cy.isSelectRow("1"); + cy.isSelectRow("2"); + + //Check the value present in the textfield which is selectedRowIndices is [0,1,2] + cy.get(`${widgetsPage.textWidget} .bp3-ui-text`).should( + "have.text", + "[ 0, 1, 2]", + ); + }); +}); +// diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Multiple_Widgets_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Multiple_Widgets_spec.js index 635397c62b..1e6ff7aaf3 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Multiple_Widgets_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Multiple_Widgets_spec.js @@ -35,18 +35,19 @@ describe("Entity explorer tests related to widgets and validation", function() { .last() .click({ force: true }); cy.get(apiwidget.propertyList).then(function($lis) { - expect($lis).to.have.length(11); + expect($lis).to.have.length(12); expect($lis.eq(0)).to.contain("{{Table1.selectedRow}}"); expect($lis.eq(1)).to.contain("{{Table1.selectedRows}}"); - expect($lis.eq(2)).to.contain("{{Table1.triggeredRow}}"); - expect($lis.eq(3)).to.contain("{{Table1.selectedRowIndex}}"); - expect($lis.eq(4)).to.contain("{{Table1.tableData}}"); - expect($lis.eq(5)).to.contain("{{Table1.pageNo}}"); - expect($lis.eq(6)).to.contain("{{Table1.pageSize}}"); - expect($lis.eq(7)).to.contain("{{Table1.isVisible}}"); - expect($lis.eq(8)).to.contain("{{Table1.searchText}}"); - expect($lis.eq(9)).to.contain("{{Table1.totalRecordsCount}}"); - expect($lis.eq(10)).to.contain("{{Table1.sortOrder}}"); + expect($lis.eq(2)).to.contain("{{Table1.selectedRowIndices}}"); + expect($lis.eq(3)).to.contain("{{Table1.triggeredRow}}"); + expect($lis.eq(4)).to.contain("{{Table1.selectedRowIndex}}"); + expect($lis.eq(5)).to.contain("{{Table1.tableData}}"); + expect($lis.eq(6)).to.contain("{{Table1.pageNo}}"); + expect($lis.eq(7)).to.contain("{{Table1.pageSize}}"); + expect($lis.eq(8)).to.contain("{{Table1.isVisible}}"); + expect($lis.eq(9)).to.contain("{{Table1.searchText}}"); + expect($lis.eq(10)).to.contain("{{Table1.totalRecordsCount}}"); + expect($lis.eq(11)).to.contain("{{Table1.sortOrder}}"); }); }); diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts index 89c116e1e8..ac62391d71 100644 --- a/app/client/src/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts @@ -106,6 +106,7 @@ export const entityDefinitions: Record = { "!url": "https://docs.appsmith.com/widget-reference/table", selectedRow: generateTypeDef(widget.selectedRow), selectedRows: generateTypeDef(widget.selectedRows), + selectedRowIndices: generateTypeDef(widget.selectedRowIndices), triggeredRow: generateTypeDef(widget.triggeredRow), selectedRowIndex: "number", tableData: generateTypeDef(widget.tableData), diff --git a/app/client/src/utils/autocomplete/dataTypeSortRules.ts b/app/client/src/utils/autocomplete/dataTypeSortRules.ts index 99b206e111..f6ef0dd04c 100644 --- a/app/client/src/utils/autocomplete/dataTypeSortRules.ts +++ b/app/client/src/utils/autocomplete/dataTypeSortRules.ts @@ -48,7 +48,7 @@ const RULES: Record> = { "CONTAINER_WIDGET.backgroundColor", ], OBJECT: ["ACTION.data"], - ARRAY: ["ACTION.data"], + ARRAY: ["ACTION.data", "TABLE_WIDGET.selectedRowIndices"], BOOLEAN: [ "CHECKBOX_WIDGET.isChecked", "SWITCH_WIDGET.isSwitchedOn", diff --git a/app/client/src/widgets/TableWidget/widget/index.tsx b/app/client/src/widgets/TableWidget/widget/index.tsx index 5e20321f9d..432a110054 100644 --- a/app/client/src/widgets/TableWidget/widget/index.tsx +++ b/app/client/src/widgets/TableWidget/widget/index.tsx @@ -637,7 +637,11 @@ class TableWidget extends BaseWidget { // Use the selectedRowIndex if available as default selected index let selectedRowIndices: number[] = []; // Check if selectedRowIndex is valid - if (this.props.selectedRowIndex && this.props.selectedRowIndex > -1) { + if ( + this.props.selectedRowIndex !== undefined && + this.props.selectedRowIndex > -1 && + !Array.isArray(this.props.selectedRowIndex) + ) { selectedRowIndices = [this.props.selectedRowIndex]; } // Else use the defaultSelectedRow if available @@ -649,6 +653,7 @@ class TableWidget extends BaseWidget { ? [this.props.defaultSelectedRow] : this.props.defaultSelectedRow; } + this.props.updateWidgetMetaProperty( "selectedRowIndices", selectedRowIndices, diff --git a/app/client/src/widgets/TableWidget/widget/propertyUtils.test.ts b/app/client/src/widgets/TableWidget/widget/propertyUtils.test.ts new file mode 100644 index 0000000000..df3ff5d520 --- /dev/null +++ b/app/client/src/widgets/TableWidget/widget/propertyUtils.test.ts @@ -0,0 +1,162 @@ +import { defaultSelectedRowValidation } from "./propertyUtils"; +import _ from "lodash"; + +const tableWProps = { + multiRowSelection: false, + widgetName: "Table1", + defaultPageSize: 0, + columnOrder: ["step", "task", "status", "action"], + isVisibleDownload: true, + dynamicPropertyPathList: [], + displayName: "Table", + iconSVG: "/static/media/icon.db8a9cbd.svg", + topRow: 54, + bottomRow: 82, + isSortable: true, + parentRowSpace: 10, + type: "TABLE_WIDGET", + defaultSelectedRow: "0", + hideCard: false, + parentColumnSpace: 25.6875, + dynamicTriggerPathList: [], + dynamicBindingPathList: [ + { + key: "primaryColumns.step.computedValue", + }, + { + key: "primaryColumns.task.computedValue", + }, + { + key: "primaryColumns.status.computedValue", + }, + { + key: "primaryColumns.action.computedValue", + }, + ], + leftColumn: 25, + primaryColumns: { + step: { + index: 0, + width: 150, + id: "step", + horizontalAlignment: "LEFT", + verticalAlignment: "CENTER", + columnType: "text", + textSize: "PARAGRAPH", + enableFilter: true, + enableSort: true, + isVisible: true, + isCellVisible: true, + isDerived: false, + label: "step", + computedValue: + "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.step))}}", + }, + task: { + index: 1, + width: 150, + id: "task", + horizontalAlignment: "LEFT", + verticalAlignment: "CENTER", + columnType: "text", + textSize: "PARAGRAPH", + enableFilter: true, + enableSort: true, + isVisible: true, + isCellVisible: true, + isDerived: false, + label: "task", + computedValue: + "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.task))}}", + }, + status: { + index: 2, + width: 150, + id: "status", + horizontalAlignment: "LEFT", + verticalAlignment: "CENTER", + columnType: "text", + textSize: "PARAGRAPH", + enableFilter: true, + enableSort: true, + isVisible: true, + isCellVisible: true, + isDerived: false, + label: "status", + computedValue: + "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.status))}}", + }, + action: { + index: 3, + width: 150, + id: "action", + horizontalAlignment: "LEFT", + verticalAlignment: "CENTER", + columnType: "button", + textSize: "PARAGRAPH", + enableFilter: true, + enableSort: true, + isVisible: true, + isCellVisible: true, + isDisabled: false, + isDerived: false, + label: "action", + onClick: + "{{currentRow.step === '#1' ? showAlert('Done', 'success') : currentRow.step === '#2' ? navigateTo('https://docs.appsmith.com/core-concepts/connecting-to-data-sources/querying-a-database',undefined,'NEW_WINDOW') : navigateTo('https://docs.appsmith.com/core-concepts/displaying-data-read/display-data-tables',undefined,'NEW_WINDOW')}}", + computedValue: + "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.action))}}", + }, + }, + delimiter: ",", + key: "fzi9jh5j7j", + derivedColumns: {}, + rightColumn: 50, + textSize: "PARAGRAPH", + widgetId: "2tk8bgzwaz", + isVisibleFilters: true, + tableData: [ + { + step: "#1", + task: "Drop a table", + status: "✅", + action: "", + }, + { + step: "#2", + task: "Create a query fetch_users with the Mock DB", + status: "--", + action: "", + }, + { + step: "#3", + task: "Bind the query using => fetch_users.data", + status: "--", + action: "", + }, + ], + isVisible: true, + label: "Data", + searchKey: "", + version: 3, + totalRecordsCount: 0, + parentId: "0", + renderMode: "CANVAS", + isLoading: false, + horizontalAlignment: "LEFT", + isVisibleSearch: true, + isVisiblePagination: true, + verticalAlignment: "CENTER", + columnSizeMap: { + task: 245, + step: 62, + status: 75, + }, +}; +describe("unit test case for property utils", () => { + it("case: check if the defaultSelectedRowValiation returns parsed value as undefined", () => { + const value = defaultSelectedRowValidation("", tableWProps as any, _); + + expect(value.isValid).toBeTruthy(); + expect(value.parsed).toEqual(undefined); + }); +}); diff --git a/app/client/src/widgets/TableWidget/widget/propertyUtils.ts b/app/client/src/widgets/TableWidget/widget/propertyUtils.ts index 348f264526..960ff4da8d 100644 --- a/app/client/src/widgets/TableWidget/widget/propertyUtils.ts +++ b/app/client/src/widgets/TableWidget/widget/propertyUtils.ts @@ -72,6 +72,13 @@ export function defaultSelectedRowValidation( } else { try { const _value: string = value as string; + + if (_value === "") { + return { + isValid: true, + parsed: undefined, + }; + } if (Number.isInteger(parseInt(_value, 10)) && parseInt(_value, 10) > -1) return { isValid: true, parsed: parseInt(_value, 10) };