Skip to content

Commit ade059b

Browse files
amanaperbrenmalhotra5
authored
feat/fix(fontend): Get public repos via repo URL (All-Hands-AI#8223)
Co-authored-by: Robert Brennan <[email protected]> Co-authored-by: [email protected] <[email protected]>
1 parent 5073cee commit ade059b

File tree

12 files changed

+304
-378
lines changed

12 files changed

+304
-378
lines changed

frontend/__tests__/components/features/git/git-repo-selector.test.tsx

Lines changed: 0 additions & 89 deletions
This file was deleted.
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
import { render, screen } from "@testing-library/react";
2+
import { describe, expect, vi, beforeEach, it } from "vitest";
3+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4+
import userEvent from "@testing-library/user-event";
5+
import { RepositorySelectionForm } from "../../../../src/components/features/home/repo-selection-form";
6+
import OpenHands from "#/api/open-hands";
7+
import { GitRepository } from "#/types/git";
8+
9+
// Create mock functions
10+
const mockUseUserRepositories = vi.fn();
11+
const mockUseCreateConversation = vi.fn();
12+
const mockUseIsCreatingConversation = vi.fn();
13+
const mockUseTranslation = vi.fn();
14+
const mockUseAuth = vi.fn();
15+
16+
// Setup default mock returns
17+
mockUseUserRepositories.mockReturnValue({
18+
data: [],
19+
isLoading: false,
20+
isError: false,
21+
});
22+
23+
mockUseCreateConversation.mockReturnValue({
24+
mutate: vi.fn(),
25+
isPending: false,
26+
isSuccess: false,
27+
});
28+
29+
mockUseIsCreatingConversation.mockReturnValue(false);
30+
31+
mockUseTranslation.mockReturnValue({ t: (key: string) => key });
32+
33+
mockUseAuth.mockReturnValue({
34+
isAuthenticated: true,
35+
isLoading: false,
36+
providersAreSet: true,
37+
user: {
38+
id: 1,
39+
login: "testuser",
40+
avatar_url: "https://example.com/avatar.png",
41+
name: "Test User",
42+
43+
company: "Test Company",
44+
},
45+
login: vi.fn(),
46+
logout: vi.fn(),
47+
});
48+
49+
vi.mock("#/hooks/mutation/use-create-conversation", () => ({
50+
useCreateConversation: () => mockUseCreateConversation(),
51+
}));
52+
53+
vi.mock("#/hooks/use-is-creating-conversation", () => ({
54+
useIsCreatingConversation: () => mockUseIsCreatingConversation(),
55+
}));
56+
57+
vi.mock("react-i18next", () => ({
58+
useTranslation: () => mockUseTranslation(),
59+
}));
60+
61+
vi.mock("#/context/auth-context", () => ({
62+
useAuth: () => mockUseAuth(),
63+
}));
64+
65+
vi.mock("#/hooks/use-debounce", () => ({
66+
useDebounce: (value: string) => value,
67+
}));
68+
69+
const mockOnRepoSelection = vi.fn();
70+
const renderForm = () =>
71+
render(<RepositorySelectionForm onRepoSelection={mockOnRepoSelection} />, {
72+
wrapper: ({ children }) => (
73+
<QueryClientProvider
74+
client={
75+
new QueryClient({
76+
defaultOptions: {
77+
queries: {
78+
retry: false,
79+
},
80+
},
81+
})
82+
}
83+
>
84+
{children}
85+
</QueryClientProvider>
86+
),
87+
});
88+
89+
describe("RepositorySelectionForm", () => {
90+
beforeEach(() => {
91+
vi.clearAllMocks();
92+
});
93+
94+
it("shows loading indicator when repositories are being fetched", () => {
95+
const MOCK_REPOS: GitRepository[] = [
96+
{
97+
id: 1,
98+
full_name: "user/repo1",
99+
git_provider: "github",
100+
is_public: true,
101+
},
102+
{
103+
id: 2,
104+
full_name: "user/repo2",
105+
git_provider: "github",
106+
is_public: true,
107+
},
108+
];
109+
const retrieveUserGitRepositoriesSpy = vi.spyOn(
110+
OpenHands,
111+
"retrieveUserGitRepositories",
112+
);
113+
retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_REPOS);
114+
115+
renderForm();
116+
117+
// Check if loading indicator is displayed
118+
expect(screen.getByTestId("repo-dropdown-loading")).toBeInTheDocument();
119+
expect(screen.getByText("HOME$LOADING_REPOSITORIES")).toBeInTheDocument();
120+
});
121+
122+
it("shows dropdown when repositories are loaded", async () => {
123+
const MOCK_REPOS: GitRepository[] = [
124+
{
125+
id: 1,
126+
full_name: "user/repo1",
127+
git_provider: "github",
128+
is_public: true,
129+
},
130+
{
131+
id: 2,
132+
full_name: "user/repo2",
133+
git_provider: "github",
134+
is_public: true,
135+
},
136+
];
137+
const retrieveUserGitRepositoriesSpy = vi.spyOn(
138+
OpenHands,
139+
"retrieveUserGitRepositories",
140+
);
141+
retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_REPOS);
142+
143+
renderForm();
144+
expect(await screen.findByTestId("repo-dropdown")).toBeInTheDocument();
145+
});
146+
147+
it("shows error message when repository fetch fails", async () => {
148+
const retrieveUserGitRepositoriesSpy = vi.spyOn(
149+
OpenHands,
150+
"retrieveUserGitRepositories",
151+
);
152+
retrieveUserGitRepositoriesSpy.mockRejectedValue(
153+
new Error("Failed to load"),
154+
);
155+
156+
renderForm();
157+
158+
expect(
159+
await screen.findByTestId("repo-dropdown-error"),
160+
).toBeInTheDocument();
161+
expect(
162+
screen.getByText("HOME$FAILED_TO_LOAD_REPOSITORIES"),
163+
).toBeInTheDocument();
164+
});
165+
166+
it("should call the search repos API when searching a URL", async () => {
167+
const MOCK_REPOS: GitRepository[] = [
168+
{
169+
id: 1,
170+
full_name: "user/repo1",
171+
git_provider: "github",
172+
is_public: true,
173+
},
174+
{
175+
id: 2,
176+
full_name: "user/repo2",
177+
git_provider: "github",
178+
is_public: true,
179+
},
180+
];
181+
182+
const MOCK_SEARCH_REPOS: GitRepository[] = [
183+
{
184+
id: 3,
185+
full_name: "kubernetes/kubernetes",
186+
git_provider: "github",
187+
is_public: true,
188+
},
189+
];
190+
191+
const searchGitReposSpy = vi.spyOn(OpenHands, "searchGitRepositories");
192+
const retrieveUserGitRepositoriesSpy = vi.spyOn(
193+
OpenHands,
194+
"retrieveUserGitRepositories",
195+
);
196+
197+
searchGitReposSpy.mockResolvedValue(MOCK_SEARCH_REPOS);
198+
retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_REPOS);
199+
200+
renderForm();
201+
202+
const input = await screen.findByTestId("repo-dropdown");
203+
await userEvent.click(input);
204+
205+
for (const repo of MOCK_REPOS) {
206+
expect(screen.getByText(repo.full_name)).toBeInTheDocument();
207+
}
208+
expect(
209+
screen.queryByText(MOCK_SEARCH_REPOS[0].full_name),
210+
).not.toBeInTheDocument();
211+
212+
expect(searchGitReposSpy).not.toHaveBeenCalled();
213+
214+
await userEvent.type(input, "https://github.com/kubernetes/kubernetes");
215+
expect(searchGitReposSpy).toHaveBeenLastCalledWith(
216+
"kubernetes/kubernetes",
217+
3,
218+
);
219+
220+
expect(
221+
screen.getByText(MOCK_SEARCH_REPOS[0].full_name),
222+
).toBeInTheDocument();
223+
for (const repo of MOCK_REPOS) {
224+
expect(screen.queryByText(repo.full_name)).not.toBeInTheDocument();
225+
}
226+
});
227+
228+
it("should call onRepoSelection when a searched repository is selected", async () => {
229+
const MOCK_SEARCH_REPOS: GitRepository[] = [
230+
{
231+
id: 3,
232+
full_name: "kubernetes/kubernetes",
233+
git_provider: "github",
234+
is_public: true,
235+
},
236+
];
237+
238+
const searchGitReposSpy = vi.spyOn(OpenHands, "searchGitRepositories");
239+
searchGitReposSpy.mockResolvedValue(MOCK_SEARCH_REPOS);
240+
241+
renderForm();
242+
243+
const input = await screen.findByTestId("repo-dropdown");
244+
245+
await userEvent.type(input, "https://github.com/kubernetes/kubernetes");
246+
expect(searchGitReposSpy).toHaveBeenLastCalledWith(
247+
"kubernetes/kubernetes",
248+
3,
249+
);
250+
251+
const searchedRepo = screen.getByText(MOCK_SEARCH_REPOS[0].full_name);
252+
expect(searchedRepo).toBeInTheDocument();
253+
254+
await userEvent.click(searchedRepo);
255+
expect(mockOnRepoSelection).toHaveBeenCalledWith(
256+
MOCK_SEARCH_REPOS[0].full_name,
257+
);
258+
});
259+
});

0 commit comments

Comments
 (0)