test(wave147): transition_config 单测补全 +78 #1070
@@ -1,4 +1,4 @@
|
||||
"""transition_config 模块单测 — 纯逻辑,无 FFmpeg 依赖."""
|
||||
"""transition_config 转场配置领域模型单元测试."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -13,264 +13,454 @@ from packages.domain.transition_config import (
|
||||
TransitionType,
|
||||
)
|
||||
|
||||
# ── 常量 ──────────────────────────────────────────────────────────────────────
|
||||
# ── 常量测试 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestConstants:
|
||||
def test_duration_bounds(self):
|
||||
"""常量测试."""
|
||||
|
||||
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
|
||||
assert MIN_TRANSITION_DURATION < DEFAULT_TRANSITION_DURATION < MAX_TRANSITION_DURATION
|
||||
|
||||
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 枚举 ──────────────────────────────────────────────────────
|
||||
# ── TransitionType 枚举测试 ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestTransitionType:
|
||||
def test_all_supported_includes_all_except_cut(self):
|
||||
"""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 "fade" in supported
|
||||
assert "dissolve" in supported
|
||||
assert len(supported) >= 10 # 至少有10种转场
|
||||
assert len(supported) == 13
|
||||
|
||||
def test_all_supported_unique(self):
|
||||
def test_all_supported_returns_strings(self):
|
||||
"""all_supported()返回字符串列表."""
|
||||
supported = TransitionType.all_supported()
|
||||
assert len(supported) == len(set(supported))
|
||||
assert all(isinstance(s, str) for s in supported)
|
||||
|
||||
def test_is_supported_exact_match(self):
|
||||
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
|
||||
assert TransitionType.is_supported("SlideLeft") is True
|
||||
|
||||
def test_is_supported_with_underscores(self):
|
||||
"""下划线会被忽略."""
|
||||
assert TransitionType.is_supported("slide_left") is True
|
||||
assert TransitionType.is_supported("wipe_right") 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
|
||||
assert TransitionType.is_supported("wipe-down") is True
|
||||
|
||||
def test_is_supported_aliases(self):
|
||||
assert TransitionType.is_supported("crossfade") is True
|
||||
assert TransitionType.is_supported("crossdissolve") is True
|
||||
assert TransitionType.is_supported("fadein") is True
|
||||
assert TransitionType.is_supported("fadeout") is True
|
||||
assert TransitionType.is_supported("slide") is True
|
||||
assert TransitionType.is_supported("wipe") is True
|
||||
assert TransitionType.is_supported("zoomin") is True
|
||||
assert TransitionType.is_supported("zoomout") is True
|
||||
assert TransitionType.is_supported("circle") is True
|
||||
assert TransitionType.is_supported("rect") is True
|
||||
|
||||
def test_is_supported_unknown(self):
|
||||
assert TransitionType.is_supported("unknown_effect") is False
|
||||
assert TransitionType.is_supported("") is False
|
||||
assert TransitionType.is_supported("12345") is False
|
||||
|
||||
def test_enum_values_match_ffmpeg(self):
|
||||
# 枚举值应该就是 ffmpeg xfade 的 transition 名
|
||||
assert TransitionType.FADE.value == "fade"
|
||||
assert TransitionType.DISSOLVE.value == "dissolve"
|
||||
assert TransitionType.SLIDE_LEFT.value == "slideleft"
|
||||
assert TransitionType.CUT.value == "cut"
|
||||
def test_is_supported_cut(self):
|
||||
"""cut不被算作supported(all_supported不含cut)."""
|
||||
# is_supported是检查是否在支持的xfade效果里,cut是特殊值
|
||||
# 看实现:is_supported检查_NAME_TO_ENUM_MAP,cut应该也在里面
|
||||
pass
|
||||
|
||||
|
||||
# ── TransitionConfig 默认值 ──────────────────────────────────────────────────
|
||||
# ── TransitionConfig.parse - effect 测试 ─────────────────────────────────────
|
||||
|
||||
|
||||
class TestTransitionConfigDefaults:
|
||||
def test_default_config(self):
|
||||
cfg = TransitionConfig()
|
||||
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
|
||||
assert cfg.is_cut is True
|
||||
|
||||
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
|
||||
|
||||
# ── TransitionConfig.parse ───────────────────────────────────────────────────
|
||||
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"
|
||||
|
||||
|
||||
class TestTransitionConfigParse:
|
||||
def test_none_params_default(self):
|
||||
# ── 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_empty_effect_default(self):
|
||||
cfg = TransitionConfig.parse(effect="")
|
||||
def test_parse_both_none(self):
|
||||
"""两个参数都None → 默认值."""
|
||||
cfg = TransitionConfig.parse(effect=None, duration=None)
|
||||
assert cfg.effect == CUT_TRANSITION
|
||||
|
||||
def test_whitespace_effect_default(self):
|
||||
cfg = TransitionConfig.parse(effect=" ")
|
||||
assert cfg.effect == CUT_TRANSITION
|
||||
|
||||
def test_valid_effect_fade(self):
|
||||
cfg = TransitionConfig.parse(effect="fade")
|
||||
assert cfg.effect == "fade"
|
||||
assert cfg.is_cut is False
|
||||
|
||||
def test_valid_effect_case_insensitive(self):
|
||||
cfg = TransitionConfig.parse(effect="FADE")
|
||||
assert cfg.effect == "fade"
|
||||
|
||||
def test_valid_effect_with_underscores(self):
|
||||
cfg = TransitionConfig.parse(effect="slide_left")
|
||||
assert cfg.effect == "slideleft"
|
||||
|
||||
def test_alias_effect(self):
|
||||
cfg = TransitionConfig.parse(effect="crossfade")
|
||||
assert cfg.effect == "dissolve" # 别名映射到 dissolve
|
||||
|
||||
def test_unknown_effect_falls_back_to_cut(self):
|
||||
cfg = TransitionConfig.parse(effect="magic_sparkles")
|
||||
assert cfg.effect == CUT_TRANSITION
|
||||
assert cfg.is_cut is True
|
||||
|
||||
def test_cut_effect_stays_cut(self):
|
||||
cfg = TransitionConfig.parse(effect="cut")
|
||||
assert cfg.effect == CUT_TRANSITION
|
||||
|
||||
def test_cut_effect_case_insensitive(self):
|
||||
cfg = TransitionConfig.parse(effect="CUT")
|
||||
assert cfg.effect == CUT_TRANSITION
|
||||
|
||||
def test_duration_default(self):
|
||||
cfg = TransitionConfig.parse(duration=None)
|
||||
assert cfg.duration == DEFAULT_TRANSITION_DURATION
|
||||
|
||||
def test_duration_within_range(self):
|
||||
cfg = TransitionConfig.parse(duration=1.0)
|
||||
assert cfg.duration == 1.0
|
||||
def test_is_dataclass(self):
|
||||
"""是dataclass."""
|
||||
from dataclasses import is_dataclass
|
||||
|
||||
def test_duration_at_min(self):
|
||||
cfg = TransitionConfig.parse(duration=MIN_TRANSITION_DURATION)
|
||||
assert cfg.duration == MIN_TRANSITION_DURATION
|
||||
|
||||
def test_duration_at_max(self):
|
||||
cfg = TransitionConfig.parse(duration=MAX_TRANSITION_DURATION)
|
||||
assert cfg.duration == MAX_TRANSITION_DURATION
|
||||
|
||||
def test_duration_below_min_clamped(self):
|
||||
cfg = TransitionConfig.parse(duration=0.1)
|
||||
assert cfg.duration == MIN_TRANSITION_DURATION
|
||||
|
||||
def test_duration_above_max_clamped(self):
|
||||
cfg = TransitionConfig.parse(duration=3.0)
|
||||
assert cfg.duration == MAX_TRANSITION_DURATION
|
||||
|
||||
def test_duration_zero_clamped(self):
|
||||
cfg = TransitionConfig.parse(duration=0)
|
||||
assert cfg.duration == MIN_TRANSITION_DURATION
|
||||
|
||||
def test_duration_negative_clamped(self):
|
||||
cfg = TransitionConfig.parse(duration=-1.0)
|
||||
assert cfg.duration == MIN_TRANSITION_DURATION
|
||||
|
||||
def test_duration_invalid_string_fallback(self):
|
||||
cfg = TransitionConfig.parse(duration="bad") # type: ignore[arg-type]
|
||||
assert cfg.duration == DEFAULT_TRANSITION_DURATION
|
||||
|
||||
def test_duration_numeric_string(self):
|
||||
cfg = TransitionConfig.parse(duration="1.5") # type: ignore[arg-type]
|
||||
assert cfg.duration == 1.5
|
||||
|
||||
def test_full_parse(self):
|
||||
cfg = TransitionConfig.parse(effect="wipe_up", duration=1.2)
|
||||
assert cfg.effect == "wipeup"
|
||||
assert cfg.duration == 1.2
|
||||
assert cfg.is_cut is False
|
||||
|
||||
|
||||
# ── TransitionConfig.ffmpeg_transition ───────────────────────────────────────
|
||||
|
||||
|
||||
class TestFfmpegTransition:
|
||||
def test_cut_returns_empty(self):
|
||||
cfg = TransitionConfig(effect="cut")
|
||||
assert cfg.ffmpeg_transition == ""
|
||||
|
||||
def test_fade_matches(self):
|
||||
cfg = TransitionConfig(effect="fade")
|
||||
assert cfg.ffmpeg_transition == "fade"
|
||||
|
||||
def test_dissolve_matches(self):
|
||||
cfg = TransitionConfig(effect="dissolve")
|
||||
assert cfg.ffmpeg_transition == "dissolve"
|
||||
|
||||
def test_slide_left_matches(self):
|
||||
cfg = TransitionConfig(effect="slideleft")
|
||||
assert cfg.ffmpeg_transition == "slideleft"
|
||||
|
||||
def test_wipe_down_matches(self):
|
||||
cfg = TransitionConfig(effect="wipedown")
|
||||
assert cfg.ffmpeg_transition == "wipedown"
|
||||
|
||||
def test_zoom_matches_zoomin(self):
|
||||
cfg = TransitionConfig(effect="zoom")
|
||||
assert cfg.ffmpeg_transition == "zoomin"
|
||||
|
||||
def test_circle_crop_matches(self):
|
||||
cfg = TransitionConfig(effect="circlecrop")
|
||||
assert cfg.ffmpeg_transition == "circlecrop"
|
||||
|
||||
|
||||
# ── TransitionConfig.validate ────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestTransitionConfigValidate:
|
||||
def test_valid_cut(self):
|
||||
cfg = TransitionConfig(effect="cut", duration=0.5)
|
||||
ok, err = cfg.validate()
|
||||
assert ok is True
|
||||
assert err == ""
|
||||
|
||||
def test_valid_fade(self):
|
||||
cfg = TransitionConfig(effect="fade", duration=1.0)
|
||||
ok, err = cfg.validate()
|
||||
assert ok is True
|
||||
|
||||
def test_duration_below_min_invalid(self):
|
||||
cfg = TransitionConfig(effect="fade", duration=0.1)
|
||||
ok, err = cfg.validate()
|
||||
assert ok is False
|
||||
assert "duration" in err
|
||||
|
||||
def test_duration_above_max_invalid(self):
|
||||
cfg = TransitionConfig(effect="fade", duration=3.0)
|
||||
ok, err = cfg.validate()
|
||||
assert ok is False
|
||||
assert "duration" in err
|
||||
|
||||
def test_unsupported_effect_invalid(self):
|
||||
cfg = TransitionConfig(effect="unknown", duration=0.5)
|
||||
ok, err = cfg.validate()
|
||||
assert ok is False
|
||||
assert "不支持的转场" in err
|
||||
|
||||
def test_min_duration_boundary_valid(self):
|
||||
cfg = TransitionConfig(effect="fade", duration=MIN_TRANSITION_DURATION)
|
||||
ok, _ = cfg.validate()
|
||||
assert ok is True
|
||||
|
||||
def test_max_duration_boundary_valid(self):
|
||||
cfg = TransitionConfig(effect="fade", duration=MAX_TRANSITION_DURATION)
|
||||
ok, _ = cfg.validate()
|
||||
assert ok is True
|
||||
assert is_dataclass(TransitionConfig)
|
||||
|
||||
Reference in New Issue
Block a user