diff --git a/tests/unit/tools/test_width_height_selector.py b/tests/unit/tools/test_width_height_selector.py new file mode 100644 index 0000000..17d8060 --- /dev/null +++ b/tests/unit/tools/test_width_height_selector.py @@ -0,0 +1,263 @@ +"""Tests for Width Height Selector tool.""" + +import pytest +from unittest.mock import Mock +from kikotools.tools.width_height_selector.node import WidthHeightSelectorNode +from kikotools.tools.width_height_selector.logic import ( + get_preset_dimensions, + calculate_aspect_ratio, + validate_dimensions, +) +from kikotools.tools.width_height_selector.presets import ( + PRESET_OPTIONS, + SDXL_PRESETS, + FLUX_PRESETS, + ULTRA_WIDE_PRESETS, +) + + +class TestWidthHeightSelectorNode: + """Test the WidthHeightSelectorNode class.""" + + def setup_method(self): + """Set up test fixtures.""" + self.node = WidthHeightSelectorNode() + + def test_node_structure(self): + """Test that node has required ComfyUI structure.""" + # Test INPUT_TYPES + input_types = self.node.INPUT_TYPES() + assert "required" in input_types + assert "preset" in input_types["required"] + assert "width" in input_types["required"] + assert "height" in input_types["required"] + + # Test return types + assert self.node.RETURN_TYPES == ("INT", "INT") + assert self.node.RETURN_NAMES == ("width", "height") + assert self.node.FUNCTION == "get_dimensions" + assert self.node.CATEGORY == "ComfyAssets" + + def test_custom_dimensions(self): + """Test custom dimensions.""" + result = self.node.get_dimensions(preset="custom", width=1920, height=1080) + assert result == (1920, 1080) + + def test_sdxl_square_preset(self): + """Test SDXL square preset.""" + result = self.node.get_dimensions( + preset="1024×1024", + width=512, # Should be ignored + height=512, # Should be ignored + ) + assert result == (1024, 1024) + + def test_sdxl_portrait_preset(self): + """Test SDXL portrait preset.""" + result = self.node.get_dimensions(preset="832×1216", width=512, height=512) + assert result == (832, 1216) + + def test_sdxl_landscape_preset(self): + """Test SDXL landscape preset.""" + result = self.node.get_dimensions(preset="1216×832", width=512, height=512) + assert result == (1216, 832) + + def test_flux_preset(self): + """Test FLUX preset.""" + result = self.node.get_dimensions(preset="1920×1080", width=512, height=512) + assert result == (1920, 1080) + + def test_ultra_wide_preset(self): + """Test ultra-wide preset.""" + result = self.node.get_dimensions(preset="2560×1080", width=512, height=512) + assert result == (2560, 1080) + + def test_all_presets_available(self): + """Test that all presets are available in INPUT_TYPES.""" + input_types = self.node.INPUT_TYPES() + available_presets = input_types["required"]["preset"][0] + + # Check that all major preset categories are available + assert "custom" in available_presets + assert "1024×1024" in available_presets # SDXL square + assert "832×1216" in available_presets # SDXL portrait + assert "1216×832" in available_presets # SDXL landscape + assert "1920×1080" in available_presets # FLUX + assert "2560×1080" in available_presets # Ultra-wide + + def test_invalid_preset_fallback(self): + """Test handling of invalid preset.""" + # Should fall back to custom dimensions + result = self.node.get_dimensions( + preset="invalid_preset", width=800, height=600 + ) + assert result == (800, 600) + + +class TestPresetLogic: + """Test the preset logic functions.""" + + def test_get_preset_dimensions_custom(self): + """Test getting custom dimensions.""" + width, height = get_preset_dimensions("custom", 1920, 1080) + assert width == 1920 + assert height == 1080 + + def test_get_preset_dimensions_sdxl(self): + """Test getting SDXL preset dimensions.""" + width, height = get_preset_dimensions("1024×1024", 512, 512) + assert width == 1024 + assert height == 1024 + + def test_get_preset_dimensions_flux(self): + """Test getting FLUX preset dimensions.""" + width, height = get_preset_dimensions("1920×1080", 512, 512) + assert width == 1920 + assert height == 1080 + + def test_get_preset_dimensions_invalid(self): + """Test getting dimensions for invalid preset.""" + width, height = get_preset_dimensions("invalid", 800, 600) + assert width == 800 + assert height == 600 + + +class TestDimensionValidation: + """Test dimension validation.""" + + def test_validate_dimensions_valid(self): + """Test validation of valid dimensions.""" + assert validate_dimensions(1024, 1024) is True + assert validate_dimensions(1920, 1080) is True + assert validate_dimensions(832, 1216) is True + + def test_validate_dimensions_divisible_by_8(self): + """Test that dimensions must be divisible by 8.""" + assert validate_dimensions(1024, 1024) is True # Both divisible by 8 + assert validate_dimensions(1025, 1024) is False # Width not divisible by 8 + assert validate_dimensions(1024, 1025) is False # Height not divisible by 8 + assert validate_dimensions(1025, 1025) is False # Neither divisible by 8 + + def test_validate_dimensions_minimum_size(self): + """Test minimum dimension requirements.""" + assert validate_dimensions(64, 64) is True # Minimum allowed + assert validate_dimensions(32, 64) is False # Width too small + assert validate_dimensions(64, 32) is False # Height too small + assert validate_dimensions(32, 32) is False # Both too small + + def test_validate_dimensions_maximum_size(self): + """Test maximum dimension requirements.""" + assert validate_dimensions(8192, 8192) is True # Maximum allowed + assert validate_dimensions(8200, 8192) is False # Width too large + assert validate_dimensions(8192, 8200) is False # Height too large + assert validate_dimensions(8200, 8200) is False # Both too large + + +class TestPresetDefinitions: + """Test preset definitions.""" + + def test_sdxl_presets_structure(self): + """Test SDXL presets are properly defined.""" + assert "1024×1024" in SDXL_PRESETS + assert "832×1216" in SDXL_PRESETS + assert "1216×832" in SDXL_PRESETS + + # Check dimensions are tuples + for preset, dims in SDXL_PRESETS.items(): + assert isinstance(dims, tuple) + assert len(dims) == 2 + assert isinstance(dims[0], int) + assert isinstance(dims[1], int) + + def test_flux_presets_structure(self): + """Test FLUX presets are properly defined.""" + assert "1920×1080" in FLUX_PRESETS + assert "1536×1536" in FLUX_PRESETS + + # Check dimensions are tuples + for preset, dims in FLUX_PRESETS.items(): + assert isinstance(dims, tuple) + assert len(dims) == 2 + assert isinstance(dims[0], int) + assert isinstance(dims[1], int) + + def test_ultra_wide_presets_structure(self): + """Test ultra-wide presets are properly defined.""" + assert "2560×1080" in ULTRA_WIDE_PRESETS + + # Check dimensions are tuples + for preset, dims in ULTRA_WIDE_PRESETS.items(): + assert isinstance(dims, tuple) + assert len(dims) == 2 + assert isinstance(dims[0], int) + assert isinstance(dims[1], int) + + def test_preset_options_combined(self): + """Test that PRESET_OPTIONS combines all presets correctly.""" + assert "custom" in PRESET_OPTIONS + + # Check SDXL presets are included + for preset in SDXL_PRESETS: + assert preset in PRESET_OPTIONS + + # Check FLUX presets are included + for preset in FLUX_PRESETS: + assert preset in PRESET_OPTIONS + + # Check ultra-wide presets are included + for preset in ULTRA_WIDE_PRESETS: + assert preset in PRESET_OPTIONS + + def test_all_presets_divisible_by_8(self): + """Test that all preset dimensions are divisible by 8.""" + for preset_dict in [SDXL_PRESETS, FLUX_PRESETS, ULTRA_WIDE_PRESETS]: + for preset_name, (width, height) in preset_dict.items(): + assert width % 8 == 0, f"{preset_name} width {width} not divisible by 8" + assert ( + height % 8 == 0 + ), f"{preset_name} height {height} not divisible by 8" + + def test_preset_dimensions_within_limits(self): + """Test that all preset dimensions are within acceptable limits.""" + for preset_dict in [SDXL_PRESETS, FLUX_PRESETS, ULTRA_WIDE_PRESETS]: + for preset_name, (width, height) in preset_dict.items(): + assert 64 <= width <= 8192, f"{preset_name} width {width} out of range" + assert ( + 64 <= height <= 8192 + ), f"{preset_name} height {height} out of range" + + +class TestEdgeCases: + """Test edge cases and error conditions.""" + + def test_zero_dimensions(self): + """Test handling of zero dimensions.""" + assert validate_dimensions(0, 1024) is False + assert validate_dimensions(1024, 0) is False + assert validate_dimensions(0, 0) is False + + def test_negative_dimensions(self): + """Test handling of negative dimensions.""" + assert validate_dimensions(-100, 1024) is False + assert validate_dimensions(1024, -100) is False + assert validate_dimensions(-100, -100) is False + + def test_very_large_dimensions(self): + """Test handling of very large dimensions.""" + assert validate_dimensions(10000, 1024) is False + assert validate_dimensions(1024, 10000) is False + assert validate_dimensions(10000, 10000) is False + + def test_aspect_ratio_edge_cases(self): + """Test aspect ratio calculation edge cases.""" + # Very wide aspect ratio + ratio = calculate_aspect_ratio(3840, 1080) + assert ratio == "32:9" + + # Very tall aspect ratio + ratio = calculate_aspect_ratio(1080, 3840) + assert ratio == "9:32" + + # Prime number dimensions + ratio = calculate_aspect_ratio(1920, 1080) + assert ratio == "16:9"