fix: prevent dirty state on initial mount in ImageGenerationSettings (#7495)

This commit is contained in:
Daniel 2025-08-28 10:05:49 -05:00 committed by Shawn
parent fce45487b8
commit 313a674d9e
2 changed files with 119 additions and 7 deletions

View file

@ -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 (
<div className="space-y-4">
@ -67,7 +85,7 @@ export const ImageGenerationSettings = ({
</label>
<VSCodeTextField
value={openRouterApiKey}
onInput={(e: any) => 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 = ({
</label>
<VSCodeDropdown
value={selectedModel}
onChange={(e: any) => setSelectedModel(e.target.value)}
onChange={(e: any) => handleModelChange(e.target.value)}
className="w-full">
{IMAGE_GENERATION_MODELS.map((model) => (
<VSCodeOption key={model.value} value={model.value} className="py-2 px-3">

View file

@ -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(<ImageGenerationSettings {...defaultProps} />)
// 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(<ImageGenerationSettings {...defaultProps} apiConfiguration={apiConfiguration} />)
// 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(<ImageGenerationSettings {...defaultProps} enabled={true} />)
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(<ImageGenerationSettings {...defaultProps} enabled={true} />)
expect(
getByPlaceholderText("settings:experimental.IMAGE_GENERATION.openRouterApiKeyPlaceholder"),
).toBeInTheDocument()
})
it("should not render input fields when enabled is false", () => {
const { queryByPlaceholderText } = render(<ImageGenerationSettings {...defaultProps} enabled={false} />)
expect(
queryByPlaceholderText("settings:experimental.IMAGE_GENERATION.openRouterApiKeyPlaceholder"),
).not.toBeInTheDocument()
})
})
})