import { render, screen } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { ControlPanel } from "@/components/nodes/ControlPanel"; import { useWorkflowStore } from "@/store/workflowStore"; import type { WorkflowNode } from "@/types"; vi.mock("@/hooks/useInlineParameters", () => ({ useInlineParameters: () => ({ inlineParametersEnabled: false }), })); vi.mock("@/components/nodes/ModelParameters", () => ({ ModelParameters: () =>
, })); vi.mock("@/components/modals/ModelSearchDialog", () => ({ ModelSearchDialog: () => null, })); function imageNode(): WorkflowNode { return { id: "image-1", type: "nanoBanana", selected: true, position: { x: 0, y: 0 }, data: { inputImages: [], inputPrompt: "coffee spilled", outputImage: null, aspectRatio: "16:9", resolution: "1K", model: "nano-banana-pro", selectedModel: { provider: "newapiwg", modelId: "apiyi_nano_banana_2", displayName: "apiyi_nano_banana_2", capabilities: ["text-to-image", "image-to-image"], }, parameters: { quality: "auto", size: "1024x1024" }, useGoogleSearch: false, useImageSearch: false, status: "idle", error: null, imageHistory: [], selectedHistoryIndex: 0, }, } as WorkflowNode; } function videoNode(): WorkflowNode { return { id: "video-1", type: "generateVideo", selected: true, position: { x: 0, y: 0 }, data: { prompt: "camera pan", inputImage: null, outputVideo: null, selectedModel: { provider: "newapiwg", modelId: "video-model", displayName: "Video Model", capabilities: ["text-to-video"], }, parameters: {}, status: "idle", error: null, progress: 0, }, } as WorkflowNode; } function llmNode(): WorkflowNode { return { id: "llm-1", type: "llmGenerate", selected: true, position: { x: 0, y: 0 }, data: { inputPrompt: null, inputImages: [], inputVideos: [], outputText: null, provider: "newapiwg", model: "doubao-seed-2-0-lite-260428", temperature: 0.7, maxTokens: 2048, status: "idle", error: null, }, } as WorkflowNode; } describe("ControlPanel", () => { const originalProviderMode = process.env.NEXT_PUBLIC_PROVIDER_MODE; beforeEach(() => { vi.clearAllMocks(); useWorkflowStore.setState({ nodes: [], edges: [], isRunning: false, }); }); afterEach(() => { if (originalProviderMode === undefined) { delete process.env.NEXT_PUBLIC_PROVIDER_MODE; } else { process.env.NEXT_PUBLIC_PROVIDER_MODE = originalProviderMode; } }); it("does not render duplicate settings for image generation nodes controlled by the composer", () => { useWorkflowStore.setState({ nodes: [imageNode()] }); const { container } = render(); expect(container).toBeEmptyDOMElement(); expect(screen.queryByText("Generate Image Settings")).not.toBeInTheDocument(); expect(screen.queryByText("1024x1024")).not.toBeInTheDocument(); }); it("keeps the right-side panel for generation nodes not yet owned by the composer", () => { useWorkflowStore.setState({ nodes: [videoNode()] }); render(); expect(screen.getByText("Generate Video Settings")).toBeInTheDocument(); expect(screen.getByTestId("model-parameters")).toBeInTheDocument(); }); it("hides provider labels in Popi provider mode", () => { process.env.NEXT_PUBLIC_PROVIDER_MODE = "popi"; useWorkflowStore.setState({ nodes: [videoNode()] }); render(); expect(screen.getByText("Video Model")).toBeInTheDocument(); expect(screen.queryByText("Popi Models")).not.toBeInTheDocument(); }); it("does not render the side control panel for LLM nodes", () => { process.env.NEXT_PUBLIC_PROVIDER_MODE = "popi"; useWorkflowStore.setState({ nodes: [llmNode()] }); render(); expect(screen.queryByText("Provider")).not.toBeInTheDocument(); expect(screen.queryByText("Model")).not.toBeInTheDocument(); expect(screen.queryByText((content) => content.includes("0.70"))).not.toBeInTheDocument(); expect(screen.queryByText((content) => content.includes("2,048"))).not.toBeInTheDocument(); expect(screen.queryByText("模型配置")).not.toBeInTheDocument(); }); });