test(wave142): transition_presets 单测补全 +65 #1065
@@ -1,6 +1,6 @@
|
||||
from dataclasses import FrozenInstanceError
|
||||
"""转场预设库单元测试."""
|
||||
|
||||
"""transition_presets 领域层单元测试 - 转场预设库"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -12,198 +12,384 @@ from packages.domain.transition_presets import (
|
||||
list_transition_presets,
|
||||
)
|
||||
|
||||
# ── 数据类测试 ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestTransitionPreset:
|
||||
"""TransitionPreset 数据类测试"""
|
||||
"""TransitionPreset 数据类测试."""
|
||||
|
||||
def test_create_minimal(self):
|
||||
preset = TransitionPreset(id="test", name="测试", category="basic", transition="fade")
|
||||
assert preset.id == "test"
|
||||
assert preset.name == "测试"
|
||||
assert preset.category == "basic"
|
||||
assert preset.transition == "fade"
|
||||
assert preset.description == ""
|
||||
assert preset.tags == []
|
||||
assert preset.default_duration == 0.5
|
||||
assert preset.min_duration == 0.1
|
||||
assert preset.max_duration == 3.0
|
||||
assert preset.has_custom_params is False
|
||||
|
||||
def test_create_with_all_params(self):
|
||||
preset = TransitionPreset(
|
||||
id="custom",
|
||||
name="自定义转场",
|
||||
category="special",
|
||||
description="炫酷特效",
|
||||
tags=["炫酷", "特效"],
|
||||
transition="custom",
|
||||
default_duration=1.0,
|
||||
min_duration=0.5,
|
||||
max_duration=5.0,
|
||||
has_custom_params=True,
|
||||
def test_basic_attributes(self):
|
||||
"""基础属性可访问."""
|
||||
p = TransitionPreset(
|
||||
id="test_id",
|
||||
name="测试转场",
|
||||
category="fade",
|
||||
description="测试描述",
|
||||
tags=["tag1", "tag2"],
|
||||
transition="fade",
|
||||
default_duration=0.5,
|
||||
min_duration=0.1,
|
||||
max_duration=3.0,
|
||||
has_custom_params=False,
|
||||
)
|
||||
assert preset.description == "炫酷特效"
|
||||
assert preset.tags == ["炫酷", "特效"]
|
||||
assert preset.default_duration == 1.0
|
||||
assert preset.min_duration == 0.5
|
||||
assert preset.max_duration == 5.0
|
||||
assert preset.has_custom_params is True
|
||||
assert p.id == "test_id"
|
||||
assert p.name == "测试转场"
|
||||
assert p.category == "fade"
|
||||
assert p.description == "测试描述"
|
||||
assert p.tags == ["tag1", "tag2"]
|
||||
assert p.transition == "fade"
|
||||
assert p.default_duration == 0.5
|
||||
assert p.min_duration == 0.1
|
||||
assert p.max_duration == 3.0
|
||||
assert p.has_custom_params is False
|
||||
|
||||
def test_default_values(self):
|
||||
"""默认值正确."""
|
||||
p = TransitionPreset(id="t", name="T", category="basic")
|
||||
assert p.description == ""
|
||||
assert p.tags == []
|
||||
assert p.transition == "fade"
|
||||
assert p.default_duration == 0.5
|
||||
assert p.min_duration == 0.1
|
||||
assert p.max_duration == 3.0
|
||||
assert p.has_custom_params is False
|
||||
|
||||
def test_frozen_immutable(self):
|
||||
"""frozen dataclass 不可修改"""
|
||||
preset = TransitionPreset(id="test", name="测试", category="basic")
|
||||
with pytest.raises(FrozenInstanceError):
|
||||
preset.name = "改名"
|
||||
"""frozen dataclass 不可修改."""
|
||||
p = TransitionPreset(id="t", name="T", category="basic")
|
||||
with pytest.raises(Exception): # FrozenInstanceError
|
||||
p.name = "新名字"
|
||||
|
||||
def test_tags_default_empty_list(self):
|
||||
preset = TransitionPreset(id="t1", name="t1", category="basic")
|
||||
preset2 = TransitionPreset(id="t2", name="t2", category="basic")
|
||||
assert preset.tags == []
|
||||
assert preset.tags is not preset2.tags
|
||||
def test_not_hashable_due_to_list(self):
|
||||
"""含list字段(tags)的frozen dataclass不可哈希(list可变)."""
|
||||
p = TransitionPreset(id="t", name="T", category="basic")
|
||||
with pytest.raises(TypeError):
|
||||
hash(p)
|
||||
|
||||
def test_default_transition_is_fade(self):
|
||||
preset = TransitionPreset(id="test", name="测试", category="basic")
|
||||
assert preset.transition == "fade"
|
||||
def test_equality(self):
|
||||
"""相同属性的实例相等."""
|
||||
p1 = TransitionPreset(id="t", name="T", category="basic")
|
||||
p2 = TransitionPreset(id="t", name="T", category="basic")
|
||||
assert p1 == p2
|
||||
|
||||
def test_inequality(self):
|
||||
"""不同属性的实例不等."""
|
||||
p1 = TransitionPreset(id="t1", name="T", category="basic")
|
||||
p2 = TransitionPreset(id="t2", name="T", category="basic")
|
||||
assert p1 != p2
|
||||
|
||||
|
||||
# ── 预设库完整性测试 ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestTransitionPresetLibrary:
|
||||
"""TRANSITION_PRESET_LIBRARY 预设库测试"""
|
||||
"""TRANSITION_PRESET_LIBRARY 预设库完整性测试."""
|
||||
|
||||
def test_library_not_empty(self):
|
||||
"""预设库不为空."""
|
||||
assert len(TRANSITION_PRESET_LIBRARY) > 0
|
||||
|
||||
def test_all_presets_have_unique_ids(self):
|
||||
"""所有预设 ID 唯一"""
|
||||
def test_preset_count(self):
|
||||
"""预设数量应大于20."""
|
||||
assert len(TRANSITION_PRESET_LIBRARY) >= 20
|
||||
|
||||
def test_all_ids_unique(self):
|
||||
"""所有预设ID唯一."""
|
||||
ids = [p.id for p in TRANSITION_PRESET_LIBRARY]
|
||||
assert len(ids) == len(set(ids))
|
||||
assert len(ids) == len(set(ids)), f"存在重复ID: {[i for i in ids if ids.count(i) > 1]}"
|
||||
|
||||
def test_all_presets_have_required_fields(self):
|
||||
"""所有预设都有必填字段"""
|
||||
for preset in TRANSITION_PRESET_LIBRARY:
|
||||
assert preset.id, "missing id"
|
||||
assert preset.name, f"{preset.id} missing name"
|
||||
assert preset.category, f"{preset.id} missing category"
|
||||
assert preset.transition, f"{preset.id} missing transition"
|
||||
def test_all_have_required_fields(self):
|
||||
"""所有预设都有必填字段."""
|
||||
for p in TRANSITION_PRESET_LIBRARY:
|
||||
assert p.id, f"预设缺少id: {p}"
|
||||
assert p.name, f"预设 {p.id} 缺少name"
|
||||
assert p.category, f"预设 {p.id} 缺少category"
|
||||
assert p.transition, f"预设 {p.id} 缺少transition"
|
||||
|
||||
def test_transition_none_exists(self):
|
||||
"""无转场预设存在"""
|
||||
none_preset = next((p for p in TRANSITION_PRESET_LIBRARY if p.id == "transition_none"), None)
|
||||
def test_duration_range_valid(self):
|
||||
"""每个预设的时长范围合理: min <= default <= max."""
|
||||
for p in TRANSITION_PRESET_LIBRARY:
|
||||
assert (
|
||||
p.min_duration <= p.default_duration
|
||||
), f"{p.id}: min({p.min_duration}) > default({p.default_duration})"
|
||||
assert (
|
||||
p.default_duration <= p.max_duration
|
||||
), f"{p.id}: default({p.default_duration}) > max({p.max_duration})"
|
||||
|
||||
def test_min_duration_non_negative(self):
|
||||
"""最小时长不能为负."""
|
||||
for p in TRANSITION_PRESET_LIBRARY:
|
||||
assert p.min_duration >= 0, f"{p.id}: min_duration为负"
|
||||
|
||||
def test_categories_are_valid(self):
|
||||
"""分类都在预期集合内."""
|
||||
valid_categories = {"basic", "fade", "slide", "zoom", "warp", "special"}
|
||||
for p in TRANSITION_PRESET_LIBRARY:
|
||||
assert p.category in valid_categories, f"{p.id}: 未知分类 {p.category}"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"category,expected_min",
|
||||
[
|
||||
("basic", 2),
|
||||
("fade", 4),
|
||||
("slide", 4),
|
||||
("zoom", 2),
|
||||
("warp", 6),
|
||||
("special", 2),
|
||||
],
|
||||
)
|
||||
def test_category_min_count(self, category: str, expected_min: int):
|
||||
"""每个分类至少有预期数量的预设."""
|
||||
count = sum(1 for p in TRANSITION_PRESET_LIBRARY if p.category == category)
|
||||
assert count >= expected_min, f"分类 {category} 只有 {count} 个,预期至少 {expected_min}"
|
||||
|
||||
def test_tags_is_list(self):
|
||||
"""tags字段是列表."""
|
||||
for p in TRANSITION_PRESET_LIBRARY:
|
||||
assert isinstance(p.tags, list), f"{p.id}: tags不是列表"
|
||||
|
||||
def test_none_transition_has_zero_duration(self):
|
||||
"""无转场预设时长为0."""
|
||||
none_preset = get_transition_preset("transition_none")
|
||||
assert none_preset is not None
|
||||
assert none_preset.name == "无转场"
|
||||
assert none_preset.min_duration == 0.0
|
||||
assert none_preset.max_duration == 0.0
|
||||
assert none_preset.default_duration == 0.0
|
||||
assert none_preset.transition == "none"
|
||||
|
||||
def test_transition_random_exists(self):
|
||||
"""随机转场预设存在"""
|
||||
random_preset = next((p for p in TRANSITION_PRESET_LIBRARY if p.id == "transition_random"), None)
|
||||
assert random_preset is not None
|
||||
assert random_preset.name == "随机"
|
||||
|
||||
def test_fade_category_exists(self):
|
||||
"""淡入淡出分类有预设"""
|
||||
fade_presets = [p for p in TRANSITION_PRESET_LIBRARY if p.category == "fade"]
|
||||
assert len(fade_presets) >= 2
|
||||
|
||||
def test_duration_constraints_valid(self):
|
||||
"""时长约束:min <= default <= max"""
|
||||
for preset in TRANSITION_PRESET_LIBRARY:
|
||||
assert preset.min_duration <= preset.default_duration, f"{preset.id}: min > default"
|
||||
assert preset.default_duration <= preset.max_duration, f"{preset.id}: default > max"
|
||||
assert preset.min_duration >= 0, f"{preset.id}: min < 0"
|
||||
|
||||
def test_known_categories_exist(self):
|
||||
"""已知分类都有预设"""
|
||||
categories = {p.category for p in TRANSITION_PRESET_LIBRARY}
|
||||
assert "basic" in categories
|
||||
assert "fade" in categories
|
||||
# ── get_transition_preset 测试 ────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestGetTransitionPreset:
|
||||
"""get_transition_preset 函数测试"""
|
||||
"""get_transition_preset 函数测试."""
|
||||
|
||||
def test_get_existing_preset(self):
|
||||
preset = get_transition_preset("transition_none")
|
||||
assert preset is not None
|
||||
assert preset.id == "transition_none"
|
||||
|
||||
def test_get_fade_preset(self):
|
||||
preset = get_transition_preset("transition_fade")
|
||||
assert preset is not None
|
||||
assert preset.transition == "fade"
|
||||
"""获取存在的预设."""
|
||||
p = get_transition_preset("transition_fade")
|
||||
assert p is not None
|
||||
assert p.id == "transition_fade"
|
||||
assert p.name == "淡入淡出"
|
||||
assert p.category == "fade"
|
||||
|
||||
def test_get_nonexistent_preset(self):
|
||||
assert get_transition_preset("nonexistent_transition") is None
|
||||
"""获取不存在的预设返回None."""
|
||||
p = get_transition_preset("nonexistent_id")
|
||||
assert p is None
|
||||
|
||||
def test_returns_transitionpreset_type(self):
|
||||
preset = get_transition_preset("transition_fade")
|
||||
assert isinstance(preset, TransitionPreset)
|
||||
def test_get_none_preset(self):
|
||||
"""获取无转场预设."""
|
||||
p = get_transition_preset("transition_none")
|
||||
assert p is not None
|
||||
assert p.transition == "none"
|
||||
|
||||
def test_get_random_preset(self):
|
||||
"""获取随机预设."""
|
||||
p = get_transition_preset("transition_random")
|
||||
assert p is not None
|
||||
assert p.transition == "random"
|
||||
|
||||
def test_case_sensitive(self):
|
||||
"""ID区分大小写."""
|
||||
p = get_transition_preset("TRANSITION_FADE")
|
||||
assert p is None
|
||||
|
||||
def test_empty_string(self):
|
||||
"""空字符串返回None."""
|
||||
p = get_transition_preset("")
|
||||
assert p is None
|
||||
|
||||
def test_returns_same_instance(self):
|
||||
"""多次调用返回同一个对象(库引用)."""
|
||||
p1 = get_transition_preset("transition_fade")
|
||||
p2 = get_transition_preset("transition_fade")
|
||||
assert p1 is p2
|
||||
|
||||
def test_all_presets_accessible_by_id(self):
|
||||
"""所有预设都可通过ID获取."""
|
||||
for p in TRANSITION_PRESET_LIBRARY:
|
||||
fetched = get_transition_preset(p.id)
|
||||
assert fetched is not None, f"无法通过ID获取: {p.id}"
|
||||
assert fetched.id == p.id
|
||||
|
||||
|
||||
# ── list_transition_presets 测试 ──────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestListTransitionPresets:
|
||||
"""list_transition_presets 函数测试"""
|
||||
"""list_transition_presets 函数测试."""
|
||||
|
||||
def test_list_all(self):
|
||||
"""不带参数返回所有预设"""
|
||||
all_presets = list_transition_presets()
|
||||
assert len(all_presets) == len(TRANSITION_PRESET_LIBRARY)
|
||||
def test_no_filter_returns_all(self):
|
||||
"""无筛选参数返回全部预设."""
|
||||
results = list_transition_presets()
|
||||
assert len(results) == len(TRANSITION_PRESET_LIBRARY)
|
||||
|
||||
def test_filter_by_category(self):
|
||||
"""按分类筛选"""
|
||||
fade_presets = list_transition_presets(category="fade")
|
||||
assert len(fade_presets) > 0
|
||||
assert all(p.category == "fade" for p in fade_presets)
|
||||
def test_filter_by_category_fade(self):
|
||||
"""按fade分类筛选."""
|
||||
results = list_transition_presets(category="fade")
|
||||
assert len(results) > 0
|
||||
assert all(p.category == "fade" for p in results)
|
||||
|
||||
def test_filter_by_basic_category(self):
|
||||
basic_presets = list_transition_presets(category="basic")
|
||||
assert len(basic_presets) >= 2
|
||||
def test_filter_by_category_slide(self):
|
||||
"""按slide分类筛选."""
|
||||
results = list_transition_presets(category="slide")
|
||||
assert len(results) == 4
|
||||
assert all(p.category == "slide" for p in results)
|
||||
|
||||
def test_filter_by_nonexistent_category(self):
|
||||
result = list_transition_presets(category="nonexistent")
|
||||
assert result == []
|
||||
def test_filter_by_category_basic(self):
|
||||
"""按basic分类筛选."""
|
||||
results = list_transition_presets(category="basic")
|
||||
assert len(results) == 2 # none + random
|
||||
|
||||
def test_search_by_name(self):
|
||||
"""按名称搜索"""
|
||||
result = list_transition_presets(keyword="淡入")
|
||||
assert len(result) >= 1
|
||||
assert any("淡入" in p.name for p in result)
|
||||
def test_filter_by_invalid_category(self):
|
||||
"""无效分类返回空列表."""
|
||||
results = list_transition_presets(category="nonexistent")
|
||||
assert results == []
|
||||
|
||||
def test_search_by_tag(self):
|
||||
"""按标签搜索"""
|
||||
tagged = [p for p in TRANSITION_PRESET_LIBRARY if p.tags]
|
||||
if tagged:
|
||||
tag = tagged[0].tags[0]
|
||||
result = list_transition_presets(keyword=tag)
|
||||
assert len(result) >= 1
|
||||
def test_keyword_search_in_name(self):
|
||||
"""关键词搜索name字段."""
|
||||
results = list_transition_presets(keyword="淡入淡出")
|
||||
assert len(results) >= 1
|
||||
assert any(p.id == "transition_fade" for p in results)
|
||||
|
||||
def test_search_empty_returns_all(self):
|
||||
result = list_transition_presets(keyword="")
|
||||
assert len(result) == len(TRANSITION_PRESET_LIBRARY)
|
||||
def test_keyword_search_in_description(self):
|
||||
"""关键词搜索description字段."""
|
||||
results = list_transition_presets(keyword="硬切")
|
||||
assert len(results) >= 1
|
||||
assert any(p.id == "transition_none" for p in results)
|
||||
|
||||
def test_combined_category_and_search(self):
|
||||
result = list_transition_presets(category="fade", keyword="淡入")
|
||||
assert all(p.category == "fade" for p in result)
|
||||
def test_keyword_search_in_tags(self):
|
||||
"""关键词搜索tags字段."""
|
||||
results = list_transition_presets(keyword="模糊")
|
||||
assert len(results) >= 2 # hblur + wipeblur
|
||||
ids = [p.id for p in results]
|
||||
assert "transition_hblur" in ids
|
||||
assert "transition_wipeblur" in ids
|
||||
|
||||
def test_returns_list_of_transitionpreset(self):
|
||||
result = list_transition_presets()
|
||||
assert all(isinstance(p, TransitionPreset) for p in result)
|
||||
def test_keyword_case_insensitive(self):
|
||||
"""关键词搜索不区分大小写(英文)."""
|
||||
results1 = list_transition_presets(keyword="fade")
|
||||
results2 = list_transition_presets(keyword="FADE")
|
||||
assert len(results1) == len(results2)
|
||||
|
||||
def test_keyword_chinese(self):
|
||||
"""中文关键词搜索."""
|
||||
results = list_transition_presets(keyword="滑")
|
||||
assert len(results) >= 4 # 4个slide
|
||||
assert all("滑" in p.name for p in results)
|
||||
|
||||
def test_keyword_no_match(self):
|
||||
"""无匹配关键词返回空."""
|
||||
results = list_transition_presets(keyword="完全不存在的关键词xyz")
|
||||
assert results == []
|
||||
|
||||
def test_keyword_empty_string(self):
|
||||
"""空关键词返回全部."""
|
||||
results = list_transition_presets(keyword="")
|
||||
assert len(results) == len(TRANSITION_PRESET_LIBRARY)
|
||||
|
||||
def test_category_and_keyword_combined(self):
|
||||
"""分类+关键词组合筛选."""
|
||||
results = list_transition_presets(category="warp", keyword="擦除")
|
||||
assert len(results) >= 4 # 4个wipe
|
||||
assert all(p.category == "warp" for p in results)
|
||||
assert all("擦除" in p.name for p in results)
|
||||
|
||||
def test_category_and_keyword_no_match(self):
|
||||
"""分类+关键词不匹配返回空."""
|
||||
results = list_transition_presets(category="fade", keyword="滑动")
|
||||
assert results == []
|
||||
|
||||
def test_preserve_order(self):
|
||||
"""保持预设库的顺序."""
|
||||
results = list_transition_presets()
|
||||
for i, p in enumerate(TRANSITION_PRESET_LIBRARY):
|
||||
assert results[i].id == p.id
|
||||
|
||||
def test_filtered_results_are_all_valid(self):
|
||||
"""筛选结果的每个预设都有完整属性."""
|
||||
results = list_transition_presets(category="slide")
|
||||
for p in results:
|
||||
assert p.id
|
||||
assert p.name
|
||||
assert p.category == "slide"
|
||||
assert isinstance(p.tags, list)
|
||||
|
||||
|
||||
# ── get_default_transition 测试 ───────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestGetDefaultTransition:
|
||||
"""get_default_transition 函数测试"""
|
||||
|
||||
def test_returns_preset(self):
|
||||
preset = get_default_transition()
|
||||
assert preset is not None
|
||||
assert isinstance(preset, TransitionPreset)
|
||||
"""get_default_transition 函数测试."""
|
||||
|
||||
def test_default_is_none(self):
|
||||
"""默认转场是无转场(硬切)"""
|
||||
preset = get_default_transition()
|
||||
assert preset.id == "transition_none"
|
||||
assert preset.transition == "none"
|
||||
"""默认转场是无转场."""
|
||||
p = get_default_transition()
|
||||
assert p.id == "transition_none"
|
||||
assert p.transition == "none"
|
||||
|
||||
def test_default_has_zero_duration(self):
|
||||
"""无转场默认时长为 0"""
|
||||
preset = get_default_transition()
|
||||
assert preset.default_duration == 0.0
|
||||
assert preset.min_duration == 0.0
|
||||
assert preset.max_duration == 0.0
|
||||
def test_default_zero_duration(self):
|
||||
"""默认转场时长为0."""
|
||||
p = get_default_transition()
|
||||
assert p.default_duration == 0.0
|
||||
assert p.min_duration == 0.0
|
||||
assert p.max_duration == 0.0
|
||||
|
||||
def test_default_category_basic(self):
|
||||
"""默认转场属于basic分类."""
|
||||
p = get_default_transition()
|
||||
assert p.category == "basic"
|
||||
|
||||
def test_default_same_instance(self):
|
||||
"""多次调用返回同一实例."""
|
||||
p1 = get_default_transition()
|
||||
p2 = get_default_transition()
|
||||
assert p1 is p2
|
||||
|
||||
def test_default_matches_get_preset(self):
|
||||
"""默认转场与通过ID获取的一致."""
|
||||
default = get_default_transition()
|
||||
by_id = get_transition_preset("transition_none")
|
||||
assert default is by_id
|
||||
|
||||
|
||||
# ── 预设个体属性抽样测试 ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestPresetSamples:
|
||||
"""典型预设的属性验证."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"preset_id,expected_name,expected_category,expected_transition",
|
||||
[
|
||||
("transition_none", "无转场", "basic", "none"),
|
||||
("transition_random", "随机", "basic", "random"),
|
||||
("transition_fade", "淡入淡出", "fade", "fade"),
|
||||
("transition_fadeblack", "黑场过渡", "fade", "fadeblack"),
|
||||
("transition_fadewhite", "白场过渡", "fade", "fadewhite"),
|
||||
("transition_slideleft", "左滑", "slide", "slideleft"),
|
||||
("transition_slideright", "右滑", "slide", "slideright"),
|
||||
("transition_slideup", "上滑", "slide", "slideup"),
|
||||
("transition_slidedown", "下滑", "slide", "slidedown"),
|
||||
("transition_zoomin", "放大进入", "zoom", "zoomin"),
|
||||
("transition_zoomout", "缩小退出", "zoom", "zoomout"),
|
||||
("transition_dissolve", "溶解", "warp", "dissolve"),
|
||||
("transition_circlecrop", "圆形展开", "warp", "circlecrop"),
|
||||
("transition_hblur", "水平模糊", "special", "hblur"),
|
||||
],
|
||||
)
|
||||
def test_preset_attributes(
|
||||
self, preset_id: str, expected_name: str, expected_category: str, expected_transition: str
|
||||
):
|
||||
"""典型预设属性验证."""
|
||||
p = get_transition_preset(preset_id)
|
||||
assert p is not None
|
||||
assert p.name == expected_name
|
||||
assert p.category == expected_category
|
||||
assert p.transition == expected_transition
|
||||
|
||||
def test_dissolve_longer_default(self):
|
||||
"""溶解效果默认时长较长(0.8s)."""
|
||||
p = get_transition_preset("transition_dissolve")
|
||||
assert p is not None
|
||||
assert p.default_duration == 0.8
|
||||
|
||||
Reference in New Issue
Block a user