diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.js
index 8eb164c36e..0c9b2f7049 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.js
@@ -308,8 +308,8 @@ describe("Theme validation usecases", function() {
cy.PublishtheApp();
//Bug Form backgroud colour reset in Publish mode
cy.get(formWidgetsPage.formD)
- .should("have.css", "background-color")
- .and("eq", "rgb(126, 34, 206)");
+ .should("have.css", "background-color")
+ .and("eq", "rgb(126, 34, 206)");
cy.get(".bp3-button:contains('Sub')")
.invoke("css", "background-color")
.then((CurrentBackgroudColor) => {
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/Button_onClickAction_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/Button_onClickAction_spec.js
index cab618268c..9d5b83b767 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/Button_onClickAction_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/Button_onClickAction_spec.js
@@ -129,6 +129,25 @@ describe("Button Widget Functionality", function() {
cy.get(widgetsPage.apiCallToast).should("have.text", "Success");
});
+ it("5. Toggle JS - Button-Call-SetTimeout Validation", function() {
+ //creating a query and calling it from the onClickAction of the button widget.
+ // Creating a mock query
+ cy.testJsontext(
+ "onclick",
+ "{{setTimeout(() => showAlert('Hello from setTimeout after 3 seconds'), 3000)}}",
+ );
+
+ cy.PublishtheApp();
+
+ // Clicking the button to verify the success message
+ cy.get(publishPage.buttonWidget).click();
+ cy.wait(3000);
+ cy.get(widgetsPage.apiCallToast).should(
+ "have.text",
+ "Hello from setTimeout after 3 seconds",
+ );
+ });
+
afterEach(() => {
cy.goToEditFromPublish();
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/timeout_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/timeout_spec.ts
new file mode 100644
index 0000000000..f6bd939da6
--- /dev/null
+++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/timeout_spec.ts
@@ -0,0 +1,218 @@
+import { ObjectsRegistry } from "../../../../support/Objects/Registry";
+const jsEditor = ObjectsRegistry.JSEditor;
+const agHelper = ObjectsRegistry.AggregateHelper;
+const locators = ObjectsRegistry.CommonLocators;
+const apiPage = ObjectsRegistry.ApiPage;
+const deployMode = ObjectsRegistry.DeployMode;
+
+describe("Tests setTimeout API", function() {
+ it("Executes showAlert after 3 seconds and uses default value", () => {
+ jsEditor.CreateJSObject(
+ `export default {
+ myVar1: [],
+ myVar2: {},
+ myFun1: (x = "default") => {
+ setTimeout(() => {
+ showAlert("Hello world - " + x);
+ }, 3000);
+ }
+ }`,
+ {
+ paste: true,
+ completeReplace: true,
+ toRun: false,
+ shouldCreateNewJSObj: true,
+ prettify: true,
+ },
+ );
+ agHelper.Sleep(2000);
+ jsEditor.RunJSObj();
+ agHelper.Sleep(3000);
+ agHelper.AssertContains("Hello world - default", "exist");
+ });
+ it("Executes all three alerts in parallel after 3 seconds", () => {
+ jsEditor.CreateJSObject(
+ `export default {
+ myVar1: [],
+ myVar2: {},
+ myFun1: (x = "default") => {
+ setTimeout(() => {
+ showAlert("Hello world - " + x);
+ }, 3000);
+ },
+ myFun2: () => {
+ this.myFun1(1)
+ this.myFun1(2)
+ this.myFun1(3)
+ }
+ }`,
+ {
+ paste: true,
+ completeReplace: true,
+ toRun: false,
+ shouldCreateNewJSObj: true,
+ prettify: true,
+ },
+ );
+ agHelper.Sleep(2000);
+ jsEditor.SelectFunctionDropdown("myFun2");
+ jsEditor.RunJSObj();
+ agHelper.Sleep(3000);
+ agHelper.AssertContains("Hello world - 1", "exist");
+ agHelper.AssertContains("Hello world - 2", "exist");
+ agHelper.AssertContains("Hello world - 3", "exist");
+ });
+ it("Resolves promise after 3 seconds and shows alert", () => {
+ jsEditor.CreateJSObject(
+ `export default {
+ myVar1: [],
+ myVar2: {},
+ myFun1: (x) => {
+ new Promise((res, rej) => setTimeout(() => res("resolved"), 3000)).then((res) => {
+ showAlert(res);
+ });
+ },
+ }`,
+ {
+ paste: true,
+ completeReplace: true,
+ toRun: false,
+ shouldCreateNewJSObj: true,
+ prettify: true,
+ },
+ );
+ agHelper.Sleep(2000);
+ jsEditor.RunJSObj();
+ agHelper.Sleep(3000);
+ agHelper.AssertContains("resolved");
+ });
+ it("verifies code execution order when using setTimeout", () => {
+ jsEditor.CreateJSObject(
+ `export default {
+ myVar1: [],
+ myVar2: {},
+ myFun1: (x) => {
+ console.log("Hey there!");
+ setTimeout(() => console.log("Working!"), 3000);
+ console.log("Bye!");
+ },
+ }`,
+ {
+ paste: true,
+ completeReplace: true,
+ toRun: false,
+ shouldCreateNewJSObj: true,
+ prettify: true,
+ },
+ );
+ agHelper.Sleep(2000);
+ agHelper.GetNClick(locators._debuggerIcon);
+ agHelper.GetNClick(jsEditor._logsTab);
+ jsEditor.RunJSObj();
+ agHelper.GetNAssertContains(locators._debuggerLogMessage, "Hey there!");
+ agHelper.GetNAssertContains(locators._debuggerLogMessage, "Bye!");
+ agHelper.GetNAssertContains(
+ locators._debuggerLogMessage,
+ "Working!",
+ "not.exist",
+ 100,
+ );
+ agHelper.Sleep(3000);
+ agHelper.GetNAssertContains(locators._debuggerLogMessage, "Working!");
+ });
+
+ it("Resolves promise after 3 seconds and shows alert", () => {
+ jsEditor.CreateJSObject(
+ `export default {
+ myVar1: [],
+ myVar2: {},
+ myFun1: (x) => {
+ new Promise((res, rej) => setTimeout(() => res("resolved"), 3000)).then((res) => {
+ showAlert(res);
+ });
+ },
+ }`,
+ {
+ paste: true,
+ completeReplace: true,
+ toRun: false,
+ shouldCreateNewJSObj: true,
+ prettify: true,
+ },
+ );
+ agHelper.Sleep(2000);
+ jsEditor.RunJSObj();
+ agHelper.Sleep(3000);
+ agHelper.AssertContains("resolved");
+ });
+ it("Access to args passed into success/error callback functions in API.run when using setTimeout", () => {
+ apiPage.CreateAndFillApi("https://mock-api.appsmith.com/users");
+ jsEditor.CreateJSObject(
+ `export default {
+ myVar1: [],
+ myVar2: {},
+ myFun1: (x) => {
+ Api1.run((res) => {
+ setTimeout(() => {
+ showAlert(res.users[0].name);
+ }, 3000);
+ }, (error) => {
+ console.log(error);
+ });
+ },
+ myFun2: (x) => {
+ Api1.run().then((res) => {
+ setTimeout(() => {
+ showAlert(res.users[0].name);
+ }, 3000);
+ });
+ }
+ }`,
+ {
+ paste: true,
+ completeReplace: true,
+ toRun: false,
+ shouldCreateNewJSObj: true,
+ prettify: true,
+ },
+ );
+ jsEditor.RenameJSObjFromPane("Timeouts");
+ agHelper.Sleep(2000);
+ jsEditor.RunJSObj();
+ agHelper.Sleep(3000);
+ agHelper.AssertContains("Barty Crouch");
+ agHelper.Sleep(2000);
+ jsEditor.SelectFunctionDropdown("myFun2");
+ jsEditor.RunJSObj();
+ agHelper.Sleep(3000);
+ agHelper.AssertContains("Barty Crouch");
+ });
+ it("Verifies whether setTimeout executes on page load", () => {
+ apiPage.CreateAndFillApi("https://mock-api.appsmith.com/users");
+ jsEditor.CreateJSObject(
+ `export default {
+ myVar1: [],
+ myVar2: {},
+ myFun1: (x) => {
+ setTimeout(() => {
+ Api1.run().then(() => showAlert("Success!"));
+ Timeouts.myFun2();
+ }, 3000)
+ },
+ }`,
+ {
+ paste: true,
+ completeReplace: true,
+ toRun: false,
+ shouldCreateNewJSObj: true,
+ prettify: true,
+ },
+ );
+ jsEditor.EnableDisableAsyncFuncSettings("myFun1", true, false);
+ deployMode.DeployApp();
+ agHelper.Sleep(3000);
+ agHelper.AssertContains("Success!");
+ agHelper.Sleep(3000);
+ agHelper.AssertContains("Barty Crouch");
+ });
+});
diff --git a/app/client/cypress/support/Pages/AggregateHelper.ts b/app/client/cypress/support/Pages/AggregateHelper.ts
index ff067468ea..50c0d62817 100644
--- a/app/client/cypress/support/Pages/AggregateHelper.ts
+++ b/app/client/cypress/support/Pages/AggregateHelper.ts
@@ -893,8 +893,9 @@ export class AggregateHelper {
selector: ElementType,
text: string | RegExp,
exists: "exist" | "not.exist" = "exist",
+ timeout?: number,
) {
- return this.GetElement(selector)
+ return this.GetElement(selector, timeout)
.contains(text)
.should(exists);
}
diff --git a/app/client/src/constants/defs/browser.json b/app/client/src/constants/defs/browser.json
index d0a93d4764..9b5942b7c0 100644
--- a/app/client/src/constants/defs/browser.json
+++ b/app/client/src/constants/defs/browser.json
@@ -1,245 +1,255 @@
{
- "!name": "browser",
- "console": {
- "assert": {
- "!type": "fn(assertion: bool, text: string)",
- "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.assert",
- "!doc": "Writes an error message to the console if the assertion is false."
- },
- "clear": {
- "!type": "fn()",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Console/clear",
- "!doc": " Clear the console."
- },
- "count": {
- "!type": "fn(label?: string)",
- "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.count",
- "!doc": "Logs the number of times that this particular call to count() has been called."
- },
- "debug": "console.log",
- "dir": {
- "!type": "fn(object: ?)",
- "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.dir",
- "!doc": "Displays an interactive list of the properties of the specified JavaScript object."
- },
- "error": {
- "!type": "fn(...msg: ?)",
- "!url": "https://developer.mozilla.org/en/docs/DOM/console.error",
- "!doc": "Outputs an error message to the Web Console."
- },
- "group": {
- "!type": "fn(label?: string)",
- "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.group",
- "!doc": "Creates a new inline group in the Web Console log."
- },
- "groupCollapsed": {
- "!type": "fn(label?: string)",
- "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.groupCollapsed",
- "!doc": "Creates a new inline group in the Web Console log."
- },
- "groupEnd": {
- "!type": "fn()",
- "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.groupEnd",
- "!doc": "Exits the current inline group in the Web Console."
- },
- "info": {
- "!type": "fn(...msg: ?)",
- "!url": "https://developer.mozilla.org/en/docs/DOM/console.info",
- "!doc": "Outputs an informational message to the Web Console."
- },
- "log": {
- "!type": "fn(...msg: ?)",
- "!url": "https://developer.mozilla.org/en/docs/DOM/console.log",
- "!doc": "Outputs a message to the Web Console."
- },
- "table": {
- "!type": "fn(data: []|?, columns?: [])",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Console/table",
- "!doc": " Displays tabular data as a table."
- },
- "time": {
- "!type": "fn(label: string)",
- "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.time",
- "!doc": "Starts a timer you can use to track how long an operation takes."
- },
- "timeEnd": {
- "!type": "fn(label: string)",
- "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.timeEnd",
- "!doc": "Stops a timer that was previously started by calling console.time()."
- },
- "trace": {
- "!type": "fn()",
- "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.trace",
- "!doc": "Outputs a stack trace to the Web Console."
- },
- "warn": {
- "!type": "fn(...msg: ?)",
- "!url": "https://developer.mozilla.org/en/docs/DOM/console.warn",
- "!doc": "Outputs a warning message to the Web Console."
- },
- "!url": "https://developer.mozilla.org/en/docs/Web/API/Console",
- "!doc": "The console object provides access to the browser's debugging console. The specifics of how it works vary from browser to browser, but there is a de facto set of features that are typically provided."
- },
- "crypto": {
- "getRandomValues": {
- "!type": "fn([number])",
- "!url": "https://developer.mozilla.org/en/docs/DOM/window.crypto.getRandomValues",
- "!doc": "This methods lets you get cryptographically random values."
- },
- "!url": "https://developer.mozilla.org/en/docs/DOM/window.crypto.getRandomValues",
- "!doc": "This methods lets you get cryptographically random values."
- },
- "Blob": {
- "!type": "fn(parts: [?], options?: ?)",
- "prototype": {
- "size": {
- "!type": "number",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Blob/size",
- "!doc": "The size, in bytes, of the data contained in the Blob object. Read only."
- },
- "type": {
- "!type": "string",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Blob/type",
- "!doc": "An ASCII-encoded string, in all lower case, indicating the MIME type of the data contained in the Blob. If the type is unknown, this string is empty. Read only."
- },
- "slice": {
- "!type": "fn(start: number, end?: number, type?: string) -> +Blob",
- "!url": "https://developer.mozilla.org/en/docs/DOM/Blob",
- "!doc": "Returns a new Blob object containing the data in the specified range of bytes of the source Blob."
- }
- },
- "!url": "https://developer.mozilla.org/en/docs/DOM/Blob",
- "!doc": "A Blob object represents a file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system."
- },
- "FileReader": {
- "!type": "fn()",
- "prototype": {
- "abort": {
- "!type": "fn()",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Aborts the read operation. Upon return, the readyState will be DONE."
- },
- "readAsArrayBuffer": {
- "!type": "fn(blob: +Blob)",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Starts reading the contents of the specified Blob, producing an ArrayBuffer."
- },
- "readAsBinaryString": {
- "!type": "fn(blob: +Blob)",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Starts reading the contents of the specified Blob, producing raw binary data."
- },
- "readAsDataURL": {
- "!type": "fn(blob: +Blob)",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Starts reading the contents of the specified Blob, producing a data: url."
- },
- "readAsText": {
- "!type": "fn(blob: +Blob, encoding?: string)",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Starts reading the contents of the specified Blob, producing a string."
- },
- "EMPTY": "number",
- "LOADING": "number",
- "DONE": "number",
- "error": {
- "!type": "?",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "The error that occurred while reading the file. Read only."
- },
- "readyState": {
- "!type": "number",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Indicates the state of the FileReader. This will be one of the State constants. Read only."
- },
- "result": {
- "!type": "?",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "The file's contents. This property is only valid after the read operation is complete, and the format of the data depends on which of the methods was used to initiate the read operation. Read only."
- },
- "onabort": {
- "!type": "?",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Called when the read operation is aborted."
- },
- "onerror": {
- "!type": "?",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Called when an error occurs."
- },
- "onload": {
- "!type": "?",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Called when the read operation is successfully completed."
- },
- "onloadend": {
- "!type": "?",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Called when the read is completed, whether successful or not. This is called after either onload or onerror."
- },
- "onloadstart": {
- "!type": "?",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Called when reading the data is about to begin."
- },
- "onprogress": {
- "!type": "?",
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "Called periodically while the data is being read."
- }
- },
- "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
- "!doc": "The FileReader object lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user's computer, using File or Blob objects to specify the file or data to read. File objects may be obtained from a FileList object returned as a result of a user selecting files using the element, from a drag and drop operation's DataTransfer object, or from the mozGetAsFile() API on an HTMLCanvasElement."
- },
- "FormData": {
- "!type": "fn()",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData",
- "prototype": {
- "append": {
- "!type": "fn(name: string, value: string|+Blob, filename?: string)",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/append",
- "!doc": "Appends a new value onto an existing key inside a FormData object, or adds the key if it does not already exist."
- },
- "delete": {
- "!type": "fn(name: string)",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/delete",
- "!doc": "Deletes a key/value pair from a FormData object."
- },
- "entries": {
- "!type": "fn() -> +iter[:t=[number, string|+Blob]]",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries",
- "!doc": "Returns an iterator allowing to go through all key/value pairs contained in this object."
- },
- "get": {
- "!type": "fn(name: string) -> string|+Blob",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/get",
- "!doc": "Returns the first value associated with a given key from within a FormData object."
- },
- "getAll": {
- "!type": "fn(name: string) -> [string|+Blob]",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/getAll",
- "!doc": "Returns an array of all the values associated with a given key from within a FormData."
- },
- "has": {
- "!type": "fn(name: string) -> bool",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/has",
- "!doc": "Returns a boolean stating whether a FormData object contains a certain key/value pair."
- },
- "set": {
- "!type": "fn(name: string, value: string|+Blob, filename?: string)",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/set",
- "!doc": "Sets a new value for an existing key inside a FormData object, or adds the key/value if it does not already exist."
- },
- "keys": {
- "!type": "fn() -> +iter[:t=number]",
- "!doc": "Returns an iterator allowing to go through all keys of the key/value pairs contained in this object.",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/keys"
- },
- "values": {
- "!type": "fn() -> +iter[:t=string|blob]",
- "!doc": "Returns an iterator allowing to go through all values of the key/value pairs contained in this object.",
- "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/values"
- }
- }
- }
+ "!name": "browser",
+ "console": {
+ "assert": {
+ "!type": "fn(assertion: bool, text: string)",
+ "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.assert",
+ "!doc": "Writes an error message to the console if the assertion is false."
+ },
+ "clear": {
+ "!type": "fn()",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Console/clear",
+ "!doc": " Clear the console."
+ },
+ "count": {
+ "!type": "fn(label?: string)",
+ "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.count",
+ "!doc": "Logs the number of times that this particular call to count() has been called."
+ },
+ "debug": "console.log",
+ "dir": {
+ "!type": "fn(object: ?)",
+ "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.dir",
+ "!doc": "Displays an interactive list of the properties of the specified JavaScript object."
+ },
+ "error": {
+ "!type": "fn(...msg: ?)",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/console.error",
+ "!doc": "Outputs an error message to the Web Console."
+ },
+ "group": {
+ "!type": "fn(label?: string)",
+ "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.group",
+ "!doc": "Creates a new inline group in the Web Console log."
+ },
+ "groupCollapsed": {
+ "!type": "fn(label?: string)",
+ "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.groupCollapsed",
+ "!doc": "Creates a new inline group in the Web Console log."
+ },
+ "groupEnd": {
+ "!type": "fn()",
+ "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.groupEnd",
+ "!doc": "Exits the current inline group in the Web Console."
+ },
+ "info": {
+ "!type": "fn(...msg: ?)",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/console.info",
+ "!doc": "Outputs an informational message to the Web Console."
+ },
+ "log": {
+ "!type": "fn(...msg: ?)",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/console.log",
+ "!doc": "Outputs a message to the Web Console."
+ },
+ "table": {
+ "!type": "fn(data: []|?, columns?: [])",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Console/table",
+ "!doc": " Displays tabular data as a table."
+ },
+ "time": {
+ "!type": "fn(label: string)",
+ "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.time",
+ "!doc": "Starts a timer you can use to track how long an operation takes."
+ },
+ "timeEnd": {
+ "!type": "fn(label: string)",
+ "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.timeEnd",
+ "!doc": "Stops a timer that was previously started by calling console.time()."
+ },
+ "trace": {
+ "!type": "fn()",
+ "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.trace",
+ "!doc": "Outputs a stack trace to the Web Console."
+ },
+ "warn": {
+ "!type": "fn(...msg: ?)",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/console.warn",
+ "!doc": "Outputs a warning message to the Web Console."
+ },
+ "!url": "https://developer.mozilla.org/en/docs/Web/API/Console",
+ "!doc": "The console object provides access to the browser's debugging console. The specifics of how it works vary from browser to browser, but there is a de facto set of features that are typically provided."
+ },
+ "crypto": {
+ "getRandomValues": {
+ "!type": "fn([number])",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/window.crypto.getRandomValues",
+ "!doc": "This methods lets you get cryptographically random values."
+ },
+ "!url": "https://developer.mozilla.org/en/docs/DOM/window.crypto.getRandomValues",
+ "!doc": "This methods lets you get cryptographically random values."
+ },
+ "Blob": {
+ "!type": "fn(parts: [?], options?: ?)",
+ "prototype": {
+ "size": {
+ "!type": "number",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Blob/size",
+ "!doc": "The size, in bytes, of the data contained in the Blob object. Read only."
+ },
+ "type": {
+ "!type": "string",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Blob/type",
+ "!doc": "An ASCII-encoded string, in all lower case, indicating the MIME type of the data contained in the Blob. If the type is unknown, this string is empty. Read only."
+ },
+ "slice": {
+ "!type": "fn(start: number, end?: number, type?: string) -> +Blob",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/Blob",
+ "!doc": "Returns a new Blob object containing the data in the specified range of bytes of the source Blob."
+ }
+ },
+ "!url": "https://developer.mozilla.org/en/docs/DOM/Blob",
+ "!doc": "A Blob object represents a file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system."
+ },
+ "FileReader": {
+ "!type": "fn()",
+ "prototype": {
+ "abort": {
+ "!type": "fn()",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Aborts the read operation. Upon return, the readyState will be DONE."
+ },
+ "readAsArrayBuffer": {
+ "!type": "fn(blob: +Blob)",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Starts reading the contents of the specified Blob, producing an ArrayBuffer."
+ },
+ "readAsBinaryString": {
+ "!type": "fn(blob: +Blob)",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Starts reading the contents of the specified Blob, producing raw binary data."
+ },
+ "readAsDataURL": {
+ "!type": "fn(blob: +Blob)",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Starts reading the contents of the specified Blob, producing a data: url."
+ },
+ "readAsText": {
+ "!type": "fn(blob: +Blob, encoding?: string)",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Starts reading the contents of the specified Blob, producing a string."
+ },
+ "EMPTY": "number",
+ "LOADING": "number",
+ "DONE": "number",
+ "error": {
+ "!type": "?",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "The error that occurred while reading the file. Read only."
+ },
+ "readyState": {
+ "!type": "number",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Indicates the state of the FileReader. This will be one of the State constants. Read only."
+ },
+ "result": {
+ "!type": "?",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "The file's contents. This property is only valid after the read operation is complete, and the format of the data depends on which of the methods was used to initiate the read operation. Read only."
+ },
+ "onabort": {
+ "!type": "?",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Called when the read operation is aborted."
+ },
+ "onerror": {
+ "!type": "?",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Called when an error occurs."
+ },
+ "onload": {
+ "!type": "?",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Called when the read operation is successfully completed."
+ },
+ "onloadend": {
+ "!type": "?",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Called when the read is completed, whether successful or not. This is called after either onload or onerror."
+ },
+ "onloadstart": {
+ "!type": "?",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Called when reading the data is about to begin."
+ },
+ "onprogress": {
+ "!type": "?",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "Called periodically while the data is being read."
+ }
+ },
+ "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+ "!doc": "The FileReader object lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user's computer, using File or Blob objects to specify the file or data to read. File objects may be obtained from a FileList object returned as a result of a user selecting files using the element, from a drag and drop operation's DataTransfer object, or from the mozGetAsFile() API on an HTMLCanvasElement."
+ },
+ "FormData": {
+ "!type": "fn()",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData",
+ "prototype": {
+ "append": {
+ "!type": "fn(name: string, value: string|+Blob, filename?: string)",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/append",
+ "!doc": "Appends a new value onto an existing key inside a FormData object, or adds the key if it does not already exist."
+ },
+ "delete": {
+ "!type": "fn(name: string)",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/delete",
+ "!doc": "Deletes a key/value pair from a FormData object."
+ },
+ "entries": {
+ "!type": "fn() -> +iter[:t=[number, string|+Blob]]",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries",
+ "!doc": "Returns an iterator allowing to go through all key/value pairs contained in this object."
+ },
+ "get": {
+ "!type": "fn(name: string) -> string|+Blob",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/get",
+ "!doc": "Returns the first value associated with a given key from within a FormData object."
+ },
+ "getAll": {
+ "!type": "fn(name: string) -> [string|+Blob]",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/getAll",
+ "!doc": "Returns an array of all the values associated with a given key from within a FormData."
+ },
+ "has": {
+ "!type": "fn(name: string) -> bool",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/has",
+ "!doc": "Returns a boolean stating whether a FormData object contains a certain key/value pair."
+ },
+ "set": {
+ "!type": "fn(name: string, value: string|+Blob, filename?: string)",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/set",
+ "!doc": "Sets a new value for an existing key inside a FormData object, or adds the key/value if it does not already exist."
+ },
+ "keys": {
+ "!type": "fn() -> +iter[:t=number]",
+ "!doc": "Returns an iterator allowing to go through all keys of the key/value pairs contained in this object.",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/keys"
+ },
+ "values": {
+ "!type": "fn() -> +iter[:t=string|blob]",
+ "!doc": "Returns an iterator allowing to go through all values of the key/value pairs contained in this object.",
+ "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/values"
+ }
+ }
+ },
+ "setTimeout": {
+ "!type": "fn(f: fn(), ms: number) -> number",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/window.setTimeout",
+ "!doc": "Calls a function or executes a code snippet after specified delay."
+ },
+ "clearTimeout": {
+ "!type": "fn(timeout: number)",
+ "!url": "https://developer.mozilla.org/en/docs/DOM/window.clearTimeout",
+ "!doc": "Clears the delay set by window.setTimeout()."
+ }
}
\ No newline at end of file
diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts
index f7571e7943..17885697e1 100644
--- a/app/client/src/sagas/EvaluationsSaga.ts
+++ b/app/client/src/sagas/EvaluationsSaga.ts
@@ -273,6 +273,7 @@ export function* evaluateAndExecuteDynamicTrigger(
callbackData,
globalContext,
eventType,
+ triggerMeta,
},
);
@@ -353,6 +354,18 @@ export function* executeDynamicTriggerRequest(
mainThreadRequestChannel,
);
log.debug({ requestData });
+ if (requestData?.logs) {
+ const { eventType, triggerMeta } = requestData;
+ yield call(
+ storeLogs,
+ requestData.logs,
+ triggerMeta?.source?.name || triggerMeta?.triggerPropertyName || "",
+ eventType === EventType.ON_JS_FUNCTION_EXECUTE
+ ? ENTITY_TYPE.JSACTION
+ : ENTITY_TYPE.WIDGET,
+ triggerMeta?.source?.id || "",
+ );
+ }
if (requestData?.trigger) {
// if we have found a trigger, we need to execute it and respond back
log.debug({ trigger: requestData.trigger });
@@ -450,7 +463,13 @@ export function* executeFunction(
evaluateAndExecuteDynamicTrigger,
functionCall,
EventType.ON_JS_FUNCTION_EXECUTE,
- {},
+ {
+ source: {
+ id: collectionId,
+ name: `${collectionName}.${action.name}`,
+ },
+ triggerPropertyName: `${collectionName}.${action.name}`,
+ },
);
} catch (e) {
if (e instanceof UncaughtPromiseError) {
diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts
index 4afd6c8c45..db202d2936 100644
--- a/app/client/src/utils/DynamicBindingUtils.ts
+++ b/app/client/src/utils/DynamicBindingUtils.ts
@@ -314,7 +314,6 @@ export const isThemeBoundProperty = (
};
export const unsafeFunctionForEval = [
- "setTimeout",
"fetch",
"setInterval",
"clearInterval",
diff --git a/app/client/src/workers/TimeoutOverride.ts b/app/client/src/workers/TimeoutOverride.ts
new file mode 100644
index 0000000000..3d5c9f2d3c
--- /dev/null
+++ b/app/client/src/workers/TimeoutOverride.ts
@@ -0,0 +1,40 @@
+import { createGlobalData } from "./evaluate";
+import { dataTreeEvaluator } from "./evaluation.worker";
+
+export const _internalSetTimeout = self.setTimeout;
+export const _internalClearTimeout = self.clearTimeout;
+
+export default function overrideTimeout() {
+ Object.defineProperty(self, "setTimeout", {
+ writable: true,
+ configurable: true,
+ value: function(cb: (...args: any) => any, delay: number, ...args: any) {
+ if (!self.ALLOW_ASYNC) {
+ self.IS_ASYNC = true;
+ throw new Error("Async function called in a sync field");
+ }
+ const globalData = createGlobalData({
+ dataTree: dataTreeEvaluator?.evalTree || {},
+ resolvedFunctions: dataTreeEvaluator?.resolvedFunctions || {},
+ isTriggerBased: true,
+ });
+ return _internalSetTimeout(
+ function(...args: any) {
+ self.ALLOW_ASYNC = true;
+ Object.assign(self, globalData);
+ cb(...args);
+ },
+ delay,
+ ...args,
+ );
+ },
+ });
+
+ Object.defineProperty(self, "clearTimeout", {
+ writable: true,
+ configurable: true,
+ value: function(timerId: number) {
+ return _internalClearTimeout(timerId);
+ },
+ });
+}
diff --git a/app/client/src/workers/UserLog.ts b/app/client/src/workers/UserLog.ts
index 68067a0b63..716cd8aad3 100644
--- a/app/client/src/workers/UserLog.ts
+++ b/app/client/src/workers/UserLog.ts
@@ -1,126 +1,116 @@
import { uuid4 } from "@sentry/utils";
+import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import { LogObject, Methods, Severity } from "entities/AppsmithConsole";
-import { klona } from "klona/lite";
import moment from "moment";
+import { TriggerMeta } from "sagas/ActionExecution/ActionExecutionSagas";
+import { _internalClearTimeout, _internalSetTimeout } from "./TimeoutOverride";
class UserLog {
- constructor() {
- this.initiate();
- }
+ private flushLogsTimerDelay = 0;
private logs: LogObject[] = [];
- // initiates the log object with the default methods and their overrides
- private initiate() {
+ private flushLogTimerId: number | undefined;
+ private requestInfo: {
+ requestId?: string;
+ eventType?: EventType;
+ triggerMeta?: TriggerMeta;
+ } | null = null;
+
+ public setCurrentRequestInfo(requestInfo: {
+ requestId?: string;
+ eventType?: EventType;
+ triggerMeta?: TriggerMeta;
+ }) {
+ this.requestInfo = requestInfo;
+ }
+
+ private resetFlushTimer() {
+ if (this.flushLogTimerId) _internalClearTimeout(this.flushLogTimerId);
+ this.flushLogTimerId = _internalSetTimeout(() => {
+ const logs = this.flushLogs();
+ self.postMessage({
+ promisified: true,
+ responseData: {
+ logs,
+ eventType: this.requestInfo?.eventType,
+ triggerMeta: this.requestInfo?.triggerMeta,
+ },
+ requestId: this.requestInfo?.requestId,
+ });
+ }, this.flushLogsTimerDelay);
+ }
+
+ private saveLog(method: Methods, data: any[]) {
+ const parsed = this.parseLogs(method, data);
+ this.logs.push(parsed);
+ this.resetFlushTimer();
+ }
+
+ public overrideConsoleAPI() {
const { debug, error, info, log, table, warn } = console;
console = {
...console,
table: (...args: any) => {
table.call(this, args);
- const parsed = this.parseLogs("table", args);
- if (parsed) {
- this.logs.push(parsed);
- }
- return;
+ this.saveLog("table", args);
},
error: (...args: any) => {
error.apply(this, args);
- const parsed = this.parseLogs("error", args);
- if (parsed) {
- this.logs.push(parsed);
- }
- return;
+ this.saveLog("error", args);
},
log: (...args: any) => {
log.apply(this, args);
- const parsed = this.parseLogs("log", args);
- if (parsed) {
- this.logs.push(parsed);
- }
- return;
+ this.saveLog("log", args);
},
debug: (...args: any) => {
debug.apply(this, args);
- const parsed = this.parseLogs("debug", args);
- if (parsed) {
- this.logs.push(parsed);
- }
- return;
+ this.saveLog("debug", args);
},
warn: (...args: any) => {
warn.apply(this, args);
- const parsed = this.parseLogs("warn", args);
- if (parsed) {
- this.logs.push(parsed);
- }
- return;
+ this.saveLog("warn", args);
},
info: (...args: any) => {
info.apply(this, args);
- const parsed = this.parseLogs("info", args);
- if (parsed) {
- this.logs.push(parsed);
- }
- return;
+ this.saveLog("info", args);
},
};
}
- public getTimestamp() {
- return moment().format("hh:mm:ss");
- }
- public replaceFunctionWithNamesFromObjects(data: any) {
- if (typeof data === "object") {
- for (const key in data) {
- if (typeof data[key] === "function") {
- data[key] = `func() ${data[key].name}`;
- } else if (data[key] instanceof Promise) {
- data[key] = "Promise";
- } else {
- this.replaceFunctionWithNamesFromObjects(data[key]);
- }
- }
- }
- return data;
+ private replaceFunctionWithNamesFromObjects(data: any) {
+ if (typeof data === "function") return `func() ${data.name}`;
+ if (!data || typeof data !== "object") return data;
+ if (data instanceof Promise) return "Promise";
+ const acc: any =
+ Object.prototype.toString.call(data) === "[object Array]" ? [] : {};
+ return Object.keys(data).reduce((acc, key) => {
+ acc[key] = this.replaceFunctionWithNamesFromObjects(data[key]);
+ return acc;
+ }, acc);
}
// iterates over the data and if data is object/array, then it will remove any functions from it
- public sanitizeData(data: any): any {
- let returnData = [];
-
+ private sanitizeData(data: any): any {
try {
- // cloning the object to avoid mutation
- const dataObject = klona(data);
- returnData = dataObject.map((item: any) => {
- if (typeof item === "object") {
- return this.replaceFunctionWithNamesFromObjects(item);
- }
-
- // if the item is a function, then remove it from the data and return it as name of the function
- if (typeof item === "function") {
- return `func() item.name`;
- }
- return item;
- });
+ const returnData = this.replaceFunctionWithNamesFromObjects(data);
+ return returnData;
} catch (e) {
- returnData = [`There was some error: ${e} ${JSON.stringify(data)}`];
+ return [`There was some error: ${e} ${JSON.stringify(data)}`];
}
- return returnData;
}
// returns the logs from the function execution after sanitising them and resets the logs object after that
- public flushLogs(softFlush = false): LogObject[] {
- const userLogs = this.logs;
- if (!softFlush) this.resetLogs();
- // sanitise the data key of the user logs
- const sanitisedLogs = userLogs.map((log) => {
+ public flushLogs(): LogObject[] {
+ const sanitisedLogs = this.logs.map((log) => {
return {
...log,
data: this.sanitizeData(log.data),
};
});
+ this.resetLogs();
return sanitisedLogs;
}
// parses the incoming log and converts it to the log object
public parseLogs(method: Methods, data: any[]): LogObject {
// Create an ID
const id = uuid4();
- const timestamp = this.getTimestamp();
+ const timestamp = moment().format("hh:mm:ss");
// Parse the methods
let output = data;
// For logs UI we only keep 3 levels of severity, info, warn, error
diff --git a/app/client/src/workers/evaluate.test.ts b/app/client/src/workers/evaluate.test.ts
index bb6ab67fd2..a8766265e8 100644
--- a/app/client/src/workers/evaluate.test.ts
+++ b/app/client/src/workers/evaluate.test.ts
@@ -110,24 +110,24 @@ describe("evaluateSync", () => {
expect(response.result).toBe("value");
});
it("disallows unsafe function calls", () => {
- const js = "setTimeout(() => {}, 100)";
+ const js = "setImmediate(() => {}, 100)";
const response = evaluate(js, dataTree, {}, false);
expect(response).toStrictEqual({
result: undefined,
logs: [],
errors: [
{
- errorMessage: "TypeError: setTimeout is not a function",
+ errorMessage: "ReferenceError: setImmediate is not defined",
errorType: "PARSE",
raw: `
function closedFunction () {
- const result = setTimeout(() => {}, 100)
+ const result = setImmediate(() => {}, 100)
return result;
}
closedFunction.call(THIS_CONTEXT)
`,
severity: "error",
- originalBinding: "setTimeout(() => {}, 100)",
+ originalBinding: "setImmediate(() => {}, 100)",
},
],
});
diff --git a/app/client/src/workers/evaluate.ts b/app/client/src/workers/evaluate.ts
index a1722f1622..b938ee515b 100644
--- a/app/client/src/workers/evaluate.ts
+++ b/app/client/src/workers/evaluate.ts
@@ -14,6 +14,8 @@ import { completePromise } from "workers/PromisifyAction";
import { ActionDescription } from "entities/DataTree/actionTriggers";
import userLogs from "./UserLog";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
+import overrideTimeout from "./TimeoutOverride";
+import { TriggerMeta } from "sagas/ActionExecution/ActionExecutionSagas";
export type EvalResult = {
result: any;
@@ -115,6 +117,8 @@ export function setupEvaluationEnvironment() {
// @ts-expect-error: Types are not available
self[func] = undefined;
});
+ userLogs.overrideConsoleAPI();
+ overrideTimeout();
}
const beginsWithLineBreakRegex = /^\s+|\s+$/;
@@ -221,6 +225,7 @@ export type EvaluateContext = {
globalContext?: Record;
requestId?: string;
eventType?: EventType;
+ triggerMeta?: TriggerMeta;
};
export const getUserScriptToEvaluate = (
@@ -330,6 +335,11 @@ export async function evaluateAsync(
let logs;
/**** Setting the eval context ****/
userLogs.resetLogs();
+ userLogs.setCurrentRequestInfo({
+ requestId,
+ eventType: context?.eventType,
+ triggerMeta: context?.triggerMeta,
+ });
const GLOBAL_DATA: Record = createGlobalData({
dataTree,
resolvedFunctions,
diff --git a/app/client/src/workers/evaluation.worker.ts b/app/client/src/workers/evaluation.worker.ts
index 7aabb30624..5a086e1fca 100644
--- a/app/client/src/workers/evaluation.worker.ts
+++ b/app/client/src/workers/evaluation.worker.ts
@@ -241,6 +241,7 @@ ctx.addEventListener(
dynamicTrigger,
eventType,
globalContext,
+ triggerMeta,
} = requestData;
if (!dataTreeEvaluator) {
return { triggers: [], errors: [] };
@@ -258,6 +259,7 @@ ctx.addEventListener(
{
globalContext,
eventType,
+ triggerMeta,
},
);
diff --git a/app/client/src/workers/timeout.test.ts b/app/client/src/workers/timeout.test.ts
new file mode 100644
index 0000000000..69e813576e
--- /dev/null
+++ b/app/client/src/workers/timeout.test.ts
@@ -0,0 +1,120 @@
+import { PluginType } from "entities/Action";
+import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
+import { createGlobalData } from "./evaluate";
+import "./TimeoutOverride";
+import overrideTimeout from "./TimeoutOverride";
+
+describe("Expects appsmith setTimeout to pass the following criteria", () => {
+ overrideTimeout();
+ jest.useFakeTimers();
+ jest.spyOn(self, "setTimeout");
+ self.postMessage = jest.fn();
+ it("returns a number a timerId", () => {
+ const timerId = setTimeout(jest.fn(), 1000);
+ expect(timerId).toBeDefined();
+ expect(typeof timerId).toBe("number");
+ });
+ it("Passes arguments into callback", () => {
+ const cb = jest.fn();
+ const args = [1, 2, "3", [4]];
+ setTimeout(cb, 1000, ...args);
+ expect(cb.mock.calls.length).toBe(0);
+ jest.runAllTimers();
+ expect(cb).toHaveBeenCalledWith(...args);
+ });
+ it("Has weird behavior with 'this' keyword", () => {
+ const cb = jest.fn();
+ const error = jest.fn();
+ const obj = {
+ var1: "myVar1",
+ getVar() {
+ try {
+ cb(this.var1);
+ } catch (e) {
+ error(e);
+ }
+ },
+ };
+ setTimeout(obj.getVar, 1000);
+ expect(cb.mock.calls.length).toBe(0);
+ jest.runAllTimers();
+ expect(error).toBeCalled();
+ });
+ it("Has weird behavior with 'this' keyword", () => {
+ const cb = jest.fn();
+ const error = jest.fn();
+ const obj = {
+ var1: "myVar1",
+ getVar() {
+ try {
+ cb(this.var1);
+ } catch (e) {
+ error(e);
+ }
+ },
+ };
+ setTimeout(obj.getVar.bind(obj), 1000);
+ expect(cb.mock.calls.length).toBe(0);
+ jest.runAllTimers();
+ expect(cb).toBeCalledWith(obj.var1);
+ });
+ it("'this' behavior should be fixed by binding this", () => {
+ const cb = jest.fn();
+ const error = jest.fn();
+ const obj = {
+ var1: "myVar1",
+ getVar() {
+ try {
+ cb(this.var1);
+ } catch (e) {
+ error(e);
+ }
+ },
+ };
+ setTimeout(obj.getVar.bind(obj), 1000);
+ expect(cb.mock.calls.length).toBe(0);
+ jest.runAllTimers();
+ expect(cb).toBeCalledWith(obj.var1);
+ });
+ it("Checks the behavior of clearTimeout", () => {
+ const cb = jest.fn();
+ const timerId = setTimeout(cb, 1000);
+ expect(cb.mock.calls.length).toBe(0);
+ clearTimeout(timerId);
+ jest.runAllTimers();
+ expect(cb.mock.calls.length).toBe(0);
+ });
+ it("Access to appsmith functions inside setTimeout", async () => {
+ const dataTree: DataTree = {
+ action1: {
+ actionId: "123",
+ pluginId: "",
+ data: {},
+ config: {},
+ datasourceUrl: "",
+ pluginType: PluginType.API,
+ dynamicBindingPathList: [],
+ name: "action1",
+ bindingPaths: {},
+ reactivePaths: {},
+ isLoading: false,
+ run: {},
+ clear: {},
+ responseMeta: { isExecutionSuccess: false },
+ ENTITY_TYPE: ENTITY_TYPE.ACTION,
+ dependencyMap: {},
+ logBlackList: {},
+ },
+ };
+ self.ALLOW_ASYNC = true;
+ const dataTreeWithFunctions = createGlobalData({
+ dataTree,
+ resolvedFunctions: {},
+ isTriggerBased: true,
+ context: {},
+ });
+ setTimeout(() => dataTreeWithFunctions.action1.run(), 1000);
+ jest.runAllTimers();
+ expect(self.postMessage).toBeCalled();
+ });
+});