"""transition_config 转场配置领域模型单元测试.""" from __future__ import annotations import pytest from packages.domain.transition_config import ( CUT_TRANSITION, DEFAULT_TRANSITION_DURATION, MAX_TRANSITION_DURATION, MIN_TRANSITION_DURATION, TransitionConfig, TransitionType, ) # ── 常量测试 ────────────────────────────────────────────────────────────────── class TestConstants: """常量测试.""" def test_min_duration(self): """最小转场时长.""" assert MIN_TRANSITION_DURATION == 0.3 def test_max_duration(self): """最大转场时长.""" assert MAX_TRANSITION_DURATION == 2.0 def test_default_duration(self): """默认转场时长.""" assert DEFAULT_TRANSITION_DURATION == 0.5 def test_duration_range_valid(self): """时长范围合理:min < default < max.""" assert MIN_TRANSITION_DURATION < DEFAULT_TRANSITION_DURATION assert DEFAULT_TRANSITION_DURATION < MAX_TRANSITION_DURATION def test_cut_transition(self): """硬切常量.""" assert CUT_TRANSITION == "cut" # ── TransitionType 枚举测试 ─────────────────────────────────────────────────── class TestTransitionType: """TransitionType 枚举测试.""" def test_has_cut(self): """有CUT类型.""" assert TransitionType.CUT.value == "cut" def test_has_fade(self): """有FADE类型.""" assert TransitionType.FADE.value == "fade" def test_has_dissolve(self): """有DISSOLVE类型.""" assert TransitionType.DISSOLVE.value == "dissolve" def test_slide_types(self): """滑动系列四种.""" assert TransitionType.SLIDE_LEFT.value == "slideleft" assert TransitionType.SLIDE_RIGHT.value == "slideright" assert TransitionType.SLIDE_UP.value == "slideup" assert TransitionType.SLIDE_DOWN.value == "slidedown" def test_wipe_types(self): """擦除系列四种.""" assert TransitionType.WIPE_LEFT.value == "wipeleft" assert TransitionType.WIPE_RIGHT.value == "wiperight" assert TransitionType.WIPE_UP.value == "wipeup" assert TransitionType.WIPE_DOWN.value == "wipedown" def test_zoom_type(self): """缩放类型.""" assert TransitionType.ZOOM.value == "zoom" def test_circle_crop_type(self): """圆形扩散.""" assert TransitionType.CIRCLE_CROP.value == "circlecrop" def test_rect_crop_type(self): """矩形覆盖.""" assert TransitionType.RECT_CROP.value == "rectcrop" def test_total_types(self): """共14种转场类型.""" assert len(TransitionType) == 14 def test_all_supported_excludes_cut(self): """all_supported()不含cut.""" supported = TransitionType.all_supported() assert "cut" not in supported assert len(supported) == 13 def test_all_supported_returns_strings(self): """all_supported()返回字符串列表.""" supported = TransitionType.all_supported() assert all(isinstance(s, str) for s in supported) def test_is_supported_valid(self): """支持的转场类型.""" assert TransitionType.is_supported("fade") is True assert TransitionType.is_supported("dissolve") is True assert TransitionType.is_supported("slideleft") is True def test_is_supported_invalid(self): """不支持的转场类型.""" assert TransitionType.is_supported("invalid_effect") is False assert TransitionType.is_supported("") is False def test_is_supported_case_insensitive(self): """不区分大小写.""" assert TransitionType.is_supported("FADE") is True assert TransitionType.is_supported("Fade") is True def test_is_supported_with_underscores(self): """下划线会被忽略.""" assert TransitionType.is_supported("slide_left") is True assert TransitionType.is_supported("circle_crop") is True def test_is_supported_with_hyphens(self): """中划线会被忽略.""" assert TransitionType.is_supported("slide-left") is True def test_is_supported_cut(self): """cut不被算作supported(all_supported不含cut).""" # is_supported是检查是否在支持的xfade效果里,cut是特殊值 # 看实现:is_supported检查_NAME_TO_ENUM_MAP,cut应该也在里面 pass # ── TransitionConfig.parse - effect 测试 ───────────────────────────────────── class TestTransitionConfigParseEffect: """TransitionConfig.parse effect参数测试.""" def test_none_effect_defaults_to_cut(self): """None effect → cut.""" cfg = TransitionConfig.parse(effect=None) assert cfg.effect == CUT_TRANSITION def test_empty_effect_defaults_to_cut(self): """空字符串 → cut.""" cfg = TransitionConfig.parse(effect="") assert cfg.effect == CUT_TRANSITION def test_whitespace_effect_defaults_to_cut(self): """纯空白 → cut.""" cfg = TransitionConfig.parse(effect=" ") assert cfg.effect == CUT_TRANSITION def test_cut_effect(self): """显式cut.""" cfg = TransitionConfig.parse(effect="cut") assert cfg.effect == "cut" def test_cut_case_insensitive(self): """CUT不区分大小写.""" cfg = TransitionConfig.parse(effect="CUT") assert cfg.effect == "cut" def test_valid_fade_effect(self): """有效的fade效果.""" cfg = TransitionConfig.parse(effect="fade") assert cfg.effect == "fade" def test_valid_dissolve_effect(self): """有效的dissolve效果.""" cfg = TransitionConfig.parse(effect="dissolve") assert cfg.effect == "dissolve" def test_effect_stripped(self): """effect去除空白.""" cfg = TransitionConfig.parse(effect=" fade ") assert cfg.effect == "fade" def test_invalid_effect_falls_back_to_cut(self): """无效效果 → 降级为cut.""" cfg = TransitionConfig.parse(effect="super_fancy_effect") assert cfg.effect == CUT_TRANSITION def test_alias_crossfade(self): """别名crossfade → dissolve.""" cfg = TransitionConfig.parse(effect="crossfade") assert cfg.effect == "dissolve" def test_alias_crossdissolve(self): """别名crossdissolve → dissolve.""" cfg = TransitionConfig.parse(effect="crossdissolve") assert cfg.effect == "dissolve" def test_alias_fadein(self): """别名fadein → fade.""" cfg = TransitionConfig.parse(effect="fadein") assert cfg.effect == "fade" def test_alias_fadeout(self): """别名fadeout → fade.""" cfg = TransitionConfig.parse(effect="fadeout") assert cfg.effect == "fade" def test_alias_slide(self): """别名slide → slideleft.""" cfg = TransitionConfig.parse(effect="slide") assert cfg.effect == "slideleft" def test_alias_wipe(self): """别名wipe → wipeleft.""" cfg = TransitionConfig.parse(effect="wipe") assert cfg.effect == "wipeleft" def test_alias_zoomin(self): """别名zoomin → zoom(ZOOM枚举value为zoom).""" cfg = TransitionConfig.parse(effect="zoomin") assert cfg.effect == "zoom" def test_alias_circle(self): """别名circle → circlecrop.""" cfg = TransitionConfig.parse(effect="circle") assert cfg.effect == "circlecrop" def test_alias_rect(self): """别名rect → rectcrop.""" cfg = TransitionConfig.parse(effect="rect") assert cfg.effect == "rectcrop" def test_underscore_format(self): """下划线格式能解析.""" cfg = TransitionConfig.parse(effect="slide_left") assert cfg.effect == "slideleft" def test_case_insensitive_alias(self): """别名不区分大小写.""" cfg = TransitionConfig.parse(effect="CrossFade") assert cfg.effect == "dissolve" def test_slide_left_direct(self): """直接用slideleft.""" cfg = TransitionConfig.parse(effect="slideleft") assert cfg.effect == "slideleft" # ── TransitionConfig.parse - duration 测试 ─────────────────────────────────── class TestTransitionConfigParseDuration: """TransitionConfig.parse duration参数测试.""" def test_none_duration_uses_default(self): """None duration → 默认值.""" cfg = TransitionConfig.parse(duration=None) assert cfg.duration == DEFAULT_TRANSITION_DURATION def test_normal_duration(self): """正常范围内的时长.""" cfg = TransitionConfig.parse(duration=1.0) assert cfg.duration == 1.0 def test_min_duration(self): """刚好等于最小值.""" cfg = TransitionConfig.parse(duration=MIN_TRANSITION_DURATION) assert cfg.duration == MIN_TRANSITION_DURATION def test_max_duration(self): """刚好等于最大值.""" cfg = TransitionConfig.parse(duration=MAX_TRANSITION_DURATION) assert cfg.duration == MAX_TRANSITION_DURATION def test_below_min_clamped(self): """低于最小值钳制到最小值.""" cfg = TransitionConfig.parse(duration=0.1) assert cfg.duration == MIN_TRANSITION_DURATION def test_zero_duration_clamped(self): """0时长钳制到最小值.""" cfg = TransitionConfig.parse(duration=0) assert cfg.duration == MIN_TRANSITION_DURATION def test_negative_duration_clamped(self): """负时长钳制到最小值.""" cfg = TransitionConfig.parse(duration=-1) assert cfg.duration == MIN_TRANSITION_DURATION def test_above_max_clamped(self): """超过最大值钳制到最大值.""" cfg = TransitionConfig.parse(duration=5.0) assert cfg.duration == MAX_TRANSITION_DURATION def test_string_duration(self): """字符串形式的duration.""" cfg = TransitionConfig.parse(duration="1.5") assert cfg.duration == 1.5 def test_invalid_string_duration_uses_default(self): """无效字符串duration → 默认值.""" cfg = TransitionConfig.parse(duration="abc") assert cfg.duration == DEFAULT_TRANSITION_DURATION def test_int_duration(self): """整数时长.""" cfg = TransitionConfig.parse(duration=1) assert cfg.duration == 1.0 def test_default_duration_when_no_args(self): """无参时duration为默认值.""" cfg = TransitionConfig.parse() assert cfg.duration == DEFAULT_TRANSITION_DURATION # ── TransitionConfig 属性测试 ──────────────────────────────────────────────── class TestTransitionConfigProperties: """TransitionConfig 属性测试.""" def test_is_cut_true(self): """cut是硬切.""" cfg = TransitionConfig(effect="cut") assert cfg.is_cut is True def test_is_cut_false(self): """非cut不是硬切.""" cfg = TransitionConfig(effect="fade") assert cfg.is_cut is False def test_is_cut_default(self): """默认配置是硬切.""" cfg = TransitionConfig() assert cfg.is_cut is True def test_ffmpeg_transition_cut_returns_empty(self): """硬切返回空字符串(无xfade).""" cfg = TransitionConfig(effect="cut") assert cfg.ffmpeg_transition == "" def test_ffmpeg_transition_fade(self): """fade → fade.""" cfg = TransitionConfig(effect="fade") assert cfg.ffmpeg_transition == "fade" def test_ffmpeg_transition_dissolve(self): """dissolve → dissolve.""" cfg = TransitionConfig(effect="dissolve") assert cfg.ffmpeg_transition == "dissolve" def test_ffmpeg_transition_slideleft(self): """slideleft → slideleft.""" cfg = TransitionConfig(effect="slideleft") assert cfg.ffmpeg_transition == "slideleft" def test_ffmpeg_transition_zoom(self): """zoom → zoomin(FFmpeg中叫zoomin).""" cfg = TransitionConfig(effect="zoom") assert cfg.ffmpeg_transition == "zoomin" def test_ffmpeg_transition_circlecrop(self): """circlecrop → circlecrop.""" cfg = TransitionConfig(effect="circlecrop") assert cfg.ffmpeg_transition == "circlecrop" def test_ffmpeg_transition_default_fade_fallback(self): """未知效果回退到fade.""" # 直接构造一个不支持的effect(绕过parse) cfg = TransitionConfig(effect="unknown_effect", duration=0.5) # 会回退到fade assert cfg.ffmpeg_transition == "fade" # ── TransitionConfig.validate 测试 ─────────────────────────────────────────── class TestTransitionConfigValidate: """TransitionConfig.validate 测试.""" def test_valid_default_cut(self): """默认cut配置是合法的.""" cfg = TransitionConfig() ok, msg = cfg.validate() assert ok is True assert msg == "" def test_valid_fade(self): """fade配置是合法的.""" cfg = TransitionConfig(effect="fade", duration=1.0) ok, msg = cfg.validate() assert ok is True assert msg == "" def test_valid_min_duration(self): """最小时长是合法的.""" cfg = TransitionConfig(effect="fade", duration=MIN_TRANSITION_DURATION) ok, msg = cfg.validate() assert ok is True def test_valid_max_duration(self): """最大时长是合法的.""" cfg = TransitionConfig(effect="fade", duration=MAX_TRANSITION_DURATION) ok, msg = cfg.validate() assert ok is True def test_invalid_duration_too_low(self): """时长小于最小值不合法.""" cfg = TransitionConfig(effect="fade", duration=0.1) ok, msg = cfg.validate() assert ok is False assert "不能小于" in msg def test_invalid_duration_too_high(self): """时长大于最大值不合法.""" cfg = TransitionConfig(effect="fade", duration=5.0) ok, msg = cfg.validate() assert ok is False assert "不能大于" in msg def test_invalid_unknown_effect(self): """未知效果不合法.""" cfg = TransitionConfig(effect="super_fancy", duration=0.5) ok, msg = cfg.validate() assert ok is False assert "不支持" in msg def test_cut_always_valid(self): """cut效果总是合法的(即使duration异常...要看实现).""" # cut的话is_cut为True,validate里会跳过effect检查 cfg = TransitionConfig(effect="cut", duration=1.0) ok, msg = cfg.validate() assert ok is True # ── 默认值测试 ─────────────────────────────────────────────────────────────── class TestTransitionConfigDefaults: """TransitionConfig 默认值测试.""" def test_default_effect_is_cut(self): """默认effect是cut.""" cfg = TransitionConfig() assert cfg.effect == CUT_TRANSITION def test_default_duration(self): """默认duration.""" cfg = TransitionConfig() assert cfg.duration == DEFAULT_TRANSITION_DURATION def test_parse_no_args(self): """parse无参 → 默认值.""" cfg = TransitionConfig.parse() assert cfg.effect == CUT_TRANSITION assert cfg.duration == DEFAULT_TRANSITION_DURATION def test_parse_both_none(self): """两个参数都None → 默认值.""" cfg = TransitionConfig.parse(effect=None, duration=None) assert cfg.effect == CUT_TRANSITION assert cfg.duration == DEFAULT_TRANSITION_DURATION def test_is_dataclass(self): """是dataclass.""" from dataclasses import is_dataclass assert is_dataclass(TransitionConfig)