mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
196 lines
6.2 KiB
TypeScript
196 lines
6.2 KiB
TypeScript
import { render, screen, within } from "@testing-library/react";
|
|
import { createRoutesStub } from "react-router";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { QueryClientProvider } from "@tanstack/react-query";
|
|
import SettingsScreen, { clientLoader } from "#/routes/settings";
|
|
import OptionService from "#/api/option-service/option-service.api";
|
|
|
|
// Mock the i18next hook
|
|
vi.mock("react-i18next", async () => {
|
|
const actual =
|
|
await vi.importActual<typeof import("react-i18next")>("react-i18next");
|
|
return {
|
|
...actual,
|
|
useTranslation: () => ({
|
|
t: (key: string) => {
|
|
const translations: Record<string, string> = {
|
|
SETTINGS$NAV_INTEGRATIONS: "Integrations",
|
|
SETTINGS$NAV_APPLICATION: "Application",
|
|
SETTINGS$NAV_CREDITS: "Credits",
|
|
SETTINGS$NAV_API_KEYS: "API Keys",
|
|
SETTINGS$NAV_LLM: "LLM",
|
|
SETTINGS$NAV_SECRETS: "Secrets",
|
|
SETTINGS$NAV_MCP: "MCP",
|
|
SETTINGS$NAV_USER: "User",
|
|
SETTINGS$TITLE: "Settings",
|
|
};
|
|
return translations[key] || key;
|
|
},
|
|
i18n: {
|
|
changeLanguage: vi.fn(),
|
|
},
|
|
}),
|
|
};
|
|
});
|
|
|
|
describe("Settings Screen", () => {
|
|
const { handleLogoutMock, mockQueryClient } = vi.hoisted(() => ({
|
|
handleLogoutMock: vi.fn(),
|
|
mockQueryClient: (() => {
|
|
const { QueryClient } = require("@tanstack/react-query");
|
|
return new QueryClient();
|
|
})(),
|
|
}));
|
|
|
|
vi.mock("#/hooks/use-app-logout", () => ({
|
|
useAppLogout: vi.fn().mockReturnValue({ handleLogout: handleLogoutMock }),
|
|
}));
|
|
|
|
vi.mock("#/query-client-config", () => ({
|
|
queryClient: mockQueryClient,
|
|
}));
|
|
|
|
const RouterStub = createRoutesStub([
|
|
{
|
|
Component: SettingsScreen,
|
|
// @ts-expect-error - custom loader
|
|
clientLoader,
|
|
path: "/settings",
|
|
children: [
|
|
{
|
|
Component: () => <div data-testid="llm-settings-screen" />,
|
|
path: "/settings",
|
|
},
|
|
{
|
|
Component: () => <div data-testid="git-settings-screen" />,
|
|
path: "/settings/integrations",
|
|
},
|
|
{
|
|
Component: () => <div data-testid="application-settings-screen" />,
|
|
path: "/settings/app",
|
|
},
|
|
{
|
|
Component: () => <div data-testid="billing-settings-screen" />,
|
|
path: "/settings/billing",
|
|
},
|
|
{
|
|
Component: () => <div data-testid="api-keys-settings-screen" />,
|
|
path: "/settings/api-keys",
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
|
|
const renderSettingsScreen = (path = "/settings") =>
|
|
render(<RouterStub initialEntries={[path]} />, {
|
|
wrapper: ({ children }) => (
|
|
<QueryClientProvider client={mockQueryClient}>
|
|
{children}
|
|
</QueryClientProvider>
|
|
),
|
|
});
|
|
|
|
it("should render the navbar", async () => {
|
|
const sectionsToInclude = ["llm", "integrations", "application", "secrets"];
|
|
const sectionsToExclude = ["api keys", "credits", "billing"];
|
|
const getConfigSpy = vi.spyOn(OptionService, "getConfig");
|
|
// @ts-expect-error - only return app mode
|
|
getConfigSpy.mockResolvedValue({
|
|
APP_MODE: "oss",
|
|
});
|
|
|
|
// Clear any existing query data
|
|
mockQueryClient.clear();
|
|
|
|
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();
|
|
});
|
|
|
|
getConfigSpy.mockRestore();
|
|
});
|
|
|
|
it("should render the saas navbar", async () => {
|
|
const saasConfig = { APP_MODE: "saas" };
|
|
|
|
// Clear any existing query data and set the config
|
|
mockQueryClient.clear();
|
|
mockQueryClient.setQueryData(["config"], saasConfig);
|
|
|
|
const sectionsToInclude = [
|
|
"llm", // LLM settings are now always shown in SaaS mode
|
|
"user",
|
|
"integrations",
|
|
"application",
|
|
"billing", // The nav item shows "Billing" text and routes to /billing
|
|
"secrets",
|
|
"api keys",
|
|
];
|
|
const sectionsToExclude: string[] = []; // No sections are excluded in SaaS mode now
|
|
|
|
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 saas-only routes in oss mode", async () => {
|
|
const getConfigSpy = vi.spyOn(OptionService, "getConfig");
|
|
// @ts-expect-error - only return app mode
|
|
getConfigSpy.mockResolvedValue({
|
|
APP_MODE: "oss",
|
|
});
|
|
|
|
// Clear any existing query data
|
|
mockQueryClient.clear();
|
|
|
|
// In OSS mode, accessing restricted routes should redirect to /settings
|
|
// Since createRoutesStub doesn't handle clientLoader redirects properly,
|
|
// we test that the correct navbar is shown (OSS navbar) and that
|
|
// the restricted route components are not rendered when accessing /settings
|
|
renderSettingsScreen("/settings");
|
|
|
|
// Verify we're in OSS mode by checking the navbar
|
|
const navbar = await screen.findByTestId("settings-navbar");
|
|
expect(within(navbar).getByText("LLM")).toBeInTheDocument();
|
|
expect(
|
|
within(navbar).queryByText("credits", { exact: false }),
|
|
).not.toBeInTheDocument();
|
|
|
|
// Verify the LLM settings screen is shown
|
|
expect(screen.getByTestId("llm-settings-screen")).toBeInTheDocument();
|
|
expect(
|
|
screen.queryByTestId("billing-settings-screen"),
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByTestId("api-keys-settings-screen"),
|
|
).not.toBeInTheDocument();
|
|
|
|
getConfigSpy.mockRestore();
|
|
});
|
|
|
|
it.todo("should not be able to access oss-only routes in saas mode");
|
|
});
|