diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/columnTypes/Table_select_validation_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/columnTypes/Table_select_validation_spec.ts new file mode 100644 index 000000000000..cb440837abce --- /dev/null +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/columnTypes/Table_select_validation_spec.ts @@ -0,0 +1,128 @@ +import commonlocators from "../../../../../../locators/commonlocators.json"; +import { + agHelper, + entityExplorer, + locators, + propPane, + table, +} from "../../../../../../support/Objects/ObjectsCore"; +import EditorNavigation, { + EntityType, +} from "../../../../../../support/Pages/EditorNavigation"; + +const TABLE_SELECT_WIDGET_ERROR_BORDER = "rgb(217, 25, 33)"; +const TABLE_SELECT_WIDGET_VALID_BORDER = "rgb(85, 61, 233)"; + +describe( + "Table widget - Select column validation", + { tags: ["@tag.Widget", "@tag.Table", "@tag.Select"] }, + () => { + before(() => { + entityExplorer.DragNDropWidget("tablewidgetv2", 350, 500); + table.AddSampleTableData(); + }); + it("1. should prevent adding a row when a required select column has no data", () => { + EditorNavigation.SelectEntityByName("Table1", EntityType.Widget); + + // Allow adding a row in table + propPane.TogglePropertyState("Allow adding a row", "On"); + + // Edit step column to select type + table.ChangeColumnType("step", "Select", "v2"); + table.EditColumn("step", "v2"); + + // Add data to select options + agHelper.UpdateCodeInput( + locators._controlOption, + ` + [ + { + "label": "#1", + "value": "#1" + }, + { + "label": "#2", + "value": "#2" + }, + { + "label": "#3", + "value": "#3" + } + ] + `, + ); + + // Set step column to editable + propPane.TogglePropertyState("Editable", "On"); + + // Set step column to required + propPane.TogglePropertyState("Required", "On"); + + // Click add a new row + table.AddNewRow(); + + // Expect the save row button to be disabled + agHelper.GetElement(table._saveNewRow).should("be.disabled"); + + // Expect select to have an error color + agHelper + .GetElement(commonlocators.singleSelectWidgetButtonControl) + .should("have.css", "border-color", TABLE_SELECT_WIDGET_ERROR_BORDER); + + // Select a valid option from the select table cell + agHelper.GetNClick(commonlocators.singleSelectWidgetButtonControl); + agHelper + .GetElement(commonlocators.singleSelectWidgetMenuItem) + .contains("#1") + .click(); + + // Expect the save row option to be enabled + agHelper.GetElement(table._saveNewRow).should("be.enabled"); + + // Expect button to have a valid color + agHelper + .GetElement(commonlocators.singleSelectWidgetButtonControl) + .should("have.css", "border-color", TABLE_SELECT_WIDGET_VALID_BORDER); + + // Discard save new row + agHelper.GetElement(table._discardRow).click({ force: true }); + }); + + it("2. should display an error when inline editing a required select cell in a table with no data", () => { + // Update table data to create emtpy cell in step column + propPane.NavigateBackToPropertyPane(); + propPane.UpdatePropertyFieldValue( + "Table data", + ` + [ + { + "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": "" + } + ] + `, + ); + + // Click the first cell in the step column + table.ClickOnEditIcon(0, 0, true); + + // Exect the select to have an error color + agHelper + .GetElement(commonlocators.singleSelectWidgetButtonControl) + .should("have.css", "border-color", TABLE_SELECT_WIDGET_ERROR_BORDER); + }); + }, +); diff --git a/app/client/cypress/locators/commonlocators.json b/app/client/cypress/locators/commonlocators.json index 986c006820ce..9fe519e15adb 100644 --- a/app/client/cypress/locators/commonlocators.json +++ b/app/client/cypress/locators/commonlocators.json @@ -72,6 +72,7 @@ "callApi": ".t--property-control-onpagechange .t--open-dropdown-Select-Action", "singleSelectMenuItem": ".bp3-menu-item.single-select div", "singleSelectWidgetMenuItem": ".menu-item-link", + "singleSelectWidgetButtonControl": ".bp3-button.select-button", "singleSelectActiveMenuItem": ".menu-item-active div", "selectInputSearch": ".select-popover-wrapper .bp3-input", "multiSelectMenuItem": "rc-select-item.rc-select-item-option div", diff --git a/app/client/cypress/support/Pages/Table.ts b/app/client/cypress/support/Pages/Table.ts index 2f90efb052c7..d1439a589271 100644 --- a/app/client/cypress/support/Pages/Table.ts +++ b/app/client/cypress/support/Pages/Table.ts @@ -638,18 +638,24 @@ export class Table { this.agHelper.GetNClick(colSettings); } - public ClickOnEditIcon(rowIndex: number, colIndex: number) { + public ClickOnEditIcon( + rowIndex: number, + colIndex: number, + isSelectColumn: boolean = false, + ) { this.agHelper.HoverElement(this._tableRow(rowIndex, colIndex, "v2")); this.agHelper.GetNClick( this._tableRow(rowIndex, colIndex, "v2") + " " + this._editCellIconDiv, 0, true, ); - this.agHelper.AssertElementVisibility( - this._tableRow(rowIndex, colIndex, "v2") + - " " + - this._editCellEditorInput, - ); + if (!isSelectColumn) { + this.agHelper.AssertElementVisibility( + this._tableRow(rowIndex, colIndex, "v2") + + " " + + this._editCellEditorInput, + ); + } } public EditTableCell( diff --git a/app/client/src/widgets/TableWidgetV2/component/cellComponents/SelectCell.tsx b/app/client/src/widgets/TableWidgetV2/component/cellComponents/SelectCell.tsx index d29a7cdc4056..2e37cb7dd6c9 100644 --- a/app/client/src/widgets/TableWidgetV2/component/cellComponents/SelectCell.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/cellComponents/SelectCell.tsx @@ -18,6 +18,7 @@ const StyledSelectComponent = styled(SelectComponent)<{ accentColor: string; height: number; isNewRow: boolean; + isValid: boolean; }>` &&& { width: ${(props) => @@ -37,7 +38,6 @@ const StyledSelectComponent = styled(SelectComponent)<{ } & button.bp3-button { - border-color: #fff; padding: 0 9px; min-height: ${(props) => { return props.isNewRow @@ -82,6 +82,7 @@ type SelectProps = BaseCellComponentProps & { value: string; width: number; isEditable: boolean; + isEditableCellValid: boolean; tableWidth: number; isCellEditable?: boolean; isCellEditMode?: boolean; @@ -131,6 +132,7 @@ export const SelectCell = (props: SelectProps) => { isCellEditMode, isCellVisible, isEditable, + isEditableCellValid, isFilterable = false, isHidden, isNewRow, @@ -236,13 +238,14 @@ export const SelectCell = (props: SelectProps) => { compactMode dropDownWidth={width} filterText={filterText} + hasError={!isEditableCellValid} height={TABLE_SIZES[compactMode].ROW_HEIGHT} hideCancelIcon isFilterable={isFilterable} isLoading={false} isNewRow={isNewRow} isOpen={autoOpen} - isValid + isValid={isEditableCellValid} labelText="" onClose={onClose} onFilterChange={onFilter} diff --git a/app/client/src/widgets/TableWidgetV2/widget/derived.js b/app/client/src/widgets/TableWidgetV2/widget/derived.js index 988cdd6b2a72..d27e7e84c593 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/derived.js +++ b/app/client/src/widgets/TableWidgetV2/widget/derived.js @@ -1037,7 +1037,7 @@ export default { }; let editableColumns = []; - const validatableColumns = ["text", "number", "currency", "date"]; + const validatableColumns = ["text", "number", "currency", "date", "select"]; if (props.isAddRowInProgress) { Object.values(props.primaryColumns) diff --git a/app/client/src/widgets/TableWidgetV2/widget/index.tsx b/app/client/src/widgets/TableWidgetV2/widget/index.tsx index fc428163caac..046366812074 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/widget/index.tsx @@ -2156,6 +2156,7 @@ class TableWidgetV2 extends BaseWidget { isCellEditable={isCellEditable} isCellVisible={cellProperties.isCellVisible ?? true} isEditable={isColumnEditable} + isEditableCellValid={this.isColumnCellValid(alias)} isFilterable={cellProperties.isFilterable} isHidden={isHidden} isNewRow={isNewRow} diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Validation.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Validation.ts index 64ae317f5b42..ec1bdb86505b 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Validation.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Validation.ts @@ -21,6 +21,7 @@ export default { ColumnTypes.NUMBER, ColumnTypes.DATE, ColumnTypes.CURRENCY, + ColumnTypes.SELECT, ], true, ) diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Validations/Common.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Validations/Common.ts index edde51d680d0..78af47a0475d 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Validations/Common.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Validations/Common.ts @@ -6,6 +6,17 @@ import { getColumnPath, } from "widgets/TableWidgetV2/widget/propertyUtils"; +const hideColumnByType = (props: TableWidgetProps, propertyPath: string) => { + const path = getColumnPath(propertyPath); + + return showByColumnType( + props, + path, + [ColumnTypes.DATE, ColumnTypes.SELECT], + true, + ); +}; + export default [ { propertyName: "validation.regex", @@ -18,11 +29,7 @@ export default [ isBindProperty: true, isTriggerProperty: false, validation: { type: ValidationTypes.REGEX }, - hidden: (props: TableWidgetProps, propertyPath: string) => { - const path = getColumnPath(propertyPath); - - return showByColumnType(props, path, [ColumnTypes.DATE], true); - }, + hidden: hideColumnByType, }, { propertyName: "validation.isColumnEditableCellValid", @@ -39,11 +46,7 @@ export default [ default: true, }, }, - hidden: (props: TableWidgetProps, propertyPath: string) => { - const path = getColumnPath(propertyPath); - - return showByColumnType(props, path, [ColumnTypes.DATE], true); - }, + hidden: hideColumnByType, }, { propertyName: "validation.errorMessage", @@ -56,11 +59,7 @@ export default [ isBindProperty: true, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, - hidden: (props: TableWidgetProps, propertyPath: string) => { - const path = getColumnPath(propertyPath); - - return showByColumnType(props, path, [ColumnTypes.DATE], true); - }, + hidden: hideColumnByType, }, { propertyName: "validation.isColumnEditableCellRequired",