Browse Source
The bottom composer is the canonical image-generation control surface now. The legacy right-side control panel was still rendering schema defaults for selected image nodes, which made aspect and size controls disagree on the same operation. Constraint: Image generation controls must stay consistent with the Popi composer UX Rejected: Synchronize both panels | duplicating controls preserves the confusing two-source UI Confidence: high Scope-risk: narrow Directive: Do not re-enable the right-side image panel unless it reads from the same composer-owned state Tested: npm run test:run -- src/components/__tests__/ControlPanel.test.tsx Tested: npm run test:run -- src/components/__tests__/GenerationComposer.test.tsx src/components/__tests__/ControlPanel.test.tsx Tested: PROVIDER_MODE=popi NEXT_PUBLIC_PROVIDER_MODE=popi npm run build Tested: Playwright localhost check for composer visible and legacy image settings absentfeature/add_agent_md
2 changed files with 111 additions and 0 deletions
@ -0,0 +1,101 @@ |
|||||
|
import { render, screen } from "@testing-library/react"; |
||||
|
import { 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: () => <div data-testid="model-parameters" />, |
||||
|
})); |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
describe("ControlPanel", () => { |
||||
|
beforeEach(() => { |
||||
|
vi.clearAllMocks(); |
||||
|
useWorkflowStore.setState({ |
||||
|
nodes: [], |
||||
|
edges: [], |
||||
|
isRunning: false, |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it("does not render duplicate settings for image generation nodes controlled by the composer", () => { |
||||
|
useWorkflowStore.setState({ nodes: [imageNode()] }); |
||||
|
|
||||
|
const { container } = render(<ControlPanel />); |
||||
|
|
||||
|
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(<ControlPanel />); |
||||
|
|
||||
|
expect(screen.getByText("Generate Video Settings")).toBeInTheDocument(); |
||||
|
expect(screen.getByTestId("model-parameters")).toBeInTheDocument(); |
||||
|
}); |
||||
|
}); |
||||
Loading…
Reference in new issue