diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js index 2732da5b7c..cf610c78f5 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js @@ -61,33 +61,33 @@ describe("Text Widget Functionality", function() { .should("have.css", "font-size", "18px"); }); - // it("Text widget depends on itself", function() { - // cy.getCodeMirror().then(($cm) => { - // if ($cm.val() !== "") { - // cy.get(".CodeMirror textarea") - // .first() - // .clear({ - // force: true, - // }); - // } - // - // cy.get(".CodeMirror textarea") - // .first() - // .type(`{{${this.data.TextName}}}`, { - // force: true, - // parseSpecialCharSequences: false, - // }); - // }); - // cy.get(commonlocators.toastBody) - // .first() - // .contains("Cyclic"); - // - // cy.PublishtheApp(); - // cy.get(commonlocators.bodyTextStyle).should( - // "have.text", - // `{{${this.data.TextName}}}`, - // ); - // }); + it("Text widget depends on itself", function() { + cy.getCodeMirror().then(($cm) => { + if ($cm.val() !== "") { + cy.get(".CodeMirror textarea") + .first() + .clear({ + force: true, + }); + } + + cy.get(".CodeMirror textarea") + .first() + .type(`{{${this.data.TextName}}}`, { + force: true, + parseSpecialCharSequences: false, + }); + }); + cy.get(commonlocators.toastBody) + .first() + .contains("Cyclic"); + + cy.PublishtheApp(); + cy.get(commonlocators.bodyTextStyle).should( + "have.text", + `{{${this.data.TextName}}}`, + ); + }); afterEach(() => { cy.get(publishPage.backToEditor).click({ force: true }); diff --git a/app/client/src/workers/DataTreeEvaluator.ts b/app/client/src/workers/DataTreeEvaluator.ts index 767815b367..052b98e9ed 100644 --- a/app/client/src/workers/DataTreeEvaluator.ts +++ b/app/client/src/workers/DataTreeEvaluator.ts @@ -305,7 +305,7 @@ export default class DataTreeEvaluator { Object.keys(dependencyMap).forEach((key) => { dependencyMap[key] = _.flatten( dependencyMap[key].map((path) => - extractReferencesFromBinding(key, path, this.allKeys), + extractReferencesFromBinding(path, this.allKeys), ), ); }); @@ -345,10 +345,21 @@ export default class DataTreeEvaluator { } if (isAction(entity)) { Object.entries(entity.dependencyMap).forEach( - ([dependent, entityDependencies]) => { - dependencies[`${entityName}.${dependent}`] = entityDependencies.map( - (propertyPath) => `${entityName}.${propertyPath}`, - ); + ([path, entityDependencies]) => { + const actionDependentPaths: Array = []; + const mainPath = `${entityName}.${path}`; + // Only add dependencies for paths which exist at the moment in appsmith world + if (this.allKeys.hasOwnProperty(mainPath)) { + // Only add dependent paths which exist in the data tree. Skip all the other paths to avoid creating + // a cyclical dependency. + entityDependencies.forEach((dependentPath) => { + const completePath = `${entityName}.${dependentPath}`; + if (this.allKeys.hasOwnProperty(completePath)) { + actionDependentPaths.push(completePath); + } + }); + dependencies[mainPath] = actionDependentPaths; + } }, ); } @@ -859,16 +870,23 @@ export default class DataTreeEvaluator { const entityDependenciesName = entity.dependencyMap[ entityPropertyPath ].map((dep) => `${entityName}.${dep}`); + + // Filter only the paths which exist in the appsmith world to avoid cyclical dependencies + const filteredEntityDependencies = entityDependenciesName.filter( + (path) => this.allKeys.hasOwnProperty(path), + ); + + // Now assign these existing dependent paths to the property path in dependencyMap if (fullPropertyPath in this.dependencyMap) { this.dependencyMap[ fullPropertyPath ] = this.dependencyMap[fullPropertyPath].concat( - entityDependenciesName, + filteredEntityDependencies, ); } else { this.dependencyMap[ fullPropertyPath - ] = entityDependenciesName; + ] = filteredEntityDependencies; } } } @@ -890,7 +908,7 @@ export default class DataTreeEvaluator { this.dependencyMap[key] = _.uniq( _.flatten( this.dependencyMap[key].map((path) => - extractReferencesFromBinding(key, path, this.allKeys), + extractReferencesFromBinding(path, this.allKeys), ), ), ); @@ -1027,7 +1045,7 @@ export default class DataTreeEvaluator { const propertyBindings = entityPropertyBindings[path]; const references = _.flatten( propertyBindings.map((binding) => - extractReferencesFromBinding(path, binding, this.allKeys), + extractReferencesFromBinding(binding, this.allKeys), ), ); references.forEach((value) => { @@ -1089,7 +1107,6 @@ export default class DataTreeEvaluator { } const extractReferencesFromBinding = ( - path: string, dependentPath: string, all: Record, ): Array => { @@ -1100,7 +1117,7 @@ const extractReferencesFromBinding = ( identifiers.forEach((identifier: string) => { // If the identifier exists directly, add it and return if (all.hasOwnProperty(identifier)) { - pushDependentsInSubDependencyArray(subDeps, path, identifier); + subDeps.push(identifier); return; } const subpaths = _.toPath(identifier); @@ -1113,7 +1130,7 @@ const extractReferencesFromBinding = ( current = convertPathToString(subpaths); // We've found the dep, add it and return if (all.hasOwnProperty(current)) { - pushDependentsInSubDependencyArray(subDeps, path, current); + subDeps.push(current); return; } subpaths.pop(); @@ -1122,19 +1139,6 @@ const extractReferencesFromBinding = ( return _.uniq(subDeps); }; -const pushDependentsInSubDependencyArray = ( - subDeps: string[], - path: string, - dependentPath: string, -): string[] => { - // Only add if path is not a child of dependentPath which ensures that cyclical dependency is not introduced - // when adding parent-child relationships later - if (!isChildPropertyPath(dependentPath, path)) { - subDeps.push(dependentPath); - } - return subDeps; -}; - // TODO cryptic comment below. Dont know if we still need this. Duplicate function // referencing DATA_BIND_REGEX fails for the value "{{Table1.tableData[Table1.selectedRowIndex]}}" if you run it multiple times and don't recreate const isDynamicValue = (value: string): boolean => DATA_BIND_REGEX.test(value);