diff --git a/src/components/__tests__/GenerateVideoNode.test.tsx b/src/components/__tests__/GenerateVideoNode.test.tsx
index 3845ed64..1be4adcf 100644
--- a/src/components/__tests__/GenerateVideoNode.test.tsx
+++ b/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(
+
+
+
+ );
+
+ // 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(
+
+
+
+ );
+
+ 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(
+
+
+
+ );
+
+ // 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(
+
+
+
+ );
+
+ // 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(
+
+
+
+ );
+
+ // 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(
+
+
+
+ );
+
+ 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(
+
+
+
+ );
+
+ // 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", () => {