Skip to content

Commit d6935e3

Browse files
Fix #7621: Allow empty query when using 'start from scratch' button
1 parent 9adfced commit d6935e3

File tree

4 files changed

+135
-2
lines changed

4 files changed

+135
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { render, screen, fireEvent } from "@testing-library/react";
2+
import { CodeNotInGitHubLink } from "#/components/features/github/code-not-in-github-link";
3+
import { useDispatch } from "react-redux";
4+
import { useCreateConversation } from "#/hooks/mutation/use-create-conversation";
5+
import { setInitialPrompt } from "#/state/initial-query-slice";
6+
import { describe, it, expect, vi, beforeEach } from "vitest";
7+
8+
// Mock dependencies
9+
vi.mock("react-redux", () => ({
10+
useDispatch: vi.fn(),
11+
}));
12+
13+
vi.mock("#/hooks/mutation/use-create-conversation", () => ({
14+
useCreateConversation: vi.fn(),
15+
}));
16+
17+
vi.mock("#/state/initial-query-slice", () => ({
18+
setInitialPrompt: vi.fn(),
19+
}));
20+
21+
describe("CodeNotInGitHubLink", () => {
22+
const mockDispatch = vi.fn();
23+
const mockCreateConversation = vi.fn();
24+
25+
beforeEach(() => {
26+
vi.clearAllMocks();
27+
(useDispatch as any).mockReturnValue(mockDispatch);
28+
(useCreateConversation as any).mockReturnValue({
29+
mutate: mockCreateConversation,
30+
});
31+
});
32+
33+
it("renders correctly", () => {
34+
render(<CodeNotInGitHubLink />);
35+
expect(screen.getByText(/Code not in GitHub\?/)).toBeInTheDocument();
36+
expect(screen.getByText("Start from scratch")).toBeInTheDocument();
37+
});
38+
39+
it("calls createConversation with allowEmptyQuery when clicked", () => {
40+
render(<CodeNotInGitHubLink />);
41+
42+
fireEvent.click(screen.getByText("Start from scratch"));
43+
44+
expect(mockDispatch).toHaveBeenCalledWith(setInitialPrompt(""));
45+
expect(mockCreateConversation).toHaveBeenCalledWith({
46+
q: "",
47+
allowEmptyQuery: true,
48+
});
49+
});
50+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { renderHook, act } from "@testing-library/react";
2+
import { useCreateConversation } from "#/hooks/mutation/use-create-conversation";
3+
import OpenHands from "#/api/open-hands";
4+
import { useNavigate } from "react-router";
5+
import { useDispatch, useSelector } from "react-redux";
6+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
7+
import { describe, it, expect, vi, beforeEach } from "vitest";
8+
9+
// Mock dependencies
10+
vi.mock("react-router", () => ({
11+
useNavigate: vi.fn(),
12+
}));
13+
14+
vi.mock("react-redux", () => ({
15+
useDispatch: vi.fn(),
16+
useSelector: vi.fn(),
17+
}));
18+
19+
vi.mock("#/api/open-hands", () => ({
20+
default: {
21+
createConversation: vi.fn(),
22+
},
23+
}));
24+
25+
vi.mock("posthog-js", () => ({
26+
default: {
27+
capture: vi.fn(),
28+
},
29+
}));
30+
31+
describe("useCreateConversation", () => {
32+
const mockNavigate = vi.fn();
33+
const mockDispatch = vi.fn();
34+
const mockQueryClient = new QueryClient();
35+
36+
beforeEach(() => {
37+
vi.clearAllMocks();
38+
(useNavigate as any).mockReturnValue(mockNavigate);
39+
(useDispatch as any).mockReturnValue(mockDispatch);
40+
(useSelector as any).mockReturnValue({
41+
selectedRepository: null,
42+
files: [],
43+
replayJson: null,
44+
});
45+
(OpenHands.createConversation as any).mockResolvedValue({
46+
conversation_id: "test-id",
47+
});
48+
});
49+
50+
const wrapper = ({ children }: { children: React.ReactNode }) => (
51+
<QueryClientProvider client={mockQueryClient}>{children}</QueryClientProvider>
52+
);
53+
54+
it("should throw an error when no query, repository, files, or replayJson is provided", async () => {
55+
const { result } = renderHook(() => useCreateConversation(), { wrapper });
56+
57+
await act(async () => {
58+
await expect(result.current.mutateAsync({ q: "" })).rejects.toThrow(
59+
"No query provided"
60+
);
61+
});
62+
});
63+
64+
it("should allow empty query when allowEmptyQuery is true", async () => {
65+
const { result } = renderHook(() => useCreateConversation(), { wrapper });
66+
67+
await act(async () => {
68+
await result.current.mutateAsync({ q: "", allowEmptyQuery: true });
69+
});
70+
71+
expect(OpenHands.createConversation).toHaveBeenCalledWith(
72+
undefined,
73+
"",
74+
[],
75+
undefined
76+
);
77+
expect(mockNavigate).toHaveBeenCalledWith("/conversations/test-id");
78+
});
79+
});

frontend/src/components/features/github/code-not-in-github-link.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export function CodeNotInGitHubLink() {
1212
const handleStartFromScratch = () => {
1313
// Set the initial prompt and create a new conversation
1414
dispatch(setInitialPrompt(INITIAL_PROMPT));
15-
createConversation({ q: INITIAL_PROMPT });
15+
createConversation({ q: INITIAL_PROMPT, allowEmptyQuery: true });
1616
};
1717

1818
return (

frontend/src/hooks/mutation/use-create-conversation.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ export const useCreateConversation = () => {
1616
);
1717

1818
return useMutation({
19-
mutationFn: async (variables: { q?: string }) => {
19+
mutationFn: async (variables: {
20+
q?: string;
21+
allowEmptyQuery?: boolean;
22+
}) => {
2023
if (
24+
!variables.allowEmptyQuery &&
2125
!variables.q?.trim() &&
2226
!selectedRepository &&
2327
files.length === 0 &&

0 commit comments

Comments
 (0)