Skip to content

Commit ead4915

Browse files
authored
fix: evaluation of navigateScreen (#977)
fix evaluation of navigateScreen
1 parent cea3dee commit ead4915

File tree

3 files changed

+175
-15
lines changed

3 files changed

+175
-15
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ensembleui/react-runtime": patch
3+
---
4+
5+
fix navigateScreen action screen name and inputs evaluation

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

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
/* eslint import/first: 0 */
22
import { act, renderHook } from "@testing-library/react";
3-
import { ScreenContextProvider } from "@ensembleui/react-framework";
3+
import {
4+
ScreenContextProvider,
5+
ApplicationContextProvider,
6+
} from "@ensembleui/react-framework";
47
import { useEnsembleAction } from "../useEnsembleAction";
8+
import { useNavigateScreen } from "../useNavigateScreen";
59

610
jest.mock("react-markdown", jest.fn());
7-
jest.mock("react-router-dom");
11+
12+
const mockNavigate = jest.fn();
13+
const mockLocation = jest.fn();
14+
15+
jest.mock("react-router-dom", () => ({
16+
useNavigate: () => mockNavigate,
17+
useLocation: () => mockLocation,
18+
}));
819

920
const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
1021
<ScreenContextProvider
@@ -61,3 +72,143 @@ describe("Test cases for useEnsembleAction Hook", () => {
6172
expect(execResult).toBe(2);
6273
});
6374
});
75+
76+
const navigateScreenWrapper: React.FC<React.PropsWithChildren> = ({
77+
children,
78+
}) => (
79+
<ApplicationContextProvider
80+
app={{
81+
screens: [
82+
{ name: "Home", id: "home", body: { name: "Button", properties: {} } },
83+
{
84+
name: "Details",
85+
id: "details",
86+
body: { name: "Button", properties: {} },
87+
},
88+
],
89+
customWidgets: [],
90+
home: {
91+
name: "Home",
92+
id: "home",
93+
body: { name: "Button", properties: {} },
94+
},
95+
themes: {},
96+
id: "testApp",
97+
scripts: [],
98+
}}
99+
>
100+
<ScreenContextProvider
101+
context={{
102+
widgets: {
103+
myWidget: {
104+
values: {
105+
value: "home",
106+
},
107+
invokable: {
108+
id: "myWidget",
109+
},
110+
},
111+
},
112+
data: {},
113+
storage: {},
114+
}}
115+
screen={{
116+
id: "home",
117+
name: "Home",
118+
body: { name: "Button", properties: {} },
119+
}}
120+
>
121+
{children}
122+
</ScreenContextProvider>
123+
</ApplicationContextProvider>
124+
);
125+
126+
describe("Test cases for useNavigateScreen Hook", () => {
127+
beforeEach(() => {
128+
mockNavigate.mockClear();
129+
});
130+
131+
it("should not navigate when no action is provided", () => {
132+
const { result } = renderHook(() => useNavigateScreen(undefined), {
133+
wrapper: navigateScreenWrapper,
134+
});
135+
136+
act(() => {
137+
result.current?.callback();
138+
});
139+
140+
expect(mockNavigate).not.toHaveBeenCalled();
141+
});
142+
143+
it("should navigate when action is a string", () => {
144+
const { result } = renderHook(
145+
// eslint-disable-next-line no-template-curly-in-string
146+
() => useNavigateScreen("home"),
147+
{
148+
wrapper: navigateScreenWrapper,
149+
},
150+
);
151+
152+
act(() => {
153+
result.current?.callback();
154+
});
155+
156+
expect(mockNavigate).toHaveBeenCalledWith("/home", { state: undefined });
157+
});
158+
159+
it("should navigate when action name is a variable", () => {
160+
const { result } = renderHook(
161+
// eslint-disable-next-line no-template-curly-in-string
162+
() => useNavigateScreen("${myWidget.value}"),
163+
{
164+
wrapper: navigateScreenWrapper,
165+
},
166+
);
167+
168+
act(() => {
169+
result.current?.callback();
170+
});
171+
172+
expect(mockNavigate).toHaveBeenCalledWith("/home", { state: undefined });
173+
});
174+
175+
it("should navigate with inputs when action is an object", () => {
176+
const action = {
177+
name: "Details",
178+
inputs: {
179+
id: 123,
180+
category: "test",
181+
},
182+
};
183+
184+
const { result } = renderHook(() => useNavigateScreen(action), {
185+
wrapper: navigateScreenWrapper,
186+
});
187+
188+
act(() => {
189+
result.current?.callback();
190+
});
191+
192+
expect(mockNavigate).toHaveBeenCalledWith("/details", {
193+
state: {
194+
id: 123,
195+
category: "test",
196+
},
197+
});
198+
});
199+
200+
it("should not navigate when screen is not found", () => {
201+
const { result } = renderHook(
202+
() => useNavigateScreen("NonExistentScreen"),
203+
{
204+
wrapper: navigateScreenWrapper,
205+
},
206+
);
207+
208+
act(() => {
209+
result.current?.callback();
210+
});
211+
212+
expect(mockNavigate).not.toHaveBeenCalled();
213+
});
214+
});

packages/runtime/src/runtime/hooks/useNavigateScreen.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ import { isNil, isString, merge, cloneDeep } from "lodash-es";
33
import { useNavigate } from "react-router-dom";
44
import {
55
useCommandCallback,
6-
evaluate,
76
useScreenModel,
87
useApplicationContext,
98
evaluateDeep,
109
} from "@ensembleui/react-framework";
1110
import type { EnsembleActionHook } from "./useEnsembleAction";
1211

12+
type EvaluatedData = {
13+
screenName: string;
14+
inputs?: { [key: string]: unknown };
15+
};
16+
1317
export const useNavigateScreen: EnsembleActionHook<NavigateScreenAction> = (
1418
action,
1519
) => {
@@ -23,26 +27,26 @@ export const useNavigateScreen: EnsembleActionHook<NavigateScreenAction> = (
2327

2428
const context = merge({}, evalContext, args[0]);
2529

26-
const screenName = isString(action) ? action : action.name;
27-
const evaluatedName = evaluate<string>(
28-
{ model: screenModel },
29-
screenName,
30+
const { screenName, inputs } = evaluateDeep(
31+
{
32+
screenName: isString(action) ? action : action.name,
33+
inputs:
34+
!isString(action) && !isNil(action.inputs)
35+
? cloneDeep(action.inputs)
36+
: undefined,
37+
},
38+
screenModel,
3039
context,
31-
);
40+
) as EvaluatedData;
3241

3342
const matchingScreen = appContext?.application?.screens.find(
34-
(s) => s.name?.toLowerCase() === evaluatedName.toLowerCase(),
43+
(s) => s.name?.toLowerCase() === screenName.toLowerCase(),
3544
);
3645

3746
if (!matchingScreen?.name) return;
3847

39-
const evaluatedInputs =
40-
!isString(action) && !isNil(action.inputs)
41-
? evaluateDeep(cloneDeep(action.inputs), screenModel, context)
42-
: undefined;
43-
4448
navigate(`/${matchingScreen.name.toLowerCase()}`, {
45-
state: evaluatedInputs,
49+
state: inputs,
4650
});
4751
},
4852
{ navigate },

0 commit comments

Comments
 (0)