diff --git a/packages/bruno-js/src/utils.js b/packages/bruno-js/src/utils.js index 6b508d356..6bc73da83 100644 --- a/packages/bruno-js/src/utils.js +++ b/packages/bruno-js/src/utils.js @@ -26,14 +26,26 @@ const compileJsExpression = (expr) => { const matches = expr.match(/([\w\.$]+)/g) ?? []; // get valid js identifiers (foo, bar) - const names = matches - .filter(match => /^[a-zA-Z$_]/.test(match)) - .map(match => match.split('.')[0]); + const vars = new Set( + matches + .filter(match => /^[a-zA-Z$_]/.test(match)) // starts with valid js identifier (foo.bar) + .map(match => match.split('.')[0]) // top level identifier (foo) + .filter(name => !JS_KEYWORDS.includes(name)) // exclude js keywords + ); + + // globals such as Math + const globals = [...vars].filter(name => name in globalThis); + + const code = { + vars: [...vars].join(", "), + // pick global from context or globalThis + globals: globals + .map(name => ` ${name} = ${name} ?? globalThis.${name};`) + .join('') + }; + + const body = `let { ${code.vars} } = context; ${code.globals}; return ${expr}`; - // exclude js keywords and get unique vars - const vars = new Set(names.filter(name => !JS_KEYWORDS.includes(name))); - const spread = [...vars].join(", "); - const body = `const { ${spread} } = context; return ${expr}`; return new Function("context", body); }; diff --git a/packages/bruno-js/tests/utils.spec.js b/packages/bruno-js/tests/utils.spec.js index 36a51c94d..6d5212407 100644 --- a/packages/bruno-js/tests/utils.spec.js +++ b/packages/bruno-js/tests/utils.spec.js @@ -44,32 +44,32 @@ describe("utils", () => { it("should identify top level variables", () => { const expr = "res.data.pets[0].toUpperCase()"; evaluateJsExpression(expr, context); - expect(cache.get(expr).toString()).toContain("const { res } = context;"); + expect(cache.get(expr).toString()).toContain("let { res } = context;"); }); it("should not duplicate variables", () => { const expr = "res.data.pets[0] + res.data.pets[1]"; evaluateJsExpression(expr, context); - expect(cache.get(expr).toString()).toContain("const { res } = context;"); + expect(cache.get(expr).toString()).toContain("let { res } = context;"); }); it("should exclude js keywords like true false from vars", () => { const expr = "res.data.pets.length > 0 ? true : false"; evaluateJsExpression(expr, context); - expect(cache.get(expr).toString()).toContain("const { res } = context;"); + expect(cache.get(expr).toString()).toContain("let { res } = context;"); }); it("should exclude numbers from vars", () => { const expr = "res.data.pets.length + 10"; evaluateJsExpression(expr, context); - expect(cache.get(expr).toString()).toContain("const { res } = context;"); + expect(cache.get(expr).toString()).toContain("let { res } = context;"); }); it("should pick variables from complex expressions", () => { const expr = "res.data.pets.map(pet => pet.length)"; const result = evaluateJsExpression(expr, context); expect(result).toEqual([5, 3]); - expect(cache.get(expr).toString()).toContain("const { res, pet } = context;"); + expect(cache.get(expr).toString()).toContain("let { res, pet } = context;"); }); it("should be ok picking extra vars from strings", () => { @@ -77,7 +77,39 @@ describe("utils", () => { const result = evaluateJsExpression(expr, context); expect(result).toBe("hello bruno"); // extra var hello is harmless - expect(cache.get(expr).toString()).toContain("const { hello, res } = context;"); + expect(cache.get(expr).toString()).toContain("let { hello, res } = context;"); + }); + + it("should evaluate expressions referencing globals", () => { + const startTime = new Date("2022-02-01").getTime(); + const currentTime = new Date("2022-02-02").getTime(); + + jest.useFakeTimers({ now: currentTime }); + + const expr = "Math.max(Date.now(), startTime)"; + const result = evaluateJsExpression(expr, { startTime }); + + expect(result).toBe(currentTime); + + expect(cache.get(expr).toString()).toContain("Math = Math ?? globalThis.Math;"); + expect(cache.get(expr).toString()).toContain("Date = Date ?? globalThis.Date;"); + }); + + it("should use global overridden in context", () => { + const startTime = new Date("2022-02-01").getTime(); + const currentTime = new Date("2022-02-02").getTime(); + + jest.useFakeTimers({ now: currentTime }); + + const context = { + Date: { now: () => new Date("2022-01-31").getTime() }, + startTime + }; + + const expr = "Math.max(Date.now(), startTime)"; + const result = evaluateJsExpression(expr, context); + + expect(result).toBe(startTime); }); }); });