""" 模板系统单元测试 测试 Template 类的初始化、变量解析、条件渲染和模板渲染功能 """ import pytest from pathlib import Path from loaders.yaml_loader import YAMLError from core.template import Template from core.presentation import Presentation # ============= 模板初始化测试 ============= class TestTemplateFromData: """Template.from_data 方法测试类""" def test_from_data_with_valid_template(self, temp_dir): """测试从有效数据创建模板""" template_data = { "vars": [ {"name": "title", "required": True}, {"name": "subtitle", "required": False, "default": ""} ], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "{title}"} ] } template = Template.from_data(template_data, "test-template", base_dir=temp_dir) assert template.data is not None assert "elements" in template.data assert "vars" in template.data assert template.base_dir == temp_dir def test_from_data_without_vars(self, temp_dir): """测试创建没有变量的模板""" template_data = { "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "Static Text"} ] } template = Template.from_data(template_data, "static-template", base_dir=temp_dir) assert len(template.vars_def) == 0 assert len(template.elements) == 1 def test_from_data_with_empty_vars(self, temp_dir): """测试创建空变量列表的模板""" template_data = { "vars": [], "elements": [] } template = Template.from_data(template_data, "empty-template", base_dir=temp_dir) assert len(template.vars_def) == 0 assert len(template.elements) == 0 def test_from_data_render_with_vars(self, temp_dir): """测试使用 from_data 创建的模板可以正常渲染""" template_data = { "vars": [{"name": "title", "required": True}], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "{title}"} ] } template = Template.from_data(template_data, "test-template", base_dir=temp_dir) rendered = template.render({"title": "Hello"}) assert rendered[0]["content"] == "Hello" def test_from_data_render_with_default_value(self, temp_dir): """测试使用默认值渲染""" template_data = { "vars": [ {"name": "title", "required": False, "default": "Default Title"} ], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "{title}"} ] } template = Template.from_data(template_data, "test-template", base_dir=temp_dir) rendered = template.render({}) assert rendered[0]["content"] == "Default Title" def test_from_data_with_conditional_element(self, temp_dir): """测试条件元素""" template_data = { "vars": [{"name": "show", "required": False, "default": ""}], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "Always", "visible": "True"}, {"type": "text", "box": [1, 3, 8, 1], "content": "Conditional", "visible": "{show != ''}"} ] } template = Template.from_data(template_data, "test-template", base_dir=temp_dir) # 不提供 show 变量 rendered = template.render({}) assert len(rendered) == 1 assert rendered[0]["content"] == "Always" # 提供 show 变量 rendered = template.render({"show": "yes"}) assert len(rendered) == 2 def test_from_data_with_nested_variable_in_default(self, temp_dir): """测试默认值中的嵌套变量""" template_data = { "vars": [ {"name": "prefix", "required": True}, {"name": "title", "required": False, "default": "{prefix} Title"} ], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "{title}"} ] } template = Template.from_data(template_data, "test-template", base_dir=temp_dir) rendered = template.render({"prefix": "My"}) assert rendered[0]["content"] == "My Title" def test_from_data_rejects_template_reference_in_elements(self, temp_dir): """测试元素中包含 template 字段会引发错误""" template_data = { "vars": [], "elements": [ {"template": "nested-template"} # 内联模板不支持相互引用 ] } template = Template.from_data(template_data, "test-template", base_dir=temp_dir) # 渲染时应该引发错误 with pytest.raises(YAMLError, match="内联模板不支持相互引用"): template.render({}) def test_from_data_with_description(self, temp_dir): """测试模板描述字段""" template_data = { "description": "这是一个测试模板", "vars": [], "elements": [] } template = Template.from_data(template_data, "test-template", base_dir=temp_dir) assert template.description == "这是一个测试模板" def test_from_data_without_description(self, temp_dir): """测试没有描述字段的模板""" template_data = { "vars": [], "elements": [] } template = Template.from_data(template_data, "test-template", base_dir=temp_dir) assert template.description is None # ============= 变量解析测试 ============= class TestResolveValue: """resolve_value 方法测试类""" def test_resolve_value_simple_variable(self, temp_dir): """测试解析简单变量""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) result = template.resolve_value("{title}", {"title": "My Title"}) assert result == "My Title" def test_resolve_value_multiple_variables(self, temp_dir): """测试解析多个变量""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) result = template.resolve_value( "{title} - {subtitle}", {"title": "Main", "subtitle": "Sub"} ) assert result == "Main - Sub" def test_resolve_value_undefined_variable_raises_error(self, temp_dir): """测试未定义变量会引发错误""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) with pytest.raises(YAMLError, match="未定义的变量"): template.resolve_value("{undefined}", {"title": "Test"}) def test_resolve_value_preserves_non_string(self, temp_dir): """测试非字符串值保持原样""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) assert template.resolve_value(123, {}) == 123 assert template.resolve_value(None, {}) is None assert template.resolve_value(["list"], {}) == ["list"] def test_resolve_value_converts_to_integer(self, temp_dir): """测试结果转换为整数""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) result = template.resolve_value("{value}", {"value": "42"}) assert result == 42 assert isinstance(result, int) def test_resolve_value_converts_to_float(self, temp_dir): """测试结果转换为浮点数""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) result = template.resolve_value("{value}", {"value": "3.14"}) assert result == 3.14 assert isinstance(result, float) # ============= resolve_element 测试 ============= class TestResolveElement: """resolve_element 方法测试类""" def test_resolve_element_dict(self, temp_dir): """测试解析字典元素""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) elem = {"content": "{title}", "box": [1, 2, 3, 4]} result = template.resolve_element(elem, {"title": "Test"}) assert result["content"] == "Test" assert result["box"] == [1, 2, 3, 4] def test_resolve_element_list(self, temp_dir): """测试解析列表元素""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) elem = ["{title}", "{subtitle}"] result = template.resolve_element(elem, {"title": "A", "subtitle": "B"}) assert result == ["A", "B"] def test_resolve_element_nested_structure(self, temp_dir): """测试解析嵌套结构""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) elem = { "content": "{title}", "font": {"size": "{size}", "color": "#000"} } result = template.resolve_element(elem, {"title": "Test", "size": "24"}) assert result["content"] == "Test" assert result["font"]["size"] == 24 assert result["font"]["color"] == "#000" def test_resolve_element_excludes_visible(self, temp_dir): """测试 visible 字段在 resolve_element 中被排除""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) elem = {"content": "{title}", "visible": "{show}"} result = template.resolve_element(elem, {"title": "Test", "show": "true"}) assert result["content"] == "Test" # visible 字段在 resolve_element 中被排除,在 render 中单独处理 assert "visible" not in result # ============= 条件评估测试 ============= class TestEvaluateCondition: """evaluate_condition 方法测试类""" def test_evaluate_condition_with_non_empty_variable(self, temp_dir): """测试非空变量条件""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) result = template.evaluate_condition("{title != ''}", {"title": "Hello"}) assert result is True def test_evaluate_condition_with_empty_variable(self, temp_dir): """测试空变量条件""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) result = template.evaluate_condition("{title != ''}", {"title": ""}) assert result is False def test_evaluate_condition_with_missing_variable(self, temp_dir): """测试缺失变量条件会引发错误""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) # 缺失变量应该引发错误 with pytest.raises(YAMLError, match="变量未定义"): template.evaluate_condition("{title != ''}", {}) def test_evaluate_condition_complex_logic(self, temp_dir): """测试复杂逻辑条件""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) result = template.evaluate_condition( "{a == 'yes' and b != ''}", {"a": "yes", "b": "value"} ) assert result is True def test_evaluate_condition_member_test(self, temp_dir): """测试成员测试条件""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) result = template.evaluate_condition("{type in ['A', 'B']}", {"type": "A"}) assert result is True def test_evaluate_condition_math_operation(self, temp_dir): """测试数学运算条件""" template_data = {"vars": [], "elements": []} template = Template.from_data(template_data, "test", base_dir=temp_dir) # 使用整数而不是字符串进行比较 result = template.evaluate_condition("{count > 5}", {"count": 10}) assert result is True # ============= 渲染测试 ============= class TestRender: """render 方法测试类""" def test_render_with_required_variable(self, temp_dir): """测试使用必需变量渲染""" template_data = { "vars": [{"name": "title", "required": True}], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "{title}"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({"title": "Hello"}) assert len(rendered) == 1 assert rendered[0]["content"] == "Hello" def test_render_with_optional_variable(self, temp_dir): """测试使用可选变量渲染""" template_data = { "vars": [{"name": "subtitle", "required": False, "default": ""}], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "{subtitle}"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({"subtitle": "Sub"}) assert rendered[0]["content"] == "Sub" def test_render_without_optional_variable(self, temp_dir): """测试不提供可选变量时使用默认值""" template_data = { "vars": [{"name": "subtitle", "required": False, "default": "Default"}], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "{subtitle}"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({}) assert rendered[0]["content"] == "Default" def test_render_missing_required_variable_raises_error(self, temp_dir): """测试缺少必需变量会引发错误""" template_data = { "vars": [{"name": "title", "required": True}], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "{title}"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) with pytest.raises(YAMLError, match="缺少必需变量"): template.render({}) def test_render_with_default_value(self, temp_dir): """测试使用默认值渲染""" template_data = { "vars": [{"name": "title", "required": False, "default": "Default Title"}], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "{title}"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({}) assert rendered[0]["content"] == "Default Title" def test_render_filters_elements_by_visible_condition(self, temp_dir): """测试根据 visible 条件过滤元素""" template_data = { "vars": [{"name": "show", "required": False, "default": ""}], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "Always"}, {"type": "text", "box": [1, 3, 8, 1], "content": "Conditional", "visible": "{show != ''}"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) # 不显示条件元素 rendered = template.render({}) assert len(rendered) == 1 # 显示条件元素 rendered = template.render({"show": "yes"}) assert len(rendered) == 2 # ============= 边界情况测试 ============= class TestTemplateBoundaryCases: """模板边界情况测试""" def test_nested_variable_resolution(self, temp_dir): """测试嵌套变量解析""" template_data = { "vars": [ {"name": "prefix", "required": True}, {"name": "title", "required": False, "default": "{prefix} Title"} ], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "{title}"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({"prefix": "My"}) assert rendered[0]["content"] == "My Title" def test_variable_with_special_characters(self, temp_dir): """测试包含特殊字符的变量""" template_data = { "vars": [{"name": "text", "required": True}], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "{text}"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({"text": "Hello & "}) assert rendered[0]["content"] == "Hello & " def test_variable_with_numeric_value(self, temp_dir): """测试数值变量""" template_data = { "vars": [{"name": "count", "required": True}], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "Count: {count}"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({"count": 42}) assert rendered[0]["content"] == "Count: 42" def test_empty_vars_list(self, temp_dir): """测试空变量列表""" template_data = { "vars": [], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "Static"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({}) assert len(rendered) == 1 assert rendered[0]["content"] == "Static" def test_multiple_visible_conditions(self, temp_dir): """测试多个可见性条件""" template_data = { "vars": [ {"name": "show_a", "required": False, "default": ""}, {"name": "show_b", "required": False, "default": ""} ], "elements": [ {"type": "text", "box": [1, 2, 8, 1], "content": "A", "visible": "{show_a != ''}"}, {"type": "text", "box": [1, 3, 8, 1], "content": "B", "visible": "{show_b != ''}"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({"show_a": "yes"}) assert len(rendered) == 1 assert rendered[0]["content"] == "A" def test_variable_in_position(self, temp_dir): """测试位置中的变量""" template_data = { "vars": [{"name": "x", "required": True}], "elements": [ {"type": "text", "box": ["{x}", 2, 8, 1], "content": "Text"} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({"x": "5"}) assert rendered[0]["box"][0] == 5 def test_empty_template_elements(self, temp_dir): """测试空元素列表""" template_data = { "vars": [], "elements": [] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({}) assert len(rendered) == 0 def test_variable_replacement_in_font(self, temp_dir): """测试字体属性中的变量替换""" template_data = { "vars": [{"name": "size", "required": True}], "elements": [ { "type": "text", "box": [1, 2, 8, 1], "content": "Text", "font": {"size": "{size}"} } ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({"size": "24"}) assert rendered[0]["font"]["size"] == 24 # ============= 图片路径解析测试 ============= class TestImagePathResolution: """图片路径解析测试""" def test_render_resolves_relative_image_paths(self, temp_dir, sample_image): """测试渲染时解析相对图片路径""" template_data = { "vars": [], "elements": [ {"type": "image", "box": [1, 2, 8, 4], "src": sample_image.name} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({}) # 相对路径应该被解析为绝对路径 assert Path(rendered[0]["src"]).is_absolute() assert rendered[0]["src"] == str(temp_dir / sample_image.name) def test_render_preserves_absolute_image_paths(self, temp_dir, sample_image): """测试渲染时保留绝对图片路径""" template_data = { "vars": [], "elements": [ {"type": "image", "box": [1, 2, 8, 4], "src": str(sample_image)} ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({}) # 绝对路径应该保持不变 assert rendered[0]["src"] == str(sample_image) def test_render_without_base_dir(self, temp_dir): """测试没有 base_dir 时不解析路径""" template_data = { "vars": [], "elements": [ {"type": "image", "box": [1, 2, 8, 4], "src": "image.png"} ] } template = Template.from_data(template_data, "test", base_dir=None) rendered = template.render({}) # 没有 base_dir 时,相对路径保持不变 assert rendered[0]["src"] == "image.png" def test_render_resolves_nested_image_paths(self, temp_dir): """测试解析嵌套结构中的图片路径""" template_data = { "vars": [], "elements": [ { "type": "image", "box": [1, 2, 8, 4], "src": "images/logo.png" } ] } template = Template.from_data(template_data, "test", base_dir=temp_dir) rendered = template.render({}) # 相对路径应该被解析 assert Path(rendered[0]["src"]).is_absolute() assert rendered[0]["src"] == str(temp_dir / "images" / "logo.png")