Skip to content

Commit d3b9c91

Browse files
authored
fix: added support for await in executeCode (#1053)
added support for await in executeCode action
1 parent 53d8ac8 commit d3b9c91

File tree

7 files changed

+143
-23
lines changed

7 files changed

+143
-23
lines changed

.changeset/honest-gorillas-care.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@ensembleui/react-kitchen-sink": patch
3+
"@ensembleui/react-runtime": patch
4+
---
5+
6+
Added support for await in executeCode

apps/kitchen-sink/src/ensemble/screens/testActions.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ API:
224224

225225
UPLOADFILE:
226226
method: POST
227-
uri: https://example.com
227+
uri: https://example.com/
228228

229229
Global: |
230230
function verifyFileExtension(fileName) {

packages/framework/src/evaluate/evaluate.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,27 @@ const formatJs = (js?: string): string => {
7070

7171
// multiline js
7272
if (sanitizedJs.includes("\n")) {
73+
if (sanitizedJs.includes("await ")) {
74+
return `
75+
return (async function() {
76+
${sanitizedJs}
77+
}())
78+
`;
79+
}
80+
81+
return `
82+
return (function() {
83+
${sanitizedJs}
84+
}())
85+
`;
86+
}
87+
88+
if (sanitizedJs.includes("await ")) {
7389
return `
74-
return (function() {
75-
${sanitizedJs}
76-
}())
77-
`;
90+
return (async function() {
91+
return ${sanitizedJs}
92+
}())
93+
`;
7894
}
7995

8096
return `return ${sanitizedJs}`;

packages/runtime/src/runtime/hooks/__tests__/useEnsembleAction.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,17 @@ describe("Test cases for useEnsembleAction Hook", () => {
5959
expect(result.current).toBeUndefined();
6060
});
6161

62-
it("should return useExecuteCode when action is a string", () => {
62+
it("should return useExecuteCode when action is a string", async () => {
6363
const { result } = renderHook(() => useEnsembleAction("myWidget.value"), {
6464
wrapper,
6565
});
6666

6767
let execResult;
6868

69-
act(() => {
70-
execResult = result.current?.callback();
69+
await act(async () => {
70+
execResult = await result.current?.callback();
7171
});
72+
7273
expect(execResult).toBe(2);
7374
});
7475
});

packages/runtime/src/runtime/hooks/__tests__/useExecuteCode.test.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,19 @@ const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
4747
</ScreenContextProvider>
4848
);
4949

50-
test("populates screen invokables in function context", () => {
50+
test("populates screen invokables in function context", async () => {
5151
const { result } = renderHook(() => useExecuteCode("myWidget.value"), {
5252
wrapper,
5353
});
5454

5555
let execResult;
56-
act(() => {
57-
execResult = result.current?.callback();
56+
await act(async () => {
57+
execResult = await result.current?.callback();
5858
});
5959
expect(execResult).toBe(2);
6060
});
6161

62-
test("populates context passed in", () => {
62+
test("populates context passed in", async () => {
6363
const { result } = renderHook(
6464
() =>
6565
useExecuteCode("specialScope.value", {
@@ -69,25 +69,25 @@ test("populates context passed in", () => {
6969
);
7070

7171
let execResult;
72-
act(() => {
73-
execResult = result.current?.callback();
72+
await act(async () => {
73+
execResult = await result.current?.callback();
7474
});
7575
expect(execResult).toBe(4);
7676
});
7777

78-
test("can be invoked multiple times", () => {
78+
test("can be invoked multiple times", async () => {
7979
const { result } = renderHook(() => useExecuteCode("myWidget.value"), {
8080
wrapper,
8181
});
8282

8383
let execResult;
84-
act(() => {
85-
execResult = result.current?.callback();
84+
await act(async () => {
85+
execResult = await result.current?.callback();
8686
});
8787
expect(execResult).toBe(2);
8888
let execResult2;
89-
act(() => {
90-
execResult2 = result.current?.callback();
89+
await act(async () => {
90+
execResult2 = await result.current?.callback();
9191
});
9292
expect(execResult2).toBe(2);
9393
});
@@ -101,7 +101,8 @@ test("call ensemble.invokeAPI", async () => {
101101
const { result } = renderHook(
102102
() =>
103103
useExecuteCode(
104-
"ensemble.invokeAPI('getDummyProductsByPaginate', apiConfig).then((res) => res.body.products.length)",
104+
`const res = await ensemble.invokeAPI('getDummyProductsByPaginate', apiConfig);
105+
return res.body.limit`,
105106
{ context: { apiConfig } },
106107
),
107108
{
@@ -146,7 +147,7 @@ test("call ensemble.invokeAPI with bypassCache", async () => {
146147

147148
expect(withoutForceInitialResult).toBe(withoutForceResult);
148149
expect(withForceResult).not.toBe(withoutForceResult);
149-
});
150+
}, 10000);
150151

151152
test.todo("populates application invokables");
152153

packages/runtime/src/runtime/hooks/useEnsembleAction.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,16 @@ export const useExecuteCode: EnsembleActionHook<
130130
}, [action, isCodeString, appContext?.application?.scripts]);
131131

132132
const execute = useCommandCallback(
133-
(evalContext, ...args: unknown[]) => {
133+
async (evalContext, ...args: unknown[]) => {
134134
if (!js) {
135135
return;
136136
}
137137
const context = merge({}, evalContext, ...args, options?.context) as {
138138
[key: string]: unknown;
139139
};
140-
const retVal = evaluate({ model: screenModel }, js, context);
140+
141+
const retVal = await evaluate({ model: screenModel }, js, context);
142+
141143
onCompleteAction?.callback({
142144
...(args[0] as { [key: string]: unknown }),
143145
result: retVal,

packages/runtime/src/widgets/__tests__/Button.test.tsx

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { render, screen, fireEvent, waitFor } from "@testing-library/react";
66
import "@testing-library/jest-dom";
77
import { BrowserRouter } from "react-router-dom";
88
import { useRegisterBindings } from "@ensembleui/react-framework";
9+
import { ToastContainer } from "react-toastify";
910
import { Button, type ButtonProps } from "../Button";
1011
import { createCustomWidget } from "../../runtime/customWidget";
1112
import { Column } from "../Column";
@@ -633,4 +634,97 @@ test("should update the action on bindings changes and track renders", async ()
633634
expect(mockOpen).toHaveBeenCalledWith("http://youtube.com", "_self");
634635
});
635636
});
637+
638+
describe("check for pass result of executeCode into nested actions", () => {
639+
test("should pass result of executeCode into nested action", async () => {
640+
render(
641+
<>
642+
<ToastContainer />
643+
<Button
644+
id="checkLoader"
645+
label="Set Storage"
646+
onTap={{
647+
executeCode:
648+
"ensemble.storage.set('toast_message', 'This is toast')",
649+
}}
650+
/>
651+
<Button
652+
label="Check Toast"
653+
onTap={{
654+
executeCode: {
655+
body: "ensemble.storage.get('toast_message')",
656+
onComplete: {
657+
showToast: {
658+
message: `\${result}`,
659+
options: {
660+
position: "topRight",
661+
type: "success",
662+
},
663+
},
664+
},
665+
},
666+
}}
667+
/>
668+
</>,
669+
{ wrapper: BrowserRouter },
670+
);
671+
672+
const setStorageBtn = screen.getByText("Set Storage");
673+
fireEvent.click(setStorageBtn);
674+
675+
const checkToastBtn = screen.getByText("Check Toast");
676+
userEvent.click(checkToastBtn);
677+
678+
await waitFor(() => {
679+
expect(screen.getByText("This is toast")).toBeInTheDocument();
680+
});
681+
});
682+
683+
test("should pass result of async/await executeCode into nested action", async () => {
684+
render(
685+
<>
686+
<ToastContainer />
687+
<Button
688+
id="checkLoader"
689+
label="Set Storage"
690+
onTap={{
691+
executeCode:
692+
"ensemble.storage.set('toast_message', 'This is async toast')",
693+
}}
694+
/>
695+
<Button
696+
label="Check Toast"
697+
onTap={{
698+
executeCode: {
699+
body: "await new Promise((resolve) => setTimeout(() => resolve(ensemble.storage.get('toast_message')), 1000))",
700+
onComplete: {
701+
showToast: {
702+
message: `\${result}`,
703+
options: {
704+
position: "topRight",
705+
type: "success",
706+
},
707+
},
708+
},
709+
},
710+
}}
711+
/>
712+
</>,
713+
{ wrapper: BrowserRouter },
714+
);
715+
716+
const setStorageBtn = screen.getByText("Set Storage");
717+
fireEvent.click(setStorageBtn);
718+
719+
const checkToastBtn = screen.getByText("Check Toast");
720+
userEvent.click(checkToastBtn);
721+
722+
await waitFor(
723+
() => {
724+
expect(screen.getByText("This is async toast")).toBeInTheDocument();
725+
},
726+
{ timeout: 2000 },
727+
);
728+
});
729+
});
636730
/* eslint-enable react/no-children-prop */

0 commit comments

Comments
 (0)