test(wave172): color_grade_config色彩调色 +72测 #1111
Executable
+474
@@ -0,0 +1,474 @@
|
||||
"""ColorGradeConfig 色彩调色配置单测.
|
||||
|
||||
纯逻辑模块,覆盖:数据类、resolve_params参数解析、has_effect效果判断、
|
||||
from_dict字典解析、validate校验、预设查询函数、clamp_param钳制。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from packages.domain.color_grade_config import (
|
||||
ALL_PARAM_KEYS,
|
||||
DEFAULT_PARAMS,
|
||||
PARAM_RANGES,
|
||||
PRESET_DISPLAY_NAMES,
|
||||
PRESET_PARAMS,
|
||||
VALID_PRESETS,
|
||||
ColorGradeConfig,
|
||||
clamp_param,
|
||||
get_preset_names,
|
||||
get_preset_params,
|
||||
)
|
||||
|
||||
|
||||
class TestColorGradeConfigDefaults:
|
||||
def test_default_disabled(self):
|
||||
config = ColorGradeConfig()
|
||||
assert config.enabled is False
|
||||
assert config.preset == ""
|
||||
assert config.brightness is None
|
||||
assert config.contrast is None
|
||||
assert config.saturation is None
|
||||
assert config.temperature is None
|
||||
assert config.hue is None
|
||||
|
||||
def test_default_resolve_returns_defaults(self):
|
||||
config = ColorGradeConfig()
|
||||
params = config.resolve_params()
|
||||
for key in ALL_PARAM_KEYS:
|
||||
assert params[key] == DEFAULT_PARAMS[key]
|
||||
|
||||
def test_default_has_no_effect(self):
|
||||
config = ColorGradeConfig()
|
||||
assert config.has_effect() is False
|
||||
|
||||
def test_default_validate_passes(self):
|
||||
config = ColorGradeConfig()
|
||||
ok, msg = config.validate()
|
||||
assert ok is True
|
||||
assert msg == ""
|
||||
|
||||
|
||||
class TestResolveParams:
|
||||
def test_disabled_still_resolves(self):
|
||||
"""禁用状态下仍能解析参数."""
|
||||
config = ColorGradeConfig(enabled=False, brightness=50.0)
|
||||
params = config.resolve_params()
|
||||
assert params["brightness"] == 50.0
|
||||
|
||||
def test_preset_fresh(self):
|
||||
config = ColorGradeConfig(enabled=True, preset="fresh")
|
||||
params = config.resolve_params()
|
||||
assert params["brightness"] == 8
|
||||
assert params["contrast"] == 10
|
||||
assert params["saturation"] == 120
|
||||
assert params["temperature"] == -8
|
||||
assert params["hue"] == 5
|
||||
|
||||
def test_preset_black_white(self):
|
||||
config = ColorGradeConfig(enabled=True, preset="black_white")
|
||||
params = config.resolve_params()
|
||||
assert params["saturation"] == 0
|
||||
assert params["contrast"] == 15
|
||||
|
||||
def test_preset_warm(self):
|
||||
config = ColorGradeConfig(enabled=True, preset="warm")
|
||||
params = config.resolve_params()
|
||||
assert params["temperature"] == 30
|
||||
|
||||
def test_preset_cool(self):
|
||||
config = ColorGradeConfig(enabled=True, preset="cool")
|
||||
params = config.resolve_params()
|
||||
assert params["temperature"] == -25
|
||||
|
||||
def test_invalid_preset_uses_defaults(self):
|
||||
config = ColorGradeConfig(enabled=True, preset="nonexistent")
|
||||
params = config.resolve_params()
|
||||
for key in ALL_PARAM_KEYS:
|
||||
assert params[key] == DEFAULT_PARAMS[key]
|
||||
|
||||
def test_custom_override_preset(self):
|
||||
config = ColorGradeConfig(
|
||||
enabled=True,
|
||||
preset="fresh",
|
||||
brightness=50.0,
|
||||
)
|
||||
params = config.resolve_params()
|
||||
# custom覆盖了预设
|
||||
assert params["brightness"] == 50.0
|
||||
# 其他参数仍用预设值
|
||||
assert params["contrast"] == 10
|
||||
assert params["saturation"] == 120
|
||||
|
||||
def test_multiple_custom_overrides(self):
|
||||
config = ColorGradeConfig(
|
||||
enabled=True,
|
||||
preset="vintage",
|
||||
brightness=20.0,
|
||||
saturation=150.0,
|
||||
hue=10.0,
|
||||
)
|
||||
params = config.resolve_params()
|
||||
assert params["brightness"] == 20.0
|
||||
assert params["saturation"] == 150.0
|
||||
assert params["hue"] == 10.0
|
||||
# 未覆盖的保留预设值
|
||||
assert params["contrast"] == 5
|
||||
assert params["temperature"] == 25
|
||||
|
||||
def test_custom_without_preset(self):
|
||||
config = ColorGradeConfig(
|
||||
enabled=True,
|
||||
preset="",
|
||||
brightness=30.0,
|
||||
contrast=-20.0,
|
||||
)
|
||||
params = config.resolve_params()
|
||||
assert params["brightness"] == 30.0
|
||||
assert params["contrast"] == -20.0
|
||||
# 未设置的用默认值
|
||||
assert params["saturation"] == 100.0
|
||||
assert params["temperature"] == 0.0
|
||||
assert params["hue"] == 0.0
|
||||
|
||||
def test_clamping_brightness_above_max(self):
|
||||
config = ColorGradeConfig(enabled=True, brightness=200.0)
|
||||
params = config.resolve_params()
|
||||
assert params["brightness"] == 100.0
|
||||
|
||||
def test_clamping_brightness_below_min(self):
|
||||
config = ColorGradeConfig(enabled=True, brightness=-200.0)
|
||||
params = config.resolve_params()
|
||||
assert params["brightness"] == -100.0
|
||||
|
||||
def test_clamping_saturation_below_zero(self):
|
||||
config = ColorGradeConfig(enabled=True, saturation=-10.0)
|
||||
params = config.resolve_params()
|
||||
assert params["saturation"] == 0.0
|
||||
|
||||
def test_clamping_saturation_above_max(self):
|
||||
config = ColorGradeConfig(enabled=True, saturation=300.0)
|
||||
params = config.resolve_params()
|
||||
assert params["saturation"] == 200.0
|
||||
|
||||
def test_clamping_hue_above_max(self):
|
||||
config = ColorGradeConfig(enabled=True, hue=200.0)
|
||||
params = config.resolve_params()
|
||||
assert params["hue"] == 180.0
|
||||
|
||||
def test_clamping_hue_below_min(self):
|
||||
config = ColorGradeConfig(enabled=True, hue=-200.0)
|
||||
params = config.resolve_params()
|
||||
assert params["hue"] == -180.0
|
||||
|
||||
def test_clamping_preset_plus_custom(self):
|
||||
"""预设值+自定义值超出范围时仍会钳制."""
|
||||
config = ColorGradeConfig(
|
||||
enabled=True,
|
||||
preset="fresh",
|
||||
saturation=250.0, # 超出200上限
|
||||
)
|
||||
params = config.resolve_params()
|
||||
assert params["saturation"] == 200.0
|
||||
|
||||
def test_returns_new_dict_each_time(self):
|
||||
config = ColorGradeConfig(enabled=True, brightness=10.0)
|
||||
p1 = config.resolve_params()
|
||||
p2 = config.resolve_params()
|
||||
assert p1 is not p2
|
||||
p1["brightness"] = 999
|
||||
assert p2["brightness"] == 10.0
|
||||
|
||||
|
||||
class TestHasEffect:
|
||||
def test_default_no_effect(self):
|
||||
config = ColorGradeConfig()
|
||||
assert config.has_effect() is False
|
||||
|
||||
def test_enabled_but_all_defaults(self):
|
||||
config = ColorGradeConfig(
|
||||
enabled=True,
|
||||
brightness=0.0,
|
||||
contrast=0.0,
|
||||
saturation=100.0,
|
||||
temperature=0.0,
|
||||
hue=0.0,
|
||||
)
|
||||
assert config.has_effect() is False
|
||||
|
||||
def test_brightness_change_has_effect(self):
|
||||
config = ColorGradeConfig(enabled=True, brightness=1.0)
|
||||
assert config.has_effect() is True
|
||||
|
||||
def test_contrast_change_has_effect(self):
|
||||
config = ColorGradeConfig(enabled=True, contrast=1.0)
|
||||
assert config.has_effect() is True
|
||||
|
||||
def test_saturation_change_has_effect(self):
|
||||
config = ColorGradeConfig(enabled=True, saturation=99.0)
|
||||
assert config.has_effect() is True
|
||||
|
||||
def test_temperature_change_has_effect(self):
|
||||
config = ColorGradeConfig(enabled=True, temperature=1.0)
|
||||
assert config.has_effect() is True
|
||||
|
||||
def test_hue_change_has_effect(self):
|
||||
config = ColorGradeConfig(enabled=True, hue=1.0)
|
||||
assert config.has_effect() is True
|
||||
|
||||
def test_all_presets_have_effect(self):
|
||||
for preset in VALID_PRESETS:
|
||||
config = ColorGradeConfig(enabled=True, preset=preset)
|
||||
assert config.has_effect() is True, f"preset {preset} should have effect"
|
||||
|
||||
def test_very_small_change_no_effect(self):
|
||||
"""小于0.001的浮点误差视为无效果."""
|
||||
config = ColorGradeConfig(enabled=True, brightness=0.0001)
|
||||
assert config.has_effect() is False
|
||||
|
||||
|
||||
class TestFromDict:
|
||||
def test_none_returns_disabled(self):
|
||||
config = ColorGradeConfig.from_dict(None)
|
||||
assert config.enabled is False
|
||||
|
||||
def test_empty_dict_returns_disabled(self):
|
||||
config = ColorGradeConfig.from_dict({})
|
||||
assert config.enabled is False
|
||||
|
||||
def test_enabled_false_returns_disabled(self):
|
||||
config = ColorGradeConfig.from_dict({"enabled": False})
|
||||
assert config.enabled is False
|
||||
|
||||
def test_basic_enabled(self):
|
||||
config = ColorGradeConfig.from_dict({"enabled": True})
|
||||
assert config.enabled is True
|
||||
assert config.preset == ""
|
||||
assert config.brightness is None
|
||||
|
||||
def test_with_preset(self):
|
||||
config = ColorGradeConfig.from_dict({"enabled": True, "preset": "warm"})
|
||||
assert config.enabled is True
|
||||
assert config.preset == "warm"
|
||||
|
||||
def test_invalid_preset_ignored(self):
|
||||
config = ColorGradeConfig.from_dict({"enabled": True, "preset": "invalid"})
|
||||
assert config.enabled is True
|
||||
assert config.preset == ""
|
||||
|
||||
def test_with_custom_params(self):
|
||||
config = ColorGradeConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"brightness": 50,
|
||||
"contrast": -10.5,
|
||||
"saturation": 150.0,
|
||||
"temperature": 20,
|
||||
"hue": -15.5,
|
||||
}
|
||||
)
|
||||
assert config.brightness == 50.0
|
||||
assert config.contrast == -10.5
|
||||
assert config.saturation == 150.0
|
||||
assert config.temperature == 20.0
|
||||
assert config.hue == -15.5
|
||||
|
||||
def test_invalid_float_values_return_none(self):
|
||||
config = ColorGradeConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"brightness": "not_a_number",
|
||||
"contrast": None,
|
||||
}
|
||||
)
|
||||
assert config.brightness is None
|
||||
assert config.contrast is None
|
||||
|
||||
def test_int_values_work(self):
|
||||
config = ColorGradeConfig.from_dict({"enabled": True, "brightness": 10})
|
||||
assert config.brightness == 10.0
|
||||
|
||||
def test_string_float_values_work(self):
|
||||
config = ColorGradeConfig.from_dict({"enabled": True, "brightness": "15.5"})
|
||||
assert config.brightness == 15.5
|
||||
|
||||
def test_partial_custom_params(self):
|
||||
config = ColorGradeConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"preset": "cinema",
|
||||
"brightness": 5.0,
|
||||
}
|
||||
)
|
||||
assert config.preset == "cinema"
|
||||
assert config.brightness == 5.0
|
||||
assert config.contrast is None
|
||||
|
||||
|
||||
class TestValidate:
|
||||
def test_disabled_valid(self):
|
||||
config = ColorGradeConfig(enabled=False)
|
||||
ok, msg = config.validate()
|
||||
assert ok is True
|
||||
assert msg == ""
|
||||
|
||||
def test_valid_config(self):
|
||||
config = ColorGradeConfig(
|
||||
enabled=True,
|
||||
preset="fresh",
|
||||
brightness=50.0,
|
||||
)
|
||||
ok, msg = config.validate()
|
||||
assert ok is True
|
||||
|
||||
def test_invalid_preset(self):
|
||||
config = ColorGradeConfig(enabled=True, preset="invalid")
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "预设" in msg
|
||||
|
||||
def test_brightness_above_max_invalid(self):
|
||||
config = ColorGradeConfig(enabled=True, brightness=150.0)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "brightness" in msg
|
||||
|
||||
def test_brightness_below_min_invalid(self):
|
||||
config = ColorGradeConfig(enabled=True, brightness=-150.0)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "brightness" in msg
|
||||
|
||||
def test_saturation_below_zero_invalid(self):
|
||||
config = ColorGradeConfig(enabled=True, saturation=-10.0)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "saturation" in msg
|
||||
|
||||
def test_saturation_above_max_invalid(self):
|
||||
config = ColorGradeConfig(enabled=True, saturation=250.0)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "saturation" in msg
|
||||
|
||||
def test_hue_above_max_invalid(self):
|
||||
config = ColorGradeConfig(enabled=True, hue=200.0)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "hue" in msg
|
||||
|
||||
def test_hue_below_min_invalid(self):
|
||||
config = ColorGradeConfig(enabled=True, hue=-200.0)
|
||||
ok, msg = config.validate()
|
||||
assert ok is False
|
||||
assert "hue" in msg
|
||||
|
||||
def test_boundary_values_valid(self):
|
||||
config = ColorGradeConfig(
|
||||
enabled=True,
|
||||
brightness=-100.0,
|
||||
contrast=100.0,
|
||||
saturation=0.0,
|
||||
temperature=-100.0,
|
||||
hue=-180.0,
|
||||
)
|
||||
ok, _ = config.validate()
|
||||
assert ok is True
|
||||
|
||||
def test_none_values_valid(self):
|
||||
"""None值不参与校验(视为未设置)."""
|
||||
config = ColorGradeConfig(enabled=True)
|
||||
ok, _ = config.validate()
|
||||
assert ok is True
|
||||
|
||||
|
||||
class TestGetPresetNames:
|
||||
def test_returns_sorted_list(self):
|
||||
names = get_preset_names()
|
||||
assert isinstance(names, list)
|
||||
preset_keys = [n[0] for n in names]
|
||||
assert preset_keys == sorted(preset_keys)
|
||||
|
||||
def test_count_matches_valid_presets(self):
|
||||
names = get_preset_names()
|
||||
assert len(names) == len(VALID_PRESETS)
|
||||
|
||||
def test_each_entry_has_name_and_display(self):
|
||||
names = get_preset_names()
|
||||
for name, display in names:
|
||||
assert name in VALID_PRESETS
|
||||
assert display == PRESET_DISPLAY_NAMES[name]
|
||||
assert len(display) > 0
|
||||
|
||||
|
||||
class TestGetPresetParams:
|
||||
def test_existing_preset(self):
|
||||
params = get_preset_params("fresh")
|
||||
assert params is not None
|
||||
assert params["brightness"] == 8
|
||||
|
||||
def test_nonexistent_preset(self):
|
||||
params = get_preset_params("nonexistent")
|
||||
assert params is None
|
||||
|
||||
def test_all_presets_have_all_params(self):
|
||||
for preset in VALID_PRESETS:
|
||||
params = get_preset_params(preset)
|
||||
assert params is not None
|
||||
for key in ALL_PARAM_KEYS:
|
||||
assert key in params
|
||||
|
||||
def test_returns_dict_with_correct_values(self):
|
||||
"""返回的字典包含所有预期参数."""
|
||||
params = get_preset_params("warm")
|
||||
assert params["brightness"] == 5
|
||||
assert params["temperature"] == 30
|
||||
assert len(params) == 5
|
||||
|
||||
|
||||
class TestClampParam:
|
||||
def test_brightness_within_range(self):
|
||||
assert clamp_param("brightness", 50.0) == 50.0
|
||||
|
||||
def test_brightness_above_max(self):
|
||||
assert clamp_param("brightness", 200.0) == 100.0
|
||||
|
||||
def test_brightness_below_min(self):
|
||||
assert clamp_param("brightness", -200.0) == -100.0
|
||||
|
||||
def test_saturation_within_range(self):
|
||||
assert clamp_param("saturation", 100.0) == 100.0
|
||||
|
||||
def test_saturation_at_boundary(self):
|
||||
assert clamp_param("saturation", 0.0) == 0.0
|
||||
assert clamp_param("saturation", 200.0) == 200.0
|
||||
|
||||
def test_hue_within_range(self):
|
||||
assert clamp_param("hue", 90.0) == 90.0
|
||||
|
||||
def test_hue_above_max(self):
|
||||
assert clamp_param("hue", 200.0) == 180.0
|
||||
|
||||
def test_unknown_param_passthrough(self):
|
||||
assert clamp_param("unknown", 999.0) == 999.0
|
||||
|
||||
|
||||
class TestConstants:
|
||||
def test_all_presets_have_params(self):
|
||||
for preset in VALID_PRESETS:
|
||||
assert preset in PRESET_PARAMS
|
||||
|
||||
def test_all_presets_have_display_names(self):
|
||||
for preset in VALID_PRESETS:
|
||||
assert preset in PRESET_DISPLAY_NAMES
|
||||
|
||||
def test_param_ranges_has_all_keys(self):
|
||||
for key in ALL_PARAM_KEYS:
|
||||
assert key in PARAM_RANGES
|
||||
|
||||
def test_default_params_has_all_keys(self):
|
||||
for key in ALL_PARAM_KEYS:
|
||||
assert key in DEFAULT_PARAMS
|
||||
|
||||
def test_param_ranges_min_less_than_max(self):
|
||||
for key, (min_val, max_val) in PARAM_RANGES.items():
|
||||
assert min_val < max_val, f"{key}: min ({min_val}) should be < max ({max_val})"
|
||||
Reference in New Issue
Block a user