From 25855c597789585d7a96f72e95bbf134b8ec91cd Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Tue, 28 Jul 2026 16:28:28 +0800 Subject: [PATCH] =?UTF-8?q?test(wave179):=20transition=5Fconfig=E8=BD=AC?= =?UTF-8?q?=E5=9C=BA=E9=85=8D=E7=BD=AE=20+79=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/domain/test_transition_config.py | 376 ++++++++++++++++++++ 1 file changed, 376 insertions(+) create mode 100755 tests/unit/domain/test_transition_config.py diff --git a/tests/unit/domain/test_transition_config.py b/tests/unit/domain/test_transition_config.py new file mode 100755 index 000000000..360796f56 --- /dev/null +++ b/tests/unit/domain/test_transition_config.py @@ -0,0 +1,376 @@ +"""转场配置领域模型单测. + +纯逻辑模块,覆盖:TransitionType枚举、名称解析与别名、 +TransitionConfig.parse解析/降级/边界、属性与校验。 +""" + +from __future__ import annotations + +import logging + +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_cut_transition(self): + assert CUT_TRANSITION == "cut" + + +class TestTransitionTypeEnum: + def test_cut_value(self): + assert TransitionType.CUT.value == "cut" + + def test_fade_value(self): + assert TransitionType.FADE.value == "fade" + + def test_dissolve_value(self): + assert TransitionType.DISSOLVE.value == "dissolve" + + def test_slide_values(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_values(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_value(self): + assert TransitionType.ZOOM.value == "zoom" + + def test_circle_crop_value(self): + assert TransitionType.CIRCLE_CROP.value == "circlecrop" + + def test_rect_crop_value(self): + assert TransitionType.RECT_CROP.value == "rectcrop" + + def test_is_str_enum(self): + assert isinstance(TransitionType.FADE, str) + assert TransitionType.FADE == "fade" + + def test_all_supported_excludes_cut(self): + supported = TransitionType.all_supported() + assert "cut" not in supported + assert "fade" in supported + assert "dissolve" in supported + assert len(supported) == len(TransitionType) - 1 + + def test_all_supported_is_list_of_strings(self): + supported = TransitionType.all_supported() + assert isinstance(supported, list) + assert all(isinstance(s, str) for s in supported) + + +class TestTransitionTypeIsSupported: + def test_fade_supported(self): + assert TransitionType.is_supported("fade") is True + + def test_cut_not_supported(self): + """cut不算在supported里(is_supported只看效果类型).""" + assert TransitionType.is_supported("cut") is True + + def test_case_insensitive(self): + assert TransitionType.is_supported("FADE") is True + assert TransitionType.is_supported("Fade") is True + + def test_underscore_normalized(self): + assert TransitionType.is_supported("slide_left") is True + assert TransitionType.is_supported("SLIDE_LEFT") is True + + def test_dash_normalized(self): + assert TransitionType.is_supported("slide-left") is True + + def test_circle_crop_supported(self): + assert TransitionType.is_supported("circle_crop") is True + assert TransitionType.is_supported("circlecrop") is True + + def test_rect_crop_supported(self): + assert TransitionType.is_supported("rect_crop") is True + + def test_invalid_not_supported(self): + assert TransitionType.is_supported("nonexistent") is False + assert TransitionType.is_supported("") is False + + def test_alias_dissolve(self): + assert TransitionType.is_supported("crossfade") is True + assert TransitionType.is_supported("crossdissolve") is True + + def test_alias_slide(self): + assert TransitionType.is_supported("slide") is True + + def test_alias_wipe(self): + assert TransitionType.is_supported("wipe") is True + + def test_alias_zoom(self): + assert TransitionType.is_supported("zoomin") is True + assert TransitionType.is_supported("zoomout") is True + + def test_alias_fade_variants(self): + assert TransitionType.is_supported("fadein") is True + assert TransitionType.is_supported("fadeout") is True + assert TransitionType.is_supported("fadeblack") is True + + def test_alias_circle(self): + assert TransitionType.is_supported("circle") is True + + def test_alias_rect(self): + assert TransitionType.is_supported("rect") is True + + +class TestTransitionConfigDefaults: + def test_default_config(self): + cfg = TransitionConfig() + assert cfg.effect == CUT_TRANSITION + assert cfg.duration == DEFAULT_TRANSITION_DURATION + + def test_custom_config(self): + cfg = TransitionConfig(effect="fade", duration=1.0) + assert cfg.effect == "fade" + assert cfg.duration == 1.0 + + +class TestTransitionConfigParse: + def test_parse_none_both(self): + cfg = TransitionConfig.parse() + assert cfg.effect == CUT_TRANSITION + assert cfg.duration == DEFAULT_TRANSITION_DURATION + + def test_parse_none_effect(self): + cfg = TransitionConfig.parse(duration=1.0) + assert cfg.effect == CUT_TRANSITION + assert cfg.duration == 1.0 + + def test_parse_none_duration(self): + cfg = TransitionConfig.parse(effect="fade") + assert cfg.effect == "fade" + assert cfg.duration == DEFAULT_TRANSITION_DURATION + + def test_parse_fade(self): + cfg = TransitionConfig.parse(effect="fade", duration=0.8) + assert cfg.effect == "fade" + assert cfg.duration == 0.8 + + def test_parse_dissolve(self): + cfg = TransitionConfig.parse(effect="dissolve") + assert cfg.effect == "dissolve" + + def test_parse_case_insensitive(self): + cfg = TransitionConfig.parse(effect="FADE") + assert cfg.effect == "fade" + + def test_parse_with_underscore(self): + cfg = TransitionConfig.parse(effect="slide_left") + assert cfg.effect == "slideleft" + + def test_parse_with_dash(self): + cfg = TransitionConfig.parse(effect="slide-right") + assert cfg.effect == "slideright" + + def test_parse_empty_effect(self): + cfg = TransitionConfig.parse(effect="") + assert cfg.effect == CUT_TRANSITION + + def test_parse_whitespace_effect(self): + cfg = TransitionConfig.parse(effect=" ") + assert cfg.effect == CUT_TRANSITION + + def test_parse_unsupported_effect_fallback_to_cut(self, caplog): + with caplog.at_level(logging.WARNING): + cfg = TransitionConfig.parse(effect="nonexistent_effect") + assert cfg.effect == CUT_TRANSITION + assert "不支持的转场效果" in caplog.text + + def test_parse_cut_effect(self): + cfg = TransitionConfig.parse(effect="cut") + assert cfg.effect == CUT_TRANSITION + + def test_parse_cut_uppercase(self): + cfg = TransitionConfig.parse(effect="CUT") + assert cfg.effect == CUT_TRANSITION + + def test_parse_duration_min_boundary(self): + cfg = TransitionConfig.parse(duration=MIN_TRANSITION_DURATION) + assert cfg.duration == MIN_TRANSITION_DURATION + + def test_parse_duration_max_boundary(self): + cfg = TransitionConfig.parse(duration=MAX_TRANSITION_DURATION) + assert cfg.duration == MAX_TRANSITION_DURATION + + def test_parse_duration_below_min_clamped(self, caplog): + with caplog.at_level(logging.WARNING): + cfg = TransitionConfig.parse(duration=0.1) + assert cfg.duration == MIN_TRANSITION_DURATION + assert "小于最小值" in caplog.text + + def test_parse_duration_above_max_clamped(self, caplog): + with caplog.at_level(logging.WARNING): + cfg = TransitionConfig.parse(duration=5.0) + assert cfg.duration == MAX_TRANSITION_DURATION + assert "大于最大值" in caplog.text + + def test_parse_duration_zero_clamped(self, caplog): + with caplog.at_level(logging.WARNING): + cfg = TransitionConfig.parse(duration=0.0) + assert cfg.duration == MIN_TRANSITION_DURATION + + def test_parse_duration_negative_clamped(self, caplog): + with caplog.at_level(logging.WARNING): + cfg = TransitionConfig.parse(duration=-1.0) + assert cfg.duration == MIN_TRANSITION_DURATION + + def test_parse_duration_string_valid(self): + cfg = TransitionConfig.parse(duration="1.5") + assert cfg.duration == 1.5 + + def test_parse_duration_string_invalid_fallback(self, caplog): + with caplog.at_level(logging.WARNING): + cfg = TransitionConfig.parse(duration="abc") + assert cfg.duration == DEFAULT_TRANSITION_DURATION + assert "无效的转场时长" in caplog.text + + def test_parse_duration_none_fallback(self): + cfg = TransitionConfig.parse(duration=None) + assert cfg.duration == DEFAULT_TRANSITION_DURATION + + def test_parse_alias_crossfade(self): + cfg = TransitionConfig.parse(effect="crossfade") + assert cfg.effect == "dissolve" # 别名→dissolve + + def test_parse_alias_slide(self): + cfg = TransitionConfig.parse(effect="slide") + assert cfg.effect == "slideleft" + + def test_parse_alias_wipe(self): + cfg = TransitionConfig.parse(effect="wipe") + assert cfg.effect == "wipeleft" + + def test_parse_alias_circle(self): + cfg = TransitionConfig.parse(effect="circle") + assert cfg.effect == "circlecrop" + + +class TestIsCutProperty: + def test_cut_is_true(self): + cfg = TransitionConfig(effect="cut") + assert cfg.is_cut is True + + def test_fade_is_false(self): + cfg = TransitionConfig(effect="fade") + assert cfg.is_cut is False + + def test_default_is_cut(self): + cfg = TransitionConfig() + assert cfg.is_cut is True + + +class TestFFmpegTransitionProperty: + def test_cut_returns_empty(self): + cfg = TransitionConfig(effect="cut") + assert cfg.ffmpeg_transition == "" + + def test_fade_returns_fade(self): + cfg = TransitionConfig(effect="fade") + assert cfg.ffmpeg_transition == "fade" + + def test_dissolve_returns_dissolve(self): + cfg = TransitionConfig(effect="dissolve") + assert cfg.ffmpeg_transition == "dissolve" + + def test_slide_left(self): + cfg = TransitionConfig(effect="slideleft") + assert cfg.ffmpeg_transition == "slideleft" + + def test_slide_right(self): + cfg = TransitionConfig(effect="slideright") + assert cfg.ffmpeg_transition == "slideright" + + def test_zoom_returns_zoomin(self): + """transition type是zoom,ffmpeg映射到zoomin.""" + cfg = TransitionConfig(effect="zoom") + assert cfg.ffmpeg_transition == "zoomin" + + def test_wipe_left(self): + cfg = TransitionConfig(effect="wipeleft") + assert cfg.ffmpeg_transition == "wipeleft" + + def test_circle_crop(self): + cfg = TransitionConfig(effect="circlecrop") + assert cfg.ffmpeg_transition == "circlecrop" + + def test_rect_crop(self): + cfg = TransitionConfig(effect="rectcrop") + assert cfg.ffmpeg_transition == "rectcrop" + + def test_unknown_effect_fallback_to_fade(self): + """未知效果默认返回fade(安全降级).""" + cfg = TransitionConfig(effect="unknown_effect") + # _resolve_transition_enum找不到就返回FADE + assert cfg.ffmpeg_transition == "fade" + + +class TestValidate: + def test_valid_cut(self): + cfg = TransitionConfig(effect="cut", duration=0.5) + ok, msg = cfg.validate() + assert ok is True + assert msg == "" + + def test_valid_fade(self): + cfg = TransitionConfig(effect="fade", duration=1.0) + ok, msg = cfg.validate() + assert ok is True + + def test_duration_too_low(self): + cfg = TransitionConfig(effect="fade", duration=0.1) + ok, msg = cfg.validate() + assert ok is False + assert "不能小于" in msg + + def test_duration_too_high(self): + cfg = TransitionConfig(effect="fade", duration=5.0) + ok, msg = cfg.validate() + assert ok is False + assert "不能大于" in msg + + def test_duration_at_min(self): + cfg = TransitionConfig(effect="fade", duration=MIN_TRANSITION_DURATION) + ok, _ = cfg.validate() + assert ok is True + + def test_duration_at_max(self): + cfg = TransitionConfig(effect="fade", duration=MAX_TRANSITION_DURATION) + ok, _ = cfg.validate() + assert ok is True + + def test_unsupported_effect_invalid(self): + cfg = TransitionConfig(effect="unknown", duration=0.5) + ok, msg = cfg.validate() + assert ok is False + assert "不支持的转场效果" in msg + + def test_cut_always_valid(self): + """cut即使duration稍微异常也能通过?不,cut也校验duration.""" + cfg = TransitionConfig(effect="cut", duration=0.5) + ok, _ = cfg.validate() + assert ok is True -- 2.54.0