Download tests/tools/test_pptx_tools.py from Berlm/ppt_agent: direct link, hf CLI and curl.
- Browser
- Download file 11.5 kB
-
https://huggingface.co/spaces/Berlm/ppt_agent/resolve/main/tests/tools/test_pptx_tools.py
- Command line
-
hf download hf://spaces/Berlm/ppt_agent/tests/tools/test_pptx_tools.py
-
curl -L -o test_pptx_tools.py https://huggingface.co/spaces/Berlm/ppt_agent/resolve/main/tests/tools/test_pptx_tools.py
11.5 kB
| import pytest | |
| from pptx.enum.chart import XL_CHART_TYPE | |
| from pptx.enum.shapes import MSO_CONNECTOR | |
| from pptx.util import Inches | |
| from ppt_agent.server.utils.pptx_functions import PptxEditor | |
| def editor(): | |
| return PptxEditor() | |
| def add_blank_slide(editor: PptxEditor) -> int: | |
| return editor.add_slide() | |
| def add_textbox(editor: PptxEditor, slide_index: int, text: str = "sample") -> int: | |
| slide = editor.prs.slides[slide_index] | |
| textbox = slide.shapes.add_textbox(Inches(1), Inches(1), Inches(4), Inches(1)) | |
| textbox.text_frame.text = text | |
| return len(slide.shapes) - 1 | |
| def test_add_slide_creates_slide(editor): | |
| slide_index = editor.add_slide() | |
| assert slide_index == 0 | |
| assert len(editor.prs.slides) == 1 | |
| def test_validate_slide_raises_for_invalid_indexes(editor): | |
| add_blank_slide(editor) | |
| with pytest.raises(IndexError, match="Slide index -1 out of range"): | |
| editor._validate_slide(-1) | |
| with pytest.raises(IndexError, match="Slide index 1 out of range"): | |
| editor._validate_slide(1) | |
| def test_get_shape_raises_for_invalid_shape_index(editor): | |
| slide_index = add_blank_slide(editor) | |
| with pytest.raises(IndexError, match="Shape index 0 out of range"): | |
| editor._get_shape(slide_index, 0) | |
| def test_reorder_slide_moves_slide_to_new_position(editor): | |
| for _ in range(3): | |
| add_blank_slide(editor) | |
| original_ids = [slide.slide_id for slide in editor.prs.slides] | |
| editor.reorder_slide(0, 2) | |
| reordered_ids = [slide.slide_id for slide in editor.prs.slides] | |
| assert reordered_ids == [original_ids[1], original_ids[2], original_ids[0]] | |
| def test_reorder_slide_raises_for_invalid_new_index(editor): | |
| add_blank_slide(editor) | |
| with pytest.raises(IndexError, match="new_index out of bounds"): | |
| editor.reorder_slide(0, 1) | |
| def test_insert_text_replaces_existing_text_in_textbox(editor): | |
| slide_index = add_blank_slide(editor) | |
| shape_index = add_textbox(editor, slide_index, "Before") | |
| editor.insert_text(slide_index, shape_index, "After") | |
| assert editor.prs.slides[slide_index].shapes[shape_index].text_frame.text == "After" | |
| def test_insert_text_preserves_newline_separated_paragraphs(editor): | |
| slide_index = add_blank_slide(editor) | |
| shape_index = add_textbox(editor, slide_index) | |
| editor.insert_text(slide_index, shape_index, "One\nTwo\nThree") | |
| paragraphs = ( | |
| editor.prs.slides[slide_index].shapes[shape_index].text_frame.paragraphs | |
| ) | |
| assert [paragraph.text for paragraph in paragraphs] == ["One", "Two", "Three"] | |
| def test_insert_text_rejects_non_text_shape(editor): | |
| slide_index = add_blank_slide(editor) | |
| slide = editor.prs.slides[slide_index] | |
| slide.shapes.add_connector( | |
| MSO_CONNECTOR.STRAIGHT, Inches(1), Inches(1), Inches(2), Inches(2) | |
| ) | |
| with pytest.raises(ValueError, match="Shape has no text frame"): | |
| editor.insert_text(slide_index, 0, "One") | |
| def test_add_chart_uses_default_chart_type_for_unknown_values(editor): | |
| slide_index = add_blank_slide(editor) | |
| slide = editor.prs.slides[slide_index] | |
| editor.add_chart( | |
| slide_index, | |
| "not_a_chart_type", | |
| {"categories": ["A", "B"], "series": [{"name": "Series 1", "values": [1, 2]}]}, | |
| 1, | |
| 1, | |
| 4, | |
| 3, | |
| ) | |
| assert len(slide.shapes) == 1 | |
| assert slide.shapes[0].has_chart | |
| assert slide.shapes[0].chart.chart_type == XL_CHART_TYPE.COLUMN_CLUSTERED | |
| def test_add_chart_applies_optional_style(editor): | |
| slide_index = add_blank_slide(editor) | |
| slide = editor.prs.slides[slide_index] | |
| editor.add_chart( | |
| slide_index, | |
| "column_clustered", | |
| {"categories": ["A", "B"], "series": [{"name": "Series 1", "values": [1, 2]}]}, | |
| 1, | |
| 1, | |
| 4, | |
| 3, | |
| style={ | |
| "title": "Styled Chart", | |
| "title_font_name": "Arial", | |
| "title_font_size_pt": 20, | |
| "title_color_hex": "#112233", | |
| "series_colors": ["#445566"], | |
| }, | |
| ) | |
| chart = slide.shapes[0].chart | |
| assert chart.chart_title.text_frame.text == "Styled Chart" | |
| assert chart.chart_title.text_frame.paragraphs[0].font.name == "Arial" | |
| assert chart.chart_title.text_frame.paragraphs[0].font.size.pt == 20 | |
| assert str(chart.chart_title.text_frame.paragraphs[0].font.color.rgb) == "112233" | |
| assert str(chart.series[0].format.fill.fore_color.rgb) == "445566" | |
| def test_style_chart_updates_existing_chart(editor): | |
| slide_index = add_blank_slide(editor) | |
| slide = editor.prs.slides[slide_index] | |
| editor.add_chart( | |
| slide_index, | |
| "column_clustered", | |
| {"categories": ["A", "B"], "series": [{"name": "Series 1", "values": [1, 2]}]}, | |
| 1, | |
| 1, | |
| 4, | |
| 3, | |
| ) | |
| editor.style_chart( | |
| slide_index, | |
| 0, | |
| title="Updated Title", | |
| title_font_name="Calibri", | |
| title_font_size_pt=18, | |
| title_bold=True, | |
| title_color_hex="#AA5500", | |
| series_colors=["#003366"], | |
| ) | |
| chart = slide.shapes[0].chart | |
| title_font = chart.chart_title.text_frame.paragraphs[0].font | |
| assert chart.chart_title.text_frame.text == "Updated Title" | |
| assert title_font.name == "Calibri" | |
| assert title_font.size.pt == 18 | |
| assert title_font.bold is True | |
| assert str(title_font.color.rgb) == "AA5500" | |
| assert str(chart.series[0].format.fill.fore_color.rgb) == "003366" | |
| def test_add_table_populates_available_cells_only(editor): | |
| slide_index = add_blank_slide(editor) | |
| slide = editor.prs.slides[slide_index] | |
| editor.add_table( | |
| slide_index, | |
| 2, | |
| 2, | |
| [["A1", "A2", "A3"], ["B1"]], | |
| 1, | |
| 1, | |
| 4, | |
| 2, | |
| ) | |
| table = slide.shapes[0].table | |
| assert table.cell(0, 0).text == "A1" | |
| assert table.cell(0, 1).text == "A2" | |
| assert table.cell(1, 0).text == "B1" | |
| assert table.cell(1, 1).text == "" | |
| def test_add_table_applies_optional_style(editor): | |
| slide_index = add_blank_slide(editor) | |
| slide = editor.prs.slides[slide_index] | |
| editor.add_table( | |
| slide_index, | |
| 2, | |
| 2, | |
| [["Header 1", "Header 2"], ["Value 1", "Value 2"]], | |
| 1, | |
| 1, | |
| 4, | |
| 2, | |
| style={ | |
| "header_fill_hex": "#112233", | |
| "body_fill_hex": "#F1F5F9", | |
| "header_font_name": "Arial", | |
| "body_font_name": "Calibri", | |
| "header_font_size_pt": 18, | |
| "body_font_size_pt": 14, | |
| "header_font_color_hex": "#FFFFFF", | |
| "body_font_color_hex": "#334455", | |
| }, | |
| ) | |
| table = slide.shapes[0].table | |
| assert str(table.cell(0, 0).fill.fore_color.rgb) == "112233" | |
| assert str(table.cell(1, 0).fill.fore_color.rgb) == "F1F5F9" | |
| assert table.cell(0, 0).text_frame.paragraphs[0].font.name == "Arial" | |
| assert table.cell(1, 0).text_frame.paragraphs[0].font.name == "Calibri" | |
| assert table.cell(0, 0).text_frame.paragraphs[0].font.size.pt == 18 | |
| assert table.cell(1, 0).text_frame.paragraphs[0].font.size.pt == 14 | |
| assert str(table.cell(0, 0).text_frame.paragraphs[0].font.color.rgb) == "FFFFFF" | |
| assert str(table.cell(1, 0).text_frame.paragraphs[0].font.color.rgb) == "334455" | |
| def test_style_table_updates_existing_table(editor): | |
| slide_index = add_blank_slide(editor) | |
| slide = editor.prs.slides[slide_index] | |
| editor.add_table( | |
| slide_index, | |
| 2, | |
| 2, | |
| [["Header 1", "Header 2"], ["Value 1", "Value 2"]], | |
| 1, | |
| 1, | |
| 4, | |
| 2, | |
| ) | |
| editor.style_table( | |
| slide_index, | |
| 0, | |
| header_fill_hex="#0F172A", | |
| body_fill_hex="#E2E8F0", | |
| header_font_name="Georgia", | |
| body_font_name="Verdana", | |
| header_font_size_pt=16, | |
| body_font_size_pt=12, | |
| header_font_color_hex="#FAFAFA", | |
| body_font_color_hex="#1E293B", | |
| ) | |
| table = slide.shapes[0].table | |
| assert str(table.cell(0, 1).fill.fore_color.rgb) == "0F172A" | |
| assert str(table.cell(1, 1).fill.fore_color.rgb) == "E2E8F0" | |
| assert table.cell(0, 1).text_frame.paragraphs[0].font.name == "Georgia" | |
| assert table.cell(1, 1).text_frame.paragraphs[0].font.name == "Verdana" | |
| assert str(table.cell(0, 1).text_frame.paragraphs[0].font.color.rgb) == "FAFAFA" | |
| assert str(table.cell(1, 1).text_frame.paragraphs[0].font.color.rgb) == "1E293B" | |
| def test_style_update_applies_font_settings_to_existing_runs(editor): | |
| slide_index = add_blank_slide(editor) | |
| shape_index = add_textbox(editor, slide_index, "") | |
| paragraph = ( | |
| editor.prs.slides[slide_index].shapes[shape_index].text_frame.paragraphs[0] | |
| ) | |
| run = paragraph.add_run() | |
| run.text = "Styled" | |
| editor.style_update( | |
| slide_index, | |
| shape_index, | |
| font_name="Arial", | |
| font_size_pt=18, | |
| bold=True, | |
| italic=True, | |
| color_hex="#112233", | |
| ) | |
| assert run.font.name == "Arial" | |
| assert run.font.size.pt == 18 | |
| assert run.font.bold is True | |
| assert run.font.italic is True | |
| assert str(run.font.color.rgb) == "112233" | |
| def test_add_textbox_enables_word_wrap_by_default(editor): | |
| slide_index = add_blank_slide(editor) | |
| slide_id = editor.get_slide_id(slide_index) | |
| shape_id = editor.add_textbox_by_id(slide_id, 1, 1, 4, 1) | |
| editor.insert_text_by_id(slide_id, shape_id, "Wrapped by default") | |
| text_frame = editor._get_shape_by_id(slide_id, shape_id).text_frame | |
| assert text_frame.word_wrap is True | |
| def test_style_text_applies_wrap_and_paragraph_spacing(editor): | |
| slide_index = add_blank_slide(editor) | |
| shape_index = add_textbox(editor, slide_index, "Line 1\nLine 2") | |
| editor.style_update( | |
| slide_index, | |
| shape_index, | |
| font_name="Arial", | |
| font_size_pt=18, | |
| color_hex="#112233", | |
| word_wrap=False, | |
| space_before_pt=3, | |
| space_after_pt=6, | |
| line_spacing=1.4, | |
| ) | |
| paragraphs = ( | |
| editor.prs.slides[slide_index].shapes[shape_index].text_frame.paragraphs | |
| ) | |
| text_frame = editor.prs.slides[slide_index].shapes[shape_index].text_frame | |
| assert text_frame.word_wrap is False | |
| assert paragraphs[0].space_before.pt == 3 | |
| assert paragraphs[0].space_after.pt == 6 | |
| assert paragraphs[0].line_spacing == 1.4 | |
| def test_style_update_rejects_non_text_shape(editor): | |
| slide_index = add_blank_slide(editor) | |
| slide = editor.prs.slides[slide_index] | |
| slide.shapes.add_connector( | |
| MSO_CONNECTOR.STRAIGHT, Inches(1), Inches(1), Inches(2), Inches(2) | |
| ) | |
| with pytest.raises(ValueError, match="Shape has no text frame"): | |
| editor.style_update(slide_index, 0, bold=True) | |
| def test_style_table_rejects_non_table_shape(editor): | |
| slide_index = add_blank_slide(editor) | |
| shape_index = add_textbox(editor, slide_index, "Not a table") | |
| with pytest.raises(ValueError, match="Shape has no table"): | |
| editor.style_table(slide_index, shape_index, header_fill_hex="#000000") | |
| def test_style_chart_rejects_non_chart_shape(editor): | |
| slide_index = add_blank_slide(editor) | |
| shape_index = add_textbox(editor, slide_index, "Not a chart") | |
| with pytest.raises(ValueError, match="Shape has no chart"): | |
| editor.style_chart(slide_index, shape_index, title="Nope") | |
| def test_add_image_raises_for_missing_file(editor): | |
| slide_index = add_blank_slide(editor) | |
| with pytest.raises(FileNotFoundError, match="Image path invalid"): | |
| editor.add_image(slide_index, "/tmp/does-not-exist.png", 1, 1) | |