""" Presentation 类单元测试 测试 Presentation 类的初始化、模板缓存和渲染方法 """ import pytest from pathlib import Path from unittest.mock import patch, Mock from core.presentation import Presentation from loaders.yaml_loader import YAMLError class TestPresentationInit: """Presentation 初始化测试类""" def test_init_with_valid_yaml(self, sample_yaml): """测试使用有效 YAML 初始化""" pres = Presentation(str(sample_yaml)) assert pres.data is not None assert "slides" in pres.data assert pres.pres_file == sample_yaml def test_init_with_templates_dir(self, sample_yaml, sample_template): """测试带模板库文件初始化""" pres = Presentation(str(sample_yaml), str(sample_template)) assert pres.template_file == Path(sample_template) assert pres.template_library is not None def test_init_without_templates_dir(self, sample_yaml): """测试不带模板库文件初始化""" pres = Presentation(str(sample_yaml)) assert pres.template_file is None assert pres.template_library is None @patch("core.presentation.load_yaml_file") @patch("core.presentation.validate_presentation_yaml") def test_init_loads_yaml(self, mock_validate, mock_load, temp_dir): """测试初始化时加载 YAML""" mock_data = {"slides": [{"elements": []}]} mock_load.return_value = mock_data yaml_path = temp_dir / "test.yaml" pres = Presentation(str(yaml_path)) mock_load.assert_called_once_with(str(yaml_path)) mock_validate.assert_called_once() def test_init_with_invalid_yaml(self, temp_dir): """测试使用无效 YAML 初始化""" invalid_yaml = temp_dir / "invalid.yaml" invalid_yaml.write_text("invalid: [unclosed") with pytest.raises(Exception): Presentation(str(invalid_yaml)) class TestPresentationSize: """Presentation size 属性测试类""" def test_size_16_9(self, temp_dir): """测试 16:9 尺寸""" yaml_content = """ metadata: size: "16:9" slides: - elements: [] """ yaml_path = temp_dir / "test.yaml" yaml_path.write_text(yaml_content) pres = Presentation(str(yaml_path)) assert pres.size == "16:9" def test_size_4_3(self, temp_dir): """测试 4:3 尺寸""" yaml_content = """ metadata: size: "4:3" slides: - elements: [] """ yaml_path = temp_dir / "test.yaml" yaml_path.write_text(yaml_content) pres = Presentation(str(yaml_path)) assert pres.size == "4:3" def test_size_default(self, temp_dir): """测试默认尺寸""" yaml_content = """ slides: - elements: [] """ yaml_path = temp_dir / "test.yaml" yaml_path.write_text(yaml_content) pres = Presentation(str(yaml_path)) assert pres.size == "16:9" # 默认值 def test_size_without_metadata(self, temp_dir): """测试无 metadata 时的默认尺寸""" yaml_content = """ slides: - elements: [] """ yaml_path = temp_dir / "test.yaml" yaml_path.write_text(yaml_content) pres = Presentation(str(yaml_path)) assert pres.size == "16:9" class TestRenderSlide: """render_slide 方法测试类""" def test_render_slide_without_template(self, sample_yaml): """测试渲染不使用模板的幻灯片""" pres = Presentation(str(sample_yaml)) slide_data = { "elements": [ {"type": "text", "content": "Test", "box": [0, 0, 1, 1], "font": {}} ] } result = pres.render_slide(slide_data) assert "elements" in result assert len(result["elements"]) == 1 assert result["elements"][0].content == "Test" def test_render_slide_with_background(self, sample_yaml): """测试渲染带背景的幻灯片""" pres = Presentation(str(sample_yaml)) slide_data = {"background": {"color": "#ffffff"}, "elements": []} result = pres.render_slide(slide_data) assert result["background"] == {"color": "#ffffff"} def test_render_slide_without_background(self, sample_yaml): """测试渲染无背景的幻灯片""" pres = Presentation(str(sample_yaml)) slide_data = {"elements": []} result = pres.render_slide(slide_data) assert result["background"] is None @patch("core.presentation.create_element") def test_render_slide_converts_dict_to_objects( self, mock_create_element, sample_yaml ): """测试字典转换为元素对象""" mock_elem = Mock() mock_create_element.return_value = mock_elem pres = Presentation(str(sample_yaml)) slide_data = { "elements": [ {"type": "text", "content": "Test", "box": [0, 0, 1, 1], "font": {}} ] } result = pres.render_slide(slide_data) # create_element 应该被调用 mock_create_element.assert_called() assert result["elements"][0] == mock_elem def test_render_slide_empty_elements_list(self, sample_yaml): """测试空元素列表""" pres = Presentation(str(sample_yaml)) slide_data = {"elements": []} result = pres.render_slide(slide_data) assert result["elements"] == [] @patch("core.presentation.create_element") def test_render_slide_with_multiple_elements( self, mock_create_element, sample_yaml ): """测试多个元素""" mock_elem1 = Mock() mock_elem2 = Mock() mock_create_element.side_effect = [mock_elem1, mock_elem2] pres = Presentation(str(sample_yaml)) slide_data = { "elements": [ {"type": "text", "content": "T1", "box": [0, 0, 1, 1], "font": {}}, {"type": "text", "content": "T2", "box": [1, 1, 1, 1], "font": {}}, ] } result = pres.render_slide(slide_data) assert len(result["elements"]) == 2 class TestRenderSlideHybridMode: """render_slide 混合模式测试类""" def test_hybrid_mode_empty_elements(self, temp_dir, sample_template): """测试空 elements 列表""" yaml_content = """ slides: - elements: [] templates: test-template: vars: - name: title elements: - type: text content: "{title}" box: [0, 0, 1, 1] font: {} """ yaml_path = temp_dir / "test.yaml" yaml_path.write_text(yaml_content) pres = Presentation(str(yaml_path)) slide_data = { "template": "test-template", "vars": {"title": "Test"}, "elements": [] } result = pres.render_slide(slide_data) # 空 elements 列表应该只渲染模板元素 assert len(result["elements"]) == 1 assert result["elements"][0].content == "Test" def test_hybrid_mode_with_inline_template(self, temp_dir): """测试内联模板与自定义元素混合使用""" yaml_content = """ slides: - elements: [] templates: test-template: vars: - name: title elements: - type: text content: "{title}" box: [0, 0, 1, 1] font: {} """ yaml_path = temp_dir / "test.yaml" yaml_path.write_text(yaml_content) pres = Presentation(str(yaml_path)) slide_data = { "template": "test-template", "vars": {"title": "Inline Template"}, "elements": [ {"type": "text", "content": "Custom", "box": [2, 2, 1, 1], "font": {}} ] } result = pres.render_slide(slide_data) # 应该有 2 个元素 assert len(result["elements"]) == 2 assert result["elements"][0].content == "Inline Template" assert result["elements"][1].content == "Custom" def test_metadata_with_description(self, temp_dir): """测试 metadata 包含 description 字段时正确加载""" yaml_content = """ metadata: title: "测试演示文稿" description: "这是关于项目年度总结的演示文稿" size: "16:9" slides: - elements: [] """ yaml_path = temp_dir / "test.yaml" yaml_path.write_text(yaml_content) pres = Presentation(str(yaml_path)) assert pres.description == "这是关于项目年度总结的演示文稿" def test_metadata_without_description(self, temp_dir): """测试 metadata 不包含 description 字段时正常工作""" yaml_content = """ metadata: title: "测试演示文稿" size: "16:9" slides: - elements: [] """ yaml_path = temp_dir / "test.yaml" yaml_path.write_text(yaml_content) pres = Presentation(str(yaml_path)) assert pres.description is None def test_metadata_description_empty_string(self, temp_dir): """测试 metadata description 为空字符串时正常工作""" yaml_content = """ metadata: title: "测试演示文稿" description: "" size: "16:9" slides: - elements: [] """ yaml_path = temp_dir / "test.yaml" yaml_path.write_text(yaml_content) pres = Presentation(str(yaml_path)) assert pres.description == "" def test_metadata_description_chinese_characters(self, temp_dir): """测试 metadata description 包含中文字符时正确处理""" yaml_content = """ metadata: title: "测试" description: "这是关于项目的演示文稿,包含中文和特殊字符:测试、验证、确认" size: "16:9" slides: - elements: [] """ yaml_path = temp_dir / "test.yaml" yaml_path.write_text(yaml_content) pres = Presentation(str(yaml_path)) assert "这是关于项目的演示文稿" in pres.description assert "中文" in pres.description class TestSlideDescription: """Slide description 字段测试类""" def test_slide_with_description(self, sample_yaml): """测试幻灯片包含 description 字段时正确加载""" pres = Presentation(str(sample_yaml)) slide_data = { "description": "介绍项目背景和目标", "elements": [ {"type": "text", "content": "Test", "box": [0, 0, 1, 1], "font": {}} ] } result = pres.render_slide(slide_data) assert result["description"] == "介绍项目背景和目标" def test_slide_without_description(self, sample_yaml): """测试幻灯片不包含 description 字段时正常工作""" pres = Presentation(str(sample_yaml)) slide_data = { "elements": [ {"type": "text", "content": "Test", "box": [0, 0, 1, 1], "font": {}} ] } result = pres.render_slide(slide_data) assert result["description"] is None def test_slide_description_empty_string(self, sample_yaml): """测试幻灯片 description 为空字符串时正常工作""" pres = Presentation(str(sample_yaml)) slide_data = { "description": "", "elements": [ {"type": "text", "content": "Test", "box": [0, 0, 1, 1], "font": {}} ] } result = pres.render_slide(slide_data) assert result["description"] == "" def test_slide_description_chinese_characters(self, sample_yaml): """测试幻灯片 description 包含中文字符时正确处理""" pres = Presentation(str(sample_yaml)) slide_data = { "description": "这是幻灯片描述,包含中文内容", "elements": [ {"type": "text", "content": "Test", "box": [0, 0, 1, 1], "font": {}} ] } result = pres.render_slide(slide_data) assert "这是幻灯片描述" in result["description"] assert "中文" in result["description"] def test_slide_description_does_not_affect_rendering(self, sample_yaml): """测试 description 不影响渲染输出""" pres = Presentation(str(sample_yaml)) slide_data = { "description": "这段描述不应该影响渲染", "elements": [ {"type": "text", "content": "Test Content", "box": [0, 0, 1, 1], "font": {}} ] } result = pres.render_slide(slide_data) # description 字段存在但不影响元素渲染 assert result["description"] == "这段描述不应该影响渲染" assert len(result["elements"]) == 1 assert result["elements"][0].content == "Test Content" class TestSizeConsistency: """Size 一致性校验测试类""" def test_size_consistency_16_9(self, temp_dir): """测试文档和模板库 size 都是 16:9 时正常加载""" # 创建文档 doc_content = """ metadata: size: "16:9" slides: - elements: [] """ doc_path = temp_dir / "doc.yaml" doc_path.write_text(doc_content) # 创建模板库 template_content = """ metadata: size: "16:9" templates: test: elements: [] """ template_path = temp_dir / "templates.yaml" template_path.write_text(template_content) # 应该正常加载 pres = Presentation(str(doc_path), str(template_path)) assert pres.size == "16:9" def test_size_consistency_4_3(self, temp_dir): """测试文档和模板库 size 都是 4:3 时正常加载""" # 创建文档 doc_content = """ metadata: size: "4:3" slides: - elements: [] """ doc_path = temp_dir / "doc.yaml" doc_path.write_text(doc_content) # 创建模板库 template_content = """ metadata: size: "4:3" templates: test: elements: [] """ template_path = temp_dir / "templates.yaml" template_path.write_text(template_content) # 应该正常加载 pres = Presentation(str(doc_path), str(template_path)) assert pres.size == "4:3" def test_size_mismatch_error(self, temp_dir): """测试文档和模板库 size 不一致时抛出错误""" # 创建文档 doc_content = """ metadata: size: "16:9" slides: - elements: [] """ doc_path = temp_dir / "doc.yaml" doc_path.write_text(doc_content) # 创建模板库 template_content = """ metadata: size: "4:3" templates: test: elements: [] """ template_path = temp_dir / "templates.yaml" template_path.write_text(template_content) # 应该抛出错误 with pytest.raises(YAMLError, match="文档尺寸.*与模板库尺寸.*不一致"): Presentation(str(doc_path), str(template_path)) def test_template_library_missing_metadata(self, temp_dir): """测试模板库缺少 metadata 时抛出错误""" # 创建文档 doc_content = """ metadata: size: "16:9" slides: - elements: [] """ doc_path = temp_dir / "doc.yaml" doc_path.write_text(doc_content) # 创建模板库(缺少 metadata) template_content = """ templates: test: elements: [] """ template_path = temp_dir / "templates.yaml" template_path.write_text(template_content) # 应该抛出错误 with pytest.raises(YAMLError, match="模板库必须包含 metadata 字段"): Presentation(str(doc_path), str(template_path)) def test_template_library_missing_size(self, temp_dir): """测试模板库 metadata 缺少 size 时抛出错误""" # 创建文档 doc_content = """ metadata: size: "16:9" slides: - elements: [] """ doc_path = temp_dir / "doc.yaml" doc_path.write_text(doc_content) # 创建模板库(metadata 缺少 size) template_content = """ metadata: description: "测试模板库" templates: test: elements: [] """ template_path = temp_dir / "templates.yaml" template_path.write_text(template_content) # 应该抛出错误 with pytest.raises(YAMLError, match="metadata 缺少必填字段 'size'"): Presentation(str(doc_path), str(template_path))