From 771e97862f3f09753087175c1ac4ceb7fcea554e Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 4 Dec 2019 18:15:24 +0000 Subject: [PATCH 1/5] Add distribution detection in is_linux() function - Optional argument to check Linux distro - Add a test for the new argument - Add some other tests to expand test coverage - Remove Windows from testing - If "distro" module is missing, create a replacement that returns False --- .gitlab-ci.yml | 16 +------- openlp/core/common/__init__.py | 15 ++++++- scripts/check_dependencies.py | 1 + setup.py | 1 + .../openlp_core/common/test_common.py | 40 +++++++++++++++++-- .../common/test_network_interfaces.py | 36 ++++++++++++++--- 6 files changed, 83 insertions(+), 26 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 73294d97f..64495e1a4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -63,19 +63,6 @@ test-macos: only: - master@openlp/openlp -test-windows: - stage: test - tags: - - windows - script: - - C:\Users\raoul\GitLab-Runner\venv\Scripts\pytest.exe --color=no --disable-warnings --cov openlp - - mv .coverage windows.coverage - artifacts: - paths: - - windows.coverage - only: - - master@openlp/openlp - test-display: stage: test image: openlp/angular @@ -87,7 +74,7 @@ pages: stage: deploy image: openlp/debian script: - - python3-coverage combine linux.coverage macos.coverage windows.coverage + - python3-coverage combine linux.coverage macos.coverage - fixpaths .coverage - python3-coverage html - mv htmlcov public @@ -99,6 +86,5 @@ pages: dependencies: - test-debian - test-macos - - test-windows only: - master@openlp/openlp diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index 654df4505..0eeb6fb4c 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -38,6 +38,13 @@ from PyQt5.QtCore import QCryptographicHash as QHash from PyQt5.QtNetwork import QAbstractSocket, QHostAddress, QNetworkInterface from chardet.universaldetector import UniversalDetector +try: + from distro import id as distro_id +except ImportError: + # The distro module is only valid for Linux, so if it doesn't exist, create a function that always returns False + def distro_id(): + return False + log = logging.getLogger(__name__ + '.__init__') @@ -212,13 +219,17 @@ def is_macosx(): return sys.platform.startswith('darwin') -def is_linux(): +def is_linux(distro=None): """ Returns true if running on a system with a linux kernel e.g. Ubuntu, Debian, etc + :param distro: If not None, check if running that Linux distro :return: True if system is running a linux kernel false otherwise """ - return sys.platform.startswith('linux') + result = sys.platform.startswith('linux') + if result and distro: + result = result and distro == distro_id() + return result def is_64bit_instance(): diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index 83dc2117f..e7456a1ef 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -56,6 +56,7 @@ WIN32_MODULES = [ LINUX_MODULES = [ # Optical drive detection. 'dbus', + 'distro', 'Xlib', ] diff --git a/setup.py b/setup.py index 21e2400ac..16d5f495f 100644 --- a/setup.py +++ b/setup.py @@ -101,6 +101,7 @@ using a computer and a data projector.""", 'beautifulsoup4', 'chardet', 'dbus-python; platform_system=="Linux"', + 'distro; platform_system=="Linux"', 'lxml', 'Mako', 'pymediainfo >= 2.2', diff --git a/tests/functional/openlp_core/common/test_common.py b/tests/functional/openlp_core/common/test_common.py index 507eb6d0f..f957980be 100644 --- a/tests/functional/openlp_core/common/test_common.py +++ b/tests/functional/openlp_core/common/test_common.py @@ -22,11 +22,11 @@ Functional tests to test the AppLocation class and related methods. """ from pathlib import Path -from unittest import TestCase +from unittest import TestCase, skipUnless from unittest.mock import MagicMock, call, patch from openlp.core.common import Singleton, clean_button_text, de_hump, extension_loader, is_linux, is_macosx, is_win, \ - normalize_str, path_to_module, trace_error_handler + is_64bit_instance, normalize_str, path_to_module, trace_error_handler class TestCommonFunctions(TestCase): @@ -243,7 +243,7 @@ class TestCommonFunctions(TestCase): # GIVEN: Mocked out objects with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys: - # WHEN: The mocked os.name and sys.platform are set to 'posix' and 'linux3' repectivly + # WHEN: The mocked os.name and sys.platform are set to 'posix' and 'linux3' repectively mocked_os.name = 'posix' mocked_sys.platform = 'linux3' @@ -252,6 +252,40 @@ class TestCommonFunctions(TestCase): assert is_win() is False, 'is_win() should return False' assert is_macosx() is False, 'is_macosx() should return False' + @skipUnless(is_linux(), 'This can only run on Linux') + def test_is_linux_distro(self): + """ + Test the is_linux() function for a particular Linux distribution + """ + # GIVEN: Mocked out objects + with patch('openlp.core.common.os') as mocked_os, \ + patch('openlp.core.common.sys') as mocked_sys, \ + patch('openlp.core.common.distro_id') as mocked_distro_id: + + # WHEN: The mocked os.name and sys.platform are set to 'posix' and 'linux3' repectively + # and the distro is Fedora + mocked_os.name = 'posix' + mocked_sys.platform = 'linux3' + mocked_distro_id.return_value = 'fedora' + + # THEN: The three platform functions should perform properly + assert is_linux(distro='fedora') is True, 'is_linux(distro="fedora") should return True' + assert is_win() is False, 'is_win() should return False' + assert is_macosx() is False, 'is_macosx() should return False' + + def test_is_64bit_instance(self): + """ + Test the is_64bit_instance() function + """ + # GIVEN: Mocked out objects + with patch('openlp.core.common.sys') as mocked_sys: + + # WHEN: The mocked sys.maxsize is set to 32-bit + mocked_sys.maxsize = 2**32 + + # THEN: The result should be False + assert is_64bit_instance() is False, 'is_64bit_instance() should return False' + def test_normalize_str_leaves_newlines(self): # GIVEN: a string containing newlines string = 'something\nelse' diff --git a/tests/openlp_core/common/test_network_interfaces.py b/tests/openlp_core/common/test_network_interfaces.py index 64dbda294..dff94d8c0 100644 --- a/tests/openlp_core/common/test_network_interfaces.py +++ b/tests/openlp_core/common/test_network_interfaces.py @@ -35,8 +35,9 @@ class FakeIP4InterfaceEntry(QObject): """ Class to face an interface for testing purposes """ - def __init__(self, name='lo'): + def __init__(self, name='lo', is_valid=True): self.my_name = name + self._is_valid = is_valid if name in ['localhost', 'lo']: self.my_ip = QNetworkAddressEntry() self.my_ip.setBroadcast(QHostAddress('255.0.0.0')) @@ -75,7 +76,7 @@ class FakeIP4InterfaceEntry(QObject): return self.my_name def isValid(self): - return True + return self._is_valid class TestInterfaces(TestCase, TestMixin): @@ -92,6 +93,7 @@ class TestInterfaces(TestCase, TestMixin): self.fake_lo = FakeIP4InterfaceEntry() self.fake_localhost = FakeIP4InterfaceEntry(name='localhost') self.fake_address = FakeIP4InterfaceEntry(name='eth25') + self.invalid_if = FakeIP4InterfaceEntry(name='invalid', is_valid=False) def tearDown(self): """ @@ -126,7 +128,7 @@ class TestInterfaces(TestCase, TestMixin): # GIVEN: Test environment call_debug = [ call('Getting local IPv4 interface(es) information'), - call("Filtering out interfaces we don't care about: lo") + call('Filtering out interfaces we don\'t care about: lo') ] # WHEN: get_network_interfaces() is called @@ -146,7 +148,7 @@ class TestInterfaces(TestCase, TestMixin): # GIVEN: Test environment call_debug = [ call('Getting local IPv4 interface(es) information'), - call("Filtering out interfaces we don't care about: localhost") + call('Filtering out interfaces we don\'t care about: localhost') ] # WHEN: get_network_interfaces() is called @@ -190,7 +192,7 @@ class TestInterfaces(TestCase, TestMixin): # GIVEN: Test environment call_debug = [ call('Getting local IPv4 interface(es) information'), - call("Filtering out interfaces we don't care about: lo"), + call('Filtering out interfaces we don\'t care about: lo'), call('Checking for isValid and flags == IsUP | IsRunning'), call('Checking address(es) protocol'), call('Checking for protocol == IPv4Protocol'), @@ -205,4 +207,26 @@ class TestInterfaces(TestCase, TestMixin): # THEN: There should be a fake 'eth25' interface mock_log.debug.assert_has_calls(call_debug) - assert interfaces == self.fake_address.fake_data, "There should have been only 'eth25' interface listed" + assert interfaces == self.fake_address.fake_data, 'There should have been only "eth25" interface listed' + + @patch.object(openlp.core.common, 'log') + def test_network_interfaces_invalid(self, mock_log): + """ + Test get_network_interfaces() returns an empty dictionary when there are no valid interfaces + """ + # GIVEN: Test environment + call_debug = [ + call('Getting local IPv4 interface(es) information'), + call('Checking for isValid and flags == IsUP | IsRunning') + ] + call_warning = [call('No active IPv4 network interfaces detected')] + + # WHEN: get_network_interfaces() is called + with patch('openlp.core.common.QNetworkInterface') as mock_network_interface: + mock_network_interface.allInterfaces.return_value = [self.invalid_if] + interfaces = get_network_interfaces() + + # THEN: There should be a fake 'eth25' interface + mock_log.debug.assert_has_calls(call_debug) + mock_log.warning.assert_has_calls(call_warning) + assert interfaces == {}, 'There should not be any interfaces listed' From 9fd6752a836f66894741d901296c1ebdd02c5ffd Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Wed, 4 Dec 2019 20:01:02 +0000 Subject: [PATCH 2/5] Try to fix paths in coverage report generation - Add the local path to coverage config - Fix the paths before generating an HTML report --- .gitignore | 1 + openlp/core/ui/slidecontroller.py | 29 +- tests/conftest.py | 25 +- .../openlp_core/ui/test_slidecontroller.py | 2010 +++++++++-------- 4 files changed, 1045 insertions(+), 1020 deletions(-) diff --git a/.gitignore b/.gitignore index 81724a356..04dc51a3f 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ .vscode .eggs .venv +.mypy_cache OpenLP.egg-info \#*\# __pycache__ diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index f9a88ec71..7055e0e63 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -34,7 +34,6 @@ from openlp.core.common.actions import ActionList, CategoryOrder from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.registry import Registry, RegistryBase -from openlp.core.common.settings import Settings from openlp.core.display.screens import ScreenList from openlp.core.display.window import DisplayWindow from openlp.core.lib import ServiceItemAction, image_to_byte @@ -324,7 +323,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): self.play_slides_once = create_action(self, 'playSlidesOnce', text=UiStrings().PlaySlidesToEnd, icon=UiIcons().clock, checked=False, can_shortcuts=True, category=self.category, triggers=self.on_play_slides_once) - if Settings().value(self.main_window.advanced_settings_section + '/slide limits') == SlideLimits.Wrap: + if self.settings.value(self.main_window.advanced_settings_section + '/slide limits') == SlideLimits.Wrap: self.play_slides_menu.setDefaultAction(self.play_slides_loop) else: self.play_slides_menu.setDefaultAction(self.play_slides_once) @@ -697,13 +696,13 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): """ Adjusts the value of the ``delay_spin_box`` to the given one. """ - self.delay_spin_box.setValue(Settings().value('core/loop delay')) + self.delay_spin_box.setValue(self.settings.value('core/loop delay')) def update_slide_limits(self): """ Updates the Slide Limits variable from the settings. """ - self.slide_limits = Settings().value(self.main_window.advanced_settings_section + '/slide limits') + self.slide_limits = self.settings.value(self.main_window.advanced_settings_section + '/slide limits') def enable_tool_bar(self, item): """ @@ -737,7 +736,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): self.play_slides_loop.setIcon(UiIcons().clock) self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop) if item.is_text(): - if (Settings().value(self.main_window.songs_settings_section + '/display songbar') and + if (self.settings.value(self.main_window.songs_settings_section + '/display songbar') and not self.song_menu.menu().isEmpty()): self.toolbar.set_widget_visible('song_menu', True) if item.is_capable(ItemCapabilities.CanLoop) and len(item.slides) > 1: @@ -1013,9 +1012,9 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): self.theme_screen.setChecked(False) self.desktop_screen.setChecked(False) if checked: - Settings().setValue(self.main_window.general_settings_section + '/screen blank', 'blanked') + self.settings.setValue(self.main_window.general_settings_section + '/screen blank', 'blanked') else: - Settings().remove(self.main_window.general_settings_section + '/screen blank') + self.settings.remove(self.main_window.general_settings_section + '/screen blank') self.blank_plugin() self.update_preview() self.on_toggle_loop() @@ -1034,9 +1033,9 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): self.theme_screen.setChecked(checked) self.desktop_screen.setChecked(False) if checked: - Settings().setValue(self.main_window.general_settings_section + '/screen blank', 'themed') + self.settings.setValue(self.main_window.general_settings_section + '/screen blank', 'themed') else: - Settings().remove(self.main_window.general_settings_section + '/screen blank') + self.settings.remove(self.main_window.general_settings_section + '/screen blank') self.blank_plugin() self.update_preview() self.on_toggle_loop() @@ -1056,9 +1055,9 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): self.theme_screen.setChecked(False) self.desktop_screen.setChecked(checked) if checked: - Settings().setValue(self.main_window.general_settings_section + '/screen blank', 'hidden') + self.settings.setValue(self.main_window.general_settings_section + '/screen blank', 'hidden') else: - Settings().remove(self.main_window.general_settings_section + '/screen blank') + self.settings.remove(self.main_window.general_settings_section + '/screen blank') self.hide_plugin(checked) self.update_preview() self.on_toggle_loop() @@ -1145,7 +1144,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): return # If "click live slide to unblank" is enabled, unblank the display. And start = Item is sent to Live. # Note: If this if statement is placed at the bottom of this function instead of top slide transitions are lost. - if self.is_live and Settings().value('core/click live slide to unblank'): + if self.is_live and self.settings.value('core/click live slide to unblank'): if not start: Registry().execute('slidecontroller_live_unblank') row = self.preview_widget.current_slide_number() @@ -1353,7 +1352,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): self.play_slides_once.setText(UiStrings().PlaySlidesToEnd) self.play_slides_menu.setDefaultAction(self.play_slides_loop) self.play_slides_once.setChecked(False) - if Settings().value('core/click live slide to unblank'): + if self.settings.value('core/click live slide to unblank'): Registry().execute('slidecontroller_live_unblank') else: self.play_slides_loop.setIcon(UiIcons().clock) @@ -1378,7 +1377,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop) self.play_slides_menu.setDefaultAction(self.play_slides_once) self.play_slides_loop.setChecked(False) - if Settings().value('core/click live slide to unblank'): + if self.settings.value('core/click live slide to unblank'): Registry().execute('slidecontroller_live_unblank') else: self.play_slides_once.setIcon(UiIcons().clock) @@ -1423,7 +1422,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): Triggered when a preview slide item is double clicked """ if self.service_item: - if Settings().value('advanced/double click live') and Settings().value('core/auto unblank'): + if self.settings.value('advanced/double click live') and self.settings.value('core/auto unblank'): # Live and Preview have issues if we have video or presentations # playing in both at the same time. if self.service_item.is_command(): diff --git a/tests/conftest.py b/tests/conftest.py index 9bbd2018f..d8437b741 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,6 +23,7 @@ All the tests """ import os from tempfile import mkstemp +from unittest.mock import MagicMock import pytest from PyQt5 import QtCore, QtWidgets @@ -39,24 +40,34 @@ def qapp(): del app +@pytest.fixture +def registry(): + """An instance of the Registry""" + Registry.create() + + @pytest.yield_fixture -def settings(qapp): +def settings(qapp, registry): """A Settings() instance""" fd, ini_file = mkstemp('.ini') Settings.set_filename(ini_file) - Registry.create() Settings().setDefaultFormat(QtCore.QSettings.IniFormat) # Needed on windows to make sure a Settings object is available during the tests sets = Settings() sets.setValue('themes/global theme', 'my_theme') - Registry().register('settings', set) + Registry().register('settings', sets) yield sets del sets os.close(fd) os.unlink(Settings().fileName()) -@pytest.fixture -def registry(): - """An instance of the Registry""" - Registry.create() +@pytest.yield_fixture +def mock_settings(registry): + """A Mock Settings() instance""" + # Create and register a mock settings object to work with + mock_settings = MagicMock() + Registry().register('settings', mock_settings) + yield mock_settings + Registry().remove('settings') + del mock_settings diff --git a/tests/functional/openlp_core/ui/test_slidecontroller.py b/tests/functional/openlp_core/ui/test_slidecontroller.py index 2f26b95cd..21ae2a9d1 100644 --- a/tests/functional/openlp_core/ui/test_slidecontroller.py +++ b/tests/functional/openlp_core/ui/test_slidecontroller.py @@ -21,7 +21,6 @@ """ Package to test the openlp.core.ui.slidecontroller package. """ -from unittest import TestCase from unittest.mock import MagicMock, patch from PyQt5 import QtCore, QtGui @@ -32,1011 +31,1026 @@ from openlp.core.ui.slidecontroller import NON_TEXT_MENU, WIDE_MENU, InfoLabel, SlideController -class TestSlideController(TestCase): - - def setUp(self): - """ - Set up the components need for all tests. - """ - Registry.create() - - def test_initial_slide_controller(self): - """ - Test the initial slide controller state . - """ - # GIVEN: A new SlideController instance. - slide_controller = SlideController(None) - - # WHEN: the default controller is built. - # THEN: The controller should not be a live controller. - assert slide_controller.is_live is False, 'The base slide controller should not be a live controller' - - def test_text_service_item_blank(self): - """ - Test that loading a text-based service item into the slide controller sets the correct blank menu - """ - # GIVEN: A new SlideController instance. - slide_controller = SlideController(None) - service_item = MagicMock() - toolbar = MagicMock() - toolbar.set_widget_visible = MagicMock() - slide_controller.toolbar = toolbar - slide_controller.service_item = service_item - - # WHEN: a text based service item is used - slide_controller.service_item.is_text = MagicMock(return_value=True) - slide_controller.set_blank_menu() - - # THEN: the call to set the visible items on the toolbar should be correct - toolbar.set_widget_visible.assert_called_with(WIDE_MENU, True) - - def test_non_text_service_item_blank(self): - """ - Test that loading a non-text service item into the slide controller sets the correct blank menu - """ - # GIVEN: A new SlideController instance. - slide_controller = SlideController(None) - service_item = MagicMock() - toolbar = MagicMock() - toolbar.set_widget_visible = MagicMock() - slide_controller.toolbar = toolbar - slide_controller.service_item = service_item - - # WHEN a non text based service item is used - slide_controller.service_item.is_text = MagicMock(return_value=False) - slide_controller.set_blank_menu() - - # THEN: then call set up the toolbar to blank the display screen. - toolbar.set_widget_visible.assert_called_with(NON_TEXT_MENU, True) - - @patch('openlp.core.ui.slidecontroller.Settings') - def test_receive_spin_delay(self, MockedSettings): - """ - Test that the spin box is updated accordingly after a call to receive_spin_delay() - """ - # GIVEN: A new SlideController instance. - mocked_value = MagicMock(return_value=1) - MockedSettings.return_value = MagicMock(value=mocked_value) - mocked_delay_spin_box = MagicMock() - slide_controller = SlideController(None) - slide_controller.delay_spin_box = mocked_delay_spin_box - - # WHEN: The receive_spin_delay() method is called - slide_controller.receive_spin_delay() - - # THEN: The Settings()value() and delay_spin_box.setValue() methods should have been called correctly - mocked_value.assert_called_with('core/loop delay') - mocked_delay_spin_box.setValue.assert_called_with(1) - - def test_toggle_display_blank(self): - """ - Check that the toggle_display('blank') method calls the on_blank_display() method - """ - # GIVEN: A new SlideController instance. - mocked_on_blank_display = MagicMock() - mocked_on_theme_display = MagicMock() - mocked_on_hide_display = MagicMock() - slide_controller = SlideController(None) - slide_controller.on_blank_display = mocked_on_blank_display - slide_controller.on_theme_display = mocked_on_theme_display - slide_controller.on_hide_display = mocked_on_hide_display - - # WHEN: toggle_display() is called with an argument of "blank" - slide_controller.toggle_display('blank') - - # THEN: Only on_blank_display() should have been called with an argument of True - mocked_on_blank_display.assert_called_once_with(True) - assert 0 == mocked_on_theme_display.call_count, 'on_theme_display should not have been called' - assert 0 == mocked_on_hide_display.call_count, 'on_hide_display should not have been called' - - def test_toggle_display_hide(self): - """ - Check that the toggle_display('hide') method calls the on_blank_display() method - """ - # GIVEN: A new SlideController instance. - mocked_on_blank_display = MagicMock() - mocked_on_theme_display = MagicMock() - mocked_on_hide_display = MagicMock() - slide_controller = SlideController(None) - slide_controller.on_blank_display = mocked_on_blank_display - slide_controller.on_theme_display = mocked_on_theme_display - slide_controller.on_hide_display = mocked_on_hide_display - - # WHEN: toggle_display() is called with an argument of "hide" - slide_controller.toggle_display('hide') - - # THEN: Only on_blank_display() should have been called with an argument of True - mocked_on_blank_display.assert_called_once_with(True) - assert 0 == mocked_on_theme_display.call_count, 'on_theme_display should not have been called' - assert 0 == mocked_on_hide_display.call_count, 'on_hide_display should not have been called' - - def test_toggle_display_theme(self): - """ - Check that the toggle_display('theme') method calls the on_theme_display() method - """ - # GIVEN: A new SlideController instance. - mocked_on_blank_display = MagicMock() - mocked_on_theme_display = MagicMock() - mocked_on_hide_display = MagicMock() - slide_controller = SlideController(None) - slide_controller.on_blank_display = mocked_on_blank_display - slide_controller.on_theme_display = mocked_on_theme_display - slide_controller.on_hide_display = mocked_on_hide_display - - # WHEN: toggle_display() is called with an argument of "theme" - slide_controller.toggle_display('theme') - - # THEN: Only on_theme_display() should have been called with an argument of True - mocked_on_theme_display.assert_called_once_with(True) - assert 0 == mocked_on_blank_display.call_count, 'on_blank_display should not have been called' - assert 0 == mocked_on_hide_display.call_count, 'on_hide_display should not have been called' - - def test_toggle_display_desktop(self): - """ - Check that the toggle_display('desktop') method calls the on_hide_display() method - """ - # GIVEN: A new SlideController instance. - mocked_on_blank_display = MagicMock() - mocked_on_theme_display = MagicMock() - mocked_on_hide_display = MagicMock() - slide_controller = SlideController(None) - slide_controller.on_blank_display = mocked_on_blank_display - slide_controller.on_theme_display = mocked_on_theme_display - slide_controller.on_hide_display = mocked_on_hide_display - - # WHEN: toggle_display() is called with an argument of "desktop" - slide_controller.toggle_display('desktop') - - # THEN: Only on_hide_display() should have been called with an argument of True - mocked_on_hide_display.assert_called_once_with(True) - assert 0 == mocked_on_blank_display.call_count, 'on_blank_display should not have been called' - assert 0 == mocked_on_theme_display.call_count, 'on_theme_display should not have been called' - - def test_toggle_display_show(self): - """ - Check that the toggle_display('show') method calls all the on_X_display() methods - """ - # GIVEN: A new SlideController instance. - mocked_on_blank_display = MagicMock() - mocked_on_theme_display = MagicMock() - mocked_on_hide_display = MagicMock() - slide_controller = SlideController(None) - slide_controller.on_blank_display = mocked_on_blank_display - slide_controller.on_theme_display = mocked_on_theme_display - slide_controller.on_hide_display = mocked_on_hide_display - - # WHEN: toggle_display() is called with an argument of "show" - slide_controller.toggle_display('show') - - # THEN: All the on_X_display() methods should have been called with an argument of False - mocked_on_blank_display.assert_called_once_with(False) - mocked_on_theme_display.assert_called_once_with(False) - mocked_on_hide_display.assert_called_once_with(False) - - def test_on_go_live_preview_controller(self): - """ - Test that when the on_go_preview() method is called the message is sent to the preview controller and focus is - set correctly. - """ - # GIVEN: A new SlideController instance and plugin preview then pressing go live should respond - mocked_display = MagicMock() - mocked_preview_controller = MagicMock() - mocked_preview_widget = MagicMock() - mocked_service_item = MagicMock() - mocked_service_item.from_service = False - mocked_preview_widget.current_slide_number.return_value = 1 - mocked_preview_widget.slide_count = MagicMock(return_value=2) - mocked_preview_controller.preview_widget = MagicMock() - Registry.create() - Registry().register('preview_controller', mocked_preview_controller) - slide_controller = SlideController(None) - slide_controller.service_item = mocked_service_item - slide_controller.preview_widget = mocked_preview_widget - slide_controller.displays = [mocked_display] - - # WHEN: on_go_live() is called - slide_controller.on_go_preview() - - # THEN: the preview controller should have the service item and the focus set to live - mocked_preview_controller.preview_widget.setFocus.assert_called_once_with() - - def test_on_go_live_live_controller(self): - """ - Test that when the on_go_live() method is called the message is sent to the live controller and focus is - set correctly. - """ - # GIVEN: A new SlideController instance and plugin preview then pressing go live should respond - mocked_display = MagicMock() - mocked_live_controller = MagicMock() - mocked_preview_widget = MagicMock() - mocked_service_item = MagicMock() - mocked_service_item.from_service = False - mocked_preview_widget.current_slide_number.return_value = 1 - mocked_preview_widget.slide_count = MagicMock(return_value=2) - mocked_live_controller.preview_widget = MagicMock() - Registry.create() - Registry().register('live_controller', mocked_live_controller) - slide_controller = SlideController(None) - slide_controller.service_item = mocked_service_item - slide_controller.preview_widget = mocked_preview_widget - slide_controller.displays = [mocked_display] - - # WHEN: on_go_live() is called - slide_controller.on_go_live() - - # THEN: the live controller should have the service item and the focus set to live - mocked_live_controller.add_service_manager_item.assert_called_once_with(mocked_service_item, 1) - mocked_live_controller.preview_widget.setFocus.assert_called_once_with() - - def test_on_go_live_service_manager(self): - """ - Test that when the on_go_live() method is called the message is sent to the live controller and focus is - set correctly. - """ - # GIVEN: A new SlideController instance and service manager preview then pressing go live should respond - mocked_display = MagicMock() - mocked_service_manager = MagicMock() - mocked_live_controller = MagicMock() - mocked_preview_widget = MagicMock() - mocked_service_item = MagicMock() - mocked_service_item.from_service = True - mocked_service_item.unique_identifier = 42 - mocked_preview_widget.current_slide_number.return_value = 1 - mocked_preview_widget.slide_count = MagicMock(return_value=2) - mocked_live_controller.preview_widget = MagicMock() - Registry.create() - Registry().register('live_controller', mocked_live_controller) - Registry().register('service_manager', mocked_service_manager) - slide_controller = SlideController(None) - slide_controller.service_item = mocked_service_item - slide_controller.preview_widget = mocked_preview_widget - slide_controller.displays = [mocked_display] - - # WHEN: on_go_live() is called - slide_controller.on_go_live() - - # THEN: the service manager should have the service item and the focus set to live - mocked_service_manager.preview_live.assert_called_once_with(42, 1) - mocked_live_controller.preview_widget.setFocus.assert_called_once_with() - - def test_service_previous(self): - """ - Check that calling the service_previous() method adds the previous key to the queue and processes the queue - """ - # GIVEN: A new SlideController instance. - mocked_keypress_queue = MagicMock() - mocked_process_queue = MagicMock() - slide_controller = SlideController(None) - slide_controller.keypress_queue = mocked_keypress_queue - slide_controller._process_queue = mocked_process_queue - - # WHEN: The service_previous() method is called - slide_controller.service_previous() - - # THEN: The keypress is added to the queue and the queue is processed - mocked_keypress_queue.append.assert_called_once_with(ServiceItemAction.Previous) - mocked_process_queue.assert_called_once_with() - - def test_service_next(self): - """ - Check that calling the service_next() method adds the next key to the queue and processes the queue - """ - # GIVEN: A new SlideController instance and mocked out methods - mocked_keypress_queue = MagicMock() - mocked_process_queue = MagicMock() - slide_controller = SlideController(None) - slide_controller.keypress_queue = mocked_keypress_queue - slide_controller._process_queue = mocked_process_queue - - # WHEN: The service_next() method is called - slide_controller.service_next() - - # THEN: The keypress is added to the queue and the queue is processed - mocked_keypress_queue.append.assert_called_once_with(ServiceItemAction.Next) - mocked_process_queue.assert_called_once_with() - - @patch('openlp.core.ui.slidecontroller.Settings') - def test_update_slide_limits(self, MockedSettings): - """ - Test that calling the update_slide_limits() method updates the slide limits - """ - # GIVEN: A mocked out Settings object, a new SlideController and a mocked out main_window - mocked_value = MagicMock(return_value=10) - MockedSettings.return_value = MagicMock(value=mocked_value) - mocked_main_window = MagicMock(advanced_settings_section='advanced') - Registry.create() - Registry().register('main_window', mocked_main_window) - slide_controller = SlideController(None) - - # WHEN: update_slide_limits() is called - slide_controller.update_slide_limits() - - # THEN: The value of slide_limits should be 10 - mocked_value.assert_called_once_with('advanced/slide limits') - assert 10 == slide_controller.slide_limits, 'Slide limits should have been updated to 10' - - def test_enable_tool_bar_live(self): - """ - Check that when enable_tool_bar on a live slide controller is called, enable_live_tool_bar is called - """ - # GIVEN: Mocked out enable methods and a real slide controller which is set to live - mocked_enable_live_tool_bar = MagicMock() - mocked_enable_preview_tool_bar = MagicMock() - slide_controller = SlideController(None) - slide_controller.is_live = True - slide_controller.enable_live_tool_bar = mocked_enable_live_tool_bar - slide_controller.enable_preview_tool_bar = mocked_enable_preview_tool_bar - mocked_service_item = MagicMock() - - # WHEN: enable_tool_bar() is called - slide_controller.enable_tool_bar(mocked_service_item) - - # THEN: The enable_live_tool_bar() method is called, not enable_preview_tool_bar() - mocked_enable_live_tool_bar.assert_called_once_with(mocked_service_item) - assert 0 == mocked_enable_preview_tool_bar.call_count, 'The preview method should not have been called' - - def test_enable_tool_bar_preview(self): - """ - Check that when enable_tool_bar on a preview slide controller is called, enable_preview_tool_bar is called - """ - # GIVEN: Mocked out enable methods and a real slide controller which is set to live - mocked_enable_live_tool_bar = MagicMock() - mocked_enable_preview_tool_bar = MagicMock() - slide_controller = SlideController(None) - slide_controller.is_live = False - slide_controller.enable_live_tool_bar = mocked_enable_live_tool_bar - slide_controller.enable_preview_tool_bar = mocked_enable_preview_tool_bar - mocked_service_item = MagicMock() - - # WHEN: enable_tool_bar() is called - slide_controller.enable_tool_bar(mocked_service_item) - - # THEN: The enable_preview_tool_bar() method is called, not enable_live_tool_bar() - mocked_enable_preview_tool_bar.assert_called_once_with(mocked_service_item) - assert 0 == mocked_enable_live_tool_bar.call_count, 'The live method should not have been called' - - def test_refresh_service_item_text(self): - """ - Test that the refresh_service_item() method refreshes a text service item - """ - # GIVEN: A mock service item and a fresh slide controller - mocked_service_item = MagicMock() - mocked_service_item.is_text.return_value = True - mocked_service_item.is_image.return_value = False - mocked_process_item = MagicMock() - slide_controller = SlideController(None) - slide_controller.service_item = mocked_service_item - slide_controller._process_item = mocked_process_item - slide_controller.selected_row = 5 - - # WHEN: The refresh_service_item method() is called - slide_controller.refresh_service_item() - - # THEN: The item should be re-processed - mocked_service_item.is_text.assert_called_once_with() - assert 0 == mocked_service_item.is_image.call_count, 'is_image should not have been called' - mocked_service_item.render.assert_called_once_with() - mocked_process_item.assert_called_once_with(mocked_service_item, 5) - - def test_refresh_service_item_image(self): - """ - Test that the refresh_service_item() method refreshes a image service item - """ - # GIVEN: A mock service item and a fresh slide controller - mocked_service_item = MagicMock() - mocked_service_item.is_text.return_value = False - mocked_service_item.is_image.return_value = True - mocked_process_item = MagicMock() - slide_controller = SlideController(None) - slide_controller.service_item = mocked_service_item - slide_controller._process_item = mocked_process_item - slide_controller.selected_row = 5 - - # WHEN: The refresh_service_item method() is called - slide_controller.refresh_service_item() - - # THEN: The item should be re-processed - mocked_service_item.is_text.assert_called_once_with() - mocked_service_item.is_image.assert_called_once_with() - mocked_service_item.render.assert_called_once_with() - mocked_process_item.assert_called_once_with(mocked_service_item, 5) - - def test_refresh_service_item_not_image_or_text(self): - """ - Test that the refresh_service_item() method does not refresh a service item if it's neither text or an image - """ - # GIVEN: A mock service item and a fresh slide controller - mocked_service_item = MagicMock() - mocked_service_item.is_text.return_value = False - mocked_service_item.is_image.return_value = False - mocked_process_item = MagicMock() - slide_controller = SlideController(None) - slide_controller.service_item = mocked_service_item - slide_controller._process_item = mocked_process_item - slide_controller.selected_row = 5 - - # WHEN: The refresh_service_item method() is called - slide_controller.refresh_service_item() - - # THEN: The item should be re-processed - mocked_service_item.is_text.assert_called_once_with() - mocked_service_item.is_image.assert_called_once_with() - assert 0 == mocked_service_item.render.call_count, 'The render() method should not have been called' - assert 0 == mocked_process_item.call_count, 'The mocked_process_item() method should not have been called' - - def test_add_service_item_with_song_edit(self): - """ - Test the add_service_item() method when song_edit is True - """ - # GIVEN: A slide controller and a new item to add - mocked_item = MagicMock() - mocked_process_item = MagicMock() - slide_controller = SlideController(None) - slide_controller._process_item = mocked_process_item - slide_controller.song_edit = True - slide_controller.selected_row = 2 - - # WHEN: The item is added to the service - slide_controller.add_service_item(mocked_item) - - # THEN: The item is processed, the slide number is correct, and the song is not editable (or something) - assert slide_controller.song_edit is False, 'song_edit should be False' - mocked_process_item.assert_called_once_with(mocked_item, 2) - - def test_add_service_item_without_song_edit(self): - """ - Test the add_service_item() method when song_edit is False - """ - # GIVEN: A slide controller and a new item to add - mocked_item = MagicMock() - mocked_process_item = MagicMock() - slide_controller = SlideController(None) - slide_controller._process_item = mocked_process_item - slide_controller.song_edit = False - slide_controller.selected_row = 2 - - # WHEN: The item is added to the service - slide_controller.add_service_item(mocked_item) - - # THEN: The item is processed, the slide number is correct, and the song is not editable (or something) - assert slide_controller.song_edit is False, 'song_edit should be False' - mocked_process_item.assert_called_once_with(mocked_item, 0) - - def test_replace_service_manager_item_different_items(self): - """ - Test that when the service items are not the same, nothing happens - """ - # GIVEN: A slide controller and a new item to add - mocked_item = MagicMock() - mocked_preview_widget = MagicMock() - mocked_process_item = MagicMock() - slide_controller = SlideController(None) - slide_controller.preview_widget = mocked_preview_widget - slide_controller._process_item = mocked_process_item - slide_controller.service_item = None - - # WHEN: The service item is replaced - slide_controller.replace_service_manager_item(mocked_item) - - # THEN: The service item should not be processed - assert 0 == mocked_process_item.call_count, 'The _process_item() method should not have been called' - assert 0 == mocked_preview_widget.current_slide_number.call_count, \ - 'The preview_widget current_slide_number.() method should not have been called' - - def test_replace_service_manager_item_same_item(self): - """ - Test that when the service item is the same, the service item is reprocessed - """ - # GIVEN: A slide controller and a new item to add - mocked_item = MagicMock() - mocked_preview_widget = MagicMock() - mocked_preview_widget.current_slide_number.return_value = 7 - mocked_process_item = MagicMock() - slide_controller = SlideController(None) - slide_controller.preview_widget = mocked_preview_widget - slide_controller._process_item = mocked_process_item - slide_controller.service_item = mocked_item - - # WHEN: The service item is replaced - slide_controller.replace_service_manager_item(mocked_item) - - # THEN: The service item should not be processed - mocked_preview_widget.current_slide_number.assert_called_with() - mocked_process_item.assert_called_once_with(mocked_item, 7) - - def test_on_slide_blank(self): - """ - Test on_slide_blank - """ - # GIVEN: An instance of SlideController and a mocked on_blank_display - slide_controller = SlideController(None) - slide_controller.on_blank_display = MagicMock() - - # WHEN: Calling on_slide_blank - slide_controller.on_slide_blank() - - # THEN: on_blank_display should have been called with True - slide_controller.on_blank_display.assert_called_once_with(True) - - def test_on_slide_unblank(self): - """ - Test on_slide_unblank - """ - # GIVEN: An instance of SlideController and a mocked on_blank_display - slide_controller = SlideController(None) - slide_controller.on_blank_display = MagicMock() - - # WHEN: Calling on_slide_unblank - slide_controller.on_slide_unblank() - - # THEN: on_blank_display should have been called with False - slide_controller.on_blank_display.assert_called_once_with(False) - - def test_on_slide_selected_index_no_service_item(self): - """ - Test that when there is no service item, the on_slide_selected_index() method returns immediately - """ - # GIVEN: A mocked service item and a slide controller without a service item - mocked_item = MagicMock() - slide_controller = SlideController(None) - slide_controller.service_item = None - - # WHEN: The method is called - slide_controller.on_slide_selected_index([10]) - - # THEN: It should have exited early - assert 0 == mocked_item.is_command.call_count, 'The service item should have not been called' - - @patch.object(Registry, 'execute') - def test_on_slide_selected_index_service_item_command(self, mocked_execute): - """ - Test that when there is a command service item, the command is executed - """ - # GIVEN: A mocked service item and a slide controller with a service item - mocked_item = MagicMock() - mocked_item.is_command.return_value = True - mocked_item.name = 'Mocked Item' - mocked_update_preview = MagicMock() - mocked_preview_widget = MagicMock() - mocked_slide_selected = MagicMock() - Registry.create() - slide_controller = SlideController(None) - slide_controller.service_item = mocked_item - slide_controller.update_preview = mocked_update_preview - slide_controller.preview_widget = mocked_preview_widget - slide_controller.slide_selected = mocked_slide_selected - slide_controller.is_live = True - - # WHEN: The method is called - slide_controller.on_slide_selected_index([9]) - - # THEN: It should have sent a notification - mocked_item.is_command.assert_called_once_with() - mocked_execute.assert_called_once_with('mocked item_slide', [mocked_item, True, 9]) - mocked_update_preview.assert_called_once_with() - assert 0 == mocked_preview_widget.change_slide.call_count, 'Change slide should not have been called' - assert 0 == mocked_slide_selected.call_count, 'slide_selected should not have been called' - - @patch.object(Registry, 'execute') - def test_on_slide_selected_index_service_item_not_command(self, mocked_execute): - """ - Test that when there is a service item but it's not a command, the preview widget is updated - """ - # GIVEN: A mocked service item and a slide controller with a service item - mocked_item = MagicMock() - mocked_item.is_command.return_value = False - mocked_item.name = 'Mocked Item' - mocked_update_preview = MagicMock() - mocked_preview_widget = MagicMock() - mocked_slide_selected = MagicMock() - Registry.create() - slide_controller = SlideController(None) - slide_controller.service_item = mocked_item - slide_controller.update_preview = mocked_update_preview - slide_controller.preview_widget = mocked_preview_widget - slide_controller.slide_selected = mocked_slide_selected - - # WHEN: The method is called - slide_controller.on_slide_selected_index([7]) - - # THEN: It should have sent a notification - mocked_item.is_command.assert_called_once_with() - assert 0 == mocked_execute.call_count, 'Execute should not have been called' - assert 0 == mocked_update_preview.call_count, 'Update preview should not have been called' - mocked_preview_widget.change_slide.assert_called_once_with(7) - mocked_slide_selected.assert_called_once_with() - - @patch.object(Registry, 'execute') - def test_process_item(self, mocked_execute): - """ - Test that presentation service-items is closed when followed by a media service-item - """ - # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute - # and a slide controller with many mocks. - mocked_pres_item = MagicMock() - mocked_pres_item.name = 'mocked_presentation_item' - mocked_pres_item.is_command.return_value = True - mocked_pres_item.is_media.return_value = False - mocked_pres_item.is_image.return_value = False - mocked_pres_item.from_service = False - mocked_pres_item.get_frames.return_value = [] - mocked_media_item = MagicMock() - mocked_media_item.name = 'mocked_media_item' - mocked_media_item.is_command.return_value = True - mocked_media_item.is_media.return_value = True - mocked_media_item.is_image.return_value = False - mocked_media_item.from_service = False - mocked_media_item.get_frames.return_value = [] - Registry.create() - mocked_main_window = MagicMock() - Registry().register('main_window', mocked_main_window) - Registry().register('media_controller', MagicMock()) - slide_controller = SlideController(None) - slide_controller.service_item = mocked_pres_item - slide_controller.is_live = False - slide_controller.preview_widget = MagicMock() - slide_controller.preview_display = MagicMock() - slide_controller.enable_tool_bar = MagicMock() - slide_controller.on_media_start = MagicMock() - slide_controller.slide_selected = MagicMock() - slide_controller.on_stop_loop = MagicMock() - slide_controller.info_label = MagicMock() - slide_controller.displays = [MagicMock()] - slide_controller.split = 0 - slide_controller.type_prefix = 'test' - - # WHEN: _process_item is called - slide_controller._process_item(mocked_media_item, 0) - - # THEN: Registry.execute should have been called to stop the presentation - assert 2 == mocked_execute.call_count, 'Execute should have been called 2 times' - assert 'mocked_presentation_item_stop' == mocked_execute.call_args_list[1][0][0], \ - 'The presentation should have been stopped.' - - def test_live_stolen_focus_shortcuts(self): - """ - Test that all the needed shortcuts are available in scenarios where Live has stolen focus. - These are found under def __add_actions_to_widget(self, widget): in slidecontroller.py - """ - # GIVEN: A slide controller, actions needed - slide_controller = SlideController(None) - mocked_widget = MagicMock() - slide_controller.previous_item = MagicMock() - slide_controller.next_item = MagicMock() - slide_controller.previous_service = MagicMock() - slide_controller.next_service = MagicMock() - slide_controller.desktop_screen_enable = MagicMock() - slide_controller.desktop_screen = MagicMock() - slide_controller.blank_screen = MagicMock() - slide_controller.theme_screen = MagicMock() - - # WHEN: __add_actions_to_widget is called - slide_controller._SlideController__add_actions_to_widget(mocked_widget) - - # THEN: The call to addActions should be correct - mocked_widget.addActions.assert_called_with([ - slide_controller.previous_item, slide_controller.next_item, - slide_controller.previous_service, slide_controller.next_service, - slide_controller.desktop_screen_enable, slide_controller.desktop_screen, - slide_controller.theme_screen, slide_controller.blank_screen - ]) - - @patch('openlp.core.ui.slidecontroller.Settings') - def test_on_preview_double_click_unblank_display(self, MockedSettings): - # GIVEN: A slide controller, actions needed, settins set to True. - slide_controller = SlideController(None) - mocked_settings = MagicMock() - mocked_settings.return_value = True - MockedSettings.return_value = mocked_settings - slide_controller.service_item = MagicMock() - slide_controller.service_item.is_media = MagicMock() - slide_controller.on_media_close = MagicMock() - slide_controller.on_go_live = MagicMock() - slide_controller.on_preview_add_to_service = MagicMock() - slide_controller.media_reset = MagicMock() - Registry.create() - Registry().set_flag('has doubleclick added item to service', True) - - # WHEN: on_preview_double_click is called - slide_controller.on_preview_double_click() - - # THEN: The call to addActions should be correct - assert 1 == slide_controller.on_go_live.call_count, 'on_go_live should have been called once.' - assert 0 == slide_controller.on_preview_add_to_service.call_count, 'Should have not been called.' - - @patch('openlp.core.ui.slidecontroller.Settings') - def test_on_preview_double_click_add_to_service(self, MockedSettings): - # GIVEN: A slide controller, actions needed, settins set to False. - slide_controller = SlideController(None) - mocked_settings = MagicMock() - mocked_settings.value.return_value = False - MockedSettings.return_value = mocked_settings - slide_controller.service_item = MagicMock() - slide_controller.service_item.is_media = MagicMock() - slide_controller.on_media_close = MagicMock() - slide_controller.on_go_live = MagicMock() - slide_controller.on_preview_add_to_service = MagicMock() - slide_controller.media_reset = MagicMock() - Registry.create() - Registry().set_flag('has doubleclick added item to service', False) - - # WHEN: on_preview_double_click is called - slide_controller.on_preview_double_click() - - # THEN: The call to addActions should be correct - assert 0 == slide_controller.on_go_live.call_count, 'on_go_live Should have not been called.' - assert 1 == slide_controller.on_preview_add_to_service.call_count, 'Should have been called once.' - - @patch(u'openlp.core.ui.slidecontroller.SlideController.image_manager') - @patch(u'PyQt5.QtCore.QTimer.singleShot') - def test_update_preview_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.displays = [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 - assert 0 == slide_controller.slide_preview.setPixmap.call_count, 'setPixmap should not be called' - assert 0 == slide_controller.display.preview.call_count, 'display.preview() should not be called' - assert 2 == mocked_singleShot.call_count, 'Timer to grab_maindisplay should have been called 2 times' - assert 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 test_update_preview_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.displays = [MagicMock()] - slide_controller.display.preview.return_value = QtGui.QImage() - slide_controller.grab_maindisplay = MagicMock() - slide_controller.slide_preview = MagicMock() - slide_controller.slide_count = 0 - slide_controller.preview_display = MagicMock() - - # WHEN: update_preview is called - slide_controller.update_preview() - - # THEN: setPixmap and the image_manager should have been called - assert 1 == slide_controller.preview_display.set_single_image.call_count, 'set_single_image should be called' - assert 0 == mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called' - # assert 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 test_update_preview_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.displays = [MagicMock()] - slide_controller.display.preview.return_value = QtGui.QImage() - slide_controller.grab_maindisplay = MagicMock() - slide_controller.slide_preview = MagicMock() - slide_controller.slide_count = 0 - slide_controller.preview_display = MagicMock() - - # WHEN: update_preview is called - slide_controller.update_preview() - - # THEN: setPixmap should have been called - assert 1 == slide_controller.preview_display.set_single_image.call_count, 'set_single_image should be called' - assert 0 == mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called' - assert 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 test_update_preview_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.displays = [MagicMock()] - slide_controller.grab_maindisplay = MagicMock() - slide_controller.slide_preview = MagicMock() - slide_controller.slide_count = 0 - slide_controller.preview_display = MagicMock() - - # WHEN: update_preview is called - slide_controller.update_preview() - - # THEN: setPixmap and display.preview should have been called - assert 1 == slide_controller.preview_display.go_to_slide.call_count, 'go_to_slide should be called' - assert 0 == mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called' - assert 0 == mocked_image_manager.get_image.call_count, 'image_manager should not be called' - - -class TestInfoLabel(TestCase): - - def test_paint_event_text_fits(self): - """ - Test the paintEvent method when text fits the label - """ - with patch('openlp.core.ui.slidecontroller.QtWidgets.QLabel'), \ - patch('openlp.core.ui.slidecontroller.QtGui.QFontMetrics') as MockFontMetrics, \ - patch('openlp.core.ui.slidecontroller.QtGui.QPainter') as mocked_qpainter: - - # GIVEN: An instance of InfoLabel, with mocked text return, width and rect methods - info_label = InfoLabel() - test_string = 'Label Text' - mocked_rect = MagicMock() - mocked_text = MagicMock() - mocked_width = MagicMock() - mocked_text.return_value = test_string - info_label.rect = mocked_rect - info_label.text = mocked_text - info_label.width = mocked_width - mocked_font_metrics = MagicMock() - mocked_font_metrics.elidedText.return_value = test_string - MockFontMetrics.return_value = mocked_font_metrics - - # WHEN: The instance is wider than its text, and the paintEvent method is called - info_label.paintEvent(MagicMock()) - - # THEN: The text should be drawn centered with the complete test_string - mocked_qpainter().drawText.assert_called_once_with(mocked_rect(), QtCore.Qt.AlignCenter, test_string) - - def test_paint_event_text_doesnt_fit(self): - """ - Test the paintEvent method when text fits the label - """ - with patch('openlp.core.ui.slidecontroller.QtWidgets.QLabel'), \ - patch('openlp.core.ui.slidecontroller.QtGui.QFontMetrics') as MockFontMetrics, \ - patch('openlp.core.ui.slidecontroller.QtGui.QPainter') as mocked_qpainter: - - # GIVEN: An instance of InfoLabel, with mocked text return, width and rect methods - info_label = InfoLabel() - test_string = 'Label Text' - elided_test_string = test_string[0:5] + '...' - mocked_rect = MagicMock() - mocked_text = MagicMock() - mocked_width = MagicMock() - mocked_text.return_value = test_string - info_label.rect = mocked_rect - info_label.text = mocked_text - info_label.width = mocked_width - mocked_font_metrics = MagicMock() - mocked_font_metrics.elidedText.return_value = elided_test_string - MockFontMetrics.return_value = mocked_font_metrics - - # WHEN: The instance is narrower than its text, and the paintEvent method is called - info_label.paintEvent(MagicMock()) - - # THEN: The text should be drawn aligned left with an elided test_string - mocked_qpainter().drawText.assert_called_once_with(mocked_rect(), QtCore.Qt.AlignLeft, elided_test_string) - - @patch('builtins.super') - def test_set_text(self, mocked_super): - """ - Test the reimplemented setText method - """ - # GIVEN: An instance of InfoLabel and mocked setToolTip method +def test_initial_slide_controller(registry): + """ + Test the initial slide controller state . + """ + # GIVEN: A new SlideController instance. + slide_controller = SlideController(None) + + # WHEN: the default controller is built. + # THEN: The controller should not be a live controller. + assert slide_controller.is_live is False, 'The base slide controller should not be a live controller' + + +def test_text_service_item_blank(): + """ + Test that loading a text-based service item into the slide controller sets the correct blank menu + """ + # GIVEN: A new SlideController instance. + slide_controller = SlideController(None) + service_item = MagicMock() + toolbar = MagicMock() + toolbar.set_widget_visible = MagicMock() + slide_controller.toolbar = toolbar + slide_controller.service_item = service_item + + # WHEN: a text based service item is used + slide_controller.service_item.is_text = MagicMock(return_value=True) + slide_controller.set_blank_menu() + + # THEN: the call to set the visible items on the toolbar should be correct + toolbar.set_widget_visible.assert_called_with(WIDE_MENU, True) + + +def test_non_text_service_item_blank(): + """ + Test that loading a non-text service item into the slide controller sets the correct blank menu + """ + # GIVEN: A new SlideController instance. + slide_controller = SlideController(None) + service_item = MagicMock() + toolbar = MagicMock() + toolbar.set_widget_visible = MagicMock() + slide_controller.toolbar = toolbar + slide_controller.service_item = service_item + + # WHEN a non text based service item is used + slide_controller.service_item.is_text = MagicMock(return_value=False) + slide_controller.set_blank_menu() + + # THEN: then call set up the toolbar to blank the display screen. + toolbar.set_widget_visible.assert_called_with(NON_TEXT_MENU, True) + + +def test_receive_spin_delay(mock_settings): + """ + Test that the spin box is updated accordingly after a call to receive_spin_delay() + """ + # GIVEN: A new SlideController instance. + mock_settings.value.return_value = 1 + mocked_delay_spin_box = MagicMock() + slide_controller = SlideController(None) + slide_controller.delay_spin_box = mocked_delay_spin_box + + # WHEN: The receive_spin_delay() method is called + slide_controller.receive_spin_delay() + + # THEN: The settings value() and delay_spin_box.setValue() methods should have been called correctly + msv = mock_settings.value + msv.assert_called_with('core/loop delay') + mocked_delay_spin_box.setValue.assert_called_with(1) + + +def test_toggle_display_blank(): + """ + Check that the toggle_display('blank') method calls the on_blank_display() method + """ + # GIVEN: A new SlideController instance. + mocked_on_blank_display = MagicMock() + mocked_on_theme_display = MagicMock() + mocked_on_hide_display = MagicMock() + slide_controller = SlideController(None) + slide_controller.on_blank_display = mocked_on_blank_display + slide_controller.on_theme_display = mocked_on_theme_display + slide_controller.on_hide_display = mocked_on_hide_display + + # WHEN: toggle_display() is called with an argument of "blank" + slide_controller.toggle_display('blank') + + # THEN: Only on_blank_display() should have been called with an argument of True + mocked_on_blank_display.assert_called_once_with(True) + assert 0 == mocked_on_theme_display.call_count, 'on_theme_display should not have been called' + assert 0 == mocked_on_hide_display.call_count, 'on_hide_display should not have been called' + + +def test_toggle_display_hide(): + """ + Check that the toggle_display('hide') method calls the on_blank_display() method + """ + # GIVEN: A new SlideController instance. + mocked_on_blank_display = MagicMock() + mocked_on_theme_display = MagicMock() + mocked_on_hide_display = MagicMock() + slide_controller = SlideController(None) + slide_controller.on_blank_display = mocked_on_blank_display + slide_controller.on_theme_display = mocked_on_theme_display + slide_controller.on_hide_display = mocked_on_hide_display + + # WHEN: toggle_display() is called with an argument of "hide" + slide_controller.toggle_display('hide') + + # THEN: Only on_blank_display() should have been called with an argument of True + mocked_on_blank_display.assert_called_once_with(True) + assert 0 == mocked_on_theme_display.call_count, 'on_theme_display should not have been called' + assert 0 == mocked_on_hide_display.call_count, 'on_hide_display should not have been called' + + +def test_toggle_display_theme(): + """ + Check that the toggle_display('theme') method calls the on_theme_display() method + """ + # GIVEN: A new SlideController instance. + mocked_on_blank_display = MagicMock() + mocked_on_theme_display = MagicMock() + mocked_on_hide_display = MagicMock() + slide_controller = SlideController(None) + slide_controller.on_blank_display = mocked_on_blank_display + slide_controller.on_theme_display = mocked_on_theme_display + slide_controller.on_hide_display = mocked_on_hide_display + + # WHEN: toggle_display() is called with an argument of "theme" + slide_controller.toggle_display('theme') + + # THEN: Only on_theme_display() should have been called with an argument of True + mocked_on_theme_display.assert_called_once_with(True) + assert 0 == mocked_on_blank_display.call_count, 'on_blank_display should not have been called' + assert 0 == mocked_on_hide_display.call_count, 'on_hide_display should not have been called' + + +def test_toggle_display_desktop(): + """ + Check that the toggle_display('desktop') method calls the on_hide_display() method + """ + # GIVEN: A new SlideController instance. + mocked_on_blank_display = MagicMock() + mocked_on_theme_display = MagicMock() + mocked_on_hide_display = MagicMock() + slide_controller = SlideController(None) + slide_controller.on_blank_display = mocked_on_blank_display + slide_controller.on_theme_display = mocked_on_theme_display + slide_controller.on_hide_display = mocked_on_hide_display + + # WHEN: toggle_display() is called with an argument of "desktop" + slide_controller.toggle_display('desktop') + + # THEN: Only on_hide_display() should have been called with an argument of True + mocked_on_hide_display.assert_called_once_with(True) + assert 0 == mocked_on_blank_display.call_count, 'on_blank_display should not have been called' + assert 0 == mocked_on_theme_display.call_count, 'on_theme_display should not have been called' + + +def test_toggle_display_show(): + """ + Check that the toggle_display('show') method calls all the on_X_display() methods + """ + # GIVEN: A new SlideController instance. + mocked_on_blank_display = MagicMock() + mocked_on_theme_display = MagicMock() + mocked_on_hide_display = MagicMock() + slide_controller = SlideController(None) + slide_controller.on_blank_display = mocked_on_blank_display + slide_controller.on_theme_display = mocked_on_theme_display + slide_controller.on_hide_display = mocked_on_hide_display + + # WHEN: toggle_display() is called with an argument of "show" + slide_controller.toggle_display('show') + + # THEN: All the on_X_display() methods should have been called with an argument of False + mocked_on_blank_display.assert_called_once_with(False) + mocked_on_theme_display.assert_called_once_with(False) + mocked_on_hide_display.assert_called_once_with(False) + + +def test_on_go_live_preview_controller(): + """ + Test that when the on_go_preview() method is called the message is sent to the preview controller and focus is + set correctly. + """ + # GIVEN: A new SlideController instance and plugin preview then pressing go live should respond + mocked_display = MagicMock() + mocked_preview_controller = MagicMock() + mocked_preview_widget = MagicMock() + mocked_service_item = MagicMock() + mocked_service_item.from_service = False + mocked_preview_widget.current_slide_number.return_value = 1 + mocked_preview_widget.slide_count = MagicMock(return_value=2) + mocked_preview_controller.preview_widget = MagicMock() + Registry.create() + Registry().register('preview_controller', mocked_preview_controller) + slide_controller = SlideController(None) + slide_controller.service_item = mocked_service_item + slide_controller.preview_widget = mocked_preview_widget + slide_controller.displays = [mocked_display] + + # WHEN: on_go_live() is called + slide_controller.on_go_preview() + + # THEN: the preview controller should have the service item and the focus set to live + mocked_preview_controller.preview_widget.setFocus.assert_called_once_with() + + +def test_on_go_live_live_controller(): + """ + Test that when the on_go_live() method is called the message is sent to the live controller and focus is + set correctly. + """ + # GIVEN: A new SlideController instance and plugin preview then pressing go live should respond + mocked_display = MagicMock() + mocked_live_controller = MagicMock() + mocked_preview_widget = MagicMock() + mocked_service_item = MagicMock() + mocked_service_item.from_service = False + mocked_preview_widget.current_slide_number.return_value = 1 + mocked_preview_widget.slide_count = MagicMock(return_value=2) + mocked_live_controller.preview_widget = MagicMock() + Registry.create() + Registry().register('live_controller', mocked_live_controller) + slide_controller = SlideController(None) + slide_controller.service_item = mocked_service_item + slide_controller.preview_widget = mocked_preview_widget + slide_controller.displays = [mocked_display] + + # WHEN: on_go_live() is called + slide_controller.on_go_live() + + # THEN: the live controller should have the service item and the focus set to live + mocked_live_controller.add_service_manager_item.assert_called_once_with(mocked_service_item, 1) + mocked_live_controller.preview_widget.setFocus.assert_called_once_with() + + +def test_on_go_live_service_manager(): + """ + Test that when the on_go_live() method is called the message is sent to the live controller and focus is + set correctly. + """ + # GIVEN: A new SlideController instance and service manager preview then pressing go live should respond + mocked_display = MagicMock() + mocked_service_manager = MagicMock() + mocked_live_controller = MagicMock() + mocked_preview_widget = MagicMock() + mocked_service_item = MagicMock() + mocked_service_item.from_service = True + mocked_service_item.unique_identifier = 42 + mocked_preview_widget.current_slide_number.return_value = 1 + mocked_preview_widget.slide_count = MagicMock(return_value=2) + mocked_live_controller.preview_widget = MagicMock() + Registry.create() + Registry().register('live_controller', mocked_live_controller) + Registry().register('service_manager', mocked_service_manager) + slide_controller = SlideController(None) + slide_controller.service_item = mocked_service_item + slide_controller.preview_widget = mocked_preview_widget + slide_controller.displays = [mocked_display] + + # WHEN: on_go_live() is called + slide_controller.on_go_live() + + # THEN: the service manager should have the service item and the focus set to live + mocked_service_manager.preview_live.assert_called_once_with(42, 1) + mocked_live_controller.preview_widget.setFocus.assert_called_once_with() + + +def test_service_previous(): + """ + Check that calling the service_previous() method adds the previous key to the queue and processes the queue + """ + # GIVEN: A new SlideController instance. + mocked_keypress_queue = MagicMock() + mocked_process_queue = MagicMock() + slide_controller = SlideController(None) + slide_controller.keypress_queue = mocked_keypress_queue + slide_controller._process_queue = mocked_process_queue + + # WHEN: The service_previous() method is called + slide_controller.service_previous() + + # THEN: The keypress is added to the queue and the queue is processed + mocked_keypress_queue.append.assert_called_once_with(ServiceItemAction.Previous) + mocked_process_queue.assert_called_once_with() + + +def test_service_next(): + """ + Check that calling the service_next() method adds the next key to the queue and processes the queue + """ + # GIVEN: A new SlideController instance and mocked out methods + mocked_keypress_queue = MagicMock() + mocked_process_queue = MagicMock() + slide_controller = SlideController(None) + slide_controller.keypress_queue = mocked_keypress_queue + slide_controller._process_queue = mocked_process_queue + + # WHEN: The service_next() method is called + slide_controller.service_next() + + # THEN: The keypress is added to the queue and the queue is processed + mocked_keypress_queue.append.assert_called_once_with(ServiceItemAction.Next) + mocked_process_queue.assert_called_once_with() + + +def test_update_slide_limits(mock_settings): + """ + Test that calling the update_slide_limits() method updates the slide limits + """ + # GIVEN: A mocked out Settings object, a new SlideController and a mocked out main_window + mock_settings.value.return_value = 10 + mocked_main_window = MagicMock(advanced_settings_section='advanced') + Registry().register('main_window', mocked_main_window) + slide_controller = SlideController(None) + + # WHEN: update_slide_limits() is called + slide_controller.update_slide_limits() + + # THEN: The value of slide_limits should be 10 + msv = mock_settings.value + msv.assert_called_once_with('advanced/slide limits') + assert 10 == slide_controller.slide_limits, 'Slide limits should have been updated to 10' + + +def test_enable_tool_bar_live(): + """ + Check that when enable_tool_bar on a live slide controller is called, enable_live_tool_bar is called + """ + # GIVEN: Mocked out enable methods and a real slide controller which is set to live + mocked_enable_live_tool_bar = MagicMock() + mocked_enable_preview_tool_bar = MagicMock() + slide_controller = SlideController(None) + slide_controller.is_live = True + slide_controller.enable_live_tool_bar = mocked_enable_live_tool_bar + slide_controller.enable_preview_tool_bar = mocked_enable_preview_tool_bar + mocked_service_item = MagicMock() + + # WHEN: enable_tool_bar() is called + slide_controller.enable_tool_bar(mocked_service_item) + + # THEN: The enable_live_tool_bar() method is called, not enable_preview_tool_bar() + mocked_enable_live_tool_bar.assert_called_once_with(mocked_service_item) + assert 0 == mocked_enable_preview_tool_bar.call_count, 'The preview method should not have been called' + + +def test_enable_tool_bar_preview(): + """ + Check that when enable_tool_bar on a preview slide controller is called, enable_preview_tool_bar is called + """ + # GIVEN: Mocked out enable methods and a real slide controller which is set to live + mocked_enable_live_tool_bar = MagicMock() + mocked_enable_preview_tool_bar = MagicMock() + slide_controller = SlideController(None) + slide_controller.is_live = False + slide_controller.enable_live_tool_bar = mocked_enable_live_tool_bar + slide_controller.enable_preview_tool_bar = mocked_enable_preview_tool_bar + mocked_service_item = MagicMock() + + # WHEN: enable_tool_bar() is called + slide_controller.enable_tool_bar(mocked_service_item) + + # THEN: The enable_preview_tool_bar() method is called, not enable_live_tool_bar() + mocked_enable_preview_tool_bar.assert_called_once_with(mocked_service_item) + assert 0 == mocked_enable_live_tool_bar.call_count, 'The live method should not have been called' + + +def test_refresh_service_item_text(): + """ + Test that the refresh_service_item() method refreshes a text service item + """ + # GIVEN: A mock service item and a fresh slide controller + mocked_service_item = MagicMock() + mocked_service_item.is_text.return_value = True + mocked_service_item.is_image.return_value = False + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_service_item + slide_controller._process_item = mocked_process_item + slide_controller.selected_row = 5 + + # WHEN: The refresh_service_item method() is called + slide_controller.refresh_service_item() + + # THEN: The item should be re-processed + mocked_service_item.is_text.assert_called_once_with() + assert 0 == mocked_service_item.is_image.call_count, 'is_image should not have been called' + mocked_service_item.render.assert_called_once_with() + mocked_process_item.assert_called_once_with(mocked_service_item, 5) + + +def test_refresh_service_item_image(): + """ + Test that the refresh_service_item() method refreshes a image service item + """ + # GIVEN: A mock service item and a fresh slide controller + mocked_service_item = MagicMock() + mocked_service_item.is_text.return_value = False + mocked_service_item.is_image.return_value = True + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_service_item + slide_controller._process_item = mocked_process_item + slide_controller.selected_row = 5 + + # WHEN: The refresh_service_item method() is called + slide_controller.refresh_service_item() + + # THEN: The item should be re-processed + mocked_service_item.is_text.assert_called_once_with() + mocked_service_item.is_image.assert_called_once_with() + mocked_service_item.render.assert_called_once_with() + mocked_process_item.assert_called_once_with(mocked_service_item, 5) + + +def test_refresh_service_item_not_image_or_text(): + """ + Test that the refresh_service_item() method does not refresh a service item if it's neither text or an image + """ + # GIVEN: A mock service item and a fresh slide controller + mocked_service_item = MagicMock() + mocked_service_item.is_text.return_value = False + mocked_service_item.is_image.return_value = False + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_service_item + slide_controller._process_item = mocked_process_item + slide_controller.selected_row = 5 + + # WHEN: The refresh_service_item method() is called + slide_controller.refresh_service_item() + + # THEN: The item should be re-processed + mocked_service_item.is_text.assert_called_once_with() + mocked_service_item.is_image.assert_called_once_with() + assert 0 == mocked_service_item.render.call_count, 'The render() method should not have been called' + assert 0 == mocked_process_item.call_count, 'The mocked_process_item() method should not have been called' + + +def test_add_service_item_with_song_edit(): + """ + Test the add_service_item() method when song_edit is True + """ + # GIVEN: A slide controller and a new item to add + mocked_item = MagicMock() + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller._process_item = mocked_process_item + slide_controller.song_edit = True + slide_controller.selected_row = 2 + + # WHEN: The item is added to the service + slide_controller.add_service_item(mocked_item) + + # THEN: The item is processed, the slide number is correct, and the song is not editable (or something) + assert slide_controller.song_edit is False, 'song_edit should be False' + mocked_process_item.assert_called_once_with(mocked_item, 2) + + +def test_add_service_item_without_song_edit(): + """ + Test the add_service_item() method when song_edit is False + """ + # GIVEN: A slide controller and a new item to add + mocked_item = MagicMock() + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller._process_item = mocked_process_item + slide_controller.song_edit = False + slide_controller.selected_row = 2 + + # WHEN: The item is added to the service + slide_controller.add_service_item(mocked_item) + + # THEN: The item is processed, the slide number is correct, and the song is not editable (or something) + assert slide_controller.song_edit is False, 'song_edit should be False' + mocked_process_item.assert_called_once_with(mocked_item, 0) + + +def test_replace_service_manager_item_different_items(): + """ + Test that when the service items are not the same, nothing happens + """ + # GIVEN: A slide controller and a new item to add + mocked_item = MagicMock() + mocked_preview_widget = MagicMock() + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.preview_widget = mocked_preview_widget + slide_controller._process_item = mocked_process_item + slide_controller.service_item = None + + # WHEN: The service item is replaced + slide_controller.replace_service_manager_item(mocked_item) + + # THEN: The service item should not be processed + assert 0 == mocked_process_item.call_count, 'The _process_item() method should not have been called' + assert 0 == mocked_preview_widget.current_slide_number.call_count, \ + 'The preview_widget current_slide_number.() method should not have been called' + + +def test_replace_service_manager_item_same_item(): + """ + Test that when the service item is the same, the service item is reprocessed + """ + # GIVEN: A slide controller and a new item to add + mocked_item = MagicMock() + mocked_preview_widget = MagicMock() + mocked_preview_widget.current_slide_number.return_value = 7 + mocked_process_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.preview_widget = mocked_preview_widget + slide_controller._process_item = mocked_process_item + slide_controller.service_item = mocked_item + + # WHEN: The service item is replaced + slide_controller.replace_service_manager_item(mocked_item) + + # THEN: The service item should not be processed + mocked_preview_widget.current_slide_number.assert_called_with() + mocked_process_item.assert_called_once_with(mocked_item, 7) + + +def test_on_slide_blank(): + """ + Test on_slide_blank + """ + # GIVEN: An instance of SlideController and a mocked on_blank_display + slide_controller = SlideController(None) + slide_controller.on_blank_display = MagicMock() + + # WHEN: Calling on_slide_blank + slide_controller.on_slide_blank() + + # THEN: on_blank_display should have been called with True + slide_controller.on_blank_display.assert_called_once_with(True) + + +def test_on_slide_unblank(): + """ + Test on_slide_unblank + """ + # GIVEN: An instance of SlideController and a mocked on_blank_display + slide_controller = SlideController(None) + slide_controller.on_blank_display = MagicMock() + + # WHEN: Calling on_slide_unblank + slide_controller.on_slide_unblank() + + # THEN: on_blank_display should have been called with False + slide_controller.on_blank_display.assert_called_once_with(False) + + +def test_on_slide_selected_index_no_service_item(): + """ + Test that when there is no service item, the on_slide_selected_index() method returns immediately + """ + # GIVEN: A mocked service item and a slide controller without a service item + mocked_item = MagicMock() + slide_controller = SlideController(None) + slide_controller.service_item = None + + # WHEN: The method is called + slide_controller.on_slide_selected_index([10]) + + # THEN: It should have exited early + assert 0 == mocked_item.is_command.call_count, 'The service item should have not been called' + + +@patch.object(Registry, 'execute') +def test_on_slide_selected_index_service_item_command(mocked_execute): + """ + Test that when there is a command service item, the command is executed + """ + # GIVEN: A mocked service item and a slide controller with a service item + mocked_item = MagicMock() + mocked_item.is_command.return_value = True + mocked_item.name = 'Mocked Item' + mocked_update_preview = MagicMock() + mocked_preview_widget = MagicMock() + mocked_slide_selected = MagicMock() + Registry.create() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_item + slide_controller.update_preview = mocked_update_preview + slide_controller.preview_widget = mocked_preview_widget + slide_controller.slide_selected = mocked_slide_selected + slide_controller.is_live = True + + # WHEN: The method is called + slide_controller.on_slide_selected_index([9]) + + # THEN: It should have sent a notification + mocked_item.is_command.assert_called_once_with() + mocked_execute.assert_called_once_with('mocked item_slide', [mocked_item, True, 9]) + mocked_update_preview.assert_called_once_with() + assert 0 == mocked_preview_widget.change_slide.call_count, 'Change slide should not have been called' + assert 0 == mocked_slide_selected.call_count, 'slide_selected should not have been called' + + +@patch.object(Registry, 'execute') +def test_on_slide_selected_index_service_item_not_command(mocked_execute): + """ + Test that when there is a service item but it's not a command, the preview widget is updated + """ + # GIVEN: A mocked service item and a slide controller with a service item + mocked_item = MagicMock() + mocked_item.is_command.return_value = False + mocked_item.name = 'Mocked Item' + mocked_update_preview = MagicMock() + mocked_preview_widget = MagicMock() + mocked_slide_selected = MagicMock() + Registry.create() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_item + slide_controller.update_preview = mocked_update_preview + slide_controller.preview_widget = mocked_preview_widget + slide_controller.slide_selected = mocked_slide_selected + + # WHEN: The method is called + slide_controller.on_slide_selected_index([7]) + + # THEN: It should have sent a notification + mocked_item.is_command.assert_called_once_with() + assert 0 == mocked_execute.call_count, 'Execute should not have been called' + assert 0 == mocked_update_preview.call_count, 'Update preview should not have been called' + mocked_preview_widget.change_slide.assert_called_once_with(7) + mocked_slide_selected.assert_called_once_with() + + +@patch.object(Registry, 'execute') +def test_process_item(mocked_execute): + """ + Test that presentation service-items is closed when followed by a media service-item + """ + # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute + # and a slide controller with many mocks. + mocked_pres_item = MagicMock() + mocked_pres_item.name = 'mocked_presentation_item' + mocked_pres_item.is_command.return_value = True + mocked_pres_item.is_media.return_value = False + mocked_pres_item.is_image.return_value = False + mocked_pres_item.from_service = False + mocked_pres_item.get_frames.return_value = [] + mocked_media_item = MagicMock() + mocked_media_item.name = 'mocked_media_item' + mocked_media_item.is_command.return_value = True + mocked_media_item.is_media.return_value = True + mocked_media_item.is_image.return_value = False + mocked_media_item.from_service = False + mocked_media_item.get_frames.return_value = [] + mocked_main_window = MagicMock() + Registry.create() + Registry().register('main_window', mocked_main_window) + Registry().register('media_controller', MagicMock()) + slide_controller = SlideController(None) + slide_controller.service_item = mocked_pres_item + slide_controller.is_live = False + slide_controller.preview_widget = MagicMock() + slide_controller.preview_display = MagicMock() + slide_controller.enable_tool_bar = MagicMock() + slide_controller.on_media_start = MagicMock() + slide_controller.slide_selected = MagicMock() + slide_controller.on_stop_loop = MagicMock() + slide_controller.info_label = MagicMock() + slide_controller.displays = [MagicMock()] + slide_controller.split = 0 + slide_controller.type_prefix = 'test' + + # WHEN: _process_item is called + slide_controller._process_item(mocked_media_item, 0) + + # THEN: Registry.execute should have been called to stop the presentation + assert 2 == mocked_execute.call_count, 'Execute should have been called 2 times' + assert 'mocked_presentation_item_stop' == mocked_execute.call_args_list[1][0][0], \ + 'The presentation should have been stopped.' + + +def test_live_stolen_focus_shortcuts(): + """ + Test that all the needed shortcuts are available in scenarios where Live has stolen focus. + These are found under def __add_actions_to_widget(self, widget): in slidecontroller.py + """ + # GIVEN: A slide controller, actions needed + slide_controller = SlideController(None) + mocked_widget = MagicMock() + slide_controller.previous_item = MagicMock() + slide_controller.next_item = MagicMock() + slide_controller.previous_service = MagicMock() + slide_controller.next_service = MagicMock() + slide_controller.desktop_screen_enable = MagicMock() + slide_controller.desktop_screen = MagicMock() + slide_controller.blank_screen = MagicMock() + slide_controller.theme_screen = MagicMock() + + # WHEN: __add_actions_to_widget is called + slide_controller._SlideController__add_actions_to_widget(mocked_widget) + + # THEN: The call to addActions should be correct + mocked_widget.addActions.assert_called_with([ + slide_controller.previous_item, slide_controller.next_item, + slide_controller.previous_service, slide_controller.next_service, + slide_controller.desktop_screen_enable, slide_controller.desktop_screen, + slide_controller.theme_screen, slide_controller.blank_screen + ]) + + +def test_on_preview_double_click_unblank_display(mock_settings): + # GIVEN: A slide controller, actions needed, settings set to True. + slide_controller = SlideController(None) + mocked_settings = MagicMock() + mocked_settings.return_value = True + mock_settings.return_value = mocked_settings + slide_controller.service_item = MagicMock() + slide_controller.service_item.is_media = MagicMock() + slide_controller.on_media_close = MagicMock() + slide_controller.on_go_live = MagicMock() + slide_controller.on_preview_add_to_service = MagicMock() + slide_controller.media_reset = MagicMock() + Registry().set_flag('has doubleclick added item to service', True) + + # WHEN: on_preview_double_click is called + slide_controller.on_preview_double_click() + + # THEN: The call to addActions should be correct + assert 1 == slide_controller.on_go_live.call_count, 'on_go_live should have been called once.' + assert 0 == slide_controller.on_preview_add_to_service.call_count, 'Should have not been called.' + + +def test_on_preview_double_click_add_to_service(mock_settings): + # GIVEN: A slide controller, actions needed, settings set to False. + slide_controller = SlideController(None) + mock_settings.value.return_value = False + slide_controller.service_item = MagicMock() + slide_controller.service_item.is_media = MagicMock() + slide_controller.on_media_close = MagicMock() + slide_controller.on_go_live = MagicMock() + slide_controller.on_preview_add_to_service = MagicMock() + slide_controller.media_reset = MagicMock() + Registry().set_flag('has doubleclick added item to service', False) + + # WHEN: on_preview_double_click is called + slide_controller.on_preview_double_click() + + # THEN: The call to addActions should be correct + assert 0 == slide_controller.on_go_live.call_count, 'on_go_live Should have not been called.' + assert 1 == slide_controller.on_preview_add_to_service.call_count, 'Should have been called once.' + + +@patch(u'openlp.core.ui.slidecontroller.SlideController.image_manager') +@patch(u'PyQt5.QtCore.QTimer.singleShot') +def test_update_preview_live(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.displays = [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 + assert 0 == slide_controller.slide_preview.setPixmap.call_count, 'setPixmap should not be called' + assert 0 == slide_controller.display.preview.call_count, 'display.preview() should not be called' + assert 2 == mocked_singleShot.call_count, 'Timer to grab_maindisplay should have been called 2 times' + assert 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 test_update_preview_pres(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.displays = [MagicMock()] + slide_controller.display.preview.return_value = QtGui.QImage() + slide_controller.grab_maindisplay = MagicMock() + slide_controller.slide_preview = MagicMock() + slide_controller.slide_count = 0 + slide_controller.preview_display = MagicMock() + + # WHEN: update_preview is called + slide_controller.update_preview() + + # THEN: setPixmap and the image_manager should have been called + assert 1 == slide_controller.preview_display.set_single_image.call_count, 'set_single_image should be called' + assert 0 == mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called' + + +@patch(u'openlp.core.ui.slidecontroller.SlideController.image_manager') +@patch(u'PyQt5.QtCore.QTimer.singleShot') +def test_update_preview_media(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.displays = [MagicMock()] + slide_controller.display.preview.return_value = QtGui.QImage() + slide_controller.grab_maindisplay = MagicMock() + slide_controller.slide_preview = MagicMock() + slide_controller.slide_count = 0 + slide_controller.preview_display = MagicMock() + + # WHEN: update_preview is called + slide_controller.update_preview() + + # THEN: setPixmap should have been called + assert 1 == slide_controller.preview_display.set_single_image.call_count, 'set_single_image should be called' + assert 0 == mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called' + assert 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 test_update_preview_image(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.displays = [MagicMock()] + slide_controller.grab_maindisplay = MagicMock() + slide_controller.slide_preview = MagicMock() + slide_controller.slide_count = 0 + slide_controller.preview_display = MagicMock() + + # WHEN: update_preview is called + slide_controller.update_preview() + + # THEN: setPixmap and display.preview should have been called + assert 1 == slide_controller.preview_display.go_to_slide.call_count, 'go_to_slide should be called' + assert 0 == mocked_singleShot.call_count, 'Timer to grab_maindisplay should not be called' + assert 0 == mocked_image_manager.get_image.call_count, 'image_manager should not be called' + + +def test_paint_event_text_fits(): + """ + Test the paintEvent method when text fits the label + """ + font = QtGui.QFont() + metrics = QtGui.QFontMetrics(font) + + with patch('openlp.core.ui.slidecontroller.QtWidgets.QLabel'), \ + patch('openlp.core.ui.slidecontroller.QtGui.QPainter') as mocked_qpainter: + + # GIVEN: An instance of InfoLabel, with mocked text return, width and rect methods info_label = InfoLabel() - set_tool_tip_mock = MagicMock() - info_label.setToolTip = set_tool_tip_mock + test_string = 'Label Text' + mocked_rect = MagicMock() + mocked_text = MagicMock() + mocked_width = MagicMock() + mocked_text.return_value = test_string + info_label.rect = mocked_rect + info_label.text = mocked_text + info_label.width = mocked_width - # WHEN: Calling the instance method setText - info_label.setText('Label Text') + # WHEN: The instance is wider than its text, and the paintEvent method is called + info_label.width.return_value = metrics.boundingRect(test_string).width() + 20 + info_label.paintEvent(MagicMock()) - # THEN: The setToolTip and super class setText methods should have been called with the same text - set_tool_tip_mock.assert_called_once_with('Label Text') - mocked_super().setText.assert_called_once_with('Label Text') + # THEN: The text should be drawn centered with the complete test_string + mocked_qpainter().drawText.assert_called_once_with(mocked_rect(), QtCore.Qt.AlignCenter, test_string) -class TestLiveController(TestCase): +def test_paint_event_text_doesnt_fit(): + """ + Test the paintEvent method when text fits the label + """ + font = QtGui.QFont() + metrics = QtGui.QFontMetrics(font) - def test_initial_live_controller(self): - """ - Test the initial live slide controller state . - """ - # GIVEN: A new SlideController instance. - Registry.create() - live_controller = LiveController(None) + with patch('openlp.core.ui.slidecontroller.QtWidgets.QLabel'), \ + patch('openlp.core.ui.slidecontroller.QtGui.QPainter') as mocked_qpainter: - # WHEN: the default controller is built. - # THEN: The controller should not be a live controller. - assert live_controller.is_live is True, 'The slide controller should be a live controller' + # GIVEN: An instance of InfoLabel, with mocked text return, width and rect methods + info_label = InfoLabel() + test_string = 'Label Text' + mocked_rect = MagicMock() + mocked_text = MagicMock() + mocked_width = MagicMock() + mocked_text.return_value = test_string + info_label.rect = mocked_rect + info_label.text = mocked_text + info_label.width = mocked_width + + # WHEN: The instance is narrower than its text, and the paintEvent method is called + label_width = metrics.boundingRect(test_string).width() - 20 + info_label.width.return_value = label_width + info_label.paintEvent(MagicMock()) + + # THEN: The text should be drawn aligned left with an elided test_string + elided_test_string = metrics.elidedText(test_string, QtCore.Qt.ElideRight, label_width) + mocked_qpainter().drawText.assert_called_once_with(mocked_rect(), QtCore.Qt.AlignLeft, elided_test_string) -class TestPreviewLiveController(TestCase): +@patch('builtins.super') +def test_set_text(mocked_super): + """ + Test the reimplemented setText method + """ + # GIVEN: An instance of InfoLabel and mocked setToolTip method + info_label = InfoLabel() + set_tool_tip_mock = MagicMock() + info_label.setToolTip = set_tool_tip_mock - def test_initial_preview_controller(self): - """ - Test the initial preview slide controller state. - """ - # GIVEN: A new SlideController instance. - Registry.create() - preview_controller = PreviewController(None) + # WHEN: Calling the instance method setText + info_label.setText('Label Text') - # WHEN: the default controller is built. - # THEN: The controller should not be a live controller. - assert preview_controller.is_live is False, 'The slide controller should be a Preview controller' + # THEN: The setToolTip and super class setText methods should have been called with the same text + set_tool_tip_mock.assert_called_once_with('Label Text') + mocked_super().setText.assert_called_once_with('Label Text') + + +def test_initial_live_controller(): + """ + Test the initial live slide controller state . + """ + # GIVEN: A new SlideController instance. + Registry.create() + live_controller = LiveController(None) + + # WHEN: the default controller is built. + # THEN: The controller should not be a live controller. + assert live_controller.is_live is True, 'The slide controller should be a live controller' + + +def test_initial_preview_controller(): + """ + Test the initial preview slide controller state. + """ + # GIVEN: A new SlideController instance. + Registry.create() + preview_controller = PreviewController(None) + + # WHEN: the default controller is built. + # THEN: The controller should not be a live controller. + assert preview_controller.is_live is False, 'The slide controller should be a Preview controller' From 64126cec236575aebae50d52e1014c4fac9f5704 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 4 Dec 2019 20:46:10 +0000 Subject: [PATCH 3/5] Fix a traceback when trying to display a PDF --- openlp/plugins/presentations/lib/mediaitem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 031badbbd..7d0f78d62 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -310,7 +310,7 @@ class PresentationMediaItem(MediaManagerItem): image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i) thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i) while image_path.is_file(): - service_item.add_from_image(str(image_path), file_name, thumbnail=str(thumbnail_path)) + service_item.add_from_image(image_path, file_name, thumbnail=str(thumbnail_path)) i += 1 image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i) thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i) From 507934e85ceed0ce2ca0fbfaddeb135f2129cb2b Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 4 Dec 2019 22:10:13 +0000 Subject: [PATCH 4/5] Disable BibleServer support for now since we haven't yet figured out how to extract available translations and books in translations. --- .../plugins/bibles/forms/bibleimportform.py | 20 +++++++++++-------- .../openlp_plugins/bibles/test_lib_http.py | 3 ++- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index dcc7d93e0..62973c1d3 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -43,7 +43,7 @@ from openlp.core.lib.ui import critical_error_message_box from openlp.core.widgets.edits import PathEdit from openlp.core.widgets.wizard import OpenLPWizard, WizardStrings from openlp.plugins.bibles.lib.db import clean_filename -from openlp.plugins.bibles.lib.importers.http import BGExtract, BSExtract, CWExtract +from openlp.plugins.bibles.lib.importers.http import BGExtract, CWExtract from openlp.plugins.bibles.lib.manager import BibleFormat @@ -59,7 +59,8 @@ class WebDownload(object): BibleGateway = 1 Bibleserver = 2 - Names = ['Crosswalk', 'BibleGateway', 'Bibleserver'] + # NOTE: BibleServer support has been disabled since we can't currently parse it. Re-add if/when fixed. + Names = ['Crosswalk', 'BibleGateway'] class BibleImportForm(OpenLPWizard): @@ -227,7 +228,8 @@ class BibleImportForm(OpenLPWizard): self.web_bible_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.web_source_label) self.web_source_combo_box = QtWidgets.QComboBox(self.web_widget) self.web_source_combo_box.setObjectName('WebSourceComboBox') - self.web_source_combo_box.addItems(['', '', '']) + # NOTE: Set to 2 items since BibleServer has been disabled. Set to 3 if/when fixed + self.web_source_combo_box.addItems(['', '']) self.web_source_combo_box.setEnabled(False) self.web_bible_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.web_source_combo_box) self.web_translation_label = QtWidgets.QLabel(self.web_bible_tab) @@ -239,7 +241,8 @@ class BibleImportForm(OpenLPWizard): self.web_translation_combo_box.setEnabled(False) self.web_bible_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.web_translation_combo_box) self.web_progress_bar = QtWidgets.QProgressBar(self) - self.web_progress_bar.setRange(0, 3) + # NOTE: Set to 2 since BibleServer has been disabled. Set to 3 if/when fixed + self.web_progress_bar.setRange(0, 2) self.web_progress_bar.setObjectName('WebTranslationProgressBar') self.web_progress_bar.setVisible(False) self.web_bible_layout.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.web_progress_bar) @@ -400,8 +403,9 @@ class BibleImportForm(OpenLPWizard): 'Crosswalk')) self.web_source_combo_box.setItemText(WebDownload.BibleGateway, translate('BiblesPlugin.ImportWizardForm', 'BibleGateway')) - self.web_source_combo_box.setItemText(WebDownload.Bibleserver, translate('BiblesPlugin.ImportWizardForm', - 'Bibleserver')) + # NOTE: BibleServer support has been disabled since we can't currently parse it. Re-add if/when fixed. + # self.web_source_combo_box.setItemText(WebDownload.Bibleserver, translate('BiblesPlugin.ImportWizardForm', + # 'Bibleserver')) self.web_translation_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible:')) self.sword_bible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:')) self.sword_folder_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD data folder:')) @@ -578,9 +582,9 @@ class BibleImportForm(OpenLPWizard): self.web_progress_bar.setVisible(True) self.web_progress_bar.setValue(0) # TODO: Where does critical_error_message_box get %s string from? + # NOTE: BibleServer support has been disabled since we can't currently parse it. Re-add if/when fixed. for (download_type, extractor) in ((WebDownload.Crosswalk, CWExtract()), - (WebDownload.BibleGateway, BGExtract()), - (WebDownload.Bibleserver, BSExtract())): + (WebDownload.BibleGateway, BGExtract())): try: bibles = extractor.get_bibles_from_http() except (urllib.error.URLError, ConnectionError): diff --git a/tests/interfaces/openlp_plugins/bibles/test_lib_http.py b/tests/interfaces/openlp_plugins/bibles/test_lib_http.py index dd090c526..977aae31f 100644 --- a/tests/interfaces/openlp_plugins/bibles/test_lib_http.py +++ b/tests/interfaces/openlp_plugins/bibles/test_lib_http.py @@ -22,7 +22,7 @@ Package to test the openlp.plugin.bible.lib.https package. """ import os -from unittest import TestCase, skipIf +from unittest import TestCase, skipIf, skip from unittest.mock import MagicMock from openlp.core.common.registry import Registry @@ -122,6 +122,7 @@ class TestBibleHTTP(TestCase): # THEN: We should get back a valid service item assert len(results.verse_list) == 36, 'The book of John should not have had any verses added or removed' + @skip("We can't currently parse BibelServer") def test_bibleserver_get_bibles(self): """ Test getting list of bibles from BibleServer.com From d5fc4c5b88b416a56a6ab88cd617f6bd1234a2b9 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sat, 7 Dec 2019 11:41:30 +0000 Subject: [PATCH 5/5] Display more detailed instructions when VLC and pymediainfo are missing - On Fedora, mention the need for RPMFusion - on macOS, reference the VLC website - On other systems, just display a generic message - Don't try to load the VLC path on macOS --- openlp/core/app.py | 13 +- openlp/core/ui/media/mediacontroller.py | 37 ++++-- .../ui/media/test_mediacontroller.py | 120 ++++++++++++++++++ 3 files changed, 149 insertions(+), 21 deletions(-) diff --git a/openlp/core/app.py b/openlp/core/app.py index 15d8cfc6c..e35465a3f 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -385,17 +385,12 @@ def main(): application.setApplicationName('OpenLP') set_up_logging(AppLocation.get_directory(AppLocation.CacheDir)) # Set the libvlc environment variable if we're frozen - if getattr(sys, 'frozen', False): - if is_macosx(): - vlc_lib = 'libvlc.dylib' - elif is_win(): - vlc_lib = 'libvlc.dll' - # Path to libvlc - os.environ['PYTHON_VLC_LIB_PATH'] = str(AppLocation.get_directory(AppLocation.AppDir) / 'vlc' / vlc_lib) - log.debug('VLC Path: {}'.format(os.environ['PYTHON_VLC_LIB_PATH'])) - # Path to VLC directory containing VLC's "plugins" directory + if getattr(sys, 'frozen', False) and is_win(): + # Path to libvlc and the plugins + os.environ['PYTHON_VLC_LIB_PATH'] = str(AppLocation.get_directory(AppLocation.AppDir) / 'vlc' / 'libvlc.dll') os.environ['PYTHON_VLC_MODULE_PATH'] = str(AppLocation.get_directory(AppLocation.AppDir) / 'vlc') log.debug('VLC Path: {}'.format(os.environ['PYTHON_VLC_LIB_PATH'])) + log.debug('VLC Plugins Path: {}'.format(os.environ['PYTHON_VLC_MODULE_PATH'])) # Initialise the Registry Registry.create() Registry().register('application', application) diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index c91ce3954..9b237514c 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -34,6 +34,7 @@ from PyQt5 import QtCore from openlp.core.state import State from openlp.core.api.http import register_endpoint +from openlp.core.common import is_linux, is_macosx from openlp.core.common.i18n import translate from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.registry import Registry, RegistryBase @@ -89,23 +90,35 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): self.vlc_player = VlcPlayer(self) State().add_service('mediacontroller', 0) State().add_service('media_live', 0) - getvlc = get_vlc() - if getvlc and pymediainfo_available: + has_vlc = get_vlc() + if has_vlc and pymediainfo_available: State().update_pre_conditions('mediacontroller', True) State().update_pre_conditions('media_live', True) else: if hasattr(self.main_window, 'splash') and self.main_window.splash.isVisible(): self.main_window.splash.hide() - text_vlc = translate('OpenLP.MediaController', - 'The media integration library is missing (python - vlc is not installed)') - text_info = translate('OpenLP.MediaController', - 'The media integration library is missing (python - pymediainfo is not installed)') - if not getvlc and not pymediainfo_available: - State().missing_text('media_live', "{text}\n{base}".format(text=text_vlc, base=text_info)) - if getvlc and not pymediainfo_available: - State().missing_text('media_live', "{text}".format(text=text_info)) - if not getvlc and pymediainfo_available: - State().missing_text('media_live', "{text}".format(text=text_vlc)) + generic_message = translate('OpenLP.MediaController', + 'OpenLP requires the following libraries in order to show videos and other ' + 'media, but they are not installed. Please install these libraries to enable ' + 'media playback in OpenLP.') + fedora_rpmfusion = translate('OpenLP.MediaController', + 'To install these libraries, you will need to enable the RPMFusion ' + 'repository: https://rpmfusion.org/') + message = '' + if is_macosx(): + message = translate('OpenLP.MediaController', + 'macOS is missing VLC. Please download and install from the VLC web site: ' + 'https://www.videolan.org/vlc/') + else: + packages = [] + if not has_vlc: + packages.append('python3-vlc') + if not pymediainfo_available: + packages.append('python3-pymediainfo') + message = generic_message + '\n\n' + ', '.join(packages) + if not has_vlc and is_linux(distro='fedora'): + message += '\n\n' + fedora_rpmfusion + State().missing_text('media_live', message) return True def bootstrap_post_set_up(self): diff --git a/tests/functional/openlp_core/ui/media/test_mediacontroller.py b/tests/functional/openlp_core/ui/media/test_mediacontroller.py index 0a3ed0f35..fc92ae28b 100644 --- a/tests/functional/openlp_core/ui/media/test_mediacontroller.py +++ b/tests/functional/openlp_core/ui/media/test_mediacontroller.py @@ -25,6 +25,7 @@ from unittest import TestCase from unittest.mock import MagicMock, patch from openlp.core.common.registry import Registry +from openlp.core.ui import DisplayControllerType from openlp.core.ui.media.mediacontroller import MediaController from openlp.core.ui.media import ItemMediaInfo from tests.helpers.testmixin import TestMixin @@ -202,3 +203,122 @@ class TestMediaController(TestCase, TestMixin): # THEN you can determine the run time assert results == test_data[1], 'The correct duration is returned for ' + test_data[0] + + def test_on_media_play(self): + """ + Test the on_media_play method + """ + # GIVEN: A mocked live controller and a mocked media_play() method + mocked_live_controller = MagicMock() + Registry().register('live_controller', mocked_live_controller) + media_controller = MediaController() + media_controller.media_play = MagicMock() + + # WHEN: the on_media_play() method is called + media_controller.on_media_play() + + # The mocked live controller should be called + media_controller.media_play.assert_called_once_with(mocked_live_controller, False) + + def test_on_media_pause(self): + """ + Test the on_media_pause method + """ + # GIVEN: A mocked live controller and a mocked media_pause() method + mocked_live_controller = MagicMock() + Registry().register('live_controller', mocked_live_controller) + media_controller = MediaController() + media_controller.media_pause = MagicMock() + + # WHEN: the on_media_pause() method is called + media_controller.on_media_pause() + + # The mocked live controller should be called + media_controller.media_pause.assert_called_once_with(mocked_live_controller) + + def test_on_media_stop(self): + """ + Test the on_media_stop method + """ + # GIVEN: A mocked live controller and a mocked media_stop() method + mocked_live_controller = MagicMock() + Registry().register('live_controller', mocked_live_controller) + media_controller = MediaController() + media_controller.media_stop = MagicMock() + + # WHEN: the on_media_stop() method is called + media_controller.on_media_stop() + + # The mocked live controller should be called + media_controller.media_stop.assert_called_once_with(mocked_live_controller) + + def test_display_controllers_live(self): + """ + Test that the display_controllers() method returns the live controller when requested + """ + # GIVEN: A mocked live controller + media_controller = MediaController() + mocked_live_controller = MagicMock() + mocked_preview_controller = MagicMock() + Registry().register('live_controller', mocked_live_controller) + Registry().register('preview_controller', mocked_preview_controller) + + # WHEN: display_controllers() is called with DisplayControllerType.Live + controller = media_controller.display_controllers(DisplayControllerType.Live) + + # THEN: the controller should be the live controller + assert controller is mocked_live_controller + + def test_display_controllers_preview(self): + """ + Test that the display_controllers() method returns the preview controller when requested + """ + # GIVEN: A mocked live controller + media_controller = MediaController() + mocked_live_controller = MagicMock() + mocked_preview_controller = MagicMock() + Registry().register('live_controller', mocked_live_controller) + Registry().register('preview_controller', mocked_preview_controller) + + # WHEN: display_controllers() is called with DisplayControllerType.Preview + controller = media_controller.display_controllers(DisplayControllerType.Preview) + + # THEN: the controller should be the live controller + assert controller is mocked_preview_controller + + def test_set_controls_visible(self): + """ + Test that "set_controls_visible" sets the media controls on the controller to be visible or not + """ + # GIVEN: A mocked controller + mocked_controller = MagicMock() + + # WHEN: Set to visible + MediaController.set_controls_visible(mocked_controller, True) + + # THEN: The media controls should have been set to visible + mocked_controller.mediabar.setVisible.assert_called_once_with(True) + + @patch('openlp.core.ui.media.mediacontroller.ItemMediaInfo') + def test_setup_display(self, MockItemMediaInfo): + """ + Test that the display/controllers are set up correctly + """ + # GIVEN: A media controller object and some mocks + mocked_media_info = MagicMock() + MockItemMediaInfo.return_value = mocked_media_info + media_controller = MediaController() + media_controller.vlc_player = MagicMock() + mocked_display = MagicMock() + media_controller._define_display = MagicMock(return_value=mocked_display) + media_controller.vlc_player = MagicMock() + controller = MagicMock() + + # WHEN: setup_display() is called + media_controller.setup_display(controller, True) + + # THEN: The right calls should have been made + assert controller.media_info == mocked_media_info + assert controller.has_audio is False + media_controller._define_display.assert_called_once_with(controller) + media_controller.vlc_player.setup(controller, mocked_display, False)