- Fix batch bypass tuple handling, memory budget device-awareness, grain RNG device, roto payload limits, preview temp-file cleanup, and route double-registration. - Add native VIDEO output (fps/audio) to effect nodes that previously only returned IMAGE/MASK when fed a VIDEO input. - Split the oversized src/preview/ops/geometry.ts and add a starting golden-fixture test harness (spherize, transform) comparing backend and frontend math. - Remove ImageOpsAppendV2 (Autogrow) and ImageOpsLensArtifacts; inline Append's helper back into its single caller. - Collapse the Color Correct curve widget behind a toggle so the node no longer grows too tall. - Fix Crop/generic-node video preview playback running at the RAF rate instead of the source video's real fps. - Fix Crop losing its saved output size after a workflow reload: syncCropWidgets recomputed height from width and a preset aspect ratio on every resync (including onConfigure on load), discarding the restored value. It now only refreshes aspect-ratio bookkeeping on a resync.
116 lines
3.1 KiB
Python
116 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
|
|
EXPECTED_NODE_COUNT = 31
|
|
|
|
|
|
def test_imageops_extension_loads(imageops_extension):
|
|
"""ImageOps package must load under a real ComfyUI checkout."""
|
|
|
|
assert imageops_extension is not None
|
|
|
|
|
|
def test_all_nodes_are_registered(imageops_extension):
|
|
"""The extension must expose all expected ImageOps nodes."""
|
|
|
|
assert hasattr(imageops_extension, "NODES")
|
|
|
|
nodes = imageops_extension.NODES
|
|
|
|
assert len(nodes) == EXPECTED_NODE_COUNT
|
|
|
|
names = [node.__name__ for node in nodes]
|
|
|
|
assert len(names) == EXPECTED_NODE_COUNT
|
|
assert len(set(names)) == EXPECTED_NODE_COUNT
|
|
|
|
|
|
def test_all_nodes_have_v3_schema(imageops_extension):
|
|
"""Every registered node must expose a valid V3 schema."""
|
|
|
|
for node in imageops_extension.NODES:
|
|
|
|
assert hasattr(node, "define_schema"), (
|
|
f"{node.__name__} is missing define_schema()"
|
|
)
|
|
|
|
schema = node.define_schema()
|
|
|
|
assert schema is not None, (
|
|
f"{node.__name__}.define_schema() returned None"
|
|
)
|
|
|
|
|
|
def test_extension_get_node_list(imageops_extension):
|
|
"""ComfyExtension must expose exactly the registered nodes."""
|
|
|
|
assert hasattr(
|
|
imageops_extension,
|
|
"MajoorImageOpsExtension",
|
|
)
|
|
|
|
extension = imageops_extension.MajoorImageOpsExtension()
|
|
|
|
nodes = asyncio.run(
|
|
extension.get_node_list()
|
|
)
|
|
|
|
assert nodes == imageops_extension.NODES
|
|
assert len(nodes) == EXPECTED_NODE_COUNT
|
|
|
|
|
|
def test_entrypoint_returns_extension_and_registers_routes(
|
|
imageops_extension,
|
|
monkeypatch,
|
|
):
|
|
"""Comfy skips comfy_entrypoint when NODE_CLASS_MAPPINGS exists, so it must not exist."""
|
|
|
|
assert not hasattr(imageops_extension, "NODE_CLASS_MAPPINGS")
|
|
|
|
registered = []
|
|
monkeypatch.setattr(
|
|
imageops_extension,
|
|
"register_imageops_routes",
|
|
lambda: registered.append(True),
|
|
)
|
|
|
|
extension = asyncio.run(imageops_extension.comfy_entrypoint())
|
|
asyncio.run(extension.on_load())
|
|
|
|
assert isinstance(extension, imageops_extension.ComfyExtension)
|
|
assert registered == [True]
|
|
|
|
|
|
def test_node_ids_are_unique(imageops_extension):
|
|
ids = [node.define_schema().node_id for node in imageops_extension.NODES]
|
|
|
|
assert len(set(ids)) == EXPECTED_NODE_COUNT
|
|
|
|
|
|
def test_nodes_with_backend_ui_replay_cached_output(imageops_extension):
|
|
"""Frontend widgets read backend-computed values from `ui`; cached runs must resend it."""
|
|
|
|
flagged = {
|
|
node.define_schema().node_id
|
|
for node in imageops_extension.NODES
|
|
if node.define_schema().has_intermediate_output
|
|
}
|
|
|
|
assert flagged == {
|
|
"ImageOpsPadOut",
|
|
"ImageOpsFrameRange",
|
|
"ImageOpsAppend",
|
|
}
|
|
|
|
|
|
def test_nodes_are_listed_in_the_essentials_tab(imageops_extension):
|
|
categories = {
|
|
node.define_schema().node_id: node.define_schema().essentials_category
|
|
for node in imageops_extension.NODES
|
|
}
|
|
|
|
assert set(categories.values()) == {"Image Tools", "Video Tools"}
|
|
assert {k for k, v in categories.items() if v == "Video Tools"} == {"ImageOpsAppend", "ImageOpsFrameRange"}
|