Files
ComfyAssets-ComfyUI-KikoTools/tests/unit/tools/xyz_helpers/test_plot_parameters.py
T
Vito Sansevero 5485aa8c19 feat(xyz-helpers): add ComfyUI_essentials nodes adaptation
BREAKING CHANGE: Node categories now use emoji-based organization

Add 6 new xyz-helper nodes adapted from comfyui-essentials-nodes:
- FluxSamplerParams: FLUX-optimized parameter generator with batch support
- LoRAFolderBatch: Batch process multiple LoRAs from folders
- PlotParameters: Visualize parameter effects with graphs
- SamplerSelectHelper: Intelligent sampler selection with recommendations
- SchedulerSelectHelper: Optimal scheduler selection for samplers
- TextEncodeSamplerParams: Combined text encoding and parameter management

Changes:
- Port and enhance nodes from comfyui-essentials (now in maintenance mode)
- Add comprehensive documentation with attribution to original author (cubiq)
- Create example workflows for xyz-helpers tools
- Update all node categories to use emoji-based organization
- Fix all unit tests to pass with new category system
- Update README with xyz-helpers section and attribution

Attribution: xyz-helpers adapted from github.com/cubiq/ComfyUI_essentials

All tests passing (318 pass, 2 skip)
2025-08-07 05:41:23 -07:00

234 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for Plot Parameters node."""
import pytest
import torch
from unittest.mock import Mock, patch
from kikotools.tools.xyz_helpers.plot_sampler_params import PlotParametersNode
from kikotools.tools.xyz_helpers.plot_sampler_params.logic import (
sort_parameters,
group_by_value,
identify_changing_parameters,
filter_changing_params,
format_parameter_text,
wrap_prompt_text,
calculate_grid_dimensions,
validate_plot_parameters,
)
class TestPlotParametersLogic:
"""Test the logic functions for Plot Parameters."""
def test_sort_parameters(self):
"""Test sorting parameters."""
params = [
{"seed": 3, "steps": 20},
{"seed": 1, "steps": 30},
{"seed": 2, "steps": 10},
]
# Sort by seed
sorted_params, indices = sort_parameters(params, "seed")
assert sorted_params[0]["seed"] == 1
assert sorted_params[1]["seed"] == 2
assert sorted_params[2]["seed"] == 3
assert indices == [1, 2, 0]
# Sort by steps
sorted_params, indices = sort_parameters(params, "steps")
assert sorted_params[0]["steps"] == 10
assert sorted_params[1]["steps"] == 20
assert sorted_params[2]["steps"] == 30
# No sorting
sorted_params, indices = sort_parameters(params, "none")
assert sorted_params == params
assert indices == [0, 1, 2]
def test_group_by_value(self):
"""Test grouping by value."""
params = [
{"sampler": "euler", "seed": 1},
{"sampler": "ddim", "seed": 2},
{"sampler": "euler", "seed": 3},
{"sampler": "ddim", "seed": 4},
]
grouped, indices, num_groups = group_by_value(params, "sampler")
assert num_groups == 2
# Check that same samplers are grouped
assert grouped[0]["sampler"] == grouped[2]["sampler"]
assert grouped[1]["sampler"] == grouped[3]["sampler"]
def test_identify_changing_parameters(self):
"""Test identifying changing parameters."""
params = [
{"seed": 1, "steps": 20, "sampler": "euler"},
{"seed": 2, "steps": 20, "sampler": "ddim"},
{"seed": 3, "steps": 20, "sampler": "euler"},
]
changing = identify_changing_parameters(params)
assert changing["seed"] == True # Seed changes
assert changing["steps"] == False # Steps don't change
assert changing["sampler"] == True # Sampler changes
def test_filter_changing_params(self):
"""Test filtering to only changing parameters."""
params = [
{"seed": 1, "steps": 20, "sampler": "euler"},
{"seed": 2, "steps": 20, "sampler": "ddim"},
]
filtered = filter_changing_params(params)
assert "seed" in filtered[0]
assert "sampler" in filtered[0]
assert "steps" not in filtered[0] # Steps don't change
def test_format_parameter_text_full(self):
"""Test formatting parameter text in full mode."""
param = {
"time": 2.5,
"seed": 12345,
"steps": 20,
"width": 512,
"height": 512,
"denoise": 1.0,
"sampler": "euler",
"scheduler": "normal",
"guidance": 7.0,
"max_shift": 1.15,
"base_shift": 0.5,
}
text = format_parameter_text(param, "full")
assert "time: 2.50s" in text
assert "seed: 12345" in text
assert "steps: 20" in text
assert "512×512" in text
def test_format_parameter_text_changes_only(self):
"""Test formatting parameter text in changes only mode."""
param = {"seed": 12345, "sampler": "euler", "prompt": "test prompt"}
text = format_parameter_text(param, "changes only")
assert "seed: 12345" in text
assert "sampler: euler" in text
assert "prompt" not in text # Prompt handled separately
def test_wrap_prompt_text(self):
"""Test wrapping prompt text."""
prompt = "This is a very long prompt that needs to be wrapped"
# Full mode
lines = wrap_prompt_text(prompt, 20, "full")
assert len(lines) > 1
assert all(len(line) <= 20 for line in lines)
# Excerpt mode
long_prompt = " ".join(["word"] * 100)
lines = wrap_prompt_text(long_prompt, 50, "excerpt")
full_text = " ".join(lines)
assert "..." in full_text
def test_calculate_grid_dimensions(self):
"""Test calculating grid dimensions."""
# Auto mode
rows, cols = calculate_grid_dimensions(9, -1)
assert rows == 3
assert cols == 3
# Fixed columns
rows, cols = calculate_grid_dimensions(10, 3)
assert rows == 4
assert cols == 3
# Auto square
rows, cols = calculate_grid_dimensions(16, 0)
assert rows == 4
assert cols == 4
def test_validate_plot_parameters(self):
"""Test validating plot parameters."""
# Valid
assert validate_plot_parameters((5, 256, 256, 3), 5, "none", "none", -1) == True
# Mismatch
assert (
validate_plot_parameters((5, 256, 256, 3), 3, "none", "none", -1) == False
)
class TestPlotParametersNode:
"""Test the Plot Parameters node."""
@pytest.fixture
def node(self):
"""Create a node instance."""
return PlotParametersNode()
@pytest.fixture
def mock_images(self):
"""Create mock images tensor."""
return torch.rand(4, 256, 256, 3)
@pytest.fixture
def mock_params(self):
"""Create mock parameters."""
return [
{
"time": 2.0,
"seed": 1,
"steps": 20,
"width": 256,
"height": 256,
"sampler": "euler",
"scheduler": "normal",
"guidance": 7.0,
"denoise": 1.0,
"max_shift": 1.0,
"base_shift": 0.5,
}
for i in range(4)
]
def test_input_types(self):
"""Test that INPUT_TYPES returns correct structure."""
input_types = PlotParametersNode.INPUT_TYPES()
assert "required" in input_types
required = input_types["required"]
assert "images" in required
assert "params" in required
assert "order_by" in required
assert "cols_value" in required
assert "cols_num" in required
assert "add_prompt" in required
assert "add_params" in required
def test_plot_parameters_basic(self, node, mock_images, mock_params):
"""Test basic plot creation."""
with patch(
"kikotools.tools.xyz_helpers.plot_sampler_params.node.ImageFont.truetype"
):
result = node.plot_parameters(
mock_images,
mock_params,
order_by="none",
cols_value="none",
cols_num=-1,
add_prompt="false",
add_params="false",
)
assert isinstance(result, tuple)
assert len(result) == 1
assert isinstance(result[0], torch.Tensor)
def test_node_properties(self):
"""Test node properties."""
assert PlotParametersNode.CATEGORY == "ComfyAssets/🧰 xyz-helpers"
assert PlotParametersNode.FUNCTION == "plot_parameters"
assert PlotParametersNode.RETURN_TYPES == ("IMAGE",)
assert PlotParametersNode.RETURN_NAMES == ("image",)