Files
Artificial-Sweetener-Simple…/tests/test_load_image_list_v3_node.py
T

108 lines
3.7 KiB
Python

# SimpleSyrup - workflow-focused ComfyUI extensions for image generation
# Copyright (C) 2026 Artificial Sweetener and contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Tests for the Load Image List Comfy v3 node."""
from __future__ import annotations
from typing import ClassVar
import torch
from simple_syrup.nodes_v3.load_image_list import LoadImageListV3
class FakeService:
"""Provide deterministic image-list behavior at the node boundary."""
loaded: ClassVar[list[str] | None] = None
def available_files(self) -> tuple[str, ...]:
"""Return node combo choices."""
return ("b.png", "a.png")
def validate(self, files: str | list[str]) -> None:
"""Accept deterministic widget values."""
del files
def load(self, files: str | list[str]) -> list[torch.Tensor]:
"""Record order and return independently sized singleton images."""
ordered = [files] if isinstance(files, str) else files
type(self).loaded = ordered
return [torch.zeros((1, 2, 3, 3)), torch.ones((1, 4, 2, 3))]
def fingerprint(self, files: str | list[str]) -> str:
"""Return an order-visible node fingerprint."""
ordered = [files] if isinstance(files, str) else files
return "|".join(ordered)
def test_schema_exposes_native_ordered_image_upload_and_list_output() -> None:
"""The widget persists filenames while the IMAGE socket is a Comfy list."""
original = LoadImageListV3.service_class
LoadImageListV3.service_class = FakeService # type: ignore[assignment]
try:
schema = LoadImageListV3.define_schema()
finally:
LoadImageListV3.service_class = original
inputs = {value.id: value for value in schema.inputs}
image = inputs["image"].as_dict()
assert schema.node_id == "SimpleSyrup.LoadImageList"
assert schema.display_name == "Load Image List"
assert schema.has_intermediate_output is False
assert image["image_upload"] is True
assert image["allow_batch"] is True
assert image["image_folder"] == "input"
assert image["multiselect"] is True
assert image["default"] == []
assert schema.outputs[0].io_type == "IMAGE"
assert schema.outputs[0].is_output_list is True
def test_execute_returns_exact_ordered_python_list_without_batching() -> None:
"""Comfy receives one singleton tensor for every ordered image position."""
original = LoadImageListV3.service_class
LoadImageListV3.service_class = FakeService # type: ignore[assignment]
try:
output = LoadImageListV3.execute(["b.png", "a.png"])
fingerprint = LoadImageListV3.fingerprint_inputs(["b.png", "a.png"])
finally:
LoadImageListV3.service_class = original
assert FakeService.loaded == ["b.png", "a.png"]
assert output.result is not None
images = output.result[0]
assert isinstance(images, list)
assert [tuple(image.shape) for image in images] == [(1, 2, 3, 3), (1, 4, 2, 3)]
assert fingerprint == "b.png|a.png"
def test_validation_surfaces_actionable_service_failures() -> None:
"""Comfy validation receives the service's workflow-facing reason."""
class RejectingService(FakeService):
"""Reject an empty image list."""
def validate(self, files: str | list[str]) -> None:
"""Raise one deterministic validation failure."""
del files
raise ValueError("Load Image List requires at least one image file.")
original = LoadImageListV3.service_class
LoadImageListV3.service_class = RejectingService # type: ignore[assignment]
try:
result: bool | str = LoadImageListV3.validate_inputs([])
finally:
LoadImageListV3.service_class = original
assert result == "Load Image List requires at least one image file."