1
0
Files
PPTX/tests/unit/test_renderers/test_html_renderer.py
lanyuanxiaoyao f34405be36 feat: 移除图片适配模式功能
移除图片 fit 和 background 参数支持,简化图片渲染逻辑。系统恢复到直接使用 python-pptx 原生图片添加功能,图片将被拉伸到指定尺寸。

变更内容:
- 移除 ImageElement 的 fit 和 background 字段
- 移除 metadata.dpi 配置
- 删除 utils/image_utils.py 图片处理工具模块
- 删除 validators/image_config.py 验证器
- 简化 PPTX 和 HTML 渲染器的图片处理逻辑
- HTML 渲染器使用硬编码 DPI=96(Web 标准)
- 删除相关测试文件(单元测试、集成测试、e2e 测试)
- 更新规格文档和用户文档
- 保留 Pillow 依赖用于未来可能的图片处理需求

影响:
- 删除 11 个文件
- 修改 10 个文件
- 净减少 1558 行代码
- 所有 402 个测试通过

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-04 14:23:12 +08:00

522 lines
16 KiB
Python

"""
HTML 渲染器单元测试
测试 HtmlRenderer 类的各个渲染方法
"""
import pytest
from pathlib import Path
from renderers.html_renderer import HtmlRenderer
from core.elements import TextElement, ImageElement, ShapeElement, TableElement
class TestHtmlRenderer:
"""HtmlRenderer 测试类"""
def test_init_renderer(self):
"""测试创建渲染器"""
renderer = HtmlRenderer()
assert renderer is not None
assert renderer.dpi == 96 # 硬编码的 DPI
class TestRenderText:
"""render_text 方法测试类"""
def test_render_text_basic(self):
"""测试渲染基本文本"""
renderer = HtmlRenderer()
elem = TextElement(
content="Test Content",
box=[1, 2, 3, 0.5],
font={"size": 18, "color": "#333333"},
)
html = renderer.render_text(elem)
assert "Test Content" in html
assert "text-element" in html
assert "left: 96px" in html # 1 * 96
assert "top: 192px" in html # 2 * 96
assert "font-size: 18pt" in html
assert "color: #333333" in html
def test_render_text_with_bold(self):
"""测试渲染粗体文本"""
renderer = HtmlRenderer()
elem = TextElement(
content="Bold Text", box=[0, 0, 1, 1], font={"size": 16, "bold": True}
)
html = renderer.render_text(elem)
assert "font-weight: bold;" in html
def test_render_text_with_italic(self):
"""测试渲染斜体文本"""
renderer = HtmlRenderer()
elem = TextElement(
content="Italic Text", box=[0, 0, 1, 1], font={"size": 16, "italic": True}
)
html = renderer.render_text(elem)
assert "font-style: italic;" in html
def test_render_text_with_center_align(self):
"""测试渲染居中对齐文本"""
renderer = HtmlRenderer()
elem = TextElement(
content="Centered", box=[0, 0, 1, 1], font={"align": "center"}
)
html = renderer.render_text(elem)
assert "text-align: center;" in html
def test_render_text_with_right_align(self):
"""测试渲染右对齐文本"""
renderer = HtmlRenderer()
elem = TextElement(
content="Right Aligned", box=[0, 0, 1, 1], font={"align": "right"}
)
html = renderer.render_text(elem)
assert "text-align: right;" in html
def test_render_text_with_default_align(self):
"""测试默认左对齐"""
renderer = HtmlRenderer()
elem = TextElement(content="Default", box=[0, 0, 1, 1], font={})
html = renderer.render_text(elem)
assert "text-align: left;" in html
def test_render_text_escapes_html(self):
"""测试 HTML 特殊字符转义"""
renderer = HtmlRenderer()
elem = TextElement(
content="<script>alert('xss')</script>", box=[0, 0, 1, 1], font={}
)
html = renderer.render_text(elem)
assert "&lt;" in html
assert "&gt;" in html
assert "<script>" not in html
def test_render_text_with_special_characters(self):
"""测试特殊字符处理"""
renderer = HtmlRenderer()
elem = TextElement(content="Test & < > \" '", box=[0, 0, 1, 1], font={})
html = renderer.render_text(elem)
assert "&amp;" in html
assert "&lt;" in html
assert "&gt;" in html
def test_render_text_with_long_content(self):
"""测试长文本内容"""
renderer = HtmlRenderer()
long_content = "A" * 500
elem = TextElement(content=long_content, box=[0, 0, 5, 1], font={"size": 12})
html = renderer.render_text(elem)
assert long_content in html
assert "overflow-wrap: break-word" in html
def test_render_text_with_newlines(self):
"""测试包含换行符的文本"""
renderer = HtmlRenderer()
elem = TextElement(
content="Line 1\nLine 2\nLine 3", box=[0, 0, 5, 2], font={"size": 14}
)
html = renderer.render_text(elem)
# 换行符应该被保留
assert "Line 1" in html
assert "Line 2" in html
def test_render_text_with_unicode(self):
"""测试 Unicode 字符"""
renderer = HtmlRenderer()
elem = TextElement(content="测试中文 🌍", box=[0, 0, 5, 1], font={"size": 16})
html = renderer.render_text(elem)
assert "测试中文" in html
assert "🌍" in html
def test_render_text_with_empty_font(self):
"""测试空字体属性"""
renderer = HtmlRenderer()
elem = TextElement(content="Test", box=[0, 0, 1, 1], font={})
html = renderer.render_text(elem)
# 应该使用默认值
assert "font-size: 16pt" in html
assert "color: #000000" in html
class TestRenderShape:
"""render_shape 方法测试类"""
def test_render_rectangle(self):
"""测试渲染矩形"""
renderer = HtmlRenderer()
elem = ShapeElement(box=[1, 1, 2, 1], shape="rectangle", fill="#4a90e2")
html = renderer.render_shape(elem)
assert "shape-element" in html
assert "background: #4a90e2" in html
assert "border-radius: 0;" in html
def test_render_ellipse(self):
"""测试渲染椭圆"""
renderer = HtmlRenderer()
elem = ShapeElement(box=[1, 1, 2, 2], shape="ellipse", fill="#e24a4a")
html = renderer.render_shape(elem)
assert "border-radius: 50%" in html
assert "background: #e24a4a" in html
def test_render_rounded_rectangle(self):
"""测试渲染圆角矩形"""
renderer = HtmlRenderer()
elem = ShapeElement(box=[1, 1, 2, 1], shape="rounded_rectangle", fill="#4ae290")
html = renderer.render_shape(elem)
assert "border-radius: 8px" in html
assert "background: #4ae290" in html
def test_render_shape_without_fill(self):
"""测试无填充颜色的形状"""
renderer = HtmlRenderer()
elem = ShapeElement(box=[1, 1, 2, 1], shape="rectangle", fill=None)
html = renderer.render_shape(elem)
assert "background: transparent" in html
def test_render_shape_with_line(self):
"""测试带边框的形状"""
renderer = HtmlRenderer()
elem = ShapeElement(
box=[1, 1, 2, 1],
shape="rectangle",
fill="#4a90e2",
line={"color": "#000000", "width": 2},
)
html = renderer.render_shape(elem)
assert "border: 2pt solid #000000" in html
def test_render_shape_with_line_default_width(self):
"""测试边框默认宽度"""
renderer = HtmlRenderer()
elem = ShapeElement(
box=[1, 1, 2, 1],
shape="rectangle",
fill="#4a90e2",
line={"color": "#000000"},
)
html = renderer.render_shape(elem)
assert "border: 1pt solid #000000" in html
def test_render_shape_without_line(self):
"""测试无边框的形状"""
renderer = HtmlRenderer()
elem = ShapeElement(box=[1, 1, 2, 1], shape="rectangle", fill="#4a90e2")
html = renderer.render_shape(elem)
assert "border:" not in html
def test_render_shape_position(self):
"""测试形状位置计算"""
renderer = HtmlRenderer()
elem = ShapeElement(box=[1.5, 2.5, 3, 1.5], shape="rectangle", fill="#000000")
html = renderer.render_shape(elem)
# 1.5 * 96 = 144
assert "left: 144" in html
# 2.5 * 96 = 240
assert "top: 240" in html
# 3 * 96 = 288
assert "width: 288" in html
# 1.5 * 96 = 144
assert "height: 144" in html
class TestRenderTable:
"""render_table 方法测试类"""
def test_render_basic_table(self):
"""测试渲染基本表格"""
renderer = HtmlRenderer()
elem = TableElement(
position=[1, 1],
col_widths=[2, 2, 2],
data=[["A", "B", "C"], ["1", "2", "3"]],
style={},
)
html = renderer.render_table(elem)
assert "table-element" in html
assert "A" in html
assert "B" in html
assert "C" in html
assert "1" in html
assert "2" in html
assert "3" in html
def test_render_table_with_header_style(self):
"""测试表格表头样式"""
renderer = HtmlRenderer()
elem = TableElement(
position=[1, 1],
col_widths=[2, 2],
data=[["H1", "H2"], ["D1", "D2"]],
style={"font_size": 14, "header_bg": "#4a90e2", "header_color": "#ffffff"},
)
html = renderer.render_table(elem)
assert "background: #4a90e2" in html
assert "color: #ffffff" in html
assert "font-size: 14pt" in html
def test_render_table_position(self):
"""测试表格位置"""
renderer = HtmlRenderer()
elem = TableElement(
position=[2, 3], col_widths=[1, 1], data=[["A", "B"]], style={}
)
html = renderer.render_table(elem)
# 2 * 96 = 192
assert "left: 192px" in html
# 3 * 96 = 288
assert "top: 288px" in html
def test_render_table_with_default_font_size(self):
"""测试默认字体大小"""
renderer = HtmlRenderer()
elem = TableElement(position=[0, 0], col_widths=[1], data=[["Cell"]], style={})
html = renderer.render_table(elem)
assert "font-size: 14pt" in html
def test_render_table_escapes_content(self):
"""测试表格内容转义"""
renderer = HtmlRenderer()
elem = TableElement(
position=[0, 0], col_widths=[1], data=[["<script>"]], style={}
)
html = renderer.render_table(elem)
assert "&lt;" in html
assert "<script>" not in html
def test_render_table_with_single_row(self):
"""测试单行表格"""
renderer = HtmlRenderer()
elem = TableElement(
position=[0, 0], col_widths=[1, 2, 3], data=[["A", "B", "C"]], style={}
)
html = renderer.render_table(elem)
assert "<tr>" in html
assert "A" in html
assert "B" in html
assert "C" in html
def test_render_table_with_many_rows(self):
"""测试多行表格"""
renderer = HtmlRenderer()
elem = TableElement(
position=[0, 0],
col_widths=[1, 1],
data=[["R1C1", "R1C2"], ["R2C1", "R2C2"], ["R3C1", "R3C2"]],
style={},
)
html = renderer.render_table(elem)
assert html.count("<tr>") == 3
assert "R1C1" in html
assert "R2C2" in html
def test_render_table_with_unicode(self):
"""测试表格 Unicode 内容"""
renderer = HtmlRenderer()
elem = TableElement(position=[0, 0], col_widths=[1], data=[["测试"]], style={})
html = renderer.render_table(elem)
assert "测试" in html
class TestRenderImage:
"""render_image 方法测试类"""
def test_render_image_basic(self, temp_dir):
"""测试渲染基本图片"""
renderer = HtmlRenderer()
elem = ImageElement(box=[1, 1, 4, 3], src="test.png")
html = renderer.render_image(elem, temp_dir)
assert "image-element" in html
assert "test.png" in html
assert "left: 96px" in html # 1 * 96
assert "top: 96px" in html
def test_render_image_with_base_path(self, temp_dir):
"""测试带基础路径的图片"""
renderer = HtmlRenderer()
elem = ImageElement(box=[0, 0, 2, 2], src="subdir/image.png")
html = renderer.render_image(elem, temp_dir)
# 图片路径会被转换为绝对路径
assert "file://" in html
def test_render_image_without_base_path(self):
"""测试无基础路径的图片"""
renderer = HtmlRenderer()
elem = ImageElement(box=[0, 0, 2, 2], src="/absolute/path/image.png")
html = renderer.render_image(elem, None)
# 图片路径会被转换为绝对路径
assert "file://" in html
def test_render_image_position_calculation(self):
"""测试图片位置计算"""
renderer = HtmlRenderer()
elem = ImageElement(box=[2.5, 3.5, 4, 3], src="test.png")
html = renderer.render_image(elem, None)
# 2.5 * 96 = 240
assert "left: 240" in html
# 3.5 * 96 = 336
assert "top: 336" in html
# 4 * 96 = 384
assert "width: 384" in html
# 3 * 96 = 288
assert "height: 288" in html
class TestRenderSlide:
"""render_slide 方法测试类"""
def test_render_slide_basic(self):
"""测试渲染基本幻灯片"""
renderer = HtmlRenderer()
slide_data = {
"background": None,
"elements": [TextElement(content="Test", box=[0, 0, 1, 1], font={})],
}
html = renderer.render_slide(slide_data, 0, None)
assert '<div class="slide"' in html
assert "幻灯片 1" in html
assert "Test" in html
def test_render_slide_with_background_color(self):
"""测试带背景颜色的幻灯片"""
renderer = HtmlRenderer()
slide_data = {"background": {"color": "#ffffff"}, "elements": []}
html = renderer.render_slide(slide_data, 0, None)
assert "background: #ffffff" in html
def test_render_slide_with_multiple_elements(self):
"""测试包含多个元素的幻灯片"""
renderer = HtmlRenderer()
slide_data = {
"background": None,
"elements": [
TextElement(content="Text 1", box=[0, 0, 1, 1], font={}),
ShapeElement(box=[2, 2, 1, 1], shape="rectangle", fill="#000"),
],
}
html = renderer.render_slide(slide_data, 0, None)
assert "Text 1" in html
assert "shape-element" in html
def test_render_slide_with_different_indices(self):
"""测试不同幻灯片索引"""
renderer = HtmlRenderer()
slide_data = {"background": None, "elements": []}
html0 = renderer.render_slide(slide_data, 0, None)
html1 = renderer.render_slide(slide_data, 1, None)
html5 = renderer.render_slide(slide_data, 5, None)
assert "幻灯片 1" in html0
assert "幻灯片 2" in html1
assert "幻灯片 6" in html5
def test_render_slide_without_background(self):
"""测试无背景的幻灯片"""
renderer = HtmlRenderer()
slide_data = {"background": None, "elements": []}
html = renderer.render_slide(slide_data, 0, None)
# 应该有 slide div 但没有 background style
assert '<div class="slide"' in html
# 不应该有 background: 样式
assert "background:" not in html
def test_render_slide_empty_elements(self):
"""测试空元素列表"""
renderer = HtmlRenderer()
slide_data = {"background": None, "elements": []}
html = renderer.render_slide(slide_data, 0, None)
assert '<div class="slide"' in html
assert "幻灯片 1" in html
def test_render_slide_with_render_error(self):
"""测试元素渲染错误处理"""
renderer = HtmlRenderer()
# 创建一个不匹配任何已知类型的元素
class UnknownElement:
box = [0, 0, 1, 1]
type = "unknown_type"
slide_data = {"background": None, "elements": [UnknownElement()]}
html = renderer.render_slide(slide_data, 0, None)
# 未知类型不会被渲染,但不会报错
assert '<div class="slide"' in html