diff --git a/webview-ui/src/components/settings/ImageGenerationSettings.tsx b/webview-ui/src/components/settings/ImageGenerationSettings.tsx index 667a31d579..feb56ac5b5 100644 --- a/webview-ui/src/components/settings/ImageGenerationSettings.tsx +++ b/webview-ui/src/components/settings/ImageGenerationSettings.tsx @@ -36,14 +36,32 @@ export const ImageGenerationSettings = ({ imageGenerationSettings.selectedModel || IMAGE_GENERATION_MODELS[0].value, ) - // Update parent state when local state changes + // Update local state when apiConfiguration changes (e.g., when switching profiles) useEffect(() => { + setOpenRouterApiKey(imageGenerationSettings.openRouterApiKey || "") + setSelectedModel(imageGenerationSettings.selectedModel || IMAGE_GENERATION_MODELS[0].value) + }, [imageGenerationSettings.openRouterApiKey, imageGenerationSettings.selectedModel]) + + // Helper function to update settings + const updateSettings = (newApiKey: string, newModel: string) => { const newSettings = { - openRouterApiKey, - selectedModel, + openRouterApiKey: newApiKey, + selectedModel: newModel, } - setApiConfigurationField("openRouterImageGenerationSettings", newSettings) - }, [openRouterApiKey, selectedModel, setApiConfigurationField]) + setApiConfigurationField("openRouterImageGenerationSettings", newSettings, true) + } + + // Handle API key changes + const handleApiKeyChange = (value: string) => { + setOpenRouterApiKey(value) + updateSettings(value, selectedModel) + } + + // Handle model selection changes + const handleModelChange = (value: string) => { + setSelectedModel(value) + updateSettings(openRouterApiKey, value) + } return (
@@ -67,7 +85,7 @@ export const ImageGenerationSettings = ({ setOpenRouterApiKey(e.target.value)} + onInput={(e: any) => handleApiKeyChange(e.target.value)} placeholder={t("settings:experimental.IMAGE_GENERATION.openRouterApiKeyPlaceholder")} className="w-full" type="password" @@ -91,7 +109,7 @@ export const ImageGenerationSettings = ({ setSelectedModel(e.target.value)} + onChange={(e: any) => handleModelChange(e.target.value)} className="w-full"> {IMAGE_GENERATION_MODELS.map((model) => ( diff --git a/webview-ui/src/components/settings/__tests__/ImageGenerationSettings.spec.tsx b/webview-ui/src/components/settings/__tests__/ImageGenerationSettings.spec.tsx new file mode 100644 index 0000000000..ef3808c20b --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/ImageGenerationSettings.spec.tsx @@ -0,0 +1,94 @@ +import { render, fireEvent } from "@testing-library/react" +import { vi } from "vitest" +import { ImageGenerationSettings } from "../ImageGenerationSettings" +import type { ProviderSettings } from "@roo-code/types" + +// Mock the translation context +vi.mock("@/i18n/TranslationContext", () => ({ + useAppTranslation: () => ({ + t: (key: string) => key, + }), +})) + +describe("ImageGenerationSettings", () => { + const mockSetApiConfigurationField = vi.fn() + const mockOnChange = vi.fn() + + const defaultProps = { + enabled: false, + onChange: mockOnChange, + apiConfiguration: {} as ProviderSettings, + setApiConfigurationField: mockSetApiConfigurationField, + } + + beforeEach(() => { + vi.clearAllMocks() + }) + + describe("Initial Mount Behavior", () => { + it("should not call setApiConfigurationField on initial mount with empty configuration", () => { + render() + + // Should NOT call setApiConfigurationField on initial mount to prevent dirty state + expect(mockSetApiConfigurationField).not.toHaveBeenCalled() + }) + + it("should not call setApiConfigurationField on initial mount with existing configuration", () => { + const apiConfiguration = { + openRouterImageGenerationSettings: { + openRouterApiKey: "existing-key", + selectedModel: "google/gemini-2.5-flash-image-preview:free", + }, + } as ProviderSettings + + render() + + // Should NOT call setApiConfigurationField on initial mount to prevent dirty state + expect(mockSetApiConfigurationField).not.toHaveBeenCalled() + }) + }) + + describe("User Interaction Behavior", () => { + it("should call setApiConfigurationField when user changes API key", async () => { + const { getByPlaceholderText } = render() + + const apiKeyInput = getByPlaceholderText( + "settings:experimental.IMAGE_GENERATION.openRouterApiKeyPlaceholder", + ) + + // Simulate user typing + fireEvent.input(apiKeyInput, { target: { value: "new-api-key" } }) + + // Should call setApiConfigurationField with isUserAction=true + expect(mockSetApiConfigurationField).toHaveBeenCalledWith( + "openRouterImageGenerationSettings", + { + openRouterApiKey: "new-api-key", + selectedModel: "google/gemini-2.5-flash-image-preview", + }, + true, // This should be true for user actions + ) + }) + + // Note: Testing VSCode dropdown components is complex due to their custom nature + // The key functionality (not marking as dirty on initial mount) is already tested above + }) + + describe("Conditional Rendering", () => { + it("should render input fields when enabled is true", () => { + const { getByPlaceholderText } = render() + + expect( + getByPlaceholderText("settings:experimental.IMAGE_GENERATION.openRouterApiKeyPlaceholder"), + ).toBeInTheDocument() + }) + + it("should not render input fields when enabled is false", () => { + const { queryByPlaceholderText } = render() + + expect( + queryByPlaceholderText("settings:experimental.IMAGE_GENERATION.openRouterApiKeyPlaceholder"), + ).not.toBeInTheDocument() + }) + }) +})