1
0

feat: 实现幻灯片备注功能,将description写入PPT备注页

- 添加 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 变更
This commit is contained in:
2026-03-04 14:47:03 +08:00
parent f34405be36
commit 7ef29ea039
12 changed files with 454 additions and 15 deletions

View File

@@ -540,3 +540,158 @@ class TestRenderBackground:
# 不应该崩溃
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=[] 会抛出异常如果被访问)