refactor pt3

This commit is contained in:
yuneng-jiang 2026-01-01 13:28:10 -08:00
parent e0a07eda92
commit a0a8f0b8b4
14 changed files with 1651 additions and 33 deletions

View file

@ -1,6 +1,6 @@
"use client";
import ModelHubTable from "@/components/AIHub/model_hub_table";
import ModelHubTable from "@/components/AIHub/ModelHubTable";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
const ModelHubPage = () => {

View file

@ -1,7 +1,7 @@
"use client";
import React, { useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import ModelHubTable from "@/components/AIHub/model_hub_table";
import ModelHubTable from "@/components/AIHub/ModelHubTable";
export default function PublicModelHubTable() {
const searchParams = useSearchParams()!;

View file

@ -15,7 +15,7 @@ import GeneralSettings from "@/components/general_settings";
import GuardrailsPanel from "@/components/guardrails";
import { Team } from "@/components/key_team_helpers/key_list";
import { MCPServers } from "@/components/mcp_tools";
import ModelHubTable from "@/components/AIHub/model_hub_table";
import ModelHubTable from "@/components/AIHub/ModelHubTable";
import Navbar from "@/components/navbar";
import { getUiConfig, Organization, proxyBaseUrl, setGlobalLitellmHeaderName } from "@/components/networking";
import NewUsagePage from "@/components/UsagePage/components/UsagePageView";

View file

@ -28,7 +28,7 @@ export interface AgentHubData {
[key: string]: any;
}
export const agentHubColumns = (
export const getAgentHubTableColumns = (
showModal: (agent: AgentHubData) => void,
copyToClipboard: (text: string) => void,
publicPage: boolean = false,
@ -69,11 +69,7 @@ export const agentHubColumns = (
cell: ({ row }) => {
const agent = row.original;
return (
<Text className="text-xs line-clamp-2">
{agent.description || "-"}
</Text>
);
return <Text className="text-xs line-clamp-2">{agent.description || "-"}</Text>;
},
meta: {
className: "hidden md:table-cell",
@ -105,11 +101,7 @@ export const agentHubColumns = (
cell: ({ row }) => {
const agent = row.original;
return (
<Text className="text-xs">
{agent.protocolVersion || "-"}
</Text>
);
return <Text className="text-xs">{agent.protocolVersion || "-"}</Text>;
},
meta: {
className: "hidden lg:table-cell",
@ -135,9 +127,7 @@ export const agentHubColumns = (
{skill.name}
</Tag>
))}
{skills.length > 2 && (
<Text className="text-xs text-gray-500">+{skills.length - 2}</Text>
)}
{skills.length > 2 && <Text className="text-xs text-gray-500">+{skills.length - 2}</Text>}
</div>
)}
</div>
@ -240,4 +230,3 @@ export const agentHubColumns = (
return allColumns;
};

View file

@ -1,7 +1,7 @@
import * as networking from "@/components/networking";
import { render, screen, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import ModelHubTable from "./model_hub_table";
import ModelHubTable from "./ModelHubTable";
vi.mock("@/components/networking", () => ({
getUiConfig: vi.fn(),
@ -19,7 +19,7 @@ vi.mock("next/navigation", () => ({
}),
}));
vi.mock("./public_model_hub", () => ({
vi.mock("@/components/public_model_hub", () => ({
default: () => <div>Public Model Hub</div>,
}));
@ -51,7 +51,12 @@ describe("ModelHubTable", () => {
const getUiConfigMock = vi.mocked(networking.getUiConfig);
const modelHubPublicModelsCallMock = vi.mocked(networking.modelHubPublicModelsCall);
getUiConfigMock.mockResolvedValue({ server_root_path: "/", proxy_base_url: "http://localhost:4000" });
getUiConfigMock.mockResolvedValue({
server_root_path: "/",
proxy_base_url: "http://localhost:4000",
auto_redirect_to_sso: false,
admin_ui_disabled: false,
});
modelHubPublicModelsCallMock.mockResolvedValue([]);
render(<ModelHubTable accessToken={null} publicPage={true} premiumUser={false} userRole={null} />);

View file

@ -1,10 +1,10 @@
import { agentHubColumns, AgentHubData } from "@/components/AIHub/agent_hub_table_columns";
import MakeAgentPublicForm from "@/components/AIHub/forms/make_agent_public_form";
import MakeMCPPublicForm from "@/components/AIHub/forms/make_mcp_public_form";
import MakeModelPublicForm from "@/components/AIHub/forms/make_model_public_form";
import { AgentHubData, getAgentHubTableColumns } from "@/components/AIHub/AgentHubTableColumns";
import MakeAgentPublicForm from "@/components/AIHub/forms/MakeAgentPublicForm";
import MakeMCPPublicForm from "@/components/AIHub/forms/MakeMCPPublicForm";
import MakeModelPublicForm from "@/components/AIHub/forms/MakeModelPublicForm";
import { mcpHubColumns, MCPServerData } from "@/components/mcp_hub_table_columns";
import { modelHubColumns } from "@/components/model_hub_table_columns";
import UsefulLinksManagement from "@/components/AIHub/useful_links_management";
import UsefulLinksManagement from "@/components/AIHub/UsefulLinksManagement";
import { ModelDataTable } from "@/components/model_dashboard/table";
import ModelFilters from "@/components/model_filters";
import NotificationsManager from "@/components/molecules/notifications_manager";
@ -423,7 +423,7 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({ accessToken, publicPage,
{/* Agent Table */}
<ModelDataTable
columns={agentHubColumns(showAgentModal, copyToClipboard, publicPage)}
columns={getAgentHubTableColumns(showAgentModal, copyToClipboard, publicPage)}
data={agentHubData || []}
isLoading={agentLoading}
defaultSorting={[{ id: "name", desc: false }]}

View file

@ -2,17 +2,17 @@ import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Modal } from "antd";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import UsefulLinksManagement from "./useful_links_management";
import NotificationsManager from "./molecules/notifications_manager";
import { getPublicModelHubInfo, updateUsefulLinksCall, getProxyBaseUrl } from "./networking";
import UsefulLinksManagement from "./UsefulLinksManagement";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { getPublicModelHubInfo, updateUsefulLinksCall, getProxyBaseUrl } from "@/components/networking";
vi.mock("./networking", () => ({
vi.mock("@/components/networking", () => ({
getPublicModelHubInfo: vi.fn(),
updateUsefulLinksCall: vi.fn(),
getProxyBaseUrl: vi.fn(),
}));
vi.mock("./molecules/notifications_manager", () => ({
vi.mock("@/components/molecules/notifications_manager", () => ({
__esModule: true,
default: {
success: vi.fn(),

View file

@ -0,0 +1,505 @@
import { render, screen, fireEvent, act, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import MakeAgentPublicForm from "./MakeAgentPublicForm";
import { AgentHubData } from "@/components/AIHub/AgentHubTableColumns";
// Mock the networking function
vi.mock("../../networking", () => ({
makeAgentsPublicCall: vi.fn(),
}));
// Import the mocked function
import { makeAgentsPublicCall } from "../../networking";
const mockMakeAgentsPublicCall = vi.mocked(makeAgentsPublicCall);
// Mock antd components
vi.mock("antd", () => ({
Modal: ({ open, title, children, onCancel, footer }: any) =>
open ? (
<div data-testid="modal">
<div>{title}</div>
{children}
{footer}
</div>
) : null,
Form: Object.assign(({ children, form }: any) => <form data-testid="form">{children}</form>, {
useForm: () => [
{
resetFields: vi.fn(),
validateFields: vi.fn(),
getFieldsValue: vi.fn(),
setFieldsValue: vi.fn(),
},
vi.fn(),
],
Item: ({ children }: any) => <div>{children}</div>,
}),
Steps: Object.assign(
({ children, current, className }: any) => (
<div data-testid="steps" className={className}>
{children}
</div>
),
{
Step: ({ title }: any) => <div>{title}</div>,
},
),
Button: ({ children, onClick, disabled, loading, ...props }: any) => (
<button onClick={onClick} disabled={disabled || loading} data-loading={loading} {...props}>
{children}
</button>
),
Checkbox: ({ checked, indeterminate, onChange, children, disabled }: any) => (
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => onChange({ target: { checked: e.target.checked } })}
disabled={disabled}
data-indeterminate={indeterminate}
/>
{children}
</label>
),
}));
// Mock @tremor/react components
vi.mock("@tremor/react", () => ({
Text: ({ children, className }: any) => <span className={className}>{children}</span>,
Title: ({ children }: any) => <h3>{children}</h3>,
Badge: ({ children, color, size }: any) => (
<span data-color={color} data-size={size}>
{children}
</span>
),
}));
describe("MakeAgentPublicForm", () => {
const mockProps = {
visible: true,
onClose: vi.fn(),
accessToken: "test-token",
agentHubData: [
{
agent_id: "agent-1",
name: "Test Agent 1",
description: "Description 1",
version: "1.0",
is_public: false,
skills: [
{ id: "skill-1", name: "Skill 1", description: "Skill desc" },
{ id: "skill-2", name: "Skill 2", description: "Skill desc" },
],
protocolVersion: "1.0",
},
{
agent_id: "agent-2",
name: "Test Agent 2",
description: "Description 2",
version: "2.0",
is_public: true,
skills: [],
protocolVersion: "1.0",
},
] as AgentHubData[],
onSuccess: vi.fn(),
};
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.resetAllMocks();
});
it("should render the component", () => {
render(<MakeAgentPublicForm {...mockProps} />);
expect(screen.getByText("Make Agents Public")).toBeInTheDocument();
expect(screen.getByText("Select Agents to Make Public")).toBeInTheDocument();
});
it("should initialize with correct state", () => {
render(<MakeAgentPublicForm {...mockProps} />);
// Check that the component renders with the correct title and content
expect(screen.getByText("Make Agents Public")).toBeInTheDocument();
expect(screen.getByText("Select Agents to Make Public")).toBeInTheDocument();
// Check that all agent checkboxes are present
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes).toHaveLength(3); // Select all + 2 agents
// Check that the Next button is enabled (agents are preselected)
const nextButton = screen.getByRole("button", { name: "Next" });
expect(nextButton).not.toBeDisabled();
});
it("should handle agent selection and navigation", async () => {
render(<MakeAgentPublicForm {...mockProps} />);
// Initially on step 1
expect(screen.getByText("Select Agents to Make Public")).toBeInTheDocument();
// Select all agents using the select all checkbox
const selectAllCheckbox = screen.getByLabelText("Select All (2)");
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// Verify Next button is enabled
const nextButton = screen.getByRole("button", { name: "Next" });
expect(nextButton).not.toBeDisabled();
// Click Next
await act(async () => {
fireEvent.click(nextButton);
});
// Should move to step 2
await waitFor(() => {
expect(screen.getByText("Confirm Making Agents Public")).toBeInTheDocument();
});
});
it("should submit selected agents successfully", async () => {
mockMakeAgentsPublicCall.mockResolvedValueOnce({});
render(<MakeAgentPublicForm {...mockProps} />);
// Select all agents
const selectAllCheckbox = screen.getByLabelText("Select All (2)");
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// Navigate to confirm step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
// Wait for navigation to complete
await waitFor(() => {
expect(screen.getByText("Confirm Making Agents Public")).toBeInTheDocument();
});
// Submit
const submitButton = screen.getByRole("button", { name: "Make Public" });
await act(async () => {
fireEvent.click(submitButton);
});
await waitFor(() => {
expect(mockMakeAgentsPublicCall).toHaveBeenCalledWith("test-token", ["agent-1", "agent-2"]);
expect(mockProps.onSuccess).toHaveBeenCalled();
expect(mockProps.onClose).toHaveBeenCalled();
});
});
it("should handle select all functionality", async () => {
render(<MakeAgentPublicForm {...mockProps} />);
const checkboxes = screen.getAllByRole("checkbox");
const selectAllCheckbox = checkboxes[0];
// Select all
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// All checkboxes should be checked
checkboxes.forEach((checkbox) => {
expect(checkbox).toBeChecked();
});
// Deselect all
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// All checkboxes should be unchecked except the indeterminate state
expect(checkboxes[0]).not.toBeChecked();
expect(checkboxes[1]).not.toBeChecked();
expect(checkboxes[2]).not.toBeChecked();
});
it("should show error when no agents selected", async () => {
render(<MakeAgentPublicForm {...mockProps} />);
// Deselect all agents first
const checkboxes = screen.getAllByRole("checkbox");
await act(async () => {
fireEvent.click(checkboxes[0]); // Click select all to select all
fireEvent.click(checkboxes[0]); // Click select all again to deselect all
});
// Try to go to next step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
// Should stay on same step
expect(screen.getByText("Select Agents to Make Public")).toBeInTheDocument();
});
it("should display empty state when no agents are available", () => {
const emptyProps = {
...mockProps,
agentHubData: [] as AgentHubData[],
};
render(<MakeAgentPublicForm {...emptyProps} />);
expect(screen.getByText("No agents available.")).toBeInTheDocument();
// Select All checkbox should be disabled
const selectAllCheckbox = screen.getByLabelText("Select All");
expect(selectAllCheckbox).toBeDisabled();
// Next button should be disabled
const nextButton = screen.getByRole("button", { name: "Next" });
expect(nextButton).toBeDisabled();
});
it("should handle Cancel button functionality", async () => {
render(<MakeAgentPublicForm {...mockProps} />);
// Click Cancel button
const cancelButton = screen.getByRole("button", { name: "Cancel" });
await act(async () => {
fireEvent.click(cancelButton);
});
// Should call onClose
expect(mockProps.onClose).toHaveBeenCalled();
});
it("should handle Previous button functionality", async () => {
render(<MakeAgentPublicForm {...mockProps} />);
// Navigate to step 1
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
// Verify we're on step 1
await waitFor(() => {
expect(screen.getByText("Confirm Making Agents Public")).toBeInTheDocument();
});
// Click Previous button
const previousButton = screen.getByRole("button", { name: "Previous" });
await act(async () => {
fireEvent.click(previousButton);
});
// Should go back to step 0
expect(screen.getByText("Select Agents to Make Public")).toBeInTheDocument();
});
it("should handle individual agent selection", async () => {
render(<MakeAgentPublicForm {...mockProps} />);
// Get all checkboxes (select all + individual agents)
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes).toHaveLength(3); // Select all + 2 agents
// Initially, agent-2 should be selected (it's already public)
const agent1Checkbox = checkboxes[1]; // First agent checkbox
const agent2Checkbox = checkboxes[2]; // Second agent checkbox
expect(agent2Checkbox).toBeChecked(); // agent-2 is already public
// Select agent-1
await act(async () => {
fireEvent.click(agent1Checkbox);
});
expect(agent1Checkbox).toBeChecked();
expect(agent2Checkbox).toBeChecked();
// Deselect agent-2
await act(async () => {
fireEvent.click(agent2Checkbox);
});
expect(agent1Checkbox).toBeChecked();
expect(agent2Checkbox).not.toBeChecked();
// Select all should be indeterminate now
const selectAllCheckbox = checkboxes[0];
expect(selectAllCheckbox).toHaveAttribute("data-indeterminate", "true");
});
it("should display skills overflow text when agent has more than 3 skills", () => {
const agentWithManySkills = {
...mockProps.agentHubData[0],
skills: [
{ id: "skill-1", name: "Skill 1", description: "Skill desc" },
{ id: "skill-2", name: "Skill 2", description: "Skill desc" },
{ id: "skill-3", name: "Skill 3", description: "Skill desc" },
{ id: "skill-4", name: "Skill 4", description: "Skill desc" },
{ id: "skill-5", name: "Skill 5", description: "Skill desc" },
],
};
const propsWithManySkills = {
...mockProps,
agentHubData: [agentWithManySkills],
};
render(<MakeAgentPublicForm {...propsWithManySkills} />);
// Should show first 3 skills as badges
expect(screen.getByText("Skill 1")).toBeInTheDocument();
expect(screen.getByText("Skill 2")).toBeInTheDocument();
expect(screen.getByText("Skill 3")).toBeInTheDocument();
// Should show "+2 more" text for the remaining skills
expect(screen.getByText("+2 more")).toBeInTheDocument();
});
it("should handle submit error properly", async () => {
const errorMessage = "Network error";
mockMakeAgentsPublicCall.mockRejectedValueOnce(new Error(errorMessage));
render(<MakeAgentPublicForm {...mockProps} />);
// Navigate to confirm step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
await waitFor(() => {
expect(screen.getByText("Confirm Making Agents Public")).toBeInTheDocument();
});
// Submit
const submitButton = screen.getByRole("button", { name: "Make Public" });
await act(async () => {
fireEvent.click(submitButton);
});
// Should handle error and show error notification
await waitFor(() => {
expect(mockMakeAgentsPublicCall).toHaveBeenCalledWith("test-token", ["agent-2"]);
});
// Should not call onSuccess or onClose on error
expect(mockProps.onSuccess).not.toHaveBeenCalled();
expect(mockProps.onClose).not.toHaveBeenCalled();
});
it("should show loading state during submit", async () => {
let resolvePromise: (value: any) => void = () => {};
const pendingPromise = new Promise((resolve) => {
resolvePromise = resolve;
});
mockMakeAgentsPublicCall.mockReturnValueOnce(pendingPromise);
render(<MakeAgentPublicForm {...mockProps} />);
// Navigate to confirm step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
await waitFor(() => {
expect(screen.getByText("Confirm Making Agents Public")).toBeInTheDocument();
});
// Submit
const submitButton = screen.getByRole("button", { name: "Make Public" });
await act(async () => {
fireEvent.click(submitButton);
});
// Check loading state
expect(submitButton).toHaveAttribute("data-loading", "true");
expect(submitButton).toBeDisabled();
// Resolve the promise
resolvePromise({});
await waitFor(() => {
expect(mockProps.onSuccess).toHaveBeenCalled();
expect(mockProps.onClose).toHaveBeenCalled();
});
});
it("should not render modal when visible is false", () => {
const invisibleProps = {
...mockProps,
visible: false,
};
render(<MakeAgentPublicForm {...invisibleProps} />);
// Modal should not be rendered
expect(screen.queryByTestId("modal")).not.toBeInTheDocument();
expect(screen.queryByText("Make Agents Public")).not.toBeInTheDocument();
});
it("should preselect already public agents when modal opens", () => {
// Test data where one agent is public and one is not
const mixedPublicProps = {
...mockProps,
agentHubData: [
{
agent_id: "agent-1",
name: "Test Agent 1",
description: "Description 1",
url: "http://example.com/agent1",
version: "1.0",
is_public: false, // Not public
skills: [],
protocolVersion: "1.0",
},
{
agent_id: "agent-2",
name: "Test Agent 2",
description: "Description 2",
url: "http://example.com/agent2",
version: "2.0",
is_public: true, // Already public
skills: [],
protocolVersion: "1.0",
},
{
agent_id: "agent-3",
name: "Test Agent 3",
description: "Description 3",
url: "http://example.com/agent3",
version: "3.0",
is_public: true, // Already public
skills: [],
protocolVersion: "1.0",
},
] as AgentHubData[],
};
render(<MakeAgentPublicForm {...mixedPublicProps} />);
// Check that the correct checkboxes are selected
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes).toHaveLength(4); // Select all + 3 agents
// agent-2 and agent-3 should be checked (they're already public)
const agent1Checkbox = checkboxes[1];
const agent2Checkbox = checkboxes[2];
const agent3Checkbox = checkboxes[3];
expect(agent1Checkbox).not.toBeChecked(); // agent-1 is not public
expect(agent2Checkbox).toBeChecked(); // agent-2 is public
expect(agent3Checkbox).toBeChecked(); // agent-3 is public
// Select all should be indeterminate
const selectAllCheckbox = checkboxes[0];
expect(selectAllCheckbox).toHaveAttribute("data-indeterminate", "true");
});
});

View file

@ -3,7 +3,7 @@ import { Modal, Form, Steps, Button, Checkbox } from "antd";
import { Text, Title, Badge } from "@tremor/react";
import { makeAgentsPublicCall } from "../../networking";
import NotificationsManager from "../../molecules/notifications_manager";
import { AgentHubData } from "@/components/AIHub/agent_hub_table_columns";
import { AgentHubData } from "@/components/AIHub/AgentHubTableColumns";
const { Step } = Steps;

View file

@ -0,0 +1,562 @@
import { render, screen, fireEvent, act, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import MakeMCPPublicForm from "./MakeMCPPublicForm";
import { MCPServerData } from "../../mcp_hub_table_columns";
// Mock the networking function
vi.mock("../../networking", () => ({
makeMCPPublicCall: vi.fn(),
}));
// Import the mocked function
import { makeMCPPublicCall } from "../../networking";
const mockMakeMCPPublicCall = vi.mocked(makeMCPPublicCall);
// Mock antd components
vi.mock("antd", () => ({
Modal: ({ open, title, children, onCancel, footer }: any) =>
open ? (
<div data-testid="modal">
<div>{title}</div>
{children}
{footer}
</div>
) : null,
Form: Object.assign(({ children, form }: any) => <form data-testid="form">{children}</form>, {
useForm: () => [
{
resetFields: vi.fn(),
validateFields: vi.fn(),
getFieldsValue: vi.fn(),
setFieldsValue: vi.fn(),
},
vi.fn(),
],
Item: ({ children }: any) => <div>{children}</div>,
}),
Steps: Object.assign(
({ children, current, className }: any) => (
<div data-testid="steps" className={className}>
{children}
</div>
),
{
Step: ({ title }: any) => <div>{title}</div>,
},
),
Button: ({ children, onClick, disabled, loading, ...props }: any) => (
<button onClick={onClick} disabled={disabled || loading} data-loading={loading} {...props}>
{children}
</button>
),
Checkbox: ({ checked, indeterminate, onChange, children, disabled }: any) => (
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => onChange({ target: { checked: e.target.checked } })}
disabled={disabled}
data-indeterminate={indeterminate}
/>
{children}
</label>
),
}));
// Additional @tremor/react mocks (Button is already mocked globally)
vi.mock("@tremor/react", async (importOriginal) => {
const actual = await importOriginal<typeof import("@tremor/react")>();
return {
...actual,
Text: ({ children, className }: any) => <span className={className}>{children}</span>,
Title: ({ children }: any) => <h3>{children}</h3>,
Badge: ({ children, color, size }: any) => (
<span data-color={color} data-size={size}>
{children}
</span>
),
};
});
describe("MakeMCPPublicForm", () => {
const mockProps = {
visible: true,
onClose: vi.fn(),
accessToken: "test-token",
mcpHubData: [
{
server_id: "server-1",
server_name: "Test Server 1",
description: "Description 1",
url: "http://example.com/server1",
transport: "http",
status: "active",
mcp_info: { is_public: false },
allowed_tools: ["tool-1", "tool-2"],
auth_type: "bearer",
credentials: {},
created_at: "2024-01-01T00:00:00Z",
created_by: "user1",
updated_at: "2024-01-01T00:00:00Z",
updated_by: "user1",
teams: [],
mcp_access_groups: [],
extra_headers: [],
static_headers: {},
args: [],
env: {},
},
{
server_id: "server-2",
server_name: "Test Server 2",
description: "Description 2",
url: "http://example.com/server2",
transport: "websocket",
status: "inactive",
mcp_info: { is_public: true },
allowed_tools: [],
auth_type: "none",
credentials: {},
created_at: "2024-01-01T00:00:00Z",
created_by: "user2",
updated_at: "2024-01-01T00:00:00Z",
updated_by: "user2",
teams: [],
mcp_access_groups: [],
extra_headers: [],
static_headers: {},
args: [],
env: {},
},
] as MCPServerData[],
onSuccess: vi.fn(),
};
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.resetAllMocks();
});
it("should render the component", () => {
render(<MakeMCPPublicForm {...mockProps} />);
expect(screen.getByText("Make MCP Servers Public")).toBeInTheDocument();
expect(screen.getByText("Select MCP Servers to Make Public")).toBeInTheDocument();
});
it("should initialize with correct state", () => {
render(<MakeMCPPublicForm {...mockProps} />);
// Check that the component renders with the correct title and content
expect(screen.getByText("Make MCP Servers Public")).toBeInTheDocument();
expect(screen.getByText("Select MCP Servers to Make Public")).toBeInTheDocument();
// Check that all server checkboxes are present
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes).toHaveLength(3); // Select all + 2 servers
// Check that the Next button is enabled (servers are preselected)
const nextButton = screen.getByRole("button", { name: "Next" });
expect(nextButton).not.toBeDisabled();
});
it("should handle server selection and navigation", async () => {
render(<MakeMCPPublicForm {...mockProps} />);
// Initially on step 1
expect(screen.getByText("Select MCP Servers to Make Public")).toBeInTheDocument();
// Select all servers using the select all checkbox
const selectAllCheckbox = screen.getByLabelText("Select All (2)");
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// Verify Next button is enabled
const nextButton = screen.getByRole("button", { name: "Next" });
expect(nextButton).not.toBeDisabled();
// Click Next
await act(async () => {
fireEvent.click(nextButton);
});
// Should move to step 2
await waitFor(() => {
expect(screen.getByText("Confirm Making MCP Servers Public")).toBeInTheDocument();
});
});
it("should submit selected servers successfully", async () => {
mockMakeMCPPublicCall.mockResolvedValueOnce({});
render(<MakeMCPPublicForm {...mockProps} />);
// Select all servers
const selectAllCheckbox = screen.getByLabelText("Select All (2)");
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// Navigate to confirm step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
// Wait for navigation to complete
await waitFor(() => {
expect(screen.getByText("Confirm Making MCP Servers Public")).toBeInTheDocument();
});
// Submit
const submitButton = screen.getByRole("button", { name: "Make Public" });
await act(async () => {
fireEvent.click(submitButton);
});
await waitFor(() => {
expect(mockMakeMCPPublicCall).toHaveBeenCalledWith("test-token", ["server-1", "server-2"]);
expect(mockProps.onSuccess).toHaveBeenCalled();
expect(mockProps.onClose).toHaveBeenCalled();
});
});
it("should handle select all functionality", async () => {
render(<MakeMCPPublicForm {...mockProps} />);
const checkboxes = screen.getAllByRole("checkbox");
const selectAllCheckbox = checkboxes[0];
// Select all
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// All checkboxes should be checked
checkboxes.forEach((checkbox) => {
expect(checkbox).toBeChecked();
});
// Deselect all
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// All checkboxes should be unchecked except the indeterminate state
expect(checkboxes[0]).not.toBeChecked();
expect(checkboxes[1]).not.toBeChecked();
expect(checkboxes[2]).not.toBeChecked();
});
it("should show error when no servers selected", async () => {
render(<MakeMCPPublicForm {...mockProps} />);
// Deselect all servers first
const checkboxes = screen.getAllByRole("checkbox");
await act(async () => {
fireEvent.click(checkboxes[0]); // Click select all to select all
fireEvent.click(checkboxes[0]); // Click select all again to deselect all
});
// Try to go to next step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
// Should stay on same step
expect(screen.getByText("Select MCP Servers to Make Public")).toBeInTheDocument();
});
it("should display empty state when no servers are available", () => {
const emptyProps = {
...mockProps,
mcpHubData: [] as MCPServerData[],
};
render(<MakeMCPPublicForm {...emptyProps} />);
expect(screen.getByText("No MCP servers available.")).toBeInTheDocument();
// Select All checkbox should be disabled
const selectAllCheckbox = screen.getByLabelText("Select All");
expect(selectAllCheckbox).toBeDisabled();
// Next button should be disabled
const nextButton = screen.getByRole("button", { name: "Next" });
expect(nextButton).toBeDisabled();
});
it("should handle Cancel button functionality", async () => {
render(<MakeMCPPublicForm {...mockProps} />);
// Click Cancel button
const cancelButton = screen.getByRole("button", { name: "Cancel" });
await act(async () => {
fireEvent.click(cancelButton);
});
// Should call onClose
expect(mockProps.onClose).toHaveBeenCalled();
});
it("should handle Previous button functionality", async () => {
render(<MakeMCPPublicForm {...mockProps} />);
// Navigate to step 1
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
// Verify we're on step 1
await waitFor(() => {
expect(screen.getByText("Confirm Making MCP Servers Public")).toBeInTheDocument();
});
// Click Previous button
const previousButton = screen.getByRole("button", { name: "Previous" });
await act(async () => {
fireEvent.click(previousButton);
});
// Should go back to step 0
expect(screen.getByText("Select MCP Servers to Make Public")).toBeInTheDocument();
});
it("should handle individual server selection", async () => {
render(<MakeMCPPublicForm {...mockProps} />);
// Get all checkboxes (select all + individual servers)
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes).toHaveLength(3); // Select all + 2 servers
// Initially, server-2 should be selected (it's already public)
const server1Checkbox = checkboxes[1]; // First server checkbox
const server2Checkbox = checkboxes[2]; // Second server checkbox
expect(server2Checkbox).toBeChecked(); // server-2 is already public
// Select server-1
await act(async () => {
fireEvent.click(server1Checkbox);
});
expect(server1Checkbox).toBeChecked();
expect(server2Checkbox).toBeChecked();
// Deselect server-2
await act(async () => {
fireEvent.click(server2Checkbox);
});
expect(server1Checkbox).toBeChecked();
expect(server2Checkbox).not.toBeChecked();
// Select all should be indeterminate now
const selectAllCheckbox = checkboxes[0];
expect(selectAllCheckbox).toHaveAttribute("data-indeterminate", "true");
});
it("should display tools overflow text when server has more than 3 tools", () => {
const serverWithManyTools = {
...mockProps.mcpHubData[0],
allowed_tools: ["tool-1", "tool-2", "tool-3", "tool-4", "tool-5"],
};
const propsWithManyTools = {
...mockProps,
mcpHubData: [serverWithManyTools],
};
render(<MakeMCPPublicForm {...propsWithManyTools} />);
// Should show first 3 tools as badges
expect(screen.getByText("tool-1")).toBeInTheDocument();
expect(screen.getByText("tool-2")).toBeInTheDocument();
expect(screen.getByText("tool-3")).toBeInTheDocument();
// Should show "+2 more" text for the remaining tools
expect(screen.getByText("+2 more")).toBeInTheDocument();
});
it("should handle submit error properly", async () => {
const errorMessage = "Network error";
mockMakeMCPPublicCall.mockRejectedValueOnce(new Error(errorMessage));
render(<MakeMCPPublicForm {...mockProps} />);
// Navigate to confirm step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
await waitFor(() => {
expect(screen.getByText("Confirm Making MCP Servers Public")).toBeInTheDocument();
});
// Submit
const submitButton = screen.getByRole("button", { name: "Make Public" });
await act(async () => {
fireEvent.click(submitButton);
});
// Should handle error and show error notification
await waitFor(() => {
expect(mockMakeMCPPublicCall).toHaveBeenCalledWith("test-token", ["server-2"]);
});
// Should not call onSuccess or onClose on error
expect(mockProps.onSuccess).not.toHaveBeenCalled();
expect(mockProps.onClose).not.toHaveBeenCalled();
});
it("should show loading state during submit", async () => {
let resolvePromise: (value: any) => void = () => {};
const pendingPromise = new Promise((resolve) => {
resolvePromise = resolve;
});
mockMakeMCPPublicCall.mockReturnValueOnce(pendingPromise);
render(<MakeMCPPublicForm {...mockProps} />);
// Navigate to confirm step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
await waitFor(() => {
expect(screen.getByText("Confirm Making MCP Servers Public")).toBeInTheDocument();
});
// Submit
const submitButton = screen.getByRole("button", { name: "Make Public" });
await act(async () => {
fireEvent.click(submitButton);
});
// Check loading state
expect(submitButton).toHaveAttribute("data-loading", "true");
expect(submitButton).toBeDisabled();
// Resolve the promise
resolvePromise({});
await waitFor(() => {
expect(mockProps.onSuccess).toHaveBeenCalled();
expect(mockProps.onClose).toHaveBeenCalled();
});
});
it("should not render modal when visible is false", () => {
const invisibleProps = {
...mockProps,
visible: false,
};
render(<MakeMCPPublicForm {...invisibleProps} />);
// Modal should not be rendered
expect(screen.queryByTestId("modal")).not.toBeInTheDocument();
expect(screen.queryByText("Make MCP Servers Public")).not.toBeInTheDocument();
});
it("should preselect already public servers when modal opens", () => {
// Test data where one server is public and one is not
const mixedPublicProps = {
...mockProps,
mcpHubData: [
{
server_id: "server-1",
server_name: "Test Server 1",
description: "Description 1",
url: "http://example.com/server1",
transport: "http",
status: "active",
mcp_info: { is_public: false }, // Not public
allowed_tools: [],
auth_type: "bearer",
credentials: {},
created_at: "2024-01-01T00:00:00Z",
created_by: "user1",
updated_at: "2024-01-01T00:00:00Z",
updated_by: "user1",
teams: [],
mcp_access_groups: [],
extra_headers: [],
static_headers: {},
args: [],
env: {},
},
{
server_id: "server-2",
server_name: "Test Server 2",
description: "Description 2",
url: "http://example.com/server2",
transport: "websocket",
status: "inactive",
mcp_info: { is_public: true }, // Already public
allowed_tools: [],
auth_type: "none",
credentials: {},
created_at: "2024-01-01T00:00:00Z",
created_by: "user2",
updated_at: "2024-01-01T00:00:00Z",
updated_by: "user2",
teams: [],
mcp_access_groups: [],
extra_headers: [],
static_headers: {},
args: [],
env: {},
},
{
server_id: "server-3",
server_name: "Test Server 3",
description: "Description 3",
url: "http://example.com/server3",
transport: "sse",
status: "healthy",
mcp_info: { is_public: true }, // Already public
allowed_tools: [],
auth_type: "oauth",
credentials: {},
created_at: "2024-01-01T00:00:00Z",
created_by: "user3",
updated_at: "2024-01-01T00:00:00Z",
updated_by: "user3",
teams: [],
mcp_access_groups: [],
extra_headers: [],
static_headers: {},
args: [],
env: {},
},
] as MCPServerData[],
};
render(<MakeMCPPublicForm {...mixedPublicProps} />);
// Check that the correct checkboxes are selected
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes).toHaveLength(4); // Select all + 3 servers
// server-2 and server-3 should be checked (they're already public)
const server1Checkbox = checkboxes[1];
const server2Checkbox = checkboxes[2];
const server3Checkbox = checkboxes[3];
expect(server1Checkbox).not.toBeChecked(); // server-1 is not public
expect(server2Checkbox).toBeChecked(); // server-2 is public
expect(server3Checkbox).toBeChecked(); // server-3 is public
// Select all should be indeterminate
const selectAllCheckbox = checkboxes[0];
expect(selectAllCheckbox).toHaveAttribute("data-indeterminate", "true");
});
});

View file

@ -0,0 +1,557 @@
import { render, screen, fireEvent, act, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import MakeModelPublicForm from "./MakeModelPublicForm";
interface ModelGroupInfo {
model_group: string;
providers: string[];
max_input_tokens?: number;
max_output_tokens?: number;
input_cost_per_token?: number;
output_cost_per_token?: number;
mode?: string;
tpm?: number;
rpm?: number;
supports_parallel_function_calling: boolean;
supports_vision: boolean;
supports_function_calling: boolean;
supported_openai_params?: string[];
is_public_model_group: boolean;
[key: string]: any;
}
// Mock the networking function
vi.mock("../../networking", () => ({
makeModelGroupPublic: vi.fn(),
}));
// Import the mocked function
import { makeModelGroupPublic } from "../../networking";
const mockMakeModelGroupPublic = vi.mocked(makeModelGroupPublic);
// Mock antd components
vi.mock("antd", () => ({
Modal: ({ open, title, children, onCancel, footer }: any) =>
open ? (
<div data-testid="modal">
<div>{title}</div>
{children}
{footer}
</div>
) : null,
Form: Object.assign(({ children, form }: any) => <form data-testid="form">{children}</form>, {
useForm: () => [
{
resetFields: vi.fn(),
validateFields: vi.fn(),
getFieldsValue: vi.fn(),
setFieldsValue: vi.fn(),
},
vi.fn(),
],
Item: ({ children }: any) => <div>{children}</div>,
}),
Steps: Object.assign(
({ children, current, className }: any) => (
<div data-testid="steps" className={className}>
{children}
</div>
),
{
Step: ({ title }: any) => <div>{title}</div>,
},
),
Button: ({ children, onClick, disabled, loading, ...props }: any) => (
<button onClick={onClick} disabled={disabled || loading} data-loading={loading} {...props}>
{children}
</button>
),
Checkbox: ({ checked, indeterminate, onChange, children, disabled }: any) => (
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => onChange({ target: { checked: e.target.checked } })}
disabled={disabled}
data-indeterminate={indeterminate}
/>
{children}
</label>
),
}));
// Mock @tremor/react components
vi.mock("@tremor/react", () => ({
Text: ({ children, className }: any) => <span className={className}>{children}</span>,
Title: ({ children }: any) => <h3>{children}</h3>,
Badge: ({ children, color, size }: any) => (
<span data-color={color} data-size={size}>
{children}
</span>
),
}));
// Mock ModelFilters component
vi.mock("../../model_filters", () => ({
default: ({ onFilteredDataChange, modelHubData }: any) => (
<div data-testid="model-filters">
<button data-testid="trigger-filter-change" onClick={() => onFilteredDataChange(modelHubData)}>
Apply Filters
</button>
</div>
),
}));
// Mock NotificationsManager
vi.mock("../../molecules/notifications_manager", () => ({
default: {
fromBackend: vi.fn(),
success: vi.fn(),
},
}));
describe("MakeModelPublicForm", () => {
const mockProps = {
visible: true,
onClose: vi.fn(),
accessToken: "test-token",
modelHubData: [
{
model_group: "gpt-4",
providers: ["openai"],
max_input_tokens: 8192,
max_output_tokens: 4096,
input_cost_per_token: 0.03,
output_cost_per_token: 0.06,
mode: "chat",
tpm: 10000,
rpm: 200,
supports_parallel_function_calling: true,
supports_vision: false,
supports_function_calling: true,
supported_openai_params: ["temperature", "max_tokens"],
is_public_model_group: false,
},
{
model_group: "gpt-3.5-turbo",
providers: ["openai"],
max_input_tokens: 4096,
max_output_tokens: 2048,
input_cost_per_token: 0.0015,
output_cost_per_token: 0.002,
mode: "chat",
tpm: 60000,
rpm: 3500,
supports_parallel_function_calling: false,
supports_vision: false,
supports_function_calling: true,
supported_openai_params: ["temperature", "max_tokens"],
is_public_model_group: true,
},
] as ModelGroupInfo[],
onSuccess: vi.fn(),
};
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.resetAllMocks();
});
it("should render the component", () => {
render(<MakeModelPublicForm {...mockProps} />);
expect(screen.getByText("Make Models Public")).toBeInTheDocument();
expect(screen.getByText("Select Models to Make Public")).toBeInTheDocument();
});
it("should initialize with correct state", () => {
render(<MakeModelPublicForm {...mockProps} />);
// Check that the component renders with the correct title and content
expect(screen.getByText("Make Models Public")).toBeInTheDocument();
expect(screen.getByText("Select Models to Make Public")).toBeInTheDocument();
// Check that all model checkboxes are present
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes).toHaveLength(3); // Select all + 2 models
// Check that the Next button is enabled (models are preselected)
const nextButton = screen.getByRole("button", { name: "Next" });
expect(nextButton).not.toBeDisabled();
});
it("should handle model selection and navigation", async () => {
render(<MakeModelPublicForm {...mockProps} />);
// Initially on step 1
expect(screen.getByText("Select Models to Make Public")).toBeInTheDocument();
// Select all models using the select all checkbox
const selectAllCheckbox = screen.getByLabelText("Select All (2)");
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// Verify Next button is enabled
const nextButton = screen.getByRole("button", { name: "Next" });
expect(nextButton).not.toBeDisabled();
// Click Next
await act(async () => {
fireEvent.click(nextButton);
});
// Should move to step 2
await waitFor(() => {
expect(screen.getByText("Confirm Making Models Public")).toBeInTheDocument();
});
});
it("should submit selected models successfully", async () => {
mockMakeModelGroupPublic.mockResolvedValueOnce({});
render(<MakeModelPublicForm {...mockProps} />);
// Select all models
const selectAllCheckbox = screen.getByLabelText("Select All (2)");
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// Navigate to confirm step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
// Wait for navigation to complete
await waitFor(() => {
expect(screen.getByText("Confirm Making Models Public")).toBeInTheDocument();
});
// Submit
const submitButton = screen.getByRole("button", { name: "Make Public" });
await act(async () => {
fireEvent.click(submitButton);
});
await waitFor(() => {
expect(mockMakeModelGroupPublic).toHaveBeenCalledWith("test-token", ["gpt-4", "gpt-3.5-turbo"]);
expect(mockProps.onSuccess).toHaveBeenCalled();
expect(mockProps.onClose).toHaveBeenCalled();
});
});
it("should handle select all functionality", async () => {
render(<MakeModelPublicForm {...mockProps} />);
const checkboxes = screen.getAllByRole("checkbox");
const selectAllCheckbox = checkboxes[0];
// Select all
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// All checkboxes should be checked
checkboxes.forEach((checkbox) => {
expect(checkbox).toBeChecked();
});
// Deselect all
await act(async () => {
fireEvent.click(selectAllCheckbox);
});
// All checkboxes should be unchecked except the indeterminate state
expect(checkboxes[0]).not.toBeChecked();
expect(checkboxes[1]).not.toBeChecked();
expect(checkboxes[2]).not.toBeChecked();
});
it("should show error when no models selected", async () => {
render(<MakeModelPublicForm {...mockProps} />);
// Deselect all models first
const checkboxes = screen.getAllByRole("checkbox");
await act(async () => {
fireEvent.click(checkboxes[0]); // Click select all to select all
fireEvent.click(checkboxes[0]); // Click select all again to deselect all
});
// Try to go to next step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
// Should stay on same step
expect(screen.getByText("Select Models to Make Public")).toBeInTheDocument();
});
it("should display empty state when no models are available", () => {
const emptyProps = {
...mockProps,
modelHubData: [] as ModelGroupInfo[],
};
render(<MakeModelPublicForm {...emptyProps} />);
expect(screen.getByText("No models match the current filters.")).toBeInTheDocument();
// Select All checkbox should be disabled
const selectAllCheckbox = screen.getByLabelText("Select All");
expect(selectAllCheckbox).toBeDisabled();
// Next button should be disabled
const nextButton = screen.getByRole("button", { name: "Next" });
expect(nextButton).toBeDisabled();
});
it("should handle Cancel button functionality", async () => {
render(<MakeModelPublicForm {...mockProps} />);
// Click Cancel button
const cancelButton = screen.getByRole("button", { name: "Cancel" });
await act(async () => {
fireEvent.click(cancelButton);
});
// Should call onClose
expect(mockProps.onClose).toHaveBeenCalled();
});
it("should handle Previous button functionality", async () => {
render(<MakeModelPublicForm {...mockProps} />);
// Navigate to step 1
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
// Verify we're on step 1
await waitFor(() => {
expect(screen.getByText("Confirm Making Models Public")).toBeInTheDocument();
});
// Click Previous button
const previousButton = screen.getByRole("button", { name: "Previous" });
await act(async () => {
fireEvent.click(previousButton);
});
// Should go back to step 0
expect(screen.getByText("Select Models to Make Public")).toBeInTheDocument();
});
it("should handle individual model selection", async () => {
render(<MakeModelPublicForm {...mockProps} />);
// Get all checkboxes (select all + individual models)
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes).toHaveLength(3); // Select all + 2 models
// Initially, gpt-3.5-turbo should be selected (it's already public)
const gpt4Checkbox = checkboxes[1]; // First model checkbox
const gpt35Checkbox = checkboxes[2]; // Second model checkbox
expect(gpt35Checkbox).toBeChecked(); // gpt-3.5-turbo is already public
// Select gpt-4
await act(async () => {
fireEvent.click(gpt4Checkbox);
});
expect(gpt4Checkbox).toBeChecked();
expect(gpt35Checkbox).toBeChecked();
// Deselect gpt-3.5-turbo
await act(async () => {
fireEvent.click(gpt35Checkbox);
});
expect(gpt4Checkbox).toBeChecked();
expect(gpt35Checkbox).not.toBeChecked();
// Select all should be indeterminate now
const selectAllCheckbox = checkboxes[0];
expect(selectAllCheckbox).toHaveAttribute("data-indeterminate", "true");
});
it("should display model badges and information", () => {
render(<MakeModelPublicForm {...mockProps} />);
// Should show model names
expect(screen.getByText("gpt-4")).toBeInTheDocument();
expect(screen.getByText("gpt-3.5-turbo")).toBeInTheDocument();
// Should show mode badges
expect(screen.getAllByText("chat")).toHaveLength(2);
// Should show provider badges
expect(screen.getAllByText("openai")).toHaveLength(2);
});
it("should handle submit error properly", async () => {
const errorMessage = "Network error";
mockMakeModelGroupPublic.mockRejectedValueOnce(new Error(errorMessage));
render(<MakeModelPublicForm {...mockProps} />);
// Navigate to confirm step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
await waitFor(() => {
expect(screen.getByText("Confirm Making Models Public")).toBeInTheDocument();
});
// Submit
const submitButton = screen.getByRole("button", { name: "Make Public" });
await act(async () => {
fireEvent.click(submitButton);
});
// Should handle error and show error notification
await waitFor(() => {
expect(mockMakeModelGroupPublic).toHaveBeenCalledWith("test-token", ["gpt-3.5-turbo"]);
});
// Should not call onSuccess or onClose on error
expect(mockProps.onSuccess).not.toHaveBeenCalled();
expect(mockProps.onClose).not.toHaveBeenCalled();
});
it("should show loading state during submit", async () => {
let resolvePromise: (value: any) => void = () => {};
const pendingPromise = new Promise((resolve) => {
resolvePromise = resolve;
});
mockMakeModelGroupPublic.mockReturnValueOnce(pendingPromise);
render(<MakeModelPublicForm {...mockProps} />);
// Navigate to confirm step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
await waitFor(() => {
expect(screen.getByText("Confirm Making Models Public")).toBeInTheDocument();
});
// Submit
const submitButton = screen.getByRole("button", { name: "Make Public" });
await act(async () => {
fireEvent.click(submitButton);
});
// Check loading state
expect(submitButton).toHaveAttribute("data-loading", "true");
expect(submitButton).toBeDisabled();
// Resolve the promise
resolvePromise({});
await waitFor(() => {
expect(mockProps.onSuccess).toHaveBeenCalled();
expect(mockProps.onClose).toHaveBeenCalled();
});
});
it("should not render modal when visible is false", () => {
const invisibleProps = {
...mockProps,
visible: false,
};
render(<MakeModelPublicForm {...invisibleProps} />);
// Modal should not be rendered
expect(screen.queryByTestId("modal")).not.toBeInTheDocument();
expect(screen.queryByText("Make Models Public")).not.toBeInTheDocument();
});
it("should preselect already public models when modal opens", () => {
// Test data where one model is public and one is not
const mixedPublicProps = {
...mockProps,
modelHubData: [
{
model_group: "private-model",
providers: ["openai"],
is_public_model_group: false,
mode: "chat",
},
{
model_group: "public-model",
providers: ["anthropic"],
is_public_model_group: true,
mode: "completion",
},
{
model_group: "another-public-model",
providers: ["cohere"],
is_public_model_group: true,
mode: "chat",
},
] as ModelGroupInfo[],
};
render(<MakeModelPublicForm {...mixedPublicProps} />);
// Check that the correct checkboxes are selected
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes).toHaveLength(4); // Select all + 3 models
// private-model should not be checked, public models should be checked
const privateModelCheckbox = checkboxes[1];
const publicModelCheckbox = checkboxes[2];
const anotherPublicModelCheckbox = checkboxes[3];
expect(privateModelCheckbox).not.toBeChecked(); // private-model is not public
expect(publicModelCheckbox).toBeChecked(); // public-model is public
expect(anotherPublicModelCheckbox).toBeChecked(); // another-public-model is public
// Select all should be indeterminate
const selectAllCheckbox = checkboxes[0];
expect(selectAllCheckbox).toHaveAttribute("data-indeterminate", "true");
});
it("should show selected count", () => {
render(<MakeModelPublicForm {...mockProps} />);
// Should show that 1 model is selected (gpt-3.5-turbo is preselected)
expect(screen.getByText("1")).toBeInTheDocument();
expect(screen.getByText("model selected")).toBeInTheDocument();
});
it("should show confirmation step with selected models", async () => {
render(<MakeModelPublicForm {...mockProps} />);
// Navigate to confirm step
const nextButton = screen.getByRole("button", { name: "Next" });
await act(async () => {
fireEvent.click(nextButton);
});
await waitFor(() => {
expect(screen.getByText("Confirm Making Models Public")).toBeInTheDocument();
});
// Should show the selected model
expect(screen.getByText("gpt-3.5-turbo")).toBeInTheDocument();
// Should show the warning message
expect(screen.getByText(/Warning:/)).toBeInTheDocument();
expect(screen.getByText(/model_hub_table/)).toBeInTheDocument();
// Should show total count (already verified by checking the presence of the confirmation step)
});
});