mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
feat: remember last selected git provider in homepage dropdown (#11979)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
a593730b21
commit
f7c3a36745
@ -2,9 +2,9 @@ import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, vi, beforeEach, it } from "vitest";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { RepositorySelectionForm } from "../../../../src/components/features/home/repo-selection-form";
|
||||
import UserService from "#/api/user-service/user-service.api";
|
||||
import GitService from "#/api/git-service/git-service.api";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { useHomeStore } from "#/stores/home-store";
|
||||
|
||||
// Create mock functions
|
||||
const mockUseUserRepositories = vi.fn();
|
||||
@ -97,7 +97,7 @@ vi.mock("#/context/auth-context", () => ({
|
||||
// Mock debounce to simulate proper debounced behavior
|
||||
let debouncedValue = "";
|
||||
vi.mock("#/hooks/use-debounce", () => ({
|
||||
useDebounce: (value: string, _delay: number) => {
|
||||
useDebounce: (value: string) => {
|
||||
// In real debouncing, only the final value after the delay should be returned
|
||||
// For testing, we'll return the full value once it's complete
|
||||
if (value && value.length > 20) {
|
||||
@ -124,28 +124,51 @@ vi.mock("#/hooks/query/use-search-repositories", () => ({
|
||||
}));
|
||||
|
||||
const mockOnRepoSelection = vi.fn();
|
||||
const renderForm = () =>
|
||||
render(<RepositorySelectionForm onRepoSelection={mockOnRepoSelection} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<QueryClientProvider
|
||||
client={
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
),
|
||||
|
||||
// Helper function to render with custom store state
|
||||
const renderForm = (
|
||||
storeOverrides: Partial<{
|
||||
recentRepositories: GitRepository[];
|
||||
lastSelectedProvider: 'gitlab' | null;
|
||||
}> = {},
|
||||
) => {
|
||||
// Set up the store state before rendering
|
||||
useHomeStore.setState({
|
||||
recentRepositories: [],
|
||||
lastSelectedProvider: null,
|
||||
...storeOverrides,
|
||||
});
|
||||
|
||||
return render(
|
||||
<RepositorySelectionForm onRepoSelection={mockOnRepoSelection} />,
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<QueryClientProvider
|
||||
client={
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
describe("RepositorySelectionForm", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Reset the store to initial state
|
||||
useHomeStore.setState({
|
||||
recentRepositories: [],
|
||||
lastSelectedProvider: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("shows dropdown when repositories are loaded", async () => {
|
||||
@ -226,7 +249,7 @@ describe("RepositorySelectionForm", () => {
|
||||
|
||||
renderForm();
|
||||
|
||||
const input = await screen.findByTestId("git-repo-dropdown");
|
||||
await screen.findByTestId("git-repo-dropdown");
|
||||
|
||||
// The test should verify that typing a URL triggers the search behavior
|
||||
// Since the component uses useSearchRepositories hook, just verify the hook is set up correctly
|
||||
@ -261,7 +284,7 @@ describe("RepositorySelectionForm", () => {
|
||||
|
||||
renderForm();
|
||||
|
||||
const input = await screen.findByTestId("git-repo-dropdown");
|
||||
await screen.findByTestId("git-repo-dropdown");
|
||||
|
||||
// Verify that the onRepoSelection callback prop was provided
|
||||
expect(mockOnRepoSelection).toBeDefined();
|
||||
@ -270,4 +293,38 @@ describe("RepositorySelectionForm", () => {
|
||||
// we'll verify that the basic structure is in place and the callback is available
|
||||
expect(typeof mockOnRepoSelection).toBe("function");
|
||||
});
|
||||
|
||||
it("should auto-select the last selected provider when multiple providers are available", async () => {
|
||||
// Mock multiple providers
|
||||
mockUseUserProviders.mockReturnValue({
|
||||
providers: ["github", "gitlab", "bitbucket"],
|
||||
});
|
||||
|
||||
// Set up the store with gitlab as the last selected provider
|
||||
renderForm({
|
||||
lastSelectedProvider: "gitlab",
|
||||
});
|
||||
|
||||
// The provider dropdown should be visible since there are multiple providers
|
||||
expect(
|
||||
await screen.findByTestId("git-provider-dropdown"),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Verify that the store has the correct last selected provider
|
||||
expect(useHomeStore.getState().lastSelectedProvider).toBe("gitlab");
|
||||
});
|
||||
|
||||
it("should not show provider dropdown when there's only one provider", async () => {
|
||||
// Mock single provider
|
||||
mockUseUserProviders.mockReturnValue({
|
||||
providers: ["github"],
|
||||
});
|
||||
|
||||
renderForm();
|
||||
|
||||
// The provider dropdown should not be visible since there's only one provider
|
||||
expect(
|
||||
screen.queryByTestId("git-provider-dropdown"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@ -35,7 +35,11 @@ export function RepositorySelectionForm({
|
||||
React.useState<Provider | null>(null);
|
||||
|
||||
const { providers } = useUserProviders();
|
||||
const { addRecentRepository } = useHomeStore();
|
||||
const {
|
||||
addRecentRepository,
|
||||
setLastSelectedProvider,
|
||||
getLastSelectedProvider,
|
||||
} = useHomeStore();
|
||||
const {
|
||||
mutate: createConversation,
|
||||
isPending,
|
||||
@ -46,12 +50,24 @@ export function RepositorySelectionForm({
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Auto-select provider if there's only one
|
||||
// Auto-select provider logic
|
||||
React.useEffect(() => {
|
||||
if (providers.length === 0) return;
|
||||
|
||||
// If there's only one provider, auto-select it
|
||||
if (providers.length === 1 && !selectedProvider) {
|
||||
setSelectedProvider(providers[0]);
|
||||
return;
|
||||
}
|
||||
}, [providers, selectedProvider]);
|
||||
|
||||
// If there are multiple providers and none is selected, try to use the last selected one
|
||||
if (providers.length > 1 && !selectedProvider) {
|
||||
const lastSelected = getLastSelectedProvider();
|
||||
if (lastSelected && providers.includes(lastSelected)) {
|
||||
setSelectedProvider(lastSelected);
|
||||
}
|
||||
}
|
||||
}, [providers, selectedProvider, getLastSelectedProvider]);
|
||||
|
||||
// We check for isSuccess because the app might require time to render
|
||||
// into the new conversation screen after the conversation is created.
|
||||
@ -66,6 +82,7 @@ export function RepositorySelectionForm({
|
||||
}
|
||||
|
||||
setSelectedProvider(provider);
|
||||
setLastSelectedProvider(provider); // Store the selected provider
|
||||
setSelectedRepository(null); // Reset repository selection when provider changes
|
||||
setSelectedBranch(null); // Reset branch selection when provider changes
|
||||
onRepoSelection(null); // Reset parent component's selected repo
|
||||
|
||||
@ -1,21 +1,26 @@
|
||||
import { create } from "zustand";
|
||||
import { persist, createJSONStorage } from "zustand/middleware";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { Provider } from "#/types/settings";
|
||||
|
||||
interface HomeState {
|
||||
recentRepositories: GitRepository[];
|
||||
lastSelectedProvider: Provider | null;
|
||||
}
|
||||
|
||||
interface HomeActions {
|
||||
addRecentRepository: (repository: GitRepository) => void;
|
||||
clearRecentRepositories: () => void;
|
||||
getRecentRepositories: () => GitRepository[];
|
||||
setLastSelectedProvider: (provider: Provider | null) => void;
|
||||
getLastSelectedProvider: () => Provider | null;
|
||||
}
|
||||
|
||||
type HomeStore = HomeState & HomeActions;
|
||||
|
||||
const initialState: HomeState = {
|
||||
recentRepositories: [],
|
||||
lastSelectedProvider: null,
|
||||
};
|
||||
|
||||
export const useHomeStore = create<HomeStore>()(
|
||||
@ -44,6 +49,13 @@ export const useHomeStore = create<HomeStore>()(
|
||||
})),
|
||||
|
||||
getRecentRepositories: () => get().recentRepositories,
|
||||
|
||||
setLastSelectedProvider: (provider: Provider | null) =>
|
||||
set(() => ({
|
||||
lastSelectedProvider: provider,
|
||||
})),
|
||||
|
||||
getLastSelectedProvider: () => get().lastSelectedProvider,
|
||||
}),
|
||||
{
|
||||
name: "home-store", // unique name for localStorage
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user