import { render, screen, within } from "@testing-library/react"; import { createRoutesStub } from "react-router"; import { describe, expect, it, vi } from "vitest"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import SettingsScreen from "#/routes/settings"; import OpenHands from "#/api/open-hands"; // Mock the i18next hook vi.mock("react-i18next", async () => { const actual = await vi.importActual("react-i18next"); return { ...actual, useTranslation: () => ({ t: (key: string) => { const translations: Record = { SETTINGS$NAV_GIT: "Git", SETTINGS$NAV_APPLICATION: "Application", SETTINGS$NAV_CREDITS: "Credits", SETTINGS$NAV_API_KEYS: "API Keys", SETTINGS$NAV_LLM: "LLM", SETTINGS$TITLE: "Settings", }; return translations[key] || key; }, i18n: { changeLanguage: vi.fn(), }, }), }; }); describe("Settings Screen", () => { const { handleLogoutMock } = vi.hoisted(() => ({ handleLogoutMock: vi.fn(), })); vi.mock("#/hooks/use-app-logout", () => ({ useAppLogout: vi.fn().mockReturnValue({ handleLogout: handleLogoutMock }), })); const RouterStub = createRoutesStub([ { Component: SettingsScreen, path: "/settings", children: [ { Component: () =>
, path: "/settings", }, { Component: () =>
, path: "/settings/git", }, { Component: () =>
, path: "/settings/app", }, { Component: () =>
, path: "/settings/credits", }, { Component: () =>
, path: "/settings/api-keys", }, ], }, ]); const renderSettingsScreen = (path = "/settings") => { const queryClient = new QueryClient(); return render(, { wrapper: ({ children }) => ( {children} ), }); }; it("should render the navbar", async () => { const sectionsToInclude = ["llm", "git", "application", "secrets"]; const sectionsToExclude = ["api keys", "credits"]; const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); // @ts-expect-error - only return app mode getConfigSpy.mockResolvedValue({ APP_MODE: "oss", }); renderSettingsScreen(); const navbar = await screen.findByTestId("settings-navbar"); sectionsToInclude.forEach((section) => { const sectionElement = within(navbar).getByText(section, { exact: false, // case insensitive }); expect(sectionElement).toBeInTheDocument(); }); sectionsToExclude.forEach((section) => { const sectionElement = within(navbar).queryByText(section, { exact: false, // case insensitive }); expect(sectionElement).not.toBeInTheDocument(); }); }); it("should render the saas navbar", async () => { const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); // @ts-expect-error - only return app mode getConfigSpy.mockResolvedValue({ APP_MODE: "saas", }); const sectionsToInclude = [ "git", "application", "credits", "secrets", "api keys", ]; const sectionsToExclude = ["llm"]; renderSettingsScreen(); const navbar = await screen.findByTestId("settings-navbar"); sectionsToInclude.forEach((section) => { const sectionElement = within(navbar).getByText(section, { exact: false, // case insensitive }); expect(sectionElement).toBeInTheDocument(); }); sectionsToExclude.forEach((section) => { const sectionElement = within(navbar).queryByText(section, { exact: false, // case insensitive }); expect(sectionElement).not.toBeInTheDocument(); }); }); it("should not be able to access oss-restricted routes in oss", async () => { const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); // @ts-expect-error - only return app mode getConfigSpy.mockResolvedValue({ APP_MODE: "oss", }); const { rerender } = renderSettingsScreen("/settings/credits"); expect( screen.queryByTestId("credits-settings-screen"), ).not.toBeInTheDocument(); rerender(); expect( screen.queryByTestId("api-keys-settings-screen"), ).not.toBeInTheDocument(); rerender(); expect( screen.queryByTestId("billing-settings-screen"), ).not.toBeInTheDocument(); rerender(); }); it.todo("should not be able to access saas-restricted routes in saas"); });