""" 元素类单元测试 测试 TextElement、ImageElement、ShapeElement、TableElement 和 create_element 工厂函数 """ import pytest from core.elements import ( TextElement, ImageElement, ShapeElement, TableElement, create_element, _is_valid_color ) from validators.result import ValidationIssue # ============= TextElement 测试 ============= class TestTextElement: """TextElement 测试类""" def test_create_with_defaults(self): """测试使用默认值创建 TextElement""" elem = TextElement() assert elem.type == 'text' assert elem.content == '' assert elem.box == [1, 1, 8, 1] assert elem.font == {} def test_create_with_values(self): """测试使用指定值创建 TextElement""" elem = TextElement( content="Test", box=[0, 0, 5, 1], font={"size": 24, "bold": True} ) assert elem.content == "Test" assert elem.box == [0, 0, 5, 1] assert elem.font["size"] == 24 assert elem.font["bold"] is True def test_box_must_be_list_of_four(self): """测试 box 必须是包含 4 个数字的列表""" with pytest.raises(ValueError, match="box 必须是包含 4 个数字的列表"): TextElement(box=[1, 2, 3]) # 只有 3 个元素 with pytest.raises(ValueError, match="box 必须是包含 4 个数字的列表"): TextElement(box="not a list") def test_validate_invalid_color(self): """测试无效颜色格式验证""" elem = TextElement( font={"color": "red"} # 无效格式 ) issues = elem.validate() assert len(issues) == 1 assert issues[0].level == "ERROR" assert issues[0].code == "INVALID_COLOR_FORMAT" def test_validate_font_too_small(self): """测试字体太小警告""" elem = TextElement(font={"size": 6}) issues = elem.validate() assert len(issues) == 1 assert issues[0].level == "WARNING" assert issues[0].code == "FONT_TOO_SMALL" def test_validate_font_too_large(self): """测试字体太大警告""" elem = TextElement(font={"size": 120}) issues = elem.validate() assert len(issues) == 1 assert issues[0].level == "WARNING" assert issues[0].code == "FONT_TOO_LARGE" def test_validate_valid_font_size(self): """测试有效字体大小不产生警告""" elem = TextElement(font={"size": 18}) issues = elem.validate() assert len(issues) == 0 # ============= ImageElement 测试 ============= class TestImageElement: """ImageElement 测试类""" def test_create_with_defaults(self): """测试使用默认值创建 ImageElement""" elem = ImageElement(src="test.png") assert elem.type == 'image' assert elem.src == "test.png" assert elem.box == [1, 1, 4, 3] assert elem.fit is None assert elem.background is None def test_create_with_fit_and_background(self): """测试创建带 fit 和 background 的 ImageElement""" elem = ImageElement( src="test.png", box=[1, 1, 4, 3], fit="contain", background="#ffffff" ) assert elem.fit == "contain" assert elem.background == "#ffffff" def test_empty_src_raises_error(self): """测试空 src 会引发错误""" with pytest.raises(ValueError, match="图片元素必须指定 src"): ImageElement(src="") def test_box_must_be_list_of_four(self): """测试 box 必须是包含 4 个数字的列表""" with pytest.raises(ValueError, match="box 必须是包含 4 个数字的列表"): ImageElement(src="test.png", box=[1, 2, 3]) def test_validate_returns_empty_list(self): """测试 ImageElement.validate() 返回空列表""" elem = ImageElement(src="test.png") issues = elem.validate() assert issues == [] # ============= ShapeElement 测试 ============= class TestShapeElement: """ShapeElement 测试类""" def test_create_with_defaults(self): """测试使用默认值创建 ShapeElement""" elem = ShapeElement() assert elem.type == 'shape' assert elem.shape == 'rectangle' assert elem.box == [1, 1, 2, 1] assert elem.fill is None assert elem.line is None def test_box_must_be_list_of_four(self): """测试 box 必须是包含 4 个数字的列表""" with pytest.raises(ValueError, match="box 必须是包含 4 个数字的列表"): ShapeElement(box=[1, 2, 3]) def test_validate_invalid_shape_type(self): """测试无效形状类型""" elem = ShapeElement(shape="triangle") issues = elem.validate() assert len(issues) == 1 assert issues[0].level == "ERROR" assert issues[0].code == "INVALID_SHAPE_TYPE" assert "triangle" in issues[0].message def test_validate_valid_shape_types(self): """测试有效的形状类型""" for shape_type in ['rectangle', 'ellipse', 'rounded_rectangle']: elem = ShapeElement(shape=shape_type) issues = elem.validate() assert len(issues) == 0 def test_validate_invalid_fill_color(self): """测试无效填充颜色""" elem = ShapeElement(fill="red") issues = elem.validate() assert len(issues) == 1 assert issues[0].code == "INVALID_COLOR_FORMAT" def test_validate_invalid_line_color(self): """测试无效线条颜色""" elem = ShapeElement(line={"color": "blue"}) issues = elem.validate() assert len(issues) == 1 assert issues[0].code == "INVALID_COLOR_FORMAT" def test_validate_valid_colors(self): """测试有效的颜色""" elem = ShapeElement( fill="#4a90e2", line={"color": "#000000", "width": 2} ) issues = elem.validate() assert len(issues) == 0 # ============= TableElement 测试 ============= class TestTableElement: """TableElement 测试类""" def test_create_with_defaults(self): """测试使用默认值创建 TableElement""" elem = TableElement(data=[["A", "B"]]) assert elem.type == 'table' assert elem.data == [["A", "B"]] assert elem.position == [1, 1] assert elem.col_widths == [] assert elem.style == {} def test_empty_data_raises_error(self): """测试空数据会引发错误""" with pytest.raises(ValueError, match="表格数据不能为空"): TableElement(data=[]) def test_position_must_be_list_of_two(self): """测试 position 必须是包含 2 个数字的列表""" with pytest.raises(ValueError, match="position 必须是包含 2 个数字的列表"): TableElement(data=[["A"]], position=[1]) def test_validate_inconsistent_columns(self): """测试行列数不一致""" elem = TableElement(data=[ ["A", "B", "C"], ["X", "Y"], # 只有 2 列 ["1", "2", "3"] ]) issues = elem.validate() assert len(issues) == 1 assert issues[0].level == "ERROR" assert issues[0].code == "TABLE_INCONSISTENT_COLUMNS" def test_validate_col_widths_mismatch(self): """测试 col_widths 与列数不匹配""" elem = TableElement( data=[["A", "B"], ["C", "D"]], col_widths=[1, 2, 3] # 3 列但数据只有 2 列 ) issues = elem.validate() assert len(issues) == 1 assert issues[0].level == "WARNING" assert issues[0].code == "TABLE_COL_WIDTHS_MISMATCH" def test_validate_consistent_table(self): """测试一致的表格数据""" elem = TableElement( data=[["A", "B"], ["C", "D"]], col_widths=[2, 2] ) issues = elem.validate() assert len(issues) == 0 # ============= create_element 工厂函数测试 ============= class TestCreateElement: """create_element 工厂函数测试类""" def test_create_text_element(self): """测试创建文本元素""" elem = create_element({"type": "text", "content": "Test"}) assert isinstance(elem, TextElement) assert elem.content == "Test" def test_create_image_element(self): """测试创建图片元素""" elem = create_element({"type": "image", "src": "test.png"}) assert isinstance(elem, ImageElement) assert elem.src == "test.png" def test_create_shape_element(self): """测试创建形状元素""" elem = create_element({"type": "shape", "shape": "ellipse"}) assert isinstance(elem, ShapeElement) assert elem.shape == "ellipse" def test_create_table_element(self): """测试创建表格元素""" elem = create_element({"type": "table", "data": [["A", "B"]]}) assert isinstance(elem, TableElement) assert elem.data == [["A", "B"]] def test_unsupported_type_raises_error(self): """测试不支持的元素类型""" with pytest.raises(ValueError, match="不支持的元素类型"): create_element({"type": "video"}) def test_missing_type_raises_error(self): """测试缺少 type 字段""" with pytest.raises(ValueError, match="不支持的元素类型"): create_element({"content": "Test"}) # ============= _is_valid_color 工具函数测试 ============= class TestIsValidColor: """_is_valid_color 工具函数测试类""" def test_valid_full_hex_color(self): """测试完整的 #RRGGBB 格式""" assert _is_valid_color("#ffffff") is True assert _is_valid_color("#000000") is True assert _is_valid_color("#4a90e2") is True assert _is_valid_color("#FF0000") is True def test_valid_short_hex_color(self): """测试短格式 #RGB""" assert _is_valid_color("#fff") is True assert _is_valid_color("#000") is True assert _is_valid_color("#abc") is True def test_invalid_colors(self): """测试无效颜色""" assert _is_valid_color("red") is False assert _is_valid_color("#gggggg") is False assert _is_valid_color("ffffff") is False # 缺少 # assert _is_valid_color("#ffff") is False # 4 位 assert _is_valid_color("#fffff") is False # 5 位 assert _is_valid_color("") is False # ============= 边界情况补充测试 ============= class TestTextElementBoundaryCases: """TextElement 边界情况测试""" def test_text_with_very_long_content(self): """测试非常长的文本内容""" long_content = "A" * 1000 elem = TextElement( content=long_content, box=[0, 0, 5, 1], font={"size": 12} ) assert elem.content == long_content def test_text_with_newline_characters(self): """测试包含换行符的文本""" elem = TextElement( content="Line 1\nLine 2\nLine 3", box=[0, 0, 5, 2], font={} ) assert "\n" in elem.content def test_text_with_tab_characters(self): """测试包含制表符的文本""" elem = TextElement( content="Col1\tCol2\tCol3", box=[0, 0, 5, 1], font={} ) assert "\t" in elem.content def test_text_empty_font_size(self): """测试空字体大小""" elem = TextElement( content="Test", box=[0, 0, 1, 1], font={} ) # 默认值应该有效 assert "size" not in elem.font def test_text_with_color_variations(self): """测试不同颜色格式""" # 短格式应该被 _is_valid_color 接受,但元素也接受 valid_colors = ["#fff", "#FFF", "#000", "#abc", "#ABC"] for color in valid_colors: elem = TextElement( content="Test", box=[0, 0, 1, 1], font={"color": color} ) assert elem.font["color"] == color class TestTableElementBoundaryCases: """TableElement 边界情况测试""" def test_table_with_single_cell(self): """测试单单元格表格""" elem = TableElement( data=[["Single Cell"]], position=[0, 0] ) assert len(elem.data) == 1 assert len(elem.data[0]) == 1 def test_table_with_many_columns(self): """测试多列表格""" many_cols = ["Col" + str(i) for i in range(20)] elem = TableElement( data=[many_cols], position=[0, 0], col_widths=[1] * 20 ) assert len(elem.data[0]) == 20 def test_table_with_single_row(self): """测试单行表格""" elem = TableElement( data=[["A", "B", "C"]], position=[0, 0] ) assert len(elem.data) == 1 def test_table_with_many_rows(self): """测试多行表格""" many_rows = [["Row" + str(i)] * 3 for i in range(50)] elem = TableElement( data=many_rows, position=[0, 0], col_widths=[1, 1, 1] ) assert len(elem.data) == 50 class TestShapeElementBoundaryCases: """ShapeElement 边界情况测试""" def test_shape_without_line_attribute(self): """测试无边框的形状""" elem = ShapeElement( box=[1, 1, 2, 1], shape="rectangle", fill="#000000", line=None ) assert elem.line is None def test_shape_with_empty_line(self): """测试空 line 字典""" elem = ShapeElement( box=[1, 1, 2, 1], shape="rectangle", fill="#000000", line={} ) assert elem.line == {} def test_shape_all_shape_types(self): """测试所有支持的形状类型""" shapes = ["rectangle", "ellipse", "rounded_rectangle"] for shape_type in shapes: elem = ShapeElement( box=[1, 1, 1, 1], shape=shape_type, fill="#000000" ) assert elem.shape == shape_type class TestImageElementBoundaryCases: """ImageElement 边界情况测试""" def test_image_with_relative_path(self): """测试相对路径""" elem = ImageElement( box=[0, 0, 1, 1], src="images/test.png" ) assert "images/test.png" == elem.src def test_image_with_absolute_path(self): """测试绝对路径""" elem = ImageElement( box=[0, 0, 1, 1], src="/absolute/path/image.png" ) assert elem.src == "/absolute/path/image.png" def test_image_with_windows_path(self): """测试 Windows 路径""" elem = ImageElement( box=[0, 0, 1, 1], src="C:\\Images\\test.png" ) assert elem.src == "C:\\Images\\test.png" def test_image_with_special_characters_in_filename(self): """测试文件名包含特殊字符""" special_names = [ "image with spaces.png", "image-with-dashes.png", "image_with_underscores.png", "image.with.dots.png" ] for name in special_names: elem = ImageElement( box=[0, 0, 1, 1], src=name ) assert elem.src == name