diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 774f6a9d1..14d6fa441 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -148,6 +148,7 @@ const SettingsView = forwardRef(({ onDone, t const contentRef = useRef(null) const prevApiConfigName = useRef(currentApiConfigName) + const handledSettingsImportedAt = useRef(undefined) const confirmDialogHandler = useRef<() => void>() const [cachedState, setCachedState] = useState(() => extensionState) @@ -233,10 +234,13 @@ const SettingsView = forwardRef(({ onDone, t // Bust the cache when settings are imported. useEffect(() => { - if (settingsImportedAt) { - setCachedState((prevCachedState) => ({ ...prevCachedState, ...extensionState })) - setChangeDetected(false) + if (!settingsImportedAt || handledSettingsImportedAt.current === settingsImportedAt) { + return } + + handledSettingsImportedAt.current = settingsImportedAt + setCachedState((prevCachedState) => ({ ...prevCachedState, ...extensionState })) + setChangeDetected(false) }, [settingsImportedAt, extensionState]) const setCachedStateField: SetCachedStateField = useCallback((field, value) => { diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx index 2bbcc47b9..46e7c4881 100644 --- a/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx @@ -4,12 +4,18 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import React from "react" // Mock vscode API -const mockPostMessage = vi.fn() +const mockPostMessage = vi.hoisted(() => vi.fn()) const mockVscode = { postMessage: mockPostMessage, } ;(global as any).acquireVsCodeApi = () => mockVscode +vi.mock("@src/utils/vscode", () => ({ + vscode: { + postMessage: mockPostMessage, + }, +})) + // Import the actual component import SettingsView from "../SettingsView" import { useExtensionState } from "@src/context/ExtensionStateContext" @@ -187,7 +193,24 @@ vi.mock("../ApiConfigManager", () => ({ })) vi.mock("../ApiOptions", () => ({ - default: () => null, + default: ({ apiConfiguration, setApiConfigurationField }: any) => ( +
+ {apiConfiguration.apiProvider} + setApiConfigurationField("basetenApiKey", event.target.value)} + /> + {["openrouter", "baseten", "deepseek"].map((provider) => ( + + ))} +
+ ), })) vi.mock("../AutoApproveSettings", () => ({ @@ -369,4 +392,74 @@ describe("SettingsView - Change Detection Fix", () => { expect(true).toBe(true) // Placeholder - the real test is the running system }) + + it("preserves a DeepSeek provider edit after saving Baseten when the same import timestamp replays", async () => { + const onDone = vi.fn() + let extensionState = createExtensionState({ + settingsImportedAt: 123, + apiConfiguration: { + apiProvider: "openai", + apiModelId: "gpt-4.1", + }, + }) + + ;(useExtensionState as any).mockImplementation(() => extensionState) + + const { rerender } = render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId("provider-value")).toHaveTextContent("openai") + }) + + fireEvent.click(screen.getByTestId("set-provider-baseten")) + fireEvent.change(screen.getByTestId("baseten-api-key"), { target: { value: "test-baseten-key" } }) + expect(screen.getByTestId("provider-value")).toHaveTextContent("baseten") + + mockPostMessage.mockClear() + fireEvent.click(screen.getByTestId("save-button")) + expect(mockPostMessage).toHaveBeenCalledWith({ + type: "upsertApiConfiguration", + text: "default", + apiConfiguration: expect.objectContaining({ + apiProvider: "baseten", + basetenApiKey: "test-baseten-key", + }), + }) + + fireEvent.click(screen.getByTestId("set-provider-deepseek")) + expect(screen.getByTestId("provider-value")).toHaveTextContent("deepseek") + + extensionState = createExtensionState({ + settingsImportedAt: 123, + soundEnabled: true, + apiConfiguration: { + apiProvider: "baseten", + apiModelId: "zai-org/GLM-4.6", + basetenApiKey: "test-baseten-key", + }, + }) + + rerender( + + + , + ) + + expect(screen.getByTestId("provider-value")).toHaveTextContent("deepseek") + + mockPostMessage.mockClear() + fireEvent.click(screen.getByTestId("save-button")) + + expect(mockPostMessage).toHaveBeenCalledWith({ + type: "upsertApiConfiguration", + text: "default", + apiConfiguration: expect.objectContaining({ + apiProvider: "deepseek", + }), + }) + }) })