← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~knightrider0xd/openlp/preview-shows-live-fix-1080596 into lp:openlp

 

Ian Knight has proposed merging lp:~knightrider0xd/openlp/preview-shows-live-fix-1080596 into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)
Related bugs:
  Bug #1080596 in OpenLP: "Presentation preview shows desktop when live shows desktop"
  https://bugs.launchpad.net/openlp/+bug/1080596

For more details, see:
https://code.launchpad.net/~knightrider0xd/openlp/preview-shows-live-fix-1080596/+merge/293962

Fixes bug 1080596 where presentations in the preview pane display live view rather than preview of selected slide.
In addition, fixes the aspect ratio of thumbnails by loading them through the image manager.

New test cases implemented, or existing cases modified to test coverage complete for changes. 

Passing Jenkins, except interface & coverage tests due to issue with unrelated crosswalk import module.
-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~knightrider0xd/openlp/preview-shows-live-fix-1080596 into lp:openlp.
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py	2016-04-17 19:32:15 +0000
+++ openlp/core/lib/__init__.py	2016-05-06 02:05:04 +0000
@@ -55,9 +55,13 @@
 
     ``Theme``
         This says, that the image is used by a theme.
+
+    ``CommandPlugins``
+        This states that an image is being used by a command plugin.
     """
     ImagePlugin = 1
     Theme = 2
+    CommandPlugins = 3
 
 
 class MediaType(object):
@@ -174,10 +178,24 @@
     ext = os.path.splitext(thumb_path)[1].lower()
     reader = QtGui.QImageReader(image_path)
     if size is None:
+        # No size given; use default height of 88
         ratio = reader.size().width() / reader.size().height()
         reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
-    else:
+    elif size.isValid():
+        # Complete size given
         reader.setScaledSize(size)
+    else:
+        # Invalid size given
+        ratio = reader.size().width() / reader.size().height()
+        if size.width() >= 0:
+            # Valid width; scale height
+            reader.setScaledSize(QtCore.QSize(size.width(), int(size.width() / ratio)))
+        elif size.height() >= 0:
+            # Valid height; scale width
+            reader.setScaledSize(QtCore.QSize(int(ratio * size.height()), size.height()))
+        else:
+            # Invalid; use default height of 88
+            reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
     thumb = reader.read()
     thumb.save(thumb_path, ext[1:])
     if not return_icon:

=== modified file 'openlp/core/lib/serviceitem.py'
--- openlp/core/lib/serviceitem.py	2016-02-14 17:53:16 +0000
+++ openlp/core/lib/serviceitem.py	2016-05-06 02:05:04 +0000
@@ -334,6 +334,8 @@
                                  file_location_hash, ntpath.basename(image))
         self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
                                  'display_title': display_title, 'notes': notes})
+        if self.is_capable(ItemCapabilities.HasThumbnails):
+            self.image_manager.add_image(image, ImageSource.CommandPlugins, '#000000')
         self._new_item()
 
     def get_service_repr(self, lite_save):

=== modified file 'openlp/core/ui/lib/listpreviewwidget.py'
--- openlp/core/ui/lib/listpreviewwidget.py	2016-04-22 18:32:59 +0000
+++ openlp/core/ui/lib/listpreviewwidget.py	2016-05-06 02:05:04 +0000
@@ -27,7 +27,7 @@
 from PyQt5 import QtCore, QtGui, QtWidgets
 
 from openlp.core.common import RegistryProperties, Settings
-from openlp.core.lib import ImageSource, ServiceItem
+from openlp.core.lib import ImageSource, ItemCapabilities, ServiceItem
 
 
 class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
@@ -152,14 +152,16 @@
                 else:
                     label.setScaledContents(True)
                 if self.service_item.is_command():
-                    pixmap = QtGui.QPixmap(frame['image'])
-                    pixmap.setDevicePixelRatio(label.devicePixelRatio())
-                    label.setPixmap(pixmap)
+                    if self.service_item.is_capable(ItemCapabilities.HasThumbnails):
+                        image = self.image_manager.get_image(frame['image'], ImageSource.CommandPlugins)
+                        pixmap = QtGui.QPixmap.fromImage(image)
+                    else:
+                        pixmap = QtGui.QPixmap(frame['image'])
                 else:
                     image = self.image_manager.get_image(frame['path'], ImageSource.ImagePlugin)
                     pixmap = QtGui.QPixmap.fromImage(image)
-                    pixmap.setDevicePixelRatio(label.devicePixelRatio())
-                    label.setPixmap(pixmap)
+                pixmap.setDevicePixelRatio(label.devicePixelRatio())
+                label.setPixmap(pixmap)
                 slide_height = width // self.screen_ratio
                 # Setup and validate row height cap if in use.
                 max_img_row_height = Settings().value('advanced/slide max height')

=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py	2016-04-22 18:32:45 +0000
+++ openlp/core/ui/slidecontroller.py	2016-05-06 02:05:04 +0000
@@ -1135,9 +1135,21 @@
         """
         self.log_debug('update_preview %s ' % self.screens.current['primary'])
         if self.service_item and self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
-            # Grab now, but try again in a couple of seconds if slide change is slow
-            QtCore.QTimer.singleShot(500, self.grab_maindisplay)
-            QtCore.QTimer.singleShot(2500, self.grab_maindisplay)
+            if self.is_live:
+                # If live, grab screen-cap of main display now
+                QtCore.QTimer.singleShot(500, self.grab_maindisplay)
+                # but take another in a couple of seconds in case slide change is slow
+                QtCore.QTimer.singleShot(2500, self.grab_maindisplay)
+            else:
+                # If not live, use the slide's thumbnail/icon instead
+                image_path = self.service_item.get_rendered_frame(self.selected_row)
+                if self.service_item.is_capable(ItemCapabilities.HasThumbnails):
+                    image = self.image_manager.get_image(image_path, ImageSource.CommandPlugins)
+                    self.slide_image = QtGui.QPixmap.fromImage(image)
+                else:
+                    self.slide_image = QtGui.QPixmap(image_path)
+                self.slide_image.setDevicePixelRatio(self.main_window.devicePixelRatio())
+                self.slide_preview.setPixmap(self.slide_image)
         else:
             self.slide_image = self.display.preview()
             self.slide_image.setDevicePixelRatio(self.main_window.devicePixelRatio())

=== modified file 'openlp/plugins/presentations/lib/presentationcontroller.py'
--- openlp/plugins/presentations/lib/presentationcontroller.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/presentations/lib/presentationcontroller.py	2016-05-06 02:05:04 +0000
@@ -242,13 +242,13 @@
 
     def convert_thumbnail(self, file, idx):
         """
-        Convert the slide image the application made to a standard 320x240 .png image.
+        Convert the slide image the application made to a scaled 360px height .png image.
         """
         if self.check_thumbnails():
             return
         if os.path.isfile(file):
             thumb_path = self.get_thumbnail_path(idx, False)
-            create_thumb(file, thumb_path, False, QtCore.QSize(320, 240))
+            create_thumb(file, thumb_path, False, QtCore.QSize(-1, 360))
 
     def get_thumbnail_path(self, slide_no, check_exists):
         """

=== modified file 'tests/functional/openlp_core_lib/test_lib.py'
--- tests/functional/openlp_core_lib/test_lib.py	2015-12-31 22:46:06 +0000
+++ tests/functional/openlp_core_lib/test_lib.py	2016-05-06 02:05:04 +0000
@@ -250,7 +250,7 @@
 
     def create_thumb_with_size_test(self):
         """
-        Test the create_thumb() function
+        Test the create_thumb() function with a given size.
         """
         # GIVEN: An image to create a thumb of.
         image_path = os.path.join(TEST_PATH, 'church.jpg')
@@ -270,7 +270,7 @@
         # WHEN: Create the thumb.
         icon = create_thumb(image_path, thumb_path, size=thumb_size)
 
-        # THEN: Check if the thumb was created.
+        # THEN: Check if the thumb was created and scaled to the given size.
         self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
         self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
         self.assertFalse(icon.isNull(), 'The icon should not be null')
@@ -282,6 +282,145 @@
         except:
             pass
 
+    def create_thumb_no_size_test(self):
+        """
+        Test the create_thumb() function with no size specified.
+        """
+        # GIVEN: An image to create a thumb of.
+        image_path = os.path.join(TEST_PATH, 'church.jpg')
+        thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+        expected_size = QtCore.QSize(63, 88)
+
+        # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
+        # last test.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+        # Only continue when the thumb does not exist.
+        self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+
+        # WHEN: Create the thumb.
+        icon = create_thumb(image_path, thumb_path)
+
+        # THEN: Check if the thumb was created, retaining its aspect ratio.
+        self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+        self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
+        self.assertFalse(icon.isNull(), 'The icon should not be null')
+        self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+
+        # Remove the thumb so that the test actually tests if the thumb will be created.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+    def create_thumb_invalid_size_test(self):
+        """
+        Test the create_thumb() function with invalid size specified.
+        """
+        # GIVEN: An image to create a thumb of.
+        image_path = os.path.join(TEST_PATH, 'church.jpg')
+        thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+        thumb_size = QtCore.QSize(-1, -1)
+        expected_size = QtCore.QSize(63, 88)
+
+        # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
+        # last test.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+        # Only continue when the thumb does not exist.
+        self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+
+        # WHEN: Create the thumb.
+        icon = create_thumb(image_path, thumb_path, size=thumb_size)
+
+        # THEN: Check if the thumb was created, retaining its aspect ratio.
+        self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+        self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
+        self.assertFalse(icon.isNull(), 'The icon should not be null')
+        self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+
+        # Remove the thumb so that the test actually tests if the thumb will be created.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+    def create_thumb_width_only_test(self):
+        """
+        Test the create_thumb() function with a size of only width specified.
+        """
+        # GIVEN: An image to create a thumb of.
+        image_path = os.path.join(TEST_PATH, 'church.jpg')
+        thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+        thumb_size = QtCore.QSize(100, -1)
+        expected_size = QtCore.QSize(100, 137)
+
+        # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
+        # last test.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+        # Only continue when the thumb does not exist.
+        self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+
+        # WHEN: Create the thumb.
+        icon = create_thumb(image_path, thumb_path, size=thumb_size)
+
+        # THEN: Check if the thumb was created, retaining its aspect ratio.
+        self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+        self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
+        self.assertFalse(icon.isNull(), 'The icon should not be null')
+        self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+
+        # Remove the thumb so that the test actually tests if the thumb will be created.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+    def create_thumb_height_only_test(self):
+        """
+        Test the create_thumb() function with a size of only height specified.
+        """
+        # GIVEN: An image to create a thumb of.
+        image_path = os.path.join(TEST_PATH, 'church.jpg')
+        thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+        thumb_size = QtCore.QSize(-1, 100)
+        expected_size = QtCore.QSize(72, 100)
+
+        # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
+        # last test.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
+        # Only continue when the thumb does not exist.
+        self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+
+        # WHEN: Create the thumb.
+        icon = create_thumb(image_path, thumb_path, size=thumb_size)
+
+        # THEN: Check if the thumb was created, retaining its aspect ratio.
+        self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+        self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
+        self.assertFalse(icon.isNull(), 'The icon should not be null')
+        self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+
+        # Remove the thumb so that the test actually tests if the thumb will be created.
+        try:
+            os.remove(thumb_path)
+        except:
+            pass
+
     def check_item_selected_true_test(self):
         """
         Test that the check_item_selected() function returns True when there are selected indexes

=== modified file 'tests/functional/openlp_core_lib/test_serviceitem.py'
--- tests/functional/openlp_core_lib/test_serviceitem.py	2015-12-31 22:46:06 +0000
+++ tests/functional/openlp_core_lib/test_serviceitem.py	2016-05-06 02:05:04 +0000
@@ -244,14 +244,16 @@
         self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
         self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
 
+    @patch(u'openlp.core.lib.serviceitem.ServiceItem.image_manager')
     @patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path')
-    def add_from_command_for_a_presentation_thumb_test(self, mocked_get_section_data_path):
+    def add_from_command_for_a_presentation_thumb_test(self, mocked_get_section_data_path, mocked_image_manager):
         """
-        Test the Service Item - adding a presentation, and updating the thumb path
+        Test the Service Item - adding a presentation, updating the thumb path & adding the thumb to image_manager
         """
         # GIVEN: A service item, a mocked AppLocation and presentation data
         mocked_get_section_data_path.return_value = os.path.join('mocked', 'section', 'path')
         service_item = ServiceItem(None)
+        service_item.add_capability(ItemCapabilities.HasThumbnails)
         service_item.has_original_files = False
         service_item.name = 'presentations'
         presentation_name = 'test.pptx'
@@ -270,6 +272,7 @@
         # THEN: verify that it is setup as a Command and that the frame data matches
         self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
         self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
+        self.assertEqual(1, mocked_image_manager.add_image.call_count, 'image_manager should be used')
 
     def service_item_load_optical_media_from_service_test(self):
         """

=== modified file 'tests/functional/openlp_core_ui/test_slidecontroller.py'
--- tests/functional/openlp_core_ui/test_slidecontroller.py	2016-02-27 14:25:31 +0000
+++ tests/functional/openlp_core_ui/test_slidecontroller.py	2016-05-06 02:05:04 +0000
@@ -26,7 +26,7 @@
 
 from unittest import TestCase
 from openlp.core import Registry
-from openlp.core.lib import ServiceItemAction
+from openlp.core.lib import ImageSource, ServiceItemAction
 from openlp.core.ui import SlideController, LiveController, PreviewController
 from openlp.core.ui.slidecontroller import InfoLabel, WIDE_MENU, NON_TEXT_MENU
 
@@ -713,6 +713,175 @@
             slide_controller.theme_screen, slide_controller.blank_screen
         ])
 
+    @patch(u'openlp.core.ui.slidecontroller.SlideController.image_manager')
+    @patch(u'PyQt5.QtCore.QTimer.singleShot')
+    def update_preview_test_live(self, mocked_singleShot, mocked_image_manager):
+        """
+        Test that the preview screen is updated with a screen grab for live service items
+        """
+        # GIVEN: A mocked live service item, a mocked image_manager, a mocked Registry,
+        #        and a slide controller with many mocks.
+        # Mocked Live Item
+        mocked_live_item = MagicMock()
+        mocked_live_item.get_rendered_frame.return_value = ''
+        mocked_live_item.is_capable = MagicMock()
+        mocked_live_item.is_capable.side_effect = [True, True]
+        # Mock image_manager
+        mocked_image_manager.get_image.return_value = QtGui.QImage()
+        # Mock Registry
+        Registry.create()
+        mocked_main_window = MagicMock()
+        Registry().register('main_window', mocked_main_window)
+        # Mock SlideController
+        slide_controller = SlideController(None)
+        slide_controller.service_item = mocked_live_item
+        slide_controller.is_live = True
+        slide_controller.log_debug = MagicMock()
+        slide_controller.selected_row = MagicMock()
+        slide_controller.screens = MagicMock()
+        slide_controller.screens.current = {'primary': ''}
+        slide_controller.display = MagicMock()
+        slide_controller.display.preview.return_value = QtGui.QImage()
+        slide_controller.grab_maindisplay = MagicMock()
+        slide_controller.slide_preview = MagicMock()
+        slide_controller.slide_count = 0
+
+        # WHEN: update_preview is called
+        slide_controller.update_preview()
+
+        # THEN: A screen_grab should have been called
+        self.assertEqual(0, slide_controller.slide_preview.setPixmap.call_count, 'setPixmap should not be called')
+        self.assertEqual(0, slide_controller.display.preview.call_count, 'display.preview() should not be called')
+        self.assertEqual(2, mocked_singleShot.call_count,
+                         'Timer to grab_maindisplay should have been called 2 times')
+        self.assertEqual(0, mocked_image_manager.get_image.call_count, 'image_manager not be called')
+
+    @patch(u'openlp.core.ui.slidecontroller.SlideController.image_manager')
+    @patch(u'PyQt5.QtCore.QTimer.singleShot')
+    def update_preview_test_pres(self, mocked_singleShot, mocked_image_manager):
+        """
+        Test that the preview screen is updated with the correct preview for presentation service items
+        """
+        # GIVEN: A mocked presentation service item, a mocked image_manager, a mocked Registry,
+        #        and a slide controller with many mocks.
+        # Mocked Presentation Item
+        mocked_pres_item = MagicMock()
+        mocked_pres_item.get_rendered_frame.return_value = ''
+        mocked_pres_item.is_capable = MagicMock()
+        mocked_pres_item.is_capable.side_effect = [True, True]
+        # Mock image_manager
+        mocked_image_manager.get_image.return_value = QtGui.QImage()
+        # Mock Registry
+        Registry.create()
+        mocked_main_window = MagicMock()
+        Registry().register('main_window', mocked_main_window)
+        # Mock SlideController
+        slide_controller = SlideController(None)
+        slide_controller.service_item = mocked_pres_item
+        slide_controller.is_live = False
+        slide_controller.log_debug = MagicMock()
+        slide_controller.selected_row = MagicMock()
+        slide_controller.screens = MagicMock()
+        slide_controller.screens.current = {'primary': ''}
+        slide_controller.display = MagicMock()
+        slide_controller.display.preview.return_value = QtGui.QImage()
+        slide_controller.grab_maindisplay = MagicMock()
+        slide_controller.slide_preview = MagicMock()
+        slide_controller.slide_count = 0
+
+        # WHEN: update_preview is called
+        slide_controller.update_preview()
+
+        # THEN: setPixmap and the image_manager should have been called
+        self.assertEqual(1, slide_controller.slide_preview.setPixmap.call_count, 'setPixmap should be called')
+        self.assertEqual(0, slide_controller.display.preview.call_count, 'display.preview() should not be called')
+        self.assertEqual(0, mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called')
+        self.assertEqual(1, mocked_image_manager.get_image.call_count, 'image_manager should be called')
+
+    @patch(u'openlp.core.ui.slidecontroller.SlideController.image_manager')
+    @patch(u'PyQt5.QtCore.QTimer.singleShot')
+    def update_preview_test_media(self, mocked_singleShot, mocked_image_manager):
+        """
+        Test that the preview screen is updated with the correct preview for media service items
+        """
+        # GIVEN: A mocked media service item, a mocked image_manager, a mocked Registry,
+        #        and a slide controller with many mocks.
+        # Mocked Media Item
+        mocked_media_item = MagicMock()
+        mocked_media_item.get_rendered_frame.return_value = ''
+        mocked_media_item.is_capable = MagicMock()
+        mocked_media_item.is_capable.side_effect = [True, False]
+        # Mock image_manager
+        mocked_image_manager.get_image.return_value = QtGui.QImage()
+        # Mock Registry
+        Registry.create()
+        mocked_main_window = MagicMock()
+        Registry().register('main_window', mocked_main_window)
+        # Mock SlideController
+        slide_controller = SlideController(None)
+        slide_controller.service_item = mocked_media_item
+        slide_controller.is_live = False
+        slide_controller.log_debug = MagicMock()
+        slide_controller.selected_row = MagicMock()
+        slide_controller.screens = MagicMock()
+        slide_controller.screens.current = {'primary': ''}
+        slide_controller.display = MagicMock()
+        slide_controller.display.preview.return_value = QtGui.QImage()
+        slide_controller.grab_maindisplay = MagicMock()
+        slide_controller.slide_preview = MagicMock()
+        slide_controller.slide_count = 0
+
+        # WHEN: update_preview is called
+        slide_controller.update_preview()
+
+        # THEN: setPixmap should have been called
+        self.assertEqual(1, slide_controller.slide_preview.setPixmap.call_count, 'setPixmap should be called')
+        self.assertEqual(0, slide_controller.display.preview.call_count, 'display.preview() should not be called')
+        self.assertEqual(0, mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called')
+        self.assertEqual(0, mocked_image_manager.get_image.call_count, 'image_manager should not be called')
+
+    @patch(u'openlp.core.ui.slidecontroller.SlideController.image_manager')
+    @patch(u'PyQt5.QtCore.QTimer.singleShot')
+    def update_preview_test_image(self, mocked_singleShot, mocked_image_manager):
+        """
+        Test that the preview screen is updated with the correct preview for image service items
+        """
+        # GIVEN: A mocked image service item, a mocked image_manager, a mocked Registry,
+        #        and a slide controller with many mocks.
+        # Mocked Image Item
+        mocked_img_item = MagicMock()
+        mocked_img_item.get_rendered_frame.return_value = ''
+        mocked_img_item.is_capable = MagicMock()
+        mocked_img_item.is_capable.side_effect = [False, True]
+        # Mock image_manager
+        mocked_image_manager.get_image.return_value = QtGui.QImage()
+        # Mock Registry
+        Registry.create()
+        mocked_main_window = MagicMock()
+        Registry().register('main_window', mocked_main_window)
+        # Mock SlideController
+        slide_controller = SlideController(None)
+        slide_controller.service_item = mocked_img_item
+        slide_controller.is_live = False
+        slide_controller.log_debug = MagicMock()
+        slide_controller.selected_row = MagicMock()
+        slide_controller.screens = MagicMock()
+        slide_controller.screens.current = {'primary': ''}
+        slide_controller.display = MagicMock()
+        slide_controller.display.preview.return_value = QtGui.QImage()
+        slide_controller.grab_maindisplay = MagicMock()
+        slide_controller.slide_preview = MagicMock()
+        slide_controller.slide_count = 0
+
+        # WHEN: update_preview is called
+        slide_controller.update_preview()
+
+        # THEN: setPixmap and display.preview should have been called
+        self.assertEqual(1, slide_controller.slide_preview.setPixmap.call_count, 'setPixmap should be called')
+        self.assertEqual(1, slide_controller.display.preview.call_count, 'display.preview() should be called')
+        self.assertEqual(0, mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called')
+        self.assertEqual(0, mocked_image_manager.get_image.call_count, 'image_manager should not be called')
+
 
 class TestInfoLabel(TestCase):
 

=== modified file 'tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py'
--- tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py	2016-04-22 18:35:23 +0000
+++ tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py	2016-05-06 02:05:04 +0000
@@ -24,9 +24,11 @@
 """
 from unittest import TestCase
 
+from PyQt5 import QtGui
+
 from openlp.core.common import Settings
 from openlp.core.ui.lib.listpreviewwidget import ListPreviewWidget
-from openlp.core.lib import ServiceItem
+from openlp.core.lib import ImageSource, ServiceItem
 
 from tests.functional import MagicMock, patch, call
 
@@ -72,6 +74,53 @@
         self.assertIsNotNone(list_preview_widget, 'The ListPreviewWidget object should not be None')
         self.assertEquals(list_preview_widget.screen_ratio, 1, 'Should not be called')
 
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.image_manager')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
+    def replace_service_item_test_thumbs(self, mocked_setRowHeight, mocked_resizeRowsToContents,
+                                         mocked_image_manager):
+        """
+        Test that thubmails for different slides are loaded properly in replace_service_item.
+        """
+        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
+        #        different ServiceItem(s), an ImageManager, and a ListPreviewWidget.
+
+        # Mock Settings().value('advanced/slide max height')
+        self.mocked_Settings_obj.value.return_value = 0
+        # Mock self.viewport().width()
+        self.mocked_viewport_obj.width.return_value = 200
+        # Mock Image service item
+        mocked_img_service_item = MagicMock()
+        mocked_img_service_item.is_text.return_value = False
+        mocked_img_service_item.is_media.return_value = False
+        mocked_img_service_item.is_command.return_value = False
+        mocked_img_service_item.is_capable.return_value = False
+        mocked_img_service_item.get_frames.return_value = [{'title': None, 'path': 'TEST1', 'image': 'FAIL'},
+                                                           {'title': None, 'path': 'TEST2', 'image': 'FAIL'}]
+        # Mock Command service item
+        mocked_cmd_service_item = MagicMock()
+        mocked_cmd_service_item.is_text.return_value = False
+        mocked_cmd_service_item.is_media.return_value = False
+        mocked_cmd_service_item.is_command.return_value = True
+        mocked_cmd_service_item.is_capable.return_value = True
+        mocked_cmd_service_item.get_frames.return_value = [{'title': None, 'path': 'FAIL', 'image': 'TEST3'},
+                                                           {'title': None, 'path': 'FAIL', 'image': 'TEST4'}]
+        # Mock image_manager
+        mocked_image_manager.get_image.return_value = QtGui.QImage()
+
+        # init ListPreviewWidget and load service item
+        list_preview_widget = ListPreviewWidget(None, 1)
+
+        # WHEN: replace_service_item is called
+        list_preview_widget.replace_service_item(mocked_img_service_item, 200, 0)
+        list_preview_widget.replace_service_item(mocked_cmd_service_item, 200, 0)
+
+        # THEN: The ImageManager should be called in the appriopriate manner for each service item.
+        self.assertEquals(mocked_image_manager.get_image.call_count, 4, 'Should be called once for each slide')
+        calls = [call('TEST1', ImageSource.ImagePlugin), call('TEST2', ImageSource.ImagePlugin),
+                 call('TEST3', ImageSource.CommandPlugins), call('TEST4', ImageSource.CommandPlugins)]
+        mocked_image_manager.get_image.assert_has_calls(calls)
+
     @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
     @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
     def replace_recalculate_layout_test_text(self, mocked_setRowHeight, mocked_resizeRowsToContents):
@@ -120,6 +169,7 @@
         # Mock image service item
         service_item = MagicMock()
         service_item.is_text.return_value = False
+        service_item.is_capable.return_value = False
         service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
                                                 {'title': None, 'path': None, 'image': None}]
         # init ListPreviewWidget and load service item
@@ -156,6 +206,7 @@
         # Mock image service item
         service_item = MagicMock()
         service_item.is_text.return_value = False
+        service_item.is_capable.return_value = False
         service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
                                                 {'title': None, 'path': None, 'image': None}]
         # init ListPreviewWidget and load service item
@@ -225,6 +276,7 @@
         # Mock image service item
         service_item = MagicMock()
         service_item.is_text.return_value = False
+        service_item.is_capable.return_value = False
         service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
                                                 {'title': None, 'path': None, 'image': None}]
         # Mock self.cellWidget().children().setMaximumWidth()
@@ -261,6 +313,7 @@
         # Mock image service item
         service_item = MagicMock()
         service_item.is_text.return_value = False
+        service_item.is_capable.return_value = False
         service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
                                                 {'title': None, 'path': None, 'image': None}]
         # Mock self.cellWidget().children().setMaximumWidth()


Follow ups