Spaces:
Build error
Build error
| import { render, screen, waitFor, within } from "@testing-library/react"; | |
| import userEvent from "@testing-library/user-event"; | |
| import { beforeEach, describe, expect, it, vi } from "vitest"; | |
| import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; | |
| import LlmSettingsScreen from "#/routes/llm-settings"; | |
| import OpenHands from "#/api/open-hands"; | |
| import { | |
| MOCK_DEFAULT_USER_SETTINGS, | |
| resetTestHandlersMockSettings, | |
| } from "#/mocks/handlers"; | |
| import * as AdvancedSettingsUtlls from "#/utils/has-advanced-settings-set"; | |
| import * as ToastHandlers from "#/utils/custom-toast-handlers"; | |
| const renderLlmSettingsScreen = () => | |
| render(<LlmSettingsScreen />, { | |
| wrapper: ({ children }) => ( | |
| <QueryClientProvider client={new QueryClient()}> | |
| {children} | |
| </QueryClientProvider> | |
| ), | |
| }); | |
| beforeEach(() => { | |
| vi.resetAllMocks(); | |
| resetTestHandlersMockSettings(); | |
| }); | |
| describe("Content", () => { | |
| describe("Basic form", () => { | |
| it("should render the basic form by default", async () => { | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const basicFom = screen.getByTestId("llm-settings-form-basic"); | |
| within(basicFom).getByTestId("llm-provider-input"); | |
| within(basicFom).getByTestId("llm-model-input"); | |
| within(basicFom).getByTestId("llm-api-key-input"); | |
| within(basicFom).getByTestId("llm-api-key-help-anchor"); | |
| }); | |
| it("should render the default values if non exist", async () => { | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const provider = screen.getByTestId("llm-provider-input"); | |
| const model = screen.getByTestId("llm-model-input"); | |
| const apiKey = screen.getByTestId("llm-api-key-input"); | |
| await waitFor(() => { | |
| expect(provider).toHaveValue("Anthropic"); | |
| expect(model).toHaveValue("claude-sonnet-4-20250514"); | |
| expect(apiKey).toHaveValue(""); | |
| expect(apiKey).toHaveProperty("placeholder", ""); | |
| }); | |
| }); | |
| it("should render the existing settings values", async () => { | |
| const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); | |
| getSettingsSpy.mockResolvedValue({ | |
| ...MOCK_DEFAULT_USER_SETTINGS, | |
| llm_model: "openai/gpt-4o", | |
| llm_api_key_set: true, | |
| }); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const provider = screen.getByTestId("llm-provider-input"); | |
| const model = screen.getByTestId("llm-model-input"); | |
| const apiKey = screen.getByTestId("llm-api-key-input"); | |
| await waitFor(() => { | |
| expect(provider).toHaveValue("OpenAI"); | |
| expect(model).toHaveValue("gpt-4o"); | |
| expect(apiKey).toHaveValue(""); | |
| expect(apiKey).toHaveProperty("placeholder", "<hidden>"); | |
| expect(screen.getByTestId("set-indicator")).toBeInTheDocument(); | |
| }); | |
| }); | |
| }); | |
| describe("Advanced form", () => { | |
| it("should render the advanced form if the switch is toggled", async () => { | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const advancedSwitch = screen.getByTestId("advanced-settings-switch"); | |
| const basicForm = screen.getByTestId("llm-settings-form-basic"); | |
| expect( | |
| screen.queryByTestId("llm-settings-form-advanced"), | |
| ).not.toBeInTheDocument(); | |
| expect(basicForm).toBeInTheDocument(); | |
| await userEvent.click(advancedSwitch); | |
| expect( | |
| screen.queryByTestId("llm-settings-form-advanced"), | |
| ).toBeInTheDocument(); | |
| expect(basicForm).not.toBeInTheDocument(); | |
| const advancedForm = screen.getByTestId("llm-settings-form-advanced"); | |
| within(advancedForm).getByTestId("llm-custom-model-input"); | |
| within(advancedForm).getByTestId("base-url-input"); | |
| within(advancedForm).getByTestId("llm-api-key-input"); | |
| within(advancedForm).getByTestId("llm-api-key-help-anchor-advanced"); | |
| within(advancedForm).getByTestId("agent-input"); | |
| within(advancedForm).getByTestId("enable-confirmation-mode-switch"); | |
| within(advancedForm).getByTestId("enable-memory-condenser-switch"); | |
| await userEvent.click(advancedSwitch); | |
| expect( | |
| screen.queryByTestId("llm-settings-form-advanced"), | |
| ).not.toBeInTheDocument(); | |
| expect(screen.getByTestId("llm-settings-form-basic")).toBeInTheDocument(); | |
| }); | |
| it("should render the default advanced settings", async () => { | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const advancedSwitch = screen.getByTestId("advanced-settings-switch"); | |
| expect(advancedSwitch).not.toBeChecked(); | |
| await userEvent.click(advancedSwitch); | |
| const model = screen.getByTestId("llm-custom-model-input"); | |
| const baseUrl = screen.getByTestId("base-url-input"); | |
| const apiKey = screen.getByTestId("llm-api-key-input"); | |
| const agent = screen.getByTestId("agent-input"); | |
| const confirmation = screen.getByTestId( | |
| "enable-confirmation-mode-switch", | |
| ); | |
| const condensor = screen.getByTestId("enable-memory-condenser-switch"); | |
| expect(model).toHaveValue("anthropic/claude-sonnet-4-20250514"); | |
| expect(baseUrl).toHaveValue(""); | |
| expect(apiKey).toHaveValue(""); | |
| expect(apiKey).toHaveProperty("placeholder", ""); | |
| expect(agent).toHaveValue("CodeActAgent"); | |
| expect(confirmation).not.toBeChecked(); | |
| expect(condensor).toBeChecked(); | |
| // check that security analyzer is present | |
| expect( | |
| screen.queryByTestId("security-analyzer-input"), | |
| ).not.toBeInTheDocument(); | |
| await userEvent.click(confirmation); | |
| screen.getByTestId("security-analyzer-input"); | |
| }); | |
| it("should render the advanced form if existings settings are advanced", async () => { | |
| const hasAdvancedSettingsSetSpy = vi.spyOn( | |
| AdvancedSettingsUtlls, | |
| "hasAdvancedSettingsSet", | |
| ); | |
| hasAdvancedSettingsSetSpy.mockReturnValue(true); | |
| renderLlmSettingsScreen(); | |
| await waitFor(() => { | |
| const advancedSwitch = screen.getByTestId("advanced-settings-switch"); | |
| expect(advancedSwitch).toBeChecked(); | |
| screen.getByTestId("llm-settings-form-advanced"); | |
| }); | |
| }); | |
| it("should render existing advanced settings correctly", async () => { | |
| const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); | |
| getSettingsSpy.mockResolvedValue({ | |
| ...MOCK_DEFAULT_USER_SETTINGS, | |
| llm_model: "openai/gpt-4o", | |
| llm_base_url: "https://api.openai.com/v1/chat/completions", | |
| llm_api_key_set: true, | |
| agent: "CoActAgent", | |
| confirmation_mode: true, | |
| enable_default_condenser: false, | |
| security_analyzer: "mock-invariant", | |
| }); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const model = screen.getByTestId("llm-custom-model-input"); | |
| const baseUrl = screen.getByTestId("base-url-input"); | |
| const apiKey = screen.getByTestId("llm-api-key-input"); | |
| const agent = screen.getByTestId("agent-input"); | |
| const confirmation = screen.getByTestId( | |
| "enable-confirmation-mode-switch", | |
| ); | |
| const condensor = screen.getByTestId("enable-memory-condenser-switch"); | |
| const securityAnalyzer = screen.getByTestId("security-analyzer-input"); | |
| await waitFor(() => { | |
| expect(model).toHaveValue("openai/gpt-4o"); | |
| expect(baseUrl).toHaveValue( | |
| "https://api.openai.com/v1/chat/completions", | |
| ); | |
| expect(apiKey).toHaveValue(""); | |
| expect(apiKey).toHaveProperty("placeholder", "<hidden>"); | |
| expect(agent).toHaveValue("CoActAgent"); | |
| expect(confirmation).toBeChecked(); | |
| expect(condensor).not.toBeChecked(); | |
| expect(securityAnalyzer).toHaveValue("mock-invariant"); | |
| }); | |
| }); | |
| }); | |
| it.todo("should render an indicator if the llm api key is set"); | |
| }); | |
| describe("Form submission", () => { | |
| it("should submit the basic form with the correct values", async () => { | |
| const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const provider = screen.getByTestId("llm-provider-input"); | |
| const model = screen.getByTestId("llm-model-input"); | |
| const apiKey = screen.getByTestId("llm-api-key-input"); | |
| // select provider | |
| await userEvent.click(provider); | |
| const providerOption = screen.getByText("OpenAI"); | |
| await userEvent.click(providerOption); | |
| expect(provider).toHaveValue("OpenAI"); | |
| // enter api key | |
| await userEvent.type(apiKey, "test-api-key"); | |
| // select model | |
| await userEvent.click(model); | |
| const modelOption = screen.getByText("gpt-4o"); | |
| await userEvent.click(modelOption); | |
| expect(model).toHaveValue("gpt-4o"); | |
| const submitButton = screen.getByTestId("submit-button"); | |
| await userEvent.click(submitButton); | |
| expect(saveSettingsSpy).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| llm_model: "openai/gpt-4o", | |
| llm_api_key: "test-api-key", | |
| }), | |
| ); | |
| }); | |
| it("should submit the advanced form with the correct values", async () => { | |
| const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const advancedSwitch = screen.getByTestId("advanced-settings-switch"); | |
| await userEvent.click(advancedSwitch); | |
| const model = screen.getByTestId("llm-custom-model-input"); | |
| const baseUrl = screen.getByTestId("base-url-input"); | |
| const apiKey = screen.getByTestId("llm-api-key-input"); | |
| const agent = screen.getByTestId("agent-input"); | |
| const confirmation = screen.getByTestId("enable-confirmation-mode-switch"); | |
| const condensor = screen.getByTestId("enable-memory-condenser-switch"); | |
| // enter custom model | |
| await userEvent.clear(model); | |
| await userEvent.type(model, "openai/gpt-4o"); | |
| expect(model).toHaveValue("openai/gpt-4o"); | |
| // enter base url | |
| await userEvent.type(baseUrl, "https://api.openai.com/v1/chat/completions"); | |
| expect(baseUrl).toHaveValue("https://api.openai.com/v1/chat/completions"); | |
| // enter api key | |
| await userEvent.type(apiKey, "test-api-key"); | |
| // toggle confirmation mode | |
| await userEvent.click(confirmation); | |
| expect(confirmation).toBeChecked(); | |
| // toggle memory condensor | |
| await userEvent.click(condensor); | |
| expect(condensor).not.toBeChecked(); | |
| // select agent | |
| await userEvent.click(agent); | |
| const agentOption = screen.getByText("CoActAgent"); | |
| await userEvent.click(agentOption); | |
| expect(agent).toHaveValue("CoActAgent"); | |
| // select security analyzer | |
| const securityAnalyzer = screen.getByTestId("security-analyzer-input"); | |
| await userEvent.click(securityAnalyzer); | |
| const securityAnalyzerOption = screen.getByText("mock-invariant"); | |
| await userEvent.click(securityAnalyzerOption); | |
| const submitButton = screen.getByTestId("submit-button"); | |
| await userEvent.click(submitButton); | |
| expect(saveSettingsSpy).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| llm_model: "openai/gpt-4o", | |
| llm_base_url: "https://api.openai.com/v1/chat/completions", | |
| agent: "CoActAgent", | |
| confirmation_mode: true, | |
| enable_default_condenser: false, | |
| security_analyzer: "mock-invariant", | |
| }), | |
| ); | |
| }); | |
| it("should disable the button if there are no changes in the basic form", async () => { | |
| const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); | |
| getSettingsSpy.mockResolvedValue({ | |
| ...MOCK_DEFAULT_USER_SETTINGS, | |
| llm_model: "openai/gpt-4o", | |
| llm_api_key_set: true, | |
| }); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| screen.getByTestId("llm-settings-form-basic"); | |
| const submitButton = screen.getByTestId("submit-button"); | |
| expect(submitButton).toBeDisabled(); | |
| const model = screen.getByTestId("llm-model-input"); | |
| const apiKey = screen.getByTestId("llm-api-key-input"); | |
| // select model | |
| await userEvent.click(model); | |
| const modelOption = screen.getByText("gpt-4o-mini"); | |
| await userEvent.click(modelOption); | |
| expect(model).toHaveValue("gpt-4o-mini"); | |
| expect(submitButton).not.toBeDisabled(); | |
| // reset model | |
| await userEvent.click(model); | |
| const modelOption2 = screen.getByText("gpt-4o"); | |
| await userEvent.click(modelOption2); | |
| expect(model).toHaveValue("gpt-4o"); | |
| expect(submitButton).toBeDisabled(); | |
| // set api key | |
| await userEvent.type(apiKey, "test-api-key"); | |
| expect(apiKey).toHaveValue("test-api-key"); | |
| expect(submitButton).not.toBeDisabled(); | |
| // reset api key | |
| await userEvent.clear(apiKey); | |
| expect(apiKey).toHaveValue(""); | |
| expect(submitButton).toBeDisabled(); | |
| }); | |
| it("should disable the button if there are no changes in the advanced form", async () => { | |
| const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); | |
| getSettingsSpy.mockResolvedValue({ | |
| ...MOCK_DEFAULT_USER_SETTINGS, | |
| llm_model: "openai/gpt-4o", | |
| llm_base_url: "https://api.openai.com/v1/chat/completions", | |
| llm_api_key_set: true, | |
| confirmation_mode: true, | |
| }); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| screen.getByTestId("llm-settings-form-advanced"); | |
| const submitButton = screen.getByTestId("submit-button"); | |
| expect(submitButton).toBeDisabled(); | |
| const model = screen.getByTestId("llm-custom-model-input"); | |
| const baseUrl = screen.getByTestId("base-url-input"); | |
| const apiKey = screen.getByTestId("llm-api-key-input"); | |
| const agent = screen.getByTestId("agent-input"); | |
| const confirmation = screen.getByTestId("enable-confirmation-mode-switch"); | |
| const condensor = screen.getByTestId("enable-memory-condenser-switch"); | |
| // enter custom model | |
| await userEvent.type(model, "-mini"); | |
| expect(model).toHaveValue("openai/gpt-4o-mini"); | |
| expect(submitButton).not.toBeDisabled(); | |
| // reset model | |
| await userEvent.clear(model); | |
| expect(model).toHaveValue(""); | |
| expect(submitButton).toBeDisabled(); | |
| await userEvent.type(model, "openai/gpt-4o"); | |
| expect(model).toHaveValue("openai/gpt-4o"); | |
| expect(submitButton).toBeDisabled(); | |
| // enter base url | |
| await userEvent.type(baseUrl, "/extra"); | |
| expect(baseUrl).toHaveValue( | |
| "https://api.openai.com/v1/chat/completions/extra", | |
| ); | |
| expect(submitButton).not.toBeDisabled(); | |
| await userEvent.clear(baseUrl); | |
| expect(baseUrl).toHaveValue(""); | |
| expect(submitButton).not.toBeDisabled(); | |
| await userEvent.type(baseUrl, "https://api.openai.com/v1/chat/completions"); | |
| expect(baseUrl).toHaveValue("https://api.openai.com/v1/chat/completions"); | |
| expect(submitButton).toBeDisabled(); | |
| // set api key | |
| await userEvent.type(apiKey, "test-api-key"); | |
| expect(apiKey).toHaveValue("test-api-key"); | |
| expect(submitButton).not.toBeDisabled(); | |
| // reset api key | |
| await userEvent.clear(apiKey); | |
| expect(apiKey).toHaveValue(""); | |
| expect(submitButton).toBeDisabled(); | |
| // set agent | |
| await userEvent.clear(agent); | |
| await userEvent.type(agent, "test-agent"); | |
| expect(agent).toHaveValue("test-agent"); | |
| expect(submitButton).not.toBeDisabled(); | |
| // reset agent | |
| await userEvent.clear(agent); | |
| expect(agent).toHaveValue(""); | |
| expect(submitButton).toBeDisabled(); | |
| await userEvent.type(agent, "CodeActAgent"); | |
| expect(agent).toHaveValue("CodeActAgent"); | |
| expect(submitButton).toBeDisabled(); | |
| // toggle confirmation mode | |
| await userEvent.click(confirmation); | |
| expect(confirmation).not.toBeChecked(); | |
| expect(submitButton).not.toBeDisabled(); | |
| await userEvent.click(confirmation); | |
| expect(confirmation).toBeChecked(); | |
| expect(submitButton).toBeDisabled(); | |
| // toggle memory condensor | |
| await userEvent.click(condensor); | |
| expect(condensor).not.toBeChecked(); | |
| expect(submitButton).not.toBeDisabled(); | |
| await userEvent.click(condensor); | |
| expect(condensor).toBeChecked(); | |
| expect(submitButton).toBeDisabled(); | |
| // select security analyzer | |
| const securityAnalyzer = screen.getByTestId("security-analyzer-input"); | |
| await userEvent.click(securityAnalyzer); | |
| const securityAnalyzerOption = screen.getByText("mock-invariant"); | |
| await userEvent.click(securityAnalyzerOption); | |
| expect(securityAnalyzer).toHaveValue("mock-invariant"); | |
| expect(submitButton).not.toBeDisabled(); | |
| await userEvent.clear(securityAnalyzer); | |
| expect(securityAnalyzer).toHaveValue(""); | |
| expect(submitButton).toBeDisabled(); | |
| }); | |
| it("should reset button state when switching between forms", async () => { | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const advancedSwitch = screen.getByTestId("advanced-settings-switch"); | |
| const submitButton = screen.getByTestId("submit-button"); | |
| expect(submitButton).toBeDisabled(); | |
| // dirty the basic form | |
| const apiKey = screen.getByTestId("llm-api-key-input"); | |
| await userEvent.type(apiKey, "test-api-key"); | |
| expect(submitButton).not.toBeDisabled(); | |
| await userEvent.click(advancedSwitch); | |
| expect(submitButton).toBeDisabled(); | |
| // dirty the advanced form | |
| const model = screen.getByTestId("llm-custom-model-input"); | |
| await userEvent.type(model, "openai/gpt-4o"); | |
| expect(submitButton).not.toBeDisabled(); | |
| await userEvent.click(advancedSwitch); | |
| expect(submitButton).toBeDisabled(); | |
| }); | |
| // flaky test | |
| it.skip("should disable the button when submitting changes", async () => { | |
| const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const apiKey = screen.getByTestId("llm-api-key-input"); | |
| await userEvent.type(apiKey, "test-api-key"); | |
| const submitButton = screen.getByTestId("submit-button"); | |
| await userEvent.click(submitButton); | |
| expect(saveSettingsSpy).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| llm_api_key: "test-api-key", | |
| }), | |
| ); | |
| expect(submitButton).toHaveTextContent("Saving..."); | |
| expect(submitButton).toBeDisabled(); | |
| await waitFor(() => { | |
| expect(submitButton).toHaveTextContent("Save"); | |
| expect(submitButton).toBeDisabled(); | |
| }); | |
| }); | |
| it("should clear advanced settings when saving basic settings", async () => { | |
| const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); | |
| getSettingsSpy.mockResolvedValue({ | |
| ...MOCK_DEFAULT_USER_SETTINGS, | |
| llm_model: "openai/gpt-4o", | |
| llm_base_url: "https://api.openai.com/v1/chat/completions", | |
| llm_api_key_set: true, | |
| confirmation_mode: true, | |
| }); | |
| const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const advancedSwitch = screen.getByTestId("advanced-settings-switch"); | |
| await userEvent.click(advancedSwitch); | |
| const provider = screen.getByTestId("llm-provider-input"); | |
| const model = screen.getByTestId("llm-model-input"); | |
| // select provider | |
| await userEvent.click(provider); | |
| const providerOption = screen.getByText("Anthropic"); | |
| await userEvent.click(providerOption); | |
| // select model | |
| await userEvent.click(model); | |
| const modelOption = screen.getByText("claude-sonnet-4-20250514"); | |
| await userEvent.click(modelOption); | |
| const submitButton = screen.getByTestId("submit-button"); | |
| await userEvent.click(submitButton); | |
| expect(saveSettingsSpy).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| llm_model: "anthropic/claude-sonnet-4-20250514", | |
| llm_base_url: "", | |
| confirmation_mode: false, | |
| }), | |
| ); | |
| }); | |
| }); | |
| describe("Status toasts", () => { | |
| describe("Basic form", () => { | |
| it("should call displaySuccessToast when the settings are saved", async () => { | |
| const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); | |
| const displaySuccessToastSpy = vi.spyOn( | |
| ToastHandlers, | |
| "displaySuccessToast", | |
| ); | |
| renderLlmSettingsScreen(); | |
| // Toggle setting to change | |
| const apiKeyInput = await screen.findByTestId("llm-api-key-input"); | |
| await userEvent.type(apiKeyInput, "test-api-key"); | |
| const submit = await screen.findByTestId("submit-button"); | |
| await userEvent.click(submit); | |
| expect(saveSettingsSpy).toHaveBeenCalled(); | |
| await waitFor(() => expect(displaySuccessToastSpy).toHaveBeenCalled()); | |
| }); | |
| it("should call displayErrorToast when the settings fail to save", async () => { | |
| const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); | |
| const displayErrorToastSpy = vi.spyOn(ToastHandlers, "displayErrorToast"); | |
| saveSettingsSpy.mockRejectedValue(new Error("Failed to save settings")); | |
| renderLlmSettingsScreen(); | |
| // Toggle setting to change | |
| const apiKeyInput = await screen.findByTestId("llm-api-key-input"); | |
| await userEvent.type(apiKeyInput, "test-api-key"); | |
| const submit = await screen.findByTestId("submit-button"); | |
| await userEvent.click(submit); | |
| expect(saveSettingsSpy).toHaveBeenCalled(); | |
| expect(displayErrorToastSpy).toHaveBeenCalled(); | |
| }); | |
| }); | |
| describe("Advanced form", () => { | |
| it("should call displaySuccessToast when the settings are saved", async () => { | |
| const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); | |
| const displaySuccessToastSpy = vi.spyOn( | |
| ToastHandlers, | |
| "displaySuccessToast", | |
| ); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const advancedSwitch = screen.getByTestId("advanced-settings-switch"); | |
| await userEvent.click(advancedSwitch); | |
| await screen.findByTestId("llm-settings-form-advanced"); | |
| // Toggle setting to change | |
| const apiKeyInput = await screen.findByTestId("llm-api-key-input"); | |
| await userEvent.type(apiKeyInput, "test-api-key"); | |
| const submit = await screen.findByTestId("submit-button"); | |
| await userEvent.click(submit); | |
| expect(saveSettingsSpy).toHaveBeenCalled(); | |
| await waitFor(() => expect(displaySuccessToastSpy).toHaveBeenCalled()); | |
| }); | |
| it("should call displayErrorToast when the settings fail to save", async () => { | |
| const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); | |
| const displayErrorToastSpy = vi.spyOn(ToastHandlers, "displayErrorToast"); | |
| saveSettingsSpy.mockRejectedValue(new Error("Failed to save settings")); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const advancedSwitch = screen.getByTestId("advanced-settings-switch"); | |
| await userEvent.click(advancedSwitch); | |
| await screen.findByTestId("llm-settings-form-advanced"); | |
| // Toggle setting to change | |
| const apiKeyInput = await screen.findByTestId("llm-api-key-input"); | |
| await userEvent.type(apiKeyInput, "test-api-key"); | |
| const submit = await screen.findByTestId("submit-button"); | |
| await userEvent.click(submit); | |
| expect(saveSettingsSpy).toHaveBeenCalled(); | |
| expect(displayErrorToastSpy).toHaveBeenCalled(); | |
| }); | |
| }); | |
| }); | |
| describe("SaaS mode", () => { | |
| it("should not render the runtime settings input in oss mode", async () => { | |
| const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); | |
| // @ts-expect-error - only return mode | |
| getConfigSpy.mockResolvedValue({ | |
| APP_MODE: "oss", | |
| }); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const advancedSwitch = screen.getByTestId("advanced-settings-switch"); | |
| await userEvent.click(advancedSwitch); | |
| await screen.findByTestId("llm-settings-form-advanced"); | |
| const runtimeSettingsInput = screen.queryByTestId("runtime-settings-input"); | |
| expect(runtimeSettingsInput).not.toBeInTheDocument(); | |
| }); | |
| it("should render the runtime settings input in saas mode", async () => { | |
| const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); | |
| // @ts-expect-error - only return mode | |
| getConfigSpy.mockResolvedValue({ | |
| APP_MODE: "saas", | |
| }); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const advancedSwitch = screen.getByTestId("advanced-settings-switch"); | |
| await userEvent.click(advancedSwitch); | |
| await screen.findByTestId("llm-settings-form-advanced"); | |
| const runtimeSettingsInput = screen.queryByTestId("runtime-settings-input"); | |
| expect(runtimeSettingsInput).toBeInTheDocument(); | |
| }); | |
| it("should always render the runtime settings input as disabled", async () => { | |
| const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); | |
| // @ts-expect-error - only return mode | |
| getConfigSpy.mockResolvedValue({ | |
| APP_MODE: "saas", | |
| }); | |
| renderLlmSettingsScreen(); | |
| await screen.findByTestId("llm-settings-screen"); | |
| const advancedSwitch = screen.getByTestId("advanced-settings-switch"); | |
| await userEvent.click(advancedSwitch); | |
| await screen.findByTestId("llm-settings-form-advanced"); | |
| const runtimeSettingsInput = screen.queryByTestId("runtime-settings-input"); | |
| expect(runtimeSettingsInput).toBeInTheDocument(); | |
| expect(runtimeSettingsInput).toBeDisabled(); | |
| }); | |
| }); | |