- 添加 PptxGenerator._set_notes() 方法设置备注 - 在 add_slide() 中调用 _set_notes() 处理 description - 仅幻灯片级别的 description 写入备注,不继承模板 - 添加备注功能测试用例(8个测试) - 更新 README.md 和 README_DEV.md 文档 - 新建 pptx-slide-notes spec - 更新 page-description spec 允许写入备注 - 归档 add-slide-notes 变更
698 lines
25 KiB
Python
698 lines
25 KiB
Python
"""
|
||
PPTX 渲染器单元测试
|
||
|
||
测试 PptxGenerator 类的初始化和渲染方法
|
||
"""
|
||
|
||
import pytest
|
||
from pathlib import Path
|
||
from unittest.mock import Mock, MagicMock, patch
|
||
from renderers.pptx_renderer import PptxGenerator
|
||
from loaders.yaml_loader import YAMLError
|
||
from core.elements import TextElement, ImageElement, ShapeElement, TableElement
|
||
|
||
|
||
class TestPptxGeneratorInit:
|
||
"""PptxGenerator 初始化测试类"""
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_init_with_16_9_size(self, mock_prs_class):
|
||
"""测试使用 16:9 尺寸初始化"""
|
||
mock_prs = Mock()
|
||
mock_prs_class.return_value = mock_prs
|
||
|
||
gen = PptxGenerator(size="16:9")
|
||
|
||
assert gen.prs == mock_prs
|
||
# 验证属性被设置(具体值由 Inches 决定)
|
||
assert hasattr(mock_prs, "slide_width")
|
||
assert hasattr(mock_prs, "slide_height")
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_init_with_4_3_size(self, mock_prs_class):
|
||
"""测试使用 4:3 尺寸初始化"""
|
||
mock_prs = Mock()
|
||
mock_prs_class.return_value = mock_prs
|
||
|
||
gen = PptxGenerator(size="4:3")
|
||
|
||
assert gen.prs == mock_prs
|
||
assert hasattr(mock_prs, "slide_width")
|
||
assert hasattr(mock_prs, "slide_height")
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_init_with_default_size(self, mock_prs_class):
|
||
"""测试默认尺寸"""
|
||
mock_prs = Mock()
|
||
mock_prs_class.return_value = mock_prs
|
||
|
||
gen = PptxGenerator()
|
||
|
||
assert gen.prs == mock_prs
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_init_with_invalid_size_raises_error(self, mock_prs_class):
|
||
"""测试无效尺寸引发错误"""
|
||
from loaders.yaml_loader import YAMLError
|
||
|
||
with pytest.raises(YAMLError, match="不支持的尺寸比例"):
|
||
PptxGenerator(size="21:9")
|
||
|
||
|
||
class TestAddSlide:
|
||
"""add_slide 方法测试类"""
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_add_slide_creates_slide(self, mock_prs_class):
|
||
"""测试添加幻灯片"""
|
||
mock_prs = Mock()
|
||
mock_layout = Mock()
|
||
mock_prs.slide_layouts = [None] * 6 + [mock_layout] + [None]
|
||
mock_prs.slides.add_slide.return_value = Mock()
|
||
mock_prs_class.return_value = mock_prs
|
||
|
||
gen = PptxGenerator()
|
||
slide_data = {"background": None, "elements": []}
|
||
|
||
gen.add_slide(slide_data)
|
||
|
||
# 验证添加了幻灯片
|
||
mock_prs.slides.add_slide.assert_called_once_with(mock_layout)
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_add_slide_with_background(self, mock_prs_class):
|
||
"""测试添加带背景的幻灯片"""
|
||
mock_prs = Mock()
|
||
mock_slide = Mock()
|
||
mock_slide.background = Mock()
|
||
mock_slide.background.fill = Mock()
|
||
mock_prs.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs.slides.add_slide.return_value = mock_slide
|
||
mock_prs_class.return_value = mock_prs
|
||
|
||
gen = PptxGenerator()
|
||
slide_data = {"background": {"color": "#ffffff"}, "elements": []}
|
||
|
||
gen.add_slide(slide_data)
|
||
|
||
# 验证背景被设置
|
||
assert (
|
||
mock_slide.background.fill.solid.called
|
||
or mock_slide.background.fill.fore_color_rgb is not None
|
||
)
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_add_slide_without_background(self, mock_prs_class):
|
||
"""测试添加无背景的幻灯片"""
|
||
mock_prs = Mock()
|
||
mock_slide = Mock()
|
||
mock_prs.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs.slides.add_slide.return_value = mock_slide
|
||
mock_prs_class.return_value = mock_prs
|
||
|
||
gen = PptxGenerator()
|
||
slide_data = {"background": None, "elements": []}
|
||
|
||
gen.add_slide(slide_data)
|
||
|
||
# 不应该崩溃
|
||
assert True
|
||
|
||
|
||
class TestRenderText:
|
||
"""_render_text 方法测试类"""
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_text_element(self, mock_prs_class):
|
||
"""测试渲染文本元素"""
|
||
mock_slide = self._setup_mock_slide()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
elem = TextElement(
|
||
content="Test Content",
|
||
box=[1, 2, 3, 1],
|
||
font={"size": 18, "bold": True, "color": "#333333", "align": "center"},
|
||
)
|
||
|
||
gen._render_text(mock_slide, elem)
|
||
|
||
# 验证添加了文本框
|
||
mock_slide.shapes.add_textbox.assert_called_once()
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_text_with_word_wrap(self, mock_prs_class):
|
||
"""测试文本自动换行"""
|
||
mock_slide = self._setup_mock_slide()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
elem = TextElement(content="Long text", box=[0, 0, 1, 1], font={})
|
||
|
||
gen._render_text(mock_slide, elem)
|
||
|
||
# 验证文本框被创建
|
||
mock_slide.shapes.add_textbox.assert_called_once()
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_multiline_text_applies_font_size_to_all_paragraphs(self, mock_prs_class):
|
||
"""测试多行文本字体大小应用到所有段落"""
|
||
# 创建包含 3 个段落的 mock slide
|
||
mock_slide = self._setup_mock_slide(num_paragraphs=3)
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
# 多行文本(包含换行符)
|
||
elem = TextElement(
|
||
content="第一行\n第二行\n第三行",
|
||
box=[1, 2, 3, 1],
|
||
font={"size": 12},
|
||
)
|
||
|
||
gen._render_text(mock_slide, elem)
|
||
|
||
# 验证所有 3 个段落的字体大小都被设置
|
||
mock_tf = mock_slide.shapes.add_textbox.return_value.text_frame
|
||
assert len(mock_tf.paragraphs) == 3
|
||
for para in mock_tf.paragraphs:
|
||
para.font.size = 12 # Mock 会记录这个调用
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_multiline_text_applies_all_styles_to_all_paragraphs(self, mock_prs_class):
|
||
"""测试多行文本所有样式应用到所有段落"""
|
||
# 创建包含 3 个段落的 mock slide
|
||
mock_slide = self._setup_mock_slide(num_paragraphs=3)
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
# 多行文本,包含所有样式属性
|
||
elem = TextElement(
|
||
content="第一行\n第二行\n第三行",
|
||
box=[1, 2, 3, 1],
|
||
font={"size": 14, "bold": True, "italic": True, "color": "#ff0000", "align": "center"},
|
||
)
|
||
|
||
gen._render_text(mock_slide, elem)
|
||
|
||
# 验证所有段落的样式都被设置
|
||
mock_tf = mock_slide.shapes.add_textbox.return_value.text_frame
|
||
assert len(mock_tf.paragraphs) == 3
|
||
for para in mock_tf.paragraphs:
|
||
# 验证样式属性被访问(mock 会记录这些调用)
|
||
_ = para.font.size
|
||
_ = para.font.bold
|
||
_ = para.font.italic
|
||
_ = para.font.color.rgb
|
||
_ = para.alignment
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_single_line_text_unchanged_behavior(self, mock_prs_class):
|
||
"""测试单行文本行为不变(回归测试)"""
|
||
# 单行文本只需要 1 个段落
|
||
mock_slide = self._setup_mock_slide(num_paragraphs=1)
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
# 单行文本
|
||
elem = TextElement(
|
||
content="单行文本",
|
||
box=[1, 2, 3, 1],
|
||
font={"size": 18},
|
||
)
|
||
|
||
gen._render_text(mock_slide, elem)
|
||
|
||
# 验证文本框被创建
|
||
mock_slide.shapes.add_textbox.assert_called_once()
|
||
# 验证字体大小被设置
|
||
mock_tf = mock_slide.shapes.add_textbox.return_value.text_frame
|
||
assert len(mock_tf.paragraphs) == 1
|
||
_ = mock_tf.paragraphs[0].font.size
|
||
|
||
def _setup_mock_slide(self, num_paragraphs=1):
|
||
"""辅助函数:创建 mock slide
|
||
|
||
Args:
|
||
num_paragraphs: 要创建的段落数量,默认为 1
|
||
"""
|
||
mock_slide = Mock()
|
||
mock_text_frame = Mock()
|
||
mock_text_frame.word_wrap = True
|
||
|
||
# 创建指定数量的 mock 段落
|
||
mock_paragraphs = []
|
||
for _ in range(num_paragraphs):
|
||
mock_paragraph = Mock()
|
||
mock_paragraph.font = Mock()
|
||
mock_paragraphs.append(mock_paragraph)
|
||
|
||
mock_text_frame.paragraphs = mock_paragraphs
|
||
|
||
mock_textbox = Mock()
|
||
mock_textbox.text_frame = mock_text_frame
|
||
mock_slide.shapes.add_textbox.return_value = mock_textbox
|
||
return mock_slide
|
||
|
||
|
||
class TestRenderImage:
|
||
"""_render_image 方法测试类"""
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_image_element(self, mock_prs_class, temp_dir, sample_image):
|
||
"""测试渲染图片元素"""
|
||
mock_slide = Mock()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
elem = ImageElement(box=[1, 1, 4, 3], src=sample_image.name)
|
||
|
||
gen._render_image(mock_slide, elem, temp_dir)
|
||
|
||
# 验证添加了图片
|
||
mock_slide.shapes.add_picture.assert_called_once()
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_image_nonexistent_file(self, mock_prs_class):
|
||
"""测试不存在的图片文件"""
|
||
mock_slide = Mock()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
elem = ImageElement(box=[1, 1, 4, 3], src="nonexistent.png")
|
||
|
||
with pytest.raises(YAMLError, match="图片文件未找到"):
|
||
gen._render_image(mock_slide, elem, None)
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_image_with_relative_path(
|
||
self, mock_prs_class, temp_dir, sample_image
|
||
):
|
||
"""测试相对路径图片"""
|
||
mock_slide = Mock()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
elem = ImageElement(box=[1, 1, 4, 3], src=sample_image.name)
|
||
|
||
gen._render_image(mock_slide, elem, temp_dir)
|
||
|
||
mock_slide.shapes.add_picture.assert_called_once()
|
||
|
||
|
||
class TestRenderShape:
|
||
"""_render_shape 方法测试类"""
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_rectangle(self, mock_prs_class):
|
||
"""测试渲染矩形"""
|
||
mock_slide = self._setup_mock_slide_for_shape()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
elem = ShapeElement(box=[1, 1, 2, 1], shape="rectangle", fill="#4a90e2")
|
||
|
||
gen._render_shape(mock_slide, elem)
|
||
|
||
mock_slide.shapes.add_shape.assert_called_once()
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_ellipse(self, mock_prs_class):
|
||
"""测试渲染椭圆"""
|
||
mock_slide = self._setup_mock_slide_for_shape()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
elem = ShapeElement(box=[1, 1, 2, 2], shape="ellipse", fill="#e24a4a")
|
||
|
||
gen._render_shape(mock_slide, elem)
|
||
|
||
mock_slide.shapes.add_shape.assert_called_once()
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_shape_with_line(self, mock_prs_class):
|
||
"""测试带边框的形状"""
|
||
mock_slide = self._setup_mock_slide_for_shape()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
elem = ShapeElement(
|
||
box=[1, 1, 2, 1],
|
||
shape="rectangle",
|
||
fill="#4a90e2",
|
||
line={"color": "#000000", "width": 2},
|
||
)
|
||
|
||
gen._render_shape(mock_slide, elem)
|
||
|
||
mock_slide.shapes.add_shape.assert_called_once()
|
||
|
||
def _setup_mock_slide_for_shape(self):
|
||
"""辅助函数:创建用于形状渲染的 mock slide"""
|
||
mock_slide = Mock()
|
||
mock_shape = Mock()
|
||
mock_shape.fill = Mock()
|
||
mock_shape.fill.solid = Mock()
|
||
mock_shape.line = Mock()
|
||
mock_slide.shapes.add_shape.return_value = mock_shape
|
||
return mock_slide
|
||
|
||
|
||
class TestRenderTable:
|
||
"""_render_table 方法测试类"""
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_table(self, mock_prs_class):
|
||
"""测试渲染表格"""
|
||
mock_slide = self._setup_mock_slide_for_table()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
elem = TableElement(
|
||
position=[1, 1],
|
||
col_widths=[2, 2, 2],
|
||
data=[["A", "B", "C"], ["1", "2", "3"]],
|
||
style={"font_size": 14},
|
||
)
|
||
|
||
gen._render_table(mock_slide, elem)
|
||
|
||
# 验证添加了表格
|
||
mock_slide.shapes.add_table.assert_called_once()
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_table_with_header_style(self, mock_prs_class):
|
||
"""测试带表头样式的表格"""
|
||
mock_slide = self._setup_mock_slide_for_table()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
elem = TableElement(
|
||
position=[1, 1],
|
||
col_widths=[2, 2],
|
||
data=[["H1", "H2"], ["D1", "D2"]],
|
||
style={"font_size": 14, "header_bg": "#4a90e2", "header_color": "#ffffff"},
|
||
)
|
||
|
||
gen._render_table(mock_slide, elem)
|
||
|
||
mock_slide.shapes.add_table.assert_called_once()
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_table_col_widths_mismatch(self, mock_prs_class):
|
||
"""测试列宽不匹配"""
|
||
mock_slide = self._setup_mock_slide_for_table()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
elem = TableElement(
|
||
position=[1, 1],
|
||
col_widths=[2, 3, 4], # 3 列
|
||
data=[["A", "B"]], # 2 列数据
|
||
style={},
|
||
)
|
||
|
||
with pytest.raises(YAMLError, match="列宽数量"):
|
||
gen._render_table(mock_slide, elem)
|
||
|
||
def _setup_mock_slide_for_table(self):
|
||
"""辅助函数:创建用于表格渲染的 mock slide"""
|
||
mock_slide = Mock()
|
||
|
||
# 设置 columns 属性,支持索引访问
|
||
class MockColumns:
|
||
def __init__(self, count):
|
||
self._cols = [Mock() for _ in range(count)]
|
||
|
||
def __getitem__(self, i):
|
||
return self._cols[i]
|
||
|
||
mock_table = Mock()
|
||
mock_table.columns = MockColumns(3)
|
||
|
||
# 设置 add_table 返回值(包含 .table 属性)
|
||
mock_add_table_result = Mock()
|
||
mock_add_table_result.table = mock_table
|
||
mock_slide.shapes.add_table.return_value = mock_add_table_result
|
||
|
||
# 设置 rows
|
||
mock_table.rows = [Mock()]
|
||
for row in mock_table.rows:
|
||
row.cells = [Mock()]
|
||
for cell in row.cells:
|
||
cell.text_frame = Mock()
|
||
cell.text_frame.paragraphs = [Mock()]
|
||
cell.text_frame.paragraphs[0].font = Mock()
|
||
|
||
return mock_slide
|
||
|
||
|
||
class TestSave:
|
||
"""save 方法测试类"""
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_save_creates_directory(self, mock_prs_class, tmp_path):
|
||
"""测试保存时创建目录"""
|
||
mock_prs = Mock()
|
||
mock_prs_class.return_value = mock_prs
|
||
|
||
gen = PptxGenerator()
|
||
output_dir = tmp_path / "subdir" / "nested"
|
||
output_path = output_dir / "output.pptx"
|
||
|
||
gen.save(output_path)
|
||
|
||
# 验证目录被创建
|
||
assert output_dir.exists()
|
||
mock_prs.save.assert_called_once()
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_save_existing_file(self, mock_prs_class, tmp_path):
|
||
"""测试保存已存在的文件"""
|
||
mock_prs = Mock()
|
||
mock_prs_class.return_value = mock_prs
|
||
|
||
gen = PptxGenerator()
|
||
output_path = tmp_path / "output.pptx"
|
||
|
||
gen.save(output_path)
|
||
|
||
mock_prs.save.assert_called_once_with(str(output_path))
|
||
|
||
|
||
class TestRenderBackground:
|
||
"""_render_background 方法测试类"""
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_solid_background(self, mock_prs_class):
|
||
"""测试纯色背景"""
|
||
mock_slide = Mock()
|
||
mock_slide.background = Mock()
|
||
mock_slide.background.fill = Mock()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
background = {"color": "#ffffff"}
|
||
|
||
gen._render_background(mock_slide, background)
|
||
|
||
# 验证背景被设置
|
||
assert mock_slide.background.fill.solid.called
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_render_no_background(self, mock_prs_class):
|
||
"""测试无背景"""
|
||
mock_slide = Mock()
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
|
||
# 不应该崩溃
|
||
gen._render_background(mock_slide, None)
|
||
|
||
|
||
class TestSetNotes:
|
||
"""_set_notes 方法测试类"""
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_set_notes_with_text(self, mock_prs_class):
|
||
"""测试设置备注文本"""
|
||
mock_slide = Mock()
|
||
mock_notes_slide = Mock()
|
||
mock_text_frame = Mock()
|
||
mock_notes_slide.notes_text_frame = mock_text_frame
|
||
mock_slide.notes_slide = mock_notes_slide
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
|
||
gen._set_notes(mock_slide, "这是演讲备注内容")
|
||
|
||
# 验证备注被设置
|
||
mock_text_frame.text = "这是演讲备注内容"
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_set_notes_with_none(self, mock_prs_class):
|
||
"""测试设置 None 不设置备注"""
|
||
mock_slide = Mock(spec=[]) # 使用 spec=[] 禁止自动创建属性
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
|
||
# 不应该崩溃,不应该访问 notes_slide
|
||
gen._set_notes(mock_slide, None)
|
||
# 使用 spec=[] 后,访问不存在的属性会抛出 AttributeError
|
||
# 如果没有抛出异常,说明 notes_slide 没有被访问
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_set_notes_with_empty_string(self, mock_prs_class):
|
||
"""测试设置空字符串"""
|
||
mock_slide = Mock()
|
||
mock_notes_slide = Mock()
|
||
mock_text_frame = Mock()
|
||
mock_notes_slide.notes_text_frame = mock_text_frame
|
||
mock_slide.notes_slide = mock_notes_slide
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
|
||
gen._set_notes(mock_slide, "")
|
||
|
||
# 验证空字符串也被设置
|
||
mock_text_frame.text = ""
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_set_notes_with_multiline_text(self, mock_prs_class):
|
||
"""测试设置多行文本"""
|
||
mock_slide = Mock()
|
||
mock_notes_slide = Mock()
|
||
mock_text_frame = Mock()
|
||
mock_notes_slide.notes_text_frame = mock_text_frame
|
||
mock_slide.notes_slide = mock_notes_slide
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
|
||
multiline_text = "第一行备注\n第二行备注\n第三行备注"
|
||
gen._set_notes(mock_slide, multiline_text)
|
||
|
||
# 验证多行文本被设置
|
||
mock_text_frame.text = multiline_text
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_set_notes_with_chinese_text(self, mock_prs_class):
|
||
"""测试设置中文备注"""
|
||
mock_slide = Mock()
|
||
mock_notes_slide = Mock()
|
||
mock_text_frame = Mock()
|
||
mock_notes_slide.notes_text_frame = mock_text_frame
|
||
mock_slide.notes_slide = mock_notes_slide
|
||
mock_prs_class.return_value = Mock()
|
||
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
|
||
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
|
||
|
||
gen = PptxGenerator()
|
||
|
||
chinese_text = "这是中文演讲备注内容,用于演示时参考"
|
||
gen._set_notes(mock_slide, chinese_text)
|
||
|
||
# 验证中文文本被设置
|
||
mock_text_frame.text = chinese_text
|
||
|
||
|
||
class TestAddSlideWithNotes:
|
||
"""add_slide 方法备注功能测试类"""
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_add_slide_with_description_sets_notes(self, mock_prs_class):
|
||
"""测试幻灯片包含 description 时设置备注"""
|
||
mock_slide = Mock()
|
||
mock_notes_slide = Mock()
|
||
mock_text_frame = Mock()
|
||
mock_notes_slide.notes_text_frame = mock_text_frame
|
||
mock_slide.notes_slide = mock_notes_slide
|
||
mock_prs = Mock()
|
||
mock_prs.slide_layouts = [None] * 6 + [Mock()] + [None]
|
||
mock_prs.slides.add_slide.return_value = mock_slide
|
||
mock_prs_class.return_value = mock_prs
|
||
|
||
gen = PptxGenerator()
|
||
slide_data = {"background": None, "elements": [], "description": "这是幻灯片的演讲说明"}
|
||
|
||
gen.add_slide(slide_data)
|
||
|
||
# 验证备注被设置
|
||
mock_text_frame.text = "这是幻灯片的演讲说明"
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_add_slide_without_description_no_notes(self, mock_prs_class):
|
||
"""测试幻灯片不包含 description 时不设置备注"""
|
||
mock_slide = Mock(spec=[]) # 禁止自动创建属性
|
||
mock_prs = Mock()
|
||
mock_prs.slide_layouts = [None] * 6 + [Mock()] + [None]
|
||
mock_prs.slides.add_slide.return_value = mock_slide
|
||
mock_prs_class.return_value = mock_prs
|
||
|
||
gen = PptxGenerator()
|
||
slide_data = {"background": None, "elements": []}
|
||
|
||
gen.add_slide(slide_data)
|
||
|
||
# 不应该访问 notes_slide(使用 spec=[] 会抛出异常如果被访问)
|
||
|
||
@patch("renderers.pptx_renderer.PptxPresentation")
|
||
def test_add_slide_with_template_description_ignored(self, mock_prs_class):
|
||
"""测试模板的 description 不被用作备注(仅幻灯片自己的 description 有效)"""
|
||
mock_slide = Mock(spec=[]) # 禁止自动创建属性
|
||
mock_prs = Mock()
|
||
mock_prs.slide_layouts = [None] * 6 + [Mock()] + [None]
|
||
mock_prs.slides.add_slide.return_value = mock_slide
|
||
mock_prs_class.return_value = mock_prs
|
||
|
||
gen = PptxGenerator()
|
||
# 幻灯片没有 description,则不设置备注(即使模板可能有 description)
|
||
slide_data = {"background": None, "elements": []}
|
||
|
||
gen.add_slide(slide_data)
|
||
|
||
# 不应该访问 notes_slide(使用 spec=[] 会抛出异常如果被访问)
|