barunsaha commited on
Commit
b9d847d
·
unverified ·
2 Parent(s): e3dae23 0c15e0b

Merge pull request #162 from barun-saha/increase-test-coverage-pptx-helper

Browse files
Files changed (1) hide show
  1. tests/unit/test_pptx_helper.py +458 -7
tests/unit/test_pptx_helper.py CHANGED
@@ -30,18 +30,24 @@ def mock_pptx_presentation() -> Mock:
30
  mock_placeholder.placeholder_format = Mock()
31
  mock_placeholder.placeholder_format.idx = 1
32
  mock_placeholder.name = "Content Placeholder"
 
 
 
 
33
 
34
  # Configure mock shapes
35
  mock_shapes = Mock()
36
  mock_shapes.add_shape = Mock(return_value=mock_placeholder)
37
  mock_shapes.add_picture = Mock(return_value=mock_placeholder)
38
  mock_shapes.add_textbox = Mock(return_value=mock_placeholder)
39
- mock_shapes.title = mock_placeholder
 
40
  mock_shapes.placeholders = {1: mock_placeholder}
41
 
42
  # Configure mock slide
43
  mock_slide = Mock(spec=Slide)
44
  mock_slide.shapes = mock_shapes
 
45
  mock_pres.slides.add_slide.return_value = mock_slide
46
 
47
  return mock_pres
@@ -89,14 +95,10 @@ def mock_slide() -> Mock:
89
  # Setup shapes collection
90
  mock_shapes = Mock()
91
  mock_shapes.title = mock_title
92
- mock_shapes.placeholders = {}
93
  mock_shapes.add_shape = Mock(return_value=mock_shape)
94
  mock_shapes.add_textbox = Mock(return_value=mock_shape)
95
 
96
- # Configure placeholders dict
97
- for placeholder in mock_placeholders:
98
- mock_shapes.placeholders[placeholder.placeholder_format.idx] = placeholder
99
-
100
  mock.shapes = mock_shapes
101
  return mock
102
 
@@ -127,7 +129,7 @@ def mock_text_frame() -> Mock:
127
  mock.paragraphs.append(new_para)
128
  return new_para
129
 
130
- mock.add_paragraph = mock_add_paragraph
131
  mock.text = ""
132
  mock.clear = Mock()
133
  mock.word_wrap = True
@@ -242,6 +244,347 @@ def test_get_flat_list_of_contents():
242
  assert result == expected
243
 
244
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  def test_handle_display_image__in_background(
246
  mock_pptx_presentation: Mock,
247
  mock_text_frame: Mock
@@ -418,6 +761,32 @@ def test_handle_step_by_step_process_invalid(mock_pptx_presentation: Mock):
418
  assert not result_many
419
 
420
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  def test_handle_default_display(mock_pptx_presentation: Mock, mock_text_frame: Mock):
422
  """Test handling default display."""
423
  slide_json = {
@@ -452,6 +821,88 @@ def test_handle_default_display(mock_pptx_presentation: Mock, mock_text_frame: M
452
  assert mock_shape.text_frame.paragraphs[0].runs
453
 
454
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
  def test_handle_key_message(mock_pptx_presentation: Mock):
456
  """Test handling key message."""
457
  slide_json = {
 
30
  mock_placeholder.placeholder_format = Mock()
31
  mock_placeholder.placeholder_format.idx = 1
32
  mock_placeholder.name = "Content Placeholder"
33
+ mock_placeholder.left = 123
34
+ mock_placeholder.top = 456
35
+ mock_placeholder.width = 789
36
+ mock_placeholder.height = 101
37
 
38
  # Configure mock shapes
39
  mock_shapes = Mock()
40
  mock_shapes.add_shape = Mock(return_value=mock_placeholder)
41
  mock_shapes.add_picture = Mock(return_value=mock_placeholder)
42
  mock_shapes.add_textbox = Mock(return_value=mock_placeholder)
43
+ mock_shapes.title = Mock()
44
+ mock_shapes.title.text = "by Myself and SlideDeck AI :)"
45
  mock_shapes.placeholders = {1: mock_placeholder}
46
 
47
  # Configure mock slide
48
  mock_slide = Mock(spec=Slide)
49
  mock_slide.shapes = mock_shapes
50
+ mock_slide.placeholders = {1: mock_placeholder}
51
  mock_pres.slides.add_slide.return_value = mock_slide
52
 
53
  return mock_pres
 
95
  # Setup shapes collection
96
  mock_shapes = Mock()
97
  mock_shapes.title = mock_title
98
+ mock_shapes.placeholders = mock_placeholders
99
  mock_shapes.add_shape = Mock(return_value=mock_shape)
100
  mock_shapes.add_textbox = Mock(return_value=mock_shape)
101
 
 
 
 
 
102
  mock.shapes = mock_shapes
103
  return mock
104
 
 
129
  mock.paragraphs.append(new_para)
130
  return new_para
131
 
132
+ mock.add_paragraph = Mock(side_effect=mock_add_paragraph)
133
  mock.text = ""
134
  mock.clear = Mock()
135
  mock.word_wrap = True
 
244
  assert result == expected
245
 
246
 
247
+ @patch('slidedeckai.helpers.pptx_helper.format_text')
248
+ def test_add_bulleted_items(mock_format_text, mock_text_frame: Mock):
249
+ """Test adding bulleted items to a text frame."""
250
+ flat_items_list = [
251
+ ('Item 1', 0),
252
+ ('>> Item 1.1', 1),
253
+ ('Item 2', 0),
254
+ ]
255
+
256
+ ph.add_bulleted_items(mock_text_frame, flat_items_list)
257
+
258
+ assert len(mock_text_frame.paragraphs) == 3
259
+ assert mock_text_frame.add_paragraph.call_count == 2
260
+
261
+ # Verify paragraph levels
262
+ assert mock_text_frame.paragraphs[1].level == 1
263
+ assert mock_text_frame.paragraphs[2].level == 0
264
+
265
+ # Verify calls to format_text
266
+ mock_format_text.assert_any_call(mock_text_frame.paragraphs[0], 'Item 1')
267
+ mock_format_text.assert_any_call(mock_text_frame.paragraphs[1], 'Item 1.1')
268
+ mock_format_text.assert_any_call(mock_text_frame.paragraphs[2], 'Item 2')
269
+ assert mock_format_text.call_count == 3
270
+
271
+
272
+ def test_handle_table(mock_pptx_presentation: Mock):
273
+ """Test handling table data in slides."""
274
+ slide_json_with_table = {
275
+ 'heading': 'Test Table',
276
+ 'table': {
277
+ 'headers': ['Header 1', 'Header 2'],
278
+ 'rows': [['Row 1, Col 1', 'Row 1, Col 2'], ['Row 2, Col 1', 'Row 2, Col 2']]
279
+ }
280
+ }
281
+
282
+ # Setup mock table
283
+ mock_table = MagicMock()
284
+
285
+ def cell_side_effect(row, col):
286
+ cell_mock = MagicMock()
287
+ cell_mock.text = slide_json_with_table['table']['headers'][col] if row == 0 else slide_json_with_table['table']['rows'][row - 1][col]
288
+ return cell_mock
289
+
290
+ mock_table.cell.side_effect = cell_side_effect
291
+ mock_slide = mock_pptx_presentation.slides.add_slide.return_value
292
+ mock_slide.shapes.add_table.return_value.table = mock_table
293
+
294
+ result = ph._handle_table(
295
+ presentation=mock_pptx_presentation,
296
+ slide_json=slide_json_with_table,
297
+ slide_width_inch=10,
298
+ slide_height_inch=7.5
299
+ )
300
+
301
+ assert result is True
302
+ mock_slide.shapes.add_table.assert_called_once()
303
+ # Verify headers
304
+ assert mock_table.cell(0, 0).text == 'Header 1'
305
+ assert mock_table.cell(0, 1).text == 'Header 2'
306
+
307
+ # Verify rows
308
+ assert mock_table.cell(1, 0).text == 'Row 1, Col 1'
309
+ assert mock_table.cell(1, 1).text == 'Row 1, Col 2'
310
+ assert mock_table.cell(2, 0).text == 'Row 2, Col 1'
311
+ assert mock_table.cell(2, 1).text == 'Row 2, Col 2'
312
+
313
+
314
+ def test_handle_table_no_table(mock_pptx_presentation: Mock):
315
+ """Test handling slide with no table data."""
316
+ slide_json_no_table = {
317
+ 'heading': 'No Table Slide',
318
+ 'bullet_points': ['Point 1']
319
+ }
320
+
321
+ result = ph._handle_table(
322
+ presentation=mock_pptx_presentation,
323
+ slide_json=slide_json_no_table,
324
+ slide_width_inch=10,
325
+ slide_height_inch=7.5
326
+ )
327
+
328
+ assert result is False
329
+
330
+
331
+ @patch('slidedeckai.helpers.pptx_helper.ice.find_icons', return_value=['fallback_icon_1', 'fallback_icon_2'])
332
+ @patch('slidedeckai.helpers.pptx_helper.os.path.exists')
333
+ @patch('slidedeckai.helpers.pptx_helper._add_text_at_bottom')
334
+ def test_handle_icons_ideas(
335
+ mock_add_text,
336
+ mock_exists,
337
+ mock_find_icons,
338
+ mock_pptx_presentation: Mock,
339
+ mock_shape: Mock
340
+ ):
341
+ """Test handling icons and ideas in slides."""
342
+ slide_json = {
343
+ 'heading': 'Icons Slide',
344
+ 'bullet_points': [
345
+ '[[icon1]] Text 1',
346
+ '[[icon2]] Text 2',
347
+ ]
348
+ }
349
+ # Mock os.path.exists to return True for the first icon and False for the second
350
+ mock_exists.side_effect = [True, False]
351
+ mock_slide = mock_pptx_presentation.slides.add_slide.return_value
352
+ mock_slide.shapes.add_shape.return_value = mock_shape
353
+ mock_slide.shapes.add_picture.return_value = None # No need to return a shape
354
+
355
+ with patch('slidedeckai.helpers.pptx_helper.random.choice', return_value=pptx.dml.color.RGBColor.from_string('800000')):
356
+ result = ph._handle_icons_ideas(
357
+ presentation=mock_pptx_presentation,
358
+ slide_json=slide_json,
359
+ slide_width_inch=10,
360
+ slide_height_inch=7.5
361
+ )
362
+
363
+ assert result is True
364
+ # Two icon backgrounds, two text boxes
365
+ assert mock_slide.shapes.add_shape.call_count == 4
366
+ assert mock_slide.shapes.add_picture.call_count == 2
367
+ mock_find_icons.assert_called_once()
368
+ assert mock_add_text.call_count == 2
369
+
370
+
371
+ def test_handle_icons_ideas_invalid(mock_pptx_presentation: Mock):
372
+ """Test handling invalid content for icons and ideas layout."""
373
+ slide_json_invalid = {
374
+ 'heading': 'Invalid Icons Slide',
375
+ 'bullet_points': ['This is not an icon item']
376
+ }
377
+
378
+ result = ph._handle_icons_ideas(
379
+ presentation=mock_pptx_presentation,
380
+ slide_json=slide_json_invalid,
381
+ slide_width_inch=10,
382
+ slide_height_inch=7.5
383
+ )
384
+ assert result is False
385
+
386
+
387
+ @patch('slidedeckai.helpers.pptx_helper.pptx.Presentation')
388
+ @patch('slidedeckai.helpers.pptx_helper._handle_icons_ideas')
389
+ @patch('slidedeckai.helpers.pptx_helper._handle_table')
390
+ @patch('slidedeckai.helpers.pptx_helper._handle_double_col_layout')
391
+ @patch('slidedeckai.helpers.pptx_helper._handle_step_by_step_process')
392
+ @patch('slidedeckai.helpers.pptx_helper._handle_default_display')
393
+ def test_generate_powerpoint_presentation(
394
+ mock_handle_default,
395
+ mock_handle_step_by_step,
396
+ mock_handle_double_col,
397
+ mock_handle_table,
398
+ mock_handle_icons,
399
+ mock_presentation
400
+ ):
401
+ """Test the main function for generating a PowerPoint presentation."""
402
+ parsed_data = {
403
+ 'title': 'Test Presentation',
404
+ 'slides': [
405
+ {'heading': 'Slide 1'},
406
+ {'heading': 'Slide 2'},
407
+ {'heading': 'Slide 3'},
408
+ ]
409
+ }
410
+ # Simulate a realistic workflow
411
+ mock_handle_icons.side_effect = [True, False, False]
412
+ mock_handle_table.side_effect = [True, False]
413
+ mock_handle_double_col.side_effect = [True]
414
+
415
+ # Configure mock for the presentation object and its slides
416
+ mock_pres = MagicMock(spec=Presentation)
417
+ mock_title_slide = MagicMock(spec=Slide)
418
+ mock_thank_you_slide = MagicMock(spec=Slide)
419
+ mock_pres.slides.add_slide.side_effect = [mock_title_slide, mock_thank_you_slide]
420
+ mock_presentation.return_value = mock_pres
421
+
422
+ with patch('slidedeckai.helpers.pptx_helper.pathlib.Path'):
423
+ headers = ph.generate_powerpoint_presentation(
424
+ parsed_data=parsed_data,
425
+ slides_template='Basic',
426
+ output_file_path='dummy.pptx'
427
+ )
428
+
429
+ assert headers == ['Test Presentation']
430
+ # Title and Thank you slides
431
+ assert mock_pres.slides.add_slide.call_count == 2
432
+ # Check that title and subtitle were set
433
+ assert mock_title_slide.shapes.title.text == 'Test Presentation'
434
+ assert mock_title_slide.placeholders[1].text == 'by Myself and SlideDeck AI :)'
435
+ # Check handler calls
436
+ assert mock_handle_icons.call_count == 3
437
+ assert mock_handle_table.call_count == 2
438
+ assert mock_handle_double_col.call_count == 1
439
+ mock_handle_step_by_step.assert_not_called()
440
+ mock_handle_default.assert_not_called()
441
+ # Check thank you slide
442
+ assert mock_thank_you_slide.shapes.title.text == 'Thank you!'
443
+ mock_pres.save.assert_called_once()
444
+
445
+
446
+ @patch('slidedeckai.helpers.pptx_helper.pptx.Presentation')
447
+ @patch('slidedeckai.helpers.pptx_helper._handle_icons_ideas', side_effect=Exception('Test Error'))
448
+ @patch('slidedeckai.helpers.pptx_helper.logger.error')
449
+ def test_generate_powerpoint_presentation_error_handling(
450
+ mock_logger_error,
451
+ mock_handle_icons,
452
+ mock_presentation
453
+ ):
454
+ """Test error handling during slide processing."""
455
+ parsed_data = {
456
+ 'title': 'Error Test',
457
+ 'slides': [{'heading': 'Slide 1'}]
458
+ }
459
+ mock_pres = MagicMock(spec=Presentation)
460
+ mock_title_slide = MagicMock(spec=Slide)
461
+ mock_thank_you_slide = MagicMock(spec=Slide)
462
+ mock_pres.slides.add_slide.side_effect = [mock_title_slide, mock_thank_you_slide]
463
+ mock_presentation.return_value = mock_pres
464
+
465
+ ph.generate_powerpoint_presentation(parsed_data, 'Basic', 'dummy.pptx')
466
+ mock_logger_error.assert_called_once()
467
+ assert "An error occurred while processing a slide" in mock_logger_error.call_args[0][0]
468
+
469
+
470
+ def test_handle_double_col_layout(
471
+ mock_pptx_presentation: Mock,
472
+ mock_slide: Mock
473
+ ):
474
+ """Test handling double column layout in slides."""
475
+ slide_json = {
476
+ 'heading': 'Double Column Slide',
477
+ 'bullet_points': [
478
+ {'heading': 'Left Heading', 'bullet_points': ['Left Point 1']},
479
+ {'heading': 'Right Heading', 'bullet_points': ['Right Point 1']}
480
+ ]
481
+ }
482
+ mock_pptx_presentation.slides.add_slide.return_value = mock_slide
483
+
484
+ with patch('slidedeckai.helpers.pptx_helper._handle_key_message') as mock_handle_key_message, \
485
+ patch('slidedeckai.helpers.pptx_helper.add_bulleted_items') as mock_add_bulleted_items:
486
+ result = ph._handle_double_col_layout(
487
+ presentation=mock_pptx_presentation,
488
+ slide_json=slide_json,
489
+ slide_width_inch=10,
490
+ slide_height_inch=7.5
491
+ )
492
+
493
+ assert result is True
494
+ assert mock_slide.shapes.title.text == ph.remove_slide_number_from_heading(slide_json['heading'])
495
+ assert mock_slide.shapes.placeholders[1].text == 'Left Heading'
496
+ assert mock_slide.shapes.placeholders[3].text == 'Right Heading'
497
+ assert mock_add_bulleted_items.call_count == 2
498
+ mock_handle_key_message.assert_called_once()
499
+
500
+
501
+ def test_handle_double_col_layout_invalid(mock_pptx_presentation: Mock):
502
+ """Test handling of invalid content for double column layout."""
503
+ slide_json_invalid = {
504
+ 'heading': 'Invalid Content',
505
+ 'bullet_points': [
506
+ 'This is not a dict',
507
+ {'heading': 'Right Heading', 'bullet_points': ['Right Point 1']}
508
+ ]
509
+ }
510
+ result = ph._handle_double_col_layout(
511
+ presentation=mock_pptx_presentation,
512
+ slide_json=slide_json_invalid,
513
+ slide_width_inch=10,
514
+ slide_height_inch=7.5
515
+ )
516
+ assert result is False
517
+
518
+
519
+ @patch('slidedeckai.helpers.pptx_helper.ims.get_photo_url_from_api_response', return_value=('http://fake.url/image.jpg', 'http://fake.url/page'))
520
+ @patch('slidedeckai.helpers.pptx_helper.ims.search_pexels')
521
+ @patch('slidedeckai.helpers.pptx_helper.ims.get_image_from_url')
522
+ @patch('slidedeckai.helpers.pptx_helper.add_bulleted_items')
523
+ @patch('slidedeckai.helpers.pptx_helper._add_text_at_bottom')
524
+ def test_handle_display_image__in_foreground(
525
+ mock_add_text,
526
+ mock_add_bulleted_items,
527
+ mock_get_image,
528
+ mock_search,
529
+ mock_get_url,
530
+ mock_pptx_presentation: Mock,
531
+ mock_slide: Mock,
532
+ mock_shape: Mock
533
+ ):
534
+ """Test handling foreground image display in slides."""
535
+ slide_json = {
536
+ 'heading': 'Image Slide',
537
+ 'bullet_points': ['Point 1'],
538
+ 'img_keywords': 'test image'
539
+ }
540
+ mock_slide.shapes.placeholders = {
541
+ 1: mock_shape,
542
+ 2: mock_shape,
543
+ 'Picture Placeholder 1': mock_shape,
544
+ 'Content Placeholder 2': mock_shape
545
+ }
546
+ mock_pptx_presentation.slides.add_slide.return_value = mock_slide
547
+
548
+ result = ph._handle_display_image__in_foreground(
549
+ presentation=mock_pptx_presentation,
550
+ slide_json=slide_json,
551
+ slide_width_inch=10,
552
+ slide_height_inch=7.5
553
+ )
554
+
555
+ assert result is True
556
+ mock_add_bulleted_items.assert_called_once()
557
+ mock_shape.insert_picture.assert_called_once()
558
+ mock_add_text.assert_called_once()
559
+
560
+
561
+ @patch('slidedeckai.helpers.pptx_helper.add_bulleted_items')
562
+ def test_handle_display_image__in_foreground_no_keywords(
563
+ mock_add_bulleted_items,
564
+ mock_pptx_presentation: Mock,
565
+ mock_slide: Mock,
566
+ mock_shape: Mock
567
+ ):
568
+ """Test handling foreground image display with no image keywords."""
569
+ slide_json = {
570
+ 'heading': 'No Image Slide',
571
+ 'bullet_points': ['Point 1'],
572
+ 'img_keywords': ''
573
+ }
574
+ mock_slide.shapes.placeholders = {1: mock_shape, 2: mock_shape}
575
+ mock_pptx_presentation.slides.add_slide.return_value = mock_slide
576
+
577
+ result = ph._handle_display_image__in_foreground(
578
+ presentation=mock_pptx_presentation,
579
+ slide_json=slide_json,
580
+ slide_width_inch=10,
581
+ slide_height_inch=7.5
582
+ )
583
+
584
+ assert result is True
585
+ mock_add_bulleted_items.assert_called_once()
586
+
587
+
588
  def test_handle_display_image__in_background(
589
  mock_pptx_presentation: Mock,
590
  mock_text_frame: Mock
 
761
  assert not result_many
762
 
763
 
764
+ @patch('slidedeckai.helpers.pptx_helper._handle_display_image__in_foreground', return_value=True)
765
+ @patch('slidedeckai.helpers.pptx_helper.random.random', side_effect=[0.1, 0.7])
766
+ def test_handle_default_display_with_foreground_image(
767
+ mock_random,
768
+ mock_handle_foreground,
769
+ mock_pptx_presentation: Mock
770
+ ):
771
+ """Test default display with foreground image."""
772
+ slide_json = {'img_keywords': 'test', 'heading': 'Test', 'bullet_points': []}
773
+ ph._handle_default_display(mock_pptx_presentation, slide_json, 10, 7.5)
774
+ mock_handle_foreground.assert_called_once()
775
+
776
+
777
+ @patch('slidedeckai.helpers.pptx_helper._handle_display_image__in_background', return_value=True)
778
+ @patch('slidedeckai.helpers.pptx_helper.random.random', side_effect=[0.1, 0.9])
779
+ def test_handle_default_display_with_background_image(
780
+ mock_random,
781
+ mock_handle_background,
782
+ mock_pptx_presentation: Mock
783
+ ):
784
+ """Test default display with background image."""
785
+ slide_json = {'img_keywords': 'test', 'heading': 'Test', 'bullet_points': []}
786
+ ph._handle_default_display(mock_pptx_presentation, slide_json, 10, 7.5)
787
+ mock_handle_background.assert_called_once()
788
+
789
+
790
  def test_handle_default_display(mock_pptx_presentation: Mock, mock_text_frame: Mock):
791
  """Test handling default display."""
792
  slide_json = {
 
821
  assert mock_shape.text_frame.paragraphs[0].runs
822
 
823
 
824
+ def test_get_slide_width_height_inches(mock_pptx_presentation: Mock):
825
+ """Test getting slide width and height in inches."""
826
+ width, height = ph._get_slide_width_height_inches(mock_pptx_presentation)
827
+ assert isinstance(width, float)
828
+ assert isinstance(height, float)
829
+
830
+
831
+ def test_get_slide_placeholders(mock_slide: Mock):
832
+ """Test getting slide placeholders."""
833
+ placeholders = ph.get_slide_placeholders(mock_slide, layout_number=1, is_debug=True)
834
+ assert isinstance(placeholders, list)
835
+ assert len(placeholders) == 4
836
+ assert all(isinstance(p, tuple) for p in placeholders)
837
+
838
+
839
+ def test_add_text_at_bottom(mock_slide: Mock):
840
+ """Test adding text at the bottom of a slide."""
841
+ ph._add_text_at_bottom(
842
+ slide=mock_slide,
843
+ slide_width_inch=10,
844
+ slide_height_inch=7.5,
845
+ text='Test footer',
846
+ hyperlink='http://fake.url'
847
+ )
848
+ mock_slide.shapes.add_textbox.assert_called_once()
849
+
850
+
851
+ def test_add_text_at_bottom_no_hyperlink(mock_slide: Mock):
852
+ """Test adding text at the bottom of a slide without a hyperlink."""
853
+ ph._add_text_at_bottom(
854
+ slide=mock_slide,
855
+ slide_width_inch=10,
856
+ slide_height_inch=7.5,
857
+ text='Test footer no link'
858
+ )
859
+ mock_slide.shapes.add_textbox.assert_called_once()
860
+
861
+
862
+ def test_handle_double_col_layout_key_error(mock_pptx_presentation: Mock):
863
+ """Test KeyError handling in double column layout."""
864
+ slide_json = {
865
+ 'heading': 'Double Column Slide',
866
+ 'bullet_points': [
867
+ {'heading': 'Left', 'bullet_points': ['L1']},
868
+ {'heading': 'Right', 'bullet_points': ['R1']}
869
+ ]
870
+ }
871
+ mock_slide = MagicMock(spec=Slide)
872
+ mock_slide.shapes.placeholders = {
873
+ 10: MagicMock(spec=Shape),
874
+ 11: MagicMock(spec=Shape),
875
+ 12: MagicMock(spec=Shape),
876
+ 13: MagicMock(spec=Shape),
877
+ }
878
+ mock_pptx_presentation.slides.add_slide.return_value = mock_slide
879
+
880
+ with patch('slidedeckai.helpers.pptx_helper.get_slide_placeholders', return_value=[(10, 'text placeholder'), (11, 'content placeholder'), (12, 'text placeholder'), (13, 'content placeholder')]):
881
+ result = ph._handle_double_col_layout(
882
+ presentation=mock_pptx_presentation,
883
+ slide_json=slide_json,
884
+ slide_width_inch=10,
885
+ slide_height_inch=7.5
886
+ )
887
+ assert result is True
888
+
889
+
890
+ def test_handle_display_image__in_background_no_keywords(mock_pptx_presentation: Mock):
891
+ """Test background image display with no keywords."""
892
+ slide_json = {
893
+ 'heading': 'No Image Slide',
894
+ 'bullet_points': ['Point 1'],
895
+ 'img_keywords': ''
896
+ }
897
+ result = ph._handle_display_image__in_background(
898
+ presentation=mock_pptx_presentation,
899
+ slide_json=slide_json,
900
+ slide_width_inch=10,
901
+ slide_height_inch=7.5
902
+ )
903
+ assert result is True
904
+
905
+
906
  def test_handle_key_message(mock_pptx_presentation: Mock):
907
  """Test handling key message."""
908
  slide_json = {