Skip to content

Commit bf1ae81

Browse files
authored
Merge pull request continuedev#5337 from continuedev/nate/gui-testing
Nate/gui testing
2 parents 9b114fa + 4930a3d commit bf1ae81

File tree

12 files changed

+265
-34
lines changed

12 files changed

+265
-34
lines changed

.idea/scopes/Continue.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gui/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gui/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const router = createMemoryRouter([
4545
Prevents entire app from rerendering continuously with useSetup in App
4646
TODO - look into a more redux-esque way to do this
4747
*/
48-
function SetupListeners() {
48+
export function SetupListeners() {
4949
useSetup();
5050
return <></>;
5151
}

gui/src/components/find/FindWidget.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ import {
1111
useRef,
1212
useState,
1313
} from "react";
14-
import { useSelector } from "react-redux";
1514
import { HeaderButton, Input } from "..";
16-
import { RootState } from "../../redux/store";
1715
import HeaderButtonWithToolTip from "../gui/HeaderButtonWithToolTip";
1816
import { useAppSelector } from "../../redux/hooks";
1917

gui/src/components/mainInput/ContinueInputBox.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ function ContinueInputBox(props: ContinueInputBoxProps) {
114114
: {};
115115

116116
return (
117-
<div className={`${props.hidden ? "hidden" : ""}`}>
117+
<div
118+
className={`${props.hidden ? "hidden" : ""}`}
119+
data-testid="continue-input-box"
120+
>
118121
<div className={`relative flex flex-col px-2`}>
119122
{props.isMainInput && <Lump />}
120123
<GradientBorder
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,14 @@ export class IdeMessenger implements IIdeMessenger {
264264
export const IdeMessengerContext = createContext<IIdeMessenger>(
265265
new IdeMessenger(),
266266
);
267+
268+
export const IdeMessengerProvider: React.FC<{
269+
children: React.ReactNode;
270+
messenger?: IIdeMessenger;
271+
}> = ({ children, messenger = new IdeMessenger() }) => {
272+
return (
273+
<IdeMessengerContext.Provider value={messenger}>
274+
{children}
275+
</IdeMessengerContext.Provider>
276+
);
277+
};
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { IDE, PromptLog } from "core";
2+
import {
3+
FromWebviewProtocol,
4+
ToCoreProtocol,
5+
ToWebviewProtocol,
6+
} from "core/protocol";
7+
import { MessageIde } from "core/protocol/messenger/messageIde";
8+
import {
9+
GeneratorReturnType,
10+
GeneratorYieldType,
11+
WebviewSingleProtocolMessage,
12+
} from "core/protocol/util";
13+
import { ChatMessage } from "../redux/store";
14+
import { IIdeMessenger } from "./IdeMessenger";
15+
16+
async function defaultMockHandleMessage<T extends keyof FromWebviewProtocol>(
17+
messageType: T,
18+
data: FromWebviewProtocol[T][0],
19+
): Promise<FromWebviewProtocol[T][1]> {
20+
function returnFor<K extends keyof FromWebviewProtocol>(
21+
_: K,
22+
value: FromWebviewProtocol[K][1],
23+
): FromWebviewProtocol[T][1] {
24+
return value as unknown as FromWebviewProtocol[T][1];
25+
}
26+
27+
switch (messageType) {
28+
case "history/list":
29+
return returnFor("history/list", [
30+
{
31+
title: "Session 1",
32+
sessionId: "session-1",
33+
dateCreated: new Date().toString(),
34+
workspaceDirectory: "/tmp",
35+
},
36+
]);
37+
case "getControlPlaneSessionInfo":
38+
return returnFor("getControlPlaneSessionInfo", {
39+
accessToken: "",
40+
account: {
41+
label: "",
42+
id: "",
43+
},
44+
});
45+
case "config/getSerializedProfileInfo":
46+
return returnFor("config/getSerializedProfileInfo", {
47+
organizations: [],
48+
profileId: "test-profile",
49+
result: {
50+
config: undefined,
51+
errors: [],
52+
configLoadInterrupted: false,
53+
},
54+
selectedOrgId: "local",
55+
});
56+
default:
57+
throw new Error(`Unknown message type ${messageType}`);
58+
}
59+
}
60+
61+
export class MockIdeMessenger implements IIdeMessenger {
62+
ide: IDE;
63+
64+
constructor() {
65+
this.ide = new MessageIde(
66+
(messageType, data) => {
67+
throw new Error("Not implemented");
68+
},
69+
(messageType, callback) => {},
70+
);
71+
}
72+
73+
async *llmStreamChat(
74+
msg: ToCoreProtocol["llm/streamChat"][0],
75+
cancelToken: AbortSignal,
76+
): AsyncGenerator<ChatMessage[], PromptLog | undefined> {
77+
yield [
78+
{
79+
role: "assistant",
80+
content: "This is a test",
81+
},
82+
];
83+
84+
return undefined;
85+
}
86+
87+
post<T extends keyof FromWebviewProtocol>(
88+
messageType: T,
89+
data: FromWebviewProtocol[T][0],
90+
messageId?: string,
91+
attempt?: number,
92+
): void {}
93+
94+
async request<T extends keyof FromWebviewProtocol>(
95+
messageType: T,
96+
data: FromWebviewProtocol[T][0],
97+
): Promise<WebviewSingleProtocolMessage<T>> {
98+
const content = await defaultMockHandleMessage(messageType, data);
99+
return {
100+
status: "success",
101+
content,
102+
done: true,
103+
};
104+
}
105+
106+
respond<T extends keyof ToWebviewProtocol>(
107+
messageType: T,
108+
data: ToWebviewProtocol[T][1],
109+
messageId: string,
110+
): void {}
111+
112+
async *streamRequest<T extends keyof FromWebviewProtocol>(
113+
messageType: T,
114+
data: FromWebviewProtocol[T][0],
115+
cancelToken?: AbortSignal,
116+
): AsyncGenerator<
117+
GeneratorYieldType<FromWebviewProtocol[T][1]>[],
118+
GeneratorReturnType<FromWebviewProtocol[T][1]> | undefined
119+
> {
120+
return undefined;
121+
}
122+
}

gui/src/pages/gui/Chat.test.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { renderWithProviders } from "../../util/test/render";
2+
import { screen, waitFor } from "@testing-library/dom";
3+
import { act, fireEvent } from "@testing-library/react";
4+
import { Chat } from "./Chat";
5+
6+
describe("Chat page test", () => {
7+
it("should render input box", async () => {
8+
await renderWithProviders(<Chat />);
9+
expect(await screen.findByTestId("continue-input-box")).toBeInTheDocument();
10+
});
11+
12+
it("should be able to toggle modes", async () => {
13+
await renderWithProviders(<Chat />);
14+
expect(screen.getByText("Chat")).toBeInTheDocument();
15+
16+
// Simulate cmd+. keyboard shortcut to toggle modes
17+
act(() => {
18+
document.dispatchEvent(
19+
new KeyboardEvent("keydown", {
20+
key: ".",
21+
metaKey: true, // cmd key on Mac
22+
}),
23+
);
24+
});
25+
26+
// Check that it switched to Edit mode
27+
expect(await screen.findByText("Edit")).toBeInTheDocument();
28+
29+
act(() => {
30+
document.dispatchEvent(
31+
new KeyboardEvent("keydown", {
32+
key: ".",
33+
metaKey: true, // cmd key on Mac
34+
}),
35+
);
36+
});
37+
38+
// Check that it switched to Agent mode
39+
expect(await screen.findByText("Agent")).toBeInTheDocument();
40+
});
41+
42+
it.skip("should send a message and receive a response", async () => {
43+
const { user, container } = await renderWithProviders(<Chat />);
44+
const inputBox = await waitFor(() =>
45+
container.querySelector(".ProseMirror")!.querySelector("p"),
46+
);
47+
expect(inputBox).toBeDefined();
48+
49+
const sendButton = await screen.findByTestId("submit-input-button");
50+
51+
await act(async () => {
52+
// Focus input box
53+
inputBox!.focus();
54+
55+
// Type message
56+
await user.type(inputBox!, "Hello, world!");
57+
58+
sendButton.click();
59+
});
60+
expect(await screen.findByText("Hello, world!")).toBeInTheDocument();
61+
});
62+
});

gui/src/pages/history/history.test.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ import { screen } from "@testing-library/dom";
22
import { renderWithProviders } from "../../util/test/render";
33
import HistoryPage from "./index";
44

5-
const navigateFn = vi.fn();
6-
7-
vi.mock("react-router-dom", async () => {
8-
const original = await vi.importActual("react-router-dom");
9-
return {
10-
...original,
11-
useNavigate: () => navigateFn,
12-
};
13-
});
14-
155
describe("history Page test", () => {
16-
it("History text is existed after render", () => {
17-
renderWithProviders(<HistoryPage />);
6+
it("History text is existed after render", async () => {
7+
await renderWithProviders(<HistoryPage />);
188
expect(screen.getByTestId("history-sessions-note")).toBeInTheDocument();
199
});
10+
11+
it("History shows the first item in the list", async () => {
12+
await renderWithProviders(<HistoryPage />);
13+
const sessionElement = await screen.findByText(
14+
"Session 1",
15+
{},
16+
{
17+
timeout: 3000, // There is a 2000ms timeout before the first call to refreshSessionMetadata is called
18+
},
19+
);
20+
expect(sessionElement).toBeInTheDocument();
21+
});
2022
});

gui/src/redux/selectors/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,5 @@ export const selectUseActiveFile = createSelector(
4747
(defaultContext) => defaultContext?.includes("activeFile" as any),
4848
);
4949

50-
export const selectUseHub = createSelector(
51-
[(state: RootState) => state.config.config.usePlatform],
52-
(usePlatform) => usePlatform,
53-
);
50+
export const selectUseHub = (state: RootState) =>
51+
state.config.config.usePlatform;

0 commit comments

Comments
 (0)