Browse Source

test(22-01): extend GenerateVideoNode dynamic handle tests

Add comprehensive tests for dynamic input handles:
- Multiple image handles for multi-frame video models
- Placeholder handles with dimmed opacity
- Handle ordering (images before text)
- Gap spacing between handle groups
- Schema integration tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 6 months ago
parent
commit
6170745da9
  1. 160
      src/components/__tests__/GenerateVideoNode.test.tsx

160
src/components/__tests__/GenerateVideoNode.test.tsx

@ -645,6 +645,166 @@ describe("GenerateVideoNode", () => {
expect(imageHandle).toBeInTheDocument();
expect(textHandle).toBeInTheDocument();
});
describe("Multiple Image Inputs (common for video)", () => {
it("should render multiple image handles for multi-frame video models", () => {
const { container } = render(
<TestWrapper>
<GenerateVideoNode {...createNodeProps({
selectedModel: { provider: "fal", modelId: "kling-video/v1", displayName: "Kling Video" },
inputSchema: [
{ name: "start_image", type: "image", required: true, label: "Start Image" },
{ name: "end_image", type: "image", required: false, label: "End Image" },
{ name: "prompt", type: "text", required: true, label: "Motion Prompt" },
],
})} />
</TestWrapper>
);
// Should have two image INPUT handles (exclude any output handles)
const imageInputHandles = container.querySelectorAll('[data-handletype="image"][class*="target"]');
expect(imageInputHandles.length).toBe(2);
// Check schema names are set
const schemaNames = Array.from(imageInputHandles).map(h => h.getAttribute('data-schema-name'));
expect(schemaNames).toContain('start_image');
expect(schemaNames).toContain('end_image');
});
it("should show labels like 'Start Frame', 'End Frame' from schema", () => {
render(
<TestWrapper>
<GenerateVideoNode {...createNodeProps({
selectedModel: { provider: "fal", modelId: "kling-video/v1", displayName: "Kling Video" },
inputSchema: [
{ name: "start_image", type: "image", required: true, label: "Start Frame" },
{ name: "end_image", type: "image", required: false, label: "End Frame" },
{ name: "prompt", type: "text", required: true, label: "Motion Prompt" },
],
})} />
</TestWrapper>
);
expect(screen.getByText("Start Frame")).toBeInTheDocument();
expect(screen.getByText("End Frame")).toBeInTheDocument();
});
});
describe("Placeholder Handles", () => {
it("should show dimmed image handle when video model only needs text", () => {
const { container } = render(
<TestWrapper>
<GenerateVideoNode {...createNodeProps({
selectedModel: { provider: "fal", modelId: "text-to-video/model", displayName: "Text to Video" },
inputSchema: [
{ name: "prompt", type: "text", required: true, label: "Prompt" },
{ name: "negative_prompt", type: "text", required: false, label: "Negative" },
],
})} />
</TestWrapper>
);
// Image handle should exist with dimmed opacity
const imageHandle = container.querySelector('[data-handletype="image"]') as HTMLElement;
expect(imageHandle).toBeInTheDocument();
expect(imageHandle.style.opacity).toBe("0.3");
});
it("should show dimmed text handle when video model only needs images", () => {
const { container } = render(
<TestWrapper>
<GenerateVideoNode {...createNodeProps({
selectedModel: { provider: "fal", modelId: "image-to-video/model", displayName: "Image to Video" },
inputSchema: [
{ name: "start_frame", type: "image", required: true, label: "Start Frame" },
{ name: "end_frame", type: "image", required: false, label: "End Frame" },
],
})} />
</TestWrapper>
);
// Text handle should exist with dimmed opacity
const textHandle = container.querySelector('[data-handletype="text"]') as HTMLElement;
expect(textHandle).toBeInTheDocument();
expect(textHandle.style.opacity).toBe("0.3");
});
it("should show 'Not used by this model' description for placeholder handles", () => {
const { container } = render(
<TestWrapper>
<GenerateVideoNode {...createNodeProps({
selectedModel: { provider: "fal", modelId: "text-to-video/model", displayName: "Text to Video" },
inputSchema: [
{ name: "prompt", type: "text", required: true, label: "Prompt" },
],
})} />
</TestWrapper>
);
// Image handle should have the placeholder title
const imageHandle = container.querySelector('[data-handletype="image"]');
expect(imageHandle).toHaveAttribute("title", "Not used by this model");
});
});
describe("Schema Integration", () => {
it("should position handles correctly with image handles before text handles", () => {
const { container } = render(
<TestWrapper>
<GenerateVideoNode {...createNodeProps({
selectedModel: { provider: "fal", modelId: "kling-video/v1", displayName: "Kling Video" },
inputSchema: [
{ name: "image", type: "image", required: true, label: "Input Image" },
{ name: "prompt", type: "text", required: true, label: "Prompt" },
],
})} />
</TestWrapper>
);
const imageHandle = container.querySelector('[data-handletype="image"]') as HTMLElement;
const textHandle = container.querySelector('[data-handletype="text"]') as HTMLElement;
// Image handle should be positioned above (lower %) text handle
const imageTop = parseFloat(imageHandle.style.top);
const textTop = parseFloat(textHandle.style.top);
expect(imageTop).toBeLessThan(textTop);
});
it("should maintain gap between image and text handle groups", () => {
const { container } = render(
<TestWrapper>
<GenerateVideoNode {...createNodeProps({
selectedModel: { provider: "fal", modelId: "kling-video/v1", displayName: "Kling Video" },
inputSchema: [
{ name: "start_image", type: "image", required: true, label: "Start Image" },
{ name: "end_image", type: "image", required: false, label: "End Image" },
{ name: "prompt", type: "text", required: true, label: "Motion Prompt" },
],
})} />
</TestWrapper>
);
// Get all input handles in order (exclude output handle)
const imageInputHandles = container.querySelectorAll('[data-handletype="image"][class*="target"]');
const textHandle = container.querySelector('[data-handletype="text"]') as HTMLElement;
expect(imageInputHandles.length).toBe(2);
const image0 = imageInputHandles[0] as HTMLElement;
const image1 = imageInputHandles[1] as HTMLElement;
const top0 = parseFloat(image0.style.top);
const top1 = parseFloat(image1.style.top);
const topText = parseFloat(textHandle.style.top);
// Gap between image-1 and text should be larger than gap between image-0 and image-1
const imageDiff = top1 - top0;
const gapDiff = topText - top1;
// The gap should account for the spacing slot
expect(gapDiff).toBeGreaterThan(imageDiff * 0.9);
});
});
});
describe("Custom Title and Comment", () => {

Loading…
Cancel
Save