PromucFlow_constructor/app/client/packages/ast/src/peekOverlay/index.test.ts
Anand Srinivasan 9dd015a1e6
feat: peek overlay nested properties + perf improvements (#23414)
Fixes #23057
Fixes #23054

## Description
TL;DR Added support for peeking on nested properties. e.g.
`Api1.data[0].id`.

This won't work when:
-  local variables are involved in the expression. 
e.g. `Api1.data[x].id` won't support peeking at the variable `[x]` or
anything after that.
- library code is involved e.g. `moment`, `_` etc...
- when functions are called. e.g. Api1.data[0].id.toFixed()

Because these cases requires evaluation.

<img width="355" alt="image"
src="https://github.com/appsmithorg/appsmith/assets/66776129/d09d1f0d-1692-46f5-8ec1-592f4fe75f7a">

#### Media (old vs new)
https://www.loom.com/share/dedcf113439c4ee2a19028acca54045e




## Performance improvements:
- Use AST to identify expressions instead marking text manually.
- This reduces the number of markers we process (~ half).

- Before

![image](https://github.com/appsmithorg/appsmith/assets/66776129/bb16ac6b-46dd-4e39-8524-e4f4fa2c3243)

- After

![image](https://github.com/appsmithorg/appsmith/assets/66776129/28f0f209-5437-4718-a74a-f025c576afda)

- AST logs
https://www.loom.com/share/ddde93233cc8470ea04309d8a8332240

#### Type of change
- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)

## Testing
>
#### How Has This Been Tested?
- [x] Manual
- [x] Jest
- [x] Cypress
>
>
#### Test Plan
https://github.com/appsmithorg/TestSmith/issues/2402

#### Issues raised during DP testing

https://github.com/appsmithorg/appsmith/pull/23414#issuecomment-1553164908

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


#### QA activity:
- [x] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Test-plan-implementation#speedbreaker-features-to-consider-for-every-change)
have been covered
- [x] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans/_edit#areas-of-interest)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [x] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [x] Cypress test cases have been added and approved by SDET/manual QA
- [x] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
2023-05-26 17:12:10 +05:30

193 lines
5.4 KiB
TypeScript

import { SourceType } from "../constants/ast";
import { PeekOverlayExpressionIdentifier } from "./index";
describe("extractExpressionAtPositionWholeDoc", () => {
const scriptIdentifier = new PeekOverlayExpressionIdentifier({
sourceType: SourceType.script,
});
const jsObjectIdentifier = new PeekOverlayExpressionIdentifier({
sourceType: SourceType.module,
thisExpressionReplacement: "JsObject",
});
const checkExpressionAtScript = async (
pos: number,
resultString?: string,
) => {
let result;
try {
result = await scriptIdentifier.extractExpressionAtPosition(pos);
} catch (e) {
expect(e).toBe(
"PeekOverlayExpressionIdentifier - No expression found at position",
);
}
expect(result).toBe(resultString);
};
const checkExpressionAtJsObject = async (
pos: number,
resultString?: string,
) => {
let result;
try {
result = await jsObjectIdentifier.extractExpressionAtPosition(pos);
} catch (e) {
expect(e).toBe(
"PeekOverlayExpressionIdentifier - No expression found at position",
);
}
expect(result).toBe(resultString);
};
it("handles MemberExpressions", async () => {
// nested properties
scriptIdentifier.updateScript("Api1.data[0].id");
// at position 'A'
// 'A'pi1.data[0].id
checkExpressionAtScript(0, "Api1");
// Ap'i'1.data[0].id
checkExpressionAtScript(2, "Api1");
// Api1.'d'ata[0].id
checkExpressionAtScript(6, "Api1.data");
// Api1.data['0'].id
checkExpressionAtScript(11, "Api1.data[0]");
// Api1.data[0].i'd'
checkExpressionAtScript(14, "Api1.data[0].id");
// function call
// argument hover - Api1
scriptIdentifier.updateScript(`storeValue("abc", Api1.run)`);
checkExpressionAtScript(18, "Api1");
scriptIdentifier.updateScript("Api1.check.run()");
// Ap'i'1.check.run()
checkExpressionAtScript(2, "Api1");
// Api1.check.'r'un()
checkExpressionAtScript(12, "Api1.check.run");
// local varibles are filtered
scriptIdentifier.updateScript("Api1.check.data[x].id");
// Api1
checkExpressionAtScript(2, "Api1");
// check
checkExpressionAtScript(7, "Api1.check");
// data
checkExpressionAtScript(12, "Api1.check.data");
// x
checkExpressionAtScript(16);
// id
checkExpressionAtScript(19);
});
it("handles ExpressionStatements", async () => {
// simple statement
scriptIdentifier.updateScript("Api1");
// Ap'i'1
checkExpressionAtScript(2, "Api1");
// function call
scriptIdentifier.updateScript(`storeValue("abc", 123)`);
// storeValue("a'b'c", 123)
checkExpressionAtScript(13);
// st'o'reValue("abc", 123) - functionality not supported now
checkExpressionAtScript(2);
// consequent function calls
scriptIdentifier.updateScript(`Api1.data[0].id.toFixed().toString()`);
// toFixed
checkExpressionAtScript(16, "Api1.data[0].id.toFixed");
// toString
checkExpressionAtScript(26);
// function call argument hover
scriptIdentifier.updateScript(`storeValue("abc", Api1)`);
checkExpressionAtScript(18, "Api1");
});
it("handles BinaryExpressions", async () => {
// binary expression
scriptIdentifier.updateScript(
`Api1.data.users[0].id === "myData test" ? "Yes" : "No"`,
);
// id
checkExpressionAtScript(19, "Api1.data.users[0].id");
// myData
checkExpressionAtScript(27);
// ?
checkExpressionAtScript(40);
// Yes
checkExpressionAtScript(43);
// :
checkExpressionAtScript(48);
// No
checkExpressionAtScript(51);
// hardcoded LHS
scriptIdentifier.updateScript(`"sample" === "myData test" ? "Yes" : "No"`);
// sample
checkExpressionAtScript(1);
// nested expressions
scriptIdentifier.updateScript(
`"sample" === "myData test" ? "nested" === "nested check" ? "Yes" : "No" : "No"`,
);
// nested
checkExpressionAtScript(31);
// nested check
checkExpressionAtScript(44);
// Yes
checkExpressionAtScript(61);
// No
checkExpressionAtScript(69);
});
it("handles JsObject cases", async () => {
jsObjectIdentifier.updateScript(JsObjectWithThisKeyword);
// this keyword cases
// this
checkExpressionAtJsObject(140, "JsObject");
// numArray
checkExpressionAtJsObject(159, "JsObject.numArray");
// objectArray
checkExpressionAtJsObject(180, "JsObject.objectArray");
// [0]
checkExpressionAtJsObject(183, "JsObject.objectArray[0]");
// x
checkExpressionAtJsObject(186, "JsObject.objectArray[0].x");
// 'x'
checkExpressionAtJsObject(208, "JsObject.objectData['x']");
// 'a'
checkExpressionAtJsObject(238, "JsObject.objectData['x']['a']");
// b
checkExpressionAtJsObject(243, "JsObject.objectData['x']['a'].b");
// await keyword cases
// resetWidget
checkExpressionAtJsObject(255);
// "Switch1"
checkExpressionAtJsObject(266);
// Api1
checkExpressionAtJsObject(287, "Api1");
// run
checkExpressionAtJsObject(292, "Api1.run");
});
});
const JsObjectWithThisKeyword = `export default {
numArray: [1, 2, 3],
objectArray: [ {x: 123}, { y: "123"} ],
objectData: { x: 123, y: "123" },
myFun1: async () => {
this;
this.numArray;
this.objectArray[0].x;
this.objectData["x"];
this.objectData["x"]["a"].b;
await resetWidget("Switch1");
await Api1.run();
},
}`;