feat: enabled setTimeout/clearTimeout APIs (#17445)
This commit is contained in:
parent
06f1b23625
commit
28138c18c8
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -241,5 +241,15 @@
|
|||
"!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()."
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -314,7 +314,6 @@ export const isThemeBoundProperty = (
|
|||
};
|
||||
|
||||
export const unsafeFunctionForEval = [
|
||||
"setTimeout",
|
||||
"fetch",
|
||||
"setInterval",
|
||||
"clearInterval",
|
||||
|
|
|
|||
40
app/client/src/workers/TimeoutOverride.ts
Normal file
40
app/client/src/workers/TimeoutOverride.ts
Normal file
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -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;
|
||||
});
|
||||
} catch (e) {
|
||||
returnData = [`There was some error: ${e} ${JSON.stringify(data)}`];
|
||||
}
|
||||
const returnData = this.replaceFunctionWithNamesFromObjects(data);
|
||||
return returnData;
|
||||
} catch (e) {
|
||||
return [`There was some error: ${e} ${JSON.stringify(data)}`];
|
||||
}
|
||||
}
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -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)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<string, any>;
|
||||
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<string, any> = createGlobalData({
|
||||
dataTree,
|
||||
resolvedFunctions,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
120
app/client/src/workers/timeout.test.ts
Normal file
120
app/client/src/workers/timeout.test.ts
Normal file
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user