"""转场预设库单元测试.""" from __future__ import annotations import dataclasses import pytest from packages.domain.transition_presets import ( TRANSITION_PRESET_LIBRARY, TransitionPreset, get_default_transition, get_transition_preset, list_transition_presets, ) # ── 数据类测试 ──────────────────────────────────────────────────────────────── class TestTransitionPreset: """TransitionPreset 数据类测试.""" 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 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 不可修改.""" p = TransitionPreset(id="t", name="T", category="basic") with pytest.raises(dataclasses.FrozenInstanceError): p.name = "新名字" 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_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 预设库完整性测试.""" def test_library_not_empty(self): """预设库不为空.""" assert len(TRANSITION_PRESET_LIBRARY) > 0 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)), f"存在重复ID: {[i for i in ids if ids.count(i) > 1]}" 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_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.min_duration == 0.0 assert none_preset.max_duration == 0.0 assert none_preset.default_duration == 0.0 assert none_preset.transition == "none" # ── get_transition_preset 测试 ──────────────────────────────────────────────── class TestGetTransitionPreset: """get_transition_preset 函数测试.""" def test_get_existing_preset(self): """获取存在的预设.""" 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): """获取不存在的预设返回None.""" p = get_transition_preset("nonexistent_id") assert p is None 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 函数测试.""" def test_no_filter_returns_all(self): """无筛选参数返回全部预设.""" results = list_transition_presets() assert len(results) == len(TRANSITION_PRESET_LIBRARY) 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_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_category_basic(self): """按basic分类筛选.""" results = list_transition_presets(category="basic") assert len(results) == 2 # none + random def test_filter_by_invalid_category(self): """无效分类返回空列表.""" results = list_transition_presets(category="nonexistent") assert results == [] 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_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_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_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_default_is_none(self): """默认转场是无转场.""" p = get_default_transition() assert p.id == "transition_none" assert p.transition == "none" 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