fix: Unblocking CI - Unexpected cyclic dependency error and Lint spec fixes (#20260)

## Description
This PR fixes the false cyclic dependency error Cypress tests and the
basic Lint error Cypress tests.

> Add a TL;DR when description is extra long (helps content team)

Fixes # (issue)
> if no issue exists, please create an issue and ask the maintainers
about this first


Media
> A video or a GIF is preferred. when using Loom, don’t embed because it
looks like it’s a GIF. instead, just link to the video


## Type of change

> Please delete options that are not relevant.

- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- Chore (housekeeping or task changes that don't impact user perception)
- This change requires a documentation update


## How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Provide
instructions, so we can reproduce.
> Please also list any relevant details for your test configuration.
> Delete anything that is not important

- Manual
- Jest
- Cypress

### Test Plan
> Add Testsmith test cases links that relate to this PR

### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)


## Checklist:
### Dev activity
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


### QA activity:
- [ ] Test plan has been approved by relevant developers
- [ ] Test plan has been peer reviewed by QA
- [ ] Cypress test cases have been added and approved by either SDET or
manual QA
- [ ] Organized project review call with relevant stakeholders after
Round 1/2 of QA
- [ ] Added Test Plan Approved label after reveiwing all Cypress test

---------

Co-authored-by: ChandanBalajiBP <104058110+ChandanBalajiBP@users.noreply.github.com>
This commit is contained in:
subratadeypappu 2023-01-31 20:33:10 +06:00 committed by GitHub
parent e50d0c89da
commit 0ae19d6d32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 42 deletions

View File

@ -10,7 +10,7 @@ const agHelper = ObjectsRegistry.AggregateHelper;
let queryName;
/*
Cyclic Depedency Error if occurs, Message would be shown in following 6 cases:
Cyclic Dependency Error if occurs, Message would be shown in following 6 cases:
1. On page load actions
2. When updating DSL attribute
3. When updating JS Object name
@ -19,7 +19,7 @@ Cyclic Depedency Error if occurs, Message would be shown in following 6 cases:
6. When updating Datasource query
*/
describe("Cyclic Dependency Informational Error Messagaes", function() {
describe("Cyclic Dependency Informational Error Messages", function() {
before(() => {
//appId = localStorage.getItem("applicationId");
//cy.log("appID:" + appId);
@ -80,28 +80,28 @@ describe("Cyclic Dependency Informational Error Messagaes", function() {
});
//Case 1: On page load actions
it("3. Reload Page and it should provide errors in response", () => {
it("3. Reload Page and it should not provide errors in response", () => {
// cy.get(widgetsPage.NavHomePage).click({ force: true });
cy.reload();
cy.openPropertyPane("inputwidgetv2");
cy.wait("@getPage").should(
"have.nested.property",
"response.body.data.layouts[0].layoutOnLoadActionErrors.length",
1,
0,
);
});
it("4. update input widget's placeholder property and check errors array", () => {
it("4. update input widget's placeholder property and check errors array to be empty", () => {
// Case 2: When updating DSL attribute
cy.get(widgetsPage.placeholder).type("cyclic placeholder");
cy.wait("@updateLayout").should(
"have.nested.property",
"response.body.data.layoutOnLoadActionErrors.length",
1,
0,
);
});
it("5. Add JSObject and update its name, content and check for errors", () => {
it("5. Add JSObject and update its name, content and check for no errors", () => {
// Case 3: When updating JS Object name
jsEditor.CreateJSObject(
`export default {
@ -121,7 +121,7 @@ describe("Cyclic Dependency Informational Error Messagaes", function() {
cy.wait("@renameJsAction").should(
"have.nested.property",
"response.body.data.layoutOnLoadActionErrors.length",
1,
0,
);
// Case 4: When updating Js Object content
@ -135,12 +135,12 @@ describe("Cyclic Dependency Informational Error Messagaes", function() {
cy.wait("@jsCollections").should(
"have.nested.property",
"response.body.data.errorReports.length",
1,
0,
);
});
// Case 5: When updating DSL name
it("6. Update Widget Name and check for errors", () => {
it("6. Update Widget Name and check for no errors", () => {
let entityName = "gender";
let newEntityName = "newInput";
ee.SelectEntityByName(entityName, "Widgets");
@ -148,14 +148,14 @@ describe("Cyclic Dependency Informational Error Messagaes", function() {
cy.wait("@updateWidgetName").should(
"have.nested.property",
"response.body.data.layoutOnLoadActionErrors.length",
1,
0,
);
});
// Case 6: When updating Datasource query
it("7. Update Query and check for errors", () => {
ee.SelectEntityByName(queryName, "Queries/JS");
// update query and check for cyclic depedency issue
// update query and check no cyclic dependency issue should occur
cy.get(queryLocators.query).click({ force: true });
cy.get(".CodeMirror textarea")
.first()
@ -167,7 +167,7 @@ describe("Cyclic Dependency Informational Error Messagaes", function() {
cy.wait("@saveAction").should(
"have.nested.property",
"response.body.data.errorReports.length",
1,
0,
);
});
});

View File

@ -88,6 +88,14 @@ const entityRefactor = [
isJSObject: true,
evalVersion: 2,
},
{
script:
'(function(){\n try{\n ApiNever.run(); \n showAlert("Sucessful Trigger");\n }catch(error){\nshowAlert("Unsucessful Trigger");\n }\n})()',
oldName: "ApiNever",
newName: "ApiForever",
isJSObject: false,
evalVersion: 2,
},
];
afterAll((done) => {
@ -185,6 +193,11 @@ describe("AST tests", () => {
'export default {\n\tmyVar1: [],\n\tmyVar2: {},\n\tmyFun1: () => {\n\t\t//write code here\n\t\t// ApiNever.text\n\t\treturn "ApiNever.text" + ApiForever.text\n\t},\n\tmyFun2: async () => {\n\t\t//use async-await or promises\n\t\t// ApiNever.text\n\t\treturn "ApiNever.text" + ApiForever.text\n\t}\n}',
refactorCount: 2,
},
{
script:
'(function(){\n try{\n ApiForever.run(); \n showAlert("Sucessful Trigger");\n }catch(error){\nshowAlert("Unsucessful Trigger");\n }\n})()',
refactorCount: 1,
},
];
await supertest(app)

View File

@ -1,9 +1,9 @@
import { parse, Node, SourceLocation, Options, Comment } from 'acorn';
import { ancestor, simple } from 'acorn-walk';
import { ECMA_VERSION, NodeTypes } from './constants/ast';
import { has, isFinite, isString, memoize, toPath } from 'lodash';
import { isTrueObject, sanitizeScript } from './utils';
import { jsObjectDeclaration } from './jsObject/index';
import { parse, Node, SourceLocation, Options, Comment } from "acorn";
import { ancestor, simple } from "acorn-walk";
import { ECMA_VERSION, NodeTypes } from "./constants/ast";
import { has, isFinite, isString, memoize, toPath } from "lodash";
import { isTrueObject, sanitizeScript } from "./utils";
import { jsObjectDeclaration } from "./jsObject/index";
/*
* Valuable links:
*
@ -104,7 +104,7 @@ export interface PropertyNode extends Node {
type: NodeTypes.Property;
key: LiteralNode | IdentifierNode;
value: Node;
kind: 'init' | 'get' | 'set';
kind: "init" | "get" | "set";
}
// Node with location details
@ -112,7 +112,7 @@ type NodeWithLocation<NodeType> = NodeType & {
loc: SourceLocation;
};
type AstOptions = Omit<Options, 'ecmaVersion'>;
type AstOptions = Omit<Options, "ecmaVersion">;
type EntityRefactorResponse = {
isSuccess: boolean;
@ -129,7 +129,7 @@ const isMemberExpressionNode = (node: Node): node is MemberExpressionNode => {
};
export const isVariableDeclarator = (
node: Node
node: Node,
): node is VariableDeclaratorNode => {
return node.type === NodeTypes.VariableDeclarator;
};
@ -142,7 +142,7 @@ const isFunctionExpression = (node: Node): node is FunctionExpressionNode => {
return node.type === NodeTypes.FunctionExpression;
};
const isArrowFunctionExpression = (
node: Node
node: Node,
): node is ArrowFunctionExpressionNode => {
return node.type === NodeTypes.ArrowFunctionExpression;
};
@ -164,7 +164,7 @@ export const isPropertyNode = (node: Node): node is PropertyNode => {
};
export const isPropertyAFunctionNode = (
node: Node
node: Node,
): node is ArrowFunctionExpressionNode | FunctionExpressionNode => {
return (
node.type === NodeTypes.ArrowFunctionExpression ||
@ -189,14 +189,21 @@ const wrapCode = (code: string) => {
`;
};
//Tech-debt: should upgrade this to better logic
//Used slice for a quick resolve of critical bug
const unwrapCode = (code: string) => {
let unwrapedCode = code.slice(32);
return unwrapedCode.slice(0, -10);
};
const getFunctionalParamNamesFromNode = (
node:
| FunctionDeclarationNode
| FunctionExpressionNode
| ArrowFunctionExpressionNode
| ArrowFunctionExpressionNode,
) => {
return Array.from(getFunctionalParamsFromNode(node)).map(
(functionalParam) => functionalParam.paramName
(functionalParam) => functionalParam.paramName,
);
};
@ -204,7 +211,7 @@ const getFunctionalParamNamesFromNode = (
// Since this will be used by both the server and the client, we want to prevent regeneration of ast
// for the the same code snippet
export const getAST = memoize((code: string, options?: AstOptions) =>
parse(code, { ...options, ecmaVersion: ECMA_VERSION })
parse(code, { ...options, ecmaVersion: ECMA_VERSION }),
);
/**
@ -223,9 +230,9 @@ export interface IdentifierInfo {
export const extractIdentifierInfoFromCode = (
code: string,
evaluationVersion: number,
invalidIdentifiers?: Record<string, unknown>
invalidIdentifiers?: Record<string, unknown>,
): IdentifierInfo => {
let ast: Node = { end: 0, start: 0, type: '' };
let ast: Node = { end: 0, start: 0, type: "" };
try {
const sanitizedScript = sanitizeScript(code, evaluationVersion);
/* wrapCode - Wrapping code in a function, since all code/script get wrapped with a function during evaluation.
@ -275,13 +282,14 @@ export const entityRefactorFromCode = (
newName: string,
isJSObject: boolean,
evaluationVersion: number,
invalidIdentifiers?: Record<string, unknown>
invalidIdentifiers?: Record<string, unknown>,
): EntityRefactorResponse => {
//Sanitizing leads to removal of special charater.
//Hence we are not sanatizing the script. Fix(#18492)
//If script is a JSObject then replace export default to decalartion.
if (isJSObject) script = jsObjectToCode(script);
let ast: Node = { end: 0, start: 0, type: '' };
else script = wrapCode(script);
let ast: Node = { end: 0, start: 0, type: "" };
//Copy of script to refactor
let refactorScript = script;
//Difference in length of oldName and newName
@ -299,10 +307,10 @@ export const entityRefactorFromCode = (
identifierList,
}: NodeList = ancestorWalk(ast);
const identifierArray = Array.from(
identifierList
identifierList,
) as Array<RefactorIdentifierNode>;
//To handle if oldName has property ("JSObject.myfunc")
const oldNameArr = oldName.split('.');
const oldNameArr = oldName.split(".");
const referencesArr = Array.from(references).filter((reference) => {
// To remove references derived from declared variables and function params,
// We extract the topLevelIdentifier Eg. Api1.name => Api1
@ -318,7 +326,7 @@ export const entityRefactorFromCode = (
if (identifier.name === oldNameArr[0]) {
let index = 0;
while (index < referencesArr.length) {
if (identifier.name === referencesArr[index].split('.')[0]) {
if (identifier.name === referencesArr[index].split(".")[0]) {
//Replace the oldName by newName
//Get start index from node and get subarray from index 0 till start
//Append above with new name
@ -356,6 +364,7 @@ export const entityRefactorFromCode = (
});
//If script is a JSObject then revert decalartion to export default.
if (isJSObject) refactorScript = jsCodeToObject(refactorScript);
else refactorScript = unwrapCode(refactorScript);
return {
isSuccess: true,
body: { script: refactorScript, refactorCount },
@ -363,7 +372,7 @@ export const entityRefactorFromCode = (
} catch (e) {
if (e instanceof SyntaxError) {
// Syntax error. Ignore and return empty list
return { isSuccess: false, body: { error: 'Syntax Error' } };
return { isSuccess: false, body: { error: "Syntax Error" } };
}
throw e;
}
@ -376,7 +385,7 @@ export const getFunctionalParamsFromNode = (
| FunctionDeclarationNode
| FunctionExpressionNode
| ArrowFunctionExpressionNode,
needValue = false
needValue = false,
): Set<functionParam> => {
const functionalParams = new Set<functionParam>();
node.params.forEach((paramNode) => {
@ -405,7 +414,7 @@ export const getFunctionalParamsFromNode = (
const constructFinalMemberExpIdentifier = (
node: MemberExpressionNode,
child = ''
child = "",
): string => {
const propertyAccessor = getPropertyAccessor(node.property);
if (isIdentifierNode(node.object)) {
@ -461,12 +470,12 @@ export interface MemberExpressionData {
export const extractInvalidTopLevelMemberExpressionsFromCode = (
code: string,
data: Record<string, any>,
evaluationVersion: number
evaluationVersion: number,
): MemberExpressionData[] => {
const invalidTopLevelMemberExpressions = new Set<MemberExpressionData>();
const variableDeclarations = new Set<string>();
let functionalParams = new Set<string>();
let ast: Node = { end: 0, start: 0, type: '' };
let ast: Node = { end: 0, start: 0, type: "" };
try {
const sanitizedScript = sanitizeScript(code, evaluationVersion);
const wrappedCode = wrapCode(sanitizedScript);
@ -539,7 +548,7 @@ export const extractInvalidTopLevelMemberExpressionsFromCode = (
});
const invalidTopLevelMemberExpressionsArray = Array.from(
invalidTopLevelMemberExpressions
invalidTopLevelMemberExpressions,
).filter((MemberExpression) => {
return !(
variableDeclarations.has(MemberExpression.object.name) ||
@ -614,7 +623,7 @@ const ancestorWalk = (ast: Node): NodeList => {
// For MemberExpression Nodes, we will construct a final reference string and then add
// it to the references list
const memberExpIdentifier = constructFinalMemberExpIdentifier(
candidateTopLevelNode
candidateTopLevelNode,
);
references.add(memberExpIdentifier);
}
@ -671,5 +680,5 @@ const jsObjectToCode = (script: string) => {
//Revert the string replacement from 'jsObjectToCode'.
//variable declaration is replaced back by export default.
const jsCodeToObject = (script: string) => {
return script.replace(jsObjectDeclaration, 'export default');
return script.replace(jsObjectDeclaration, "export default");
};