fix: Refactor validation logic in TableWidgetV2 to improve clarity and correctness (#40679)
## Description <ins>Problem</ins> The table widget skipped validations for number columns when users typed quickly and pressed Enter, accepting invalid values despite min/max constraints. <ins>Root cause</ins> The validation logic had an early exit if the value was empty and the column wasn't required. This incorrectly allowed empty values even when min/max constraints were set. Due to async evaluations and timing differences, a quick backspace followed by a new input and Enter led to premature validation success. <ins>Solution</ins> This PR handles a refactor of the validation logic in `TableWidgetV2` to improve clarity and correctness. - Consolidates checks for required fields and regex validations. - Ensures column type validations accurately reflect constraints like min/max. - Prevents premature validation passes caused by async evaluation race conditions. Fixes #`Issue Number` _or_ Fixes `Issue URL` > [!WARNING] > _If no issue exists, please create an issue first, and check with the maintainers if the issue is valid._ ## Automation /ok-to-test tags="@tag.Table" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/15160290045> > Commit: b3022119656954765a79b1e03c5af2a4338a89c4 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=15160290045&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Table` > Spec: > <hr>Wed, 21 May 2025 11:35:27 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Enhanced validation for editable table cells to better enforce required fields and regex patterns. - Improved handling of empty values in required and non-required cells for more accurate validation feedback. - **Tests** - Refactored and reorganized validation test suites for improved clarity and maintainability without changing validation behavior. - Updated end-to-end tests to improve timing and synchronization by removing fixed waits and relying on automatic retries. - Adjusted inline editing validation tests to reflect updated error visibility expectations during editing. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
a13e09f33f
commit
d64361e225
|
|
@ -18,36 +18,25 @@ describe(
|
|||
_.table.toggleColumnEditableViaColSettingsPane("step", "v2", true, false);
|
||||
|
||||
_.propPane.UpdatePropertyFieldValue("Valid", "{{editedValue === '#1'}}");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.enterTableCellValue(0, 0, "22");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "#1");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.enterTableCellValue(0, 0, "22");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
_.propPane.UpdatePropertyFieldValue("Valid", "");
|
||||
|
||||
_.propPane.UpdatePropertyFieldValue("Regex", "^#1$");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "22");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "#1");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
_.propPane.UpdatePropertyFieldValue("Regex", "");
|
||||
|
||||
_.propPane.TogglePropertyState("Required", "On");
|
||||
cy.enterTableCellValue(0, 0, "22");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.enterTableCellValue(0, 0, "#1");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.enterTableCellValue(0, 0, "");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
|
||||
cy.get(commonlocators.changeColType).last().click();
|
||||
|
|
@ -56,37 +45,26 @@ describe(
|
|||
|
||||
_.propPane.UpdatePropertyFieldValue("Min", "5");
|
||||
cy.enterTableCellValue(0, 0, "6");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.enterTableCellValue(0, 0, "7");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.enterTableCellValue(0, 0, "4");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "3");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "8");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
_.propPane.UpdatePropertyFieldValue("Min", "");
|
||||
|
||||
_.propPane.UpdatePropertyFieldValue("Max", "5");
|
||||
cy.enterTableCellValue(0, 0, "6");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "7");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "4");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.enterTableCellValue(0, 0, "3");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.enterTableCellValue(0, 0, "8");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
_.propPane.UpdatePropertyFieldValue("Max", "");
|
||||
|
||||
|
|
@ -113,31 +91,24 @@ describe(
|
|||
|
||||
cy.editTableCell(0, 0);
|
||||
cy.enterTableCellValue(0, 0, "3");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "2");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.discardTableCellValue(0, 0);
|
||||
cy.wait(500);
|
||||
cy.get(".t--add-new-row").click();
|
||||
cy.wait(1000);
|
||||
cy.enterTableCellValue(0, 0, "3");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "2");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "1");
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.get(".t--discard-new-row").click({ force: true });
|
||||
});
|
||||
|
||||
it("2.3. should test that validation is working for more than one add new row cell at a time", () => {
|
||||
_.propPane.UpdatePropertyFieldValue("Valid", "{{editedValue === 1}}");
|
||||
cy.get("[data-testid='t--property-pane-back-btn']").click();
|
||||
cy.wait(500);
|
||||
_.propPane.NavigateBackToPropertyPane();
|
||||
_.table.toggleColumnEditableViaColSettingsPane("task", "v2", true, false);
|
||||
_.propPane.UpdatePropertyFieldValue(
|
||||
"Valid",
|
||||
|
|
|
|||
|
|
@ -87,8 +87,7 @@ describe(
|
|||
_.propPane.TogglePropertyState("Editable", "On");
|
||||
_.propPane.UpdatePropertyFieldValue("Regex", "^#1$");
|
||||
cy.editTableCell(0, 0);
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "22");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "#1");
|
||||
|
|
@ -104,8 +103,7 @@ describe(
|
|||
"{{editedValue === '#1'}}",
|
||||
);
|
||||
cy.editTableCell(0, 0);
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "22");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "#1");
|
||||
|
|
@ -118,7 +116,6 @@ describe(
|
|||
_.propPane.TogglePropertyState("Editable", "On");
|
||||
_.propPane.TogglePropertyState("Required", "On");
|
||||
cy.editTableCell(0, 0);
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "22");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
|
|
@ -146,8 +143,7 @@ describe(
|
|||
_.propPane.UpdatePropertyFieldValue("Min", "5");
|
||||
|
||||
cy.editTableCell(0, 0);
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.enterTableCellValue(0, 0, "6");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.enterTableCellValue(0, 0, "7");
|
||||
|
|
@ -172,7 +168,6 @@ describe(
|
|||
_.propPane.UpdatePropertyFieldValue("Max", "5");
|
||||
|
||||
cy.editTableCell(0, 0);
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.enterTableCellValue(0, 0, "6");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
|
|
@ -198,7 +193,6 @@ describe(
|
|||
"You got error mate!!",
|
||||
);
|
||||
cy.editTableCell(0, 0);
|
||||
cy.wait(1000);
|
||||
cy.enterTableCellValue(0, 0, "123");
|
||||
cy.get(".bp3-overlay.error-tooltip .bp3-popover-content").should(
|
||||
"contain",
|
||||
|
|
@ -224,12 +218,12 @@ describe(
|
|||
cy.get(`.t--inlined-cell-editor`).should("exist");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.saveTableCellValue(0, 0);
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor`).should("exist");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("exist");
|
||||
cy.get(widgetsPage.toastAction).should("not.exist");
|
||||
cy.enterTableCellValue(0, 0, "#1");
|
||||
cy.saveTableCellValue(0, 0);
|
||||
cy.wait(500);
|
||||
cy.get(`.t--inlined-cell-editor`).should("not.exist");
|
||||
cy.get(`.t--inlined-cell-editor-has-error`).should("not.exist");
|
||||
cy.get(widgetsPage.toastAction).should("be.visible");
|
||||
|
|
@ -276,7 +270,7 @@ describe(
|
|||
},
|
||||
);
|
||||
|
||||
it("should check that save/discard button is disabled when there is a validation error", () => {
|
||||
it("9. should check that save/discard button is disabled when there is a validation error", () => {
|
||||
cy.openPropertyPane("tablewidgetv2");
|
||||
cy.editColumn("step");
|
||||
_.propPane.TogglePropertyState("Editable", "On");
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1199,62 +1199,60 @@ export default {
|
|||
|
||||
const validationMap = {};
|
||||
|
||||
editableColumns.forEach((validationObj) => {
|
||||
const editedColumn = validationObj[0];
|
||||
const value = validationObj[1];
|
||||
editableColumns.forEach(([editedColumn, value]) => {
|
||||
let isValid = true;
|
||||
|
||||
if (editedColumn && editedColumn.validation) {
|
||||
const validation = editedColumn.validation;
|
||||
|
||||
/* General validations */
|
||||
/**
|
||||
* General validations
|
||||
* 1. isColumnEditableCellValid
|
||||
* 2. regex
|
||||
* 3. isColumnEditableCellRequired
|
||||
* 4. number/currency min/max
|
||||
*/
|
||||
if (
|
||||
!validation.isColumnEditableCellRequired &&
|
||||
!_.isNil(validation.isColumnEditableCellValid) &&
|
||||
!validation.isColumnEditableCellValid
|
||||
) {
|
||||
isValid = false;
|
||||
} else if (
|
||||
validation.regex &&
|
||||
!createRegex(validation.regex).test(value)
|
||||
) {
|
||||
isValid = false;
|
||||
} else if (
|
||||
validation.isColumnEditableCellRequired &&
|
||||
(value === "" || _.isNil(value))
|
||||
) {
|
||||
validationMap[editedColumn.alias] = true;
|
||||
isValid = false;
|
||||
} else {
|
||||
switch (editedColumn.columnType) {
|
||||
case "number":
|
||||
case "currency":
|
||||
if (
|
||||
!_.isNil(validation.min) &&
|
||||
validation.min !== "" &&
|
||||
validation.min > value
|
||||
) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (
|
||||
(!_.isNil(validation.isColumnEditableCellValid) &&
|
||||
!validation.isColumnEditableCellValid) ||
|
||||
(validation.regex && !createRegex(validation.regex).test(value)) ||
|
||||
(validation.isColumnEditableCellRequired &&
|
||||
(value === "" || _.isNil(value)))
|
||||
) {
|
||||
validationMap[editedColumn.alias] = false;
|
||||
if (
|
||||
!_.isNil(validation.max) &&
|
||||
validation.max !== "" &&
|
||||
validation.max < value
|
||||
) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Column type related validations */
|
||||
switch (editedColumn.columnType) {
|
||||
case "number":
|
||||
case "currency":
|
||||
if (
|
||||
!_.isNil(validation.min) &&
|
||||
validation.min !== "" &&
|
||||
validation.min > value
|
||||
) {
|
||||
validationMap[editedColumn.alias] = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!_.isNil(validation.max) &&
|
||||
validation.max !== "" &&
|
||||
validation.max < value
|
||||
) {
|
||||
validationMap[editedColumn.alias] = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validationMap[editedColumn.alias] = true;
|
||||
validationMap[editedColumn.alias] = isValid;
|
||||
});
|
||||
|
||||
return validationMap;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user