From f7a28e5754bcfdf7729cc0cc6187ead3225e6d3e Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Tue, 1 Mar 2022 15:52:28 +0000 Subject: [PATCH] ProjectorManager tests and refactoring 2022-02-27 --- openlp/core/projectors/manager.py | 170 +++--- tests/helpers/projector.py | 82 +++ .../projectors/manager/test_misc_manager.py | 51 +- .../manager/test_toolbar_triggers-01.py | 511 ++++++++++++++++++ .../manager/test_toolbar_triggers-basic.py | 141 +++++ tests/resources/projector/data.py | 9 +- 6 files changed, 819 insertions(+), 145 deletions(-) create mode 100644 tests/helpers/projector.py create mode 100644 tests/openlp_core/projectors/manager/test_toolbar_triggers-01.py create mode 100644 tests/openlp_core/projectors/manager/test_toolbar_triggers-basic.py diff --git a/openlp/core/projectors/manager.py b/openlp/core/projectors/manager.py index 2db1d2966..da1ff4dee 100644 --- a/openlp/core/projectors/manager.py +++ b/openlp/core/projectors/manager.py @@ -437,57 +437,44 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM """ self.projector_form.exec() - def on_blank_projector(self, opt=None): + def on_blank_projector(self, item=None, opt=None): """ - Calls projector thread to send blank screen command + Calls projector(s) thread to send blank screen command - :param opt: Needed by PyQt5 + :param item: Optional ProjectorItem() instance in case of direct call + :param opt: (Deprecated) """ - try: - opt.link.set_shutter_closed() - except AttributeError: - for list_item in self.projector_list_widget.selectedItems(): - if list_item is None: - return - projector = list_item.data(QtCore.Qt.UserRole) - try: - projector.link.set_shutter_closed() - except Exception: - continue + if item is not None: + return item.pjlink.set_shutter_closed() + for list_item in self.projector_list_widget.selectedItems(): + list_item.data(QtCore.Qt.UserRole).pjlink.set_shutter_closed() - def on_doubleclick_item(self, item, opt=None): + def on_doubleclick_item(self, item): """ When item is doubleclicked, will connect to projector. :param item: List widget item for connection. - :param opt: Needed by PyQt5 """ projector = item.data(QtCore.Qt.UserRole) - if QSOCKET_STATE[projector.link.state()] != S_CONNECTED: - try: - log.debug(f'ProjectorManager: Calling connect_to_host() on "{projector.link.ip}"') - projector.link.connect_to_host() - except Exception: - log.debug(f'ProjectorManager: "{projector.link.ip}" already connected - skipping') + if QSOCKET_STATE[projector.link.state()] == S_CONNECTED: + log.debug(f'ProjectorManager: "{projector.pjlink.name}" already connected - skipping') + else: + log.debug(f'ProjectorManager: "{projector.pjlink.name}" calling connect_to_host()') + projector.link.connect_to_host() return - def on_connect_projector(self, opt=None): + def on_connect_projector(self, item=None, opt=None): """ Calls projector thread to connect to projector - :param opt: Needed by PyQt5 + :param item: (Optional) ProjectorItem() for direct call + :param opt: (Deprecated) """ - try: - opt.link.connect_to_host() - except AttributeError: + if item is not None: + return item.pjlink.connect_to_host() + else: for list_item in self.projector_list_widget.selectedItems(): - if list_item is None: - return - projector = list_item.data(QtCore.Qt.UserRole) - try: - projector.link.connect_to_host() - except Exception: - continue + list_item.data(QtCore.Qt.UserRole).pjlink.connect_to_host() def on_delete_projector(self, opt=None): """ @@ -553,23 +540,18 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM log.debug(f'New projector list - item: {item.link.ip} {item.link.name}') self.udp_listen_delete(old_port) - def on_disconnect_projector(self, opt=None): + def on_disconnect_projector(self, item=None, opt=None): """ Calls projector thread to disconnect from projector - :param opt: Needed by PyQt5 + :param item: (Optional) ProjectorItem() for direct call + :param opt: (Deprecated) """ - try: - opt.link.disconnect_from_host() - except AttributeError: + if item is not None: + return item.pjlink.disconnect_from_host() + else: for list_item in self.projector_list_widget.selectedItems(): - if list_item is None: - return - projector = list_item.data(QtCore.Qt.UserRole) - try: - projector.link.disconnect_from_host() - except Exception: - continue + list_item.data(QtCore.Qt.UserRole).pjlink.disconnect_from_host() def on_edit_projector(self, opt=None): """ @@ -586,59 +568,44 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM self.projector_form.exec(projector.db_item) projector.db_item = self.projectordb.get_projector_by_id(self.old_projector.db_item.id) - def on_poweroff_projector(self, opt=None): + def on_poweroff_projector(self, item=None, opt=None): """ - Calls projector link to send Power Off command + Calls projector thread to turn projector off - :param opt: Needed by PyQt5 + :param item: (Optional) ProjectorItem() for direct call + :param opt: (Deprecated) """ - try: - opt.link.set_power_off() - except AttributeError: + if item is not None: + return item.pjlink.set_power_off() + else: for list_item in self.projector_list_widget.selectedItems(): - if list_item is None: - return - projector = list_item.data(QtCore.Qt.UserRole) - try: - projector.link.set_power_off() - except Exception: - continue + list_item.data(QtCore.Qt.UserRole).pjlink.set_power_off() - def on_poweron_projector(self, opt=None): + def on_poweron_projector(self, item=None, opt=None): """ - Calls projector link to send Power On command + Calls projector thread to turn projector on - :param opt: Needed by PyQt5 + :param item: (Optional) ProjectorItem() for direct call + :param opt: (Deprecated) """ - try: - opt.link.set_power_on() - except AttributeError: + if item is not None: + return item.pjlink.set_power_on() + else: for list_item in self.projector_list_widget.selectedItems(): - if list_item is None: - return - projector = list_item.data(QtCore.Qt.UserRole) - try: - projector.link.set_power_on() - except Exception: - continue + list_item.data(QtCore.Qt.UserRole).pjlink.set_power_on() - def on_show_projector(self, opt=None): + def on_show_projector(self, item=None, opt=None): """ - Calls projector thread to send open shutter command + Calls projector thread to open shutter - :param opt: Needed by PyQt5 + :param item: (Optional) ProjectorItem() for direct call + :param opt: (Deprecated) """ - try: - opt.link.set_shutter_open() - except AttributeError: + if item is not None: + return item.pjlink.set_shutter_open() + else: for list_item in self.projector_list_widget.selectedItems(): - if list_item is None: - return - projector = list_item.data(QtCore.Qt.UserRole) - try: - projector.link.set_shutter_open() - except Exception: - continue + list_item.data(QtCore.Qt.UserRole).pjlink.set_shutter_open() def on_status_projector(self, opt=None): """ @@ -740,27 +707,27 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM :param projector: Projector instance to add :param start: Start projector if True """ - item = ProjectorItem(link=self._add_projector(projector)) + item = ProjectorItem(parent=self, item=self._add_projector(projector)) item.db_item = projector item.icon = QtGui.QIcon(self.status_icons[S_NOT_CONNECTED]) widget = QtWidgets.QListWidgetItem(item.icon, - item.link.name, + item.pjlink.name, self.projector_list_widget ) widget.setData(QtCore.Qt.UserRole, item) - item.link.db_item = item.db_item + item.pjlink.db_item = item.db_item item.widget = widget - item.link.changeStatus.connect(self.update_status) - item.link.projectorAuthentication.connect(self.authentication_error) - item.link.projectorNoAuthentication.connect(self.no_authentication_error) - item.link.projectorUpdateIcons.connect(self.update_icons) + item.pjlink.changeStatus.connect(self.update_status) + item.pjlink.projectorAuthentication.connect(self.authentication_error) + item.pjlink.projectorNoAuthentication.connect(self.no_authentication_error) + item.pjlink.projectorUpdateIcons.connect(self.update_icons) # Add UDP listener for new projector port - self.udp_listen_add(item.link.port) + self.udp_listen_add(item.pjlink.port) self.projector_list.append(item) if start: - item.link.connect_to_host() + item.pjlink.connect_to_host() for item in self.projector_list: - log.debug(f'New projector list - item: ({item.link.ip}) {item.link.name}') + log.debug(f'New projector list - item: ({item.pjlink.ip}) {item.pjlink.name}') @QtCore.pyqtSlot(str) def add_projector_from_wizard(self, ip, opts=None): @@ -969,13 +936,20 @@ class ProjectorItem(QtCore.QObject): Class for the projector list widget item. NOTE: Actual PJLink class instance should be saved as self.link """ - def __init__(self, link=None): + def __init__(self, parent=None, link=None, item=None): """ Initialization for ProjectorItem instance - :param link: PJLink instance for QListWidgetItem + :param link: (Deprecated) PJLink instance for QListWidgetItem + :param item: PJLink instance for QListWidgetItem """ - self.link = link + # Refactor so self.link is renamed self.pjlink to clarify reference + if link is None: + self.link = item + self.pjlink = item + else: + self.link = link + self.pjlink = link self.thread = None self.icon = None self.widget = None diff --git a/tests/helpers/projector.py b/tests/helpers/projector.py new file mode 100644 index 000000000..3191863c5 --- /dev/null +++ b/tests/helpers/projector.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2022 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +########################################################################## +""" +Help classes/functions for PJLink Projector tests +""" + +from unittest.mock import MagicMock +from PyQt5 import QtNetwork + +from openlp.core.projectors.constants import S_OK, S_NOT_CONNECTED +from openlp.core.projectors.db import Projector +from tests.resources.projector.data import TEST1_DATA + + +class FakePJLink(object): + def __init__(self, projector=None, *args, **kwargs): + # Signal mocks + self.projectorStatus = MagicMock() + self.projectorAuthentication = MagicMock() + self.projectorNoAuthentication = MagicMock() + self.projectorReceivedData = MagicMock() + self.projectorUpdateIcons = MagicMock() + + # Method mocks + self.changeStatus = MagicMock() + self.connect_to_host = MagicMock() + self.disconnect_from_host = MagicMock() + self.poll_timer = MagicMock() + self.set_power_off = MagicMock() + self.set_power_on = MagicMock() + self.set_shutter_closed = MagicMock() + self.set_shutter_open = MagicMock() + self.socket_timer = MagicMock() + self.status_timer = MagicMock() + self.state = MagicMock() + + # Some tests that may include what it thinks are ProjectorItem() + # If ProjectorItem() is called, will probably overwrite these - OK + self.link = self + self.pjlink = self + + # Normal entries from PJLink + self.entry = Projector(**TEST1_DATA) if projector is None else projector + self.ip = self.entry.ip + self.qhost = QtNetwork.QHostAddress(self.ip) + self.location = self.entry.location + self.mac_adx = self.entry.mac_adx + self.name = self.entry.name + self.notes = self.entry.notes + self.pin = self.entry.pin + self.port = int(self.entry.port) + self.pjlink_class = "1" if self.entry.pjlink_class is None else self.entry.pjlink_class + self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000 + self.socket_timeout = 5000 if 'socket_timeout' not in kwargs else kwargs['socket_timeout'] * 1000 + self.no_poll = 'no_poll' in kwargs + self.status_connect = S_NOT_CONNECTED + self.last_command = '' + self.projector_status = S_NOT_CONNECTED + self.error_status = S_OK + self.send_queue = [] + self.priority_queue = [] + self.send_busy = False + self.status_timer_checks = {} # Keep track of events for the status timer + # Default mock return values diff --git a/tests/openlp_core/projectors/manager/test_misc_manager.py b/tests/openlp_core/projectors/manager/test_misc_manager.py index cec3200d8..8fbfe7f0b 100644 --- a/tests/openlp_core/projectors/manager/test_misc_manager.py +++ b/tests/openlp_core/projectors/manager/test_misc_manager.py @@ -20,6 +20,11 @@ ########################################################################## """ Test misc. functions with few test paths + +_load_projectors() +add_projector_from_wizard() +get_projector_list() + """ from unittest.mock import DEFAULT, patch @@ -47,9 +52,7 @@ def test_private_load_projectors(projector_manager_mtdb): with patch.multiple(projector_manager_mtdb, udp_listen_add=DEFAULT, udp_listen_delete=DEFAULT, - _load_projectors=DEFAULT) as mock_manager: - # Satisfy Flake8 linting - mock_manager['udp_listen_add'].return_value = None + _load_projectors=DEFAULT): projector_manager_mtdb.bootstrap_initialise() projector_manager_mtdb.bootstrap_post_set_up() @@ -67,44 +70,6 @@ def test_private_load_projectors(projector_manager_mtdb): assert t_chk == t_list, 'projector_list DB items do not match test items' -def test_on_edit_input(projector_manager): - """ - Test calling edit projector input GUI from input selection icon makes appropriate calls - """ - # GIVEN: Test environment - with patch.object(projector_manager, 'on_select_input') as mock_edit: - - # WHEN: Called - projector_manager.on_edit_input() - - # THEN: select input called with edit option - mock_edit.assert_called_with(opt=None, edit=True) - - -def test_on_add_projector(projector_manager): - """ - Test add new projector edit GUI is called properly - """ - # GIVEN: Test environment - # Mock to keep from getting event not registered error in Registry() - # during bootstrap_post_set_up() - with patch.multiple(projector_manager, - udp_listen_add=DEFAULT, - udp_listen_delete=DEFAULT) as mock_manager: - # Satisfy Flake8 linting - mock_manager['udp_listen_add'].return_value = None - projector_manager.bootstrap_initialise() - projector_manager.bootstrap_post_set_up() - - with patch.object(projector_manager, 'projector_form') as mock_form: - - # WHEN called - projector_manager.on_add_projector() - - # THEN: projector form called - mock_form.exec.assert_called_once() - - def test_add_projector_from_wizard(projector_manager): """ Test when add projector from GUI, appropriate method is called correctly @@ -138,9 +103,7 @@ def test_get_projector_list(projector_manager_mtdb): # during bootstrap_post_set_up() with patch.multiple(projector_manager_mtdb, udp_listen_add=DEFAULT, - udp_listen_delete=DEFAULT) as mock_manager: - # Satisfy Flake8 linting - mock_manager['udp_listen_add'].return_value = None + udp_listen_delete=DEFAULT): projector_manager_mtdb.bootstrap_initialise() projector_manager_mtdb.bootstrap_post_set_up() diff --git a/tests/openlp_core/projectors/manager/test_toolbar_triggers-01.py b/tests/openlp_core/projectors/manager/test_toolbar_triggers-01.py new file mode 100644 index 000000000..a7db4ebff --- /dev/null +++ b/tests/openlp_core/projectors/manager/test_toolbar_triggers-01.py @@ -0,0 +1,511 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2022 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +########################################################################## +""" +Test methods called by toolbar icons part 1 + +on_blank_projector() +on_connect_projector() +on_disconnect_projector() +on_poweroff_projector() +on_poweron_projector() +on_show_projector() + +""" + +from unittest.mock import DEFAULT, patch + +from openlp.core.projectors.db import Projector + +from tests.helpers.projector import FakePJLink +from tests.resources.projector.data import TEST1_DATA, TEST2_DATA, TEST3_DATA + + +def helper_method(test_fixture, method, effect, test_item=None): + """ + Boilerplate for tests + + :param test_fixture: Fixture used for testing + :param method: Method in fixture to test + :param effect: Dict of items for testing + {'item': Item class + 'select': Boolean to enable selected() in projector_list_widget + :param test_item: (Optional) Item to call method with + + """ + with patch.multiple(test_fixture, + udp_listen_add=DEFAULT, + udp_listen_delete=DEFAULT, + update_icons=DEFAULT, + _add_projector=DEFAULT) as mock_manager: + + mock_manager['_add_projector'].side_effect = [item['item'] for item in effect] + test_fixture.bootstrap_initialise() + # projector_list_widget created here + test_fixture.bootstrap_post_set_up() + + # Add ProjectorItem instances to projector_list_widget + for item in effect: + test_fixture.add_projector(projector=item['item']) + + # Set at least one instance as selected to verify projector_list_widget is not called + for item in range(len(effect)): + test_fixture.projector_list_widget.item(item).setSelected(effect[item]['select']) + + # WHEN: Called with projector instance + method(item=test_item) + + +def test_on_blank_projector_direct(projector_manager_mtdb): + """ + Test calling method directly - projector_list_widget should not be called + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_blank_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ], + test_item=t_1 + ) + + # THEN: Only t_1.set_shutter_closed() should be called + t_1.set_shutter_closed.assert_called_once() + t_2.set_shutter_closed.assert_not_called() + t_3.set_shutter_closed.assert_not_called() + + +def test_on_blank_projector_one_item(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_blank_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': True}, + {'item': t_3, 'select': False} + ] + ) + + # THEN: Only t_3.set_shutter_closed() should be called + t_1.set_shutter_closed.assert_not_called() + t_2.set_shutter_closed.assert_called_once() + t_3.set_shutter_closed.assert_not_called() + + +def test_on_blank_projector_multiple_items(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with more than one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_blank_projector, + effect=[{'item': t_1, 'select': True}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ] + ) + + # THEN: t_1 and t_3 set_shutter_closed() should be called + t_1.set_shutter_closed.assert_called_once() + t_2.set_shutter_closed.assert_not_called() + t_3.set_shutter_closed.assert_called_once() + + +def test_on_connect_projector_direct(projector_manager_mtdb): + """ + Test calling method directly - projector_list_widget should not be called + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_connect_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ], + test_item=t_1 + ) + + # THEN: Only t_1.connect_to_host() should be called + t_1.connect_to_host.assert_called_once() + t_2.connect_to_host.assert_not_called() + t_3.connect_to_host.assert_not_called() + + +def test_on_connect_projector_one_item(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_connect_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ] + ) + + # THEN: Only t_3.connect_to_host() should be called + t_1.connect_to_host.assert_not_called() + t_2.connect_to_host.assert_not_called() + t_3.connect_to_host.assert_called_once() + + +def test_on_connect_projector_multiple_items(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with more than one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_connect_projector, + effect=[{'item': t_1, 'select': True}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ] + ) + + # THEN: t_1 and t_3 connect_to_host() should be called + t_1.connect_to_host.assert_called_once() + t_2.connect_to_host.assert_not_called() + t_3.connect_to_host.assert_called_once() + + +def test_on_disconnect_projector_direct(projector_manager_mtdb): + """ + Test calling method directly - projector_list_widget should not be called + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_disconnect_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ], + test_item=t_1 + ) + + # THEN: Only t_1.disconnect_from_host() should be called + t_1.disconnect_from_host.assert_called_once() + t_2.disconnect_from_host.assert_not_called() + t_3.disconnect_from_host.assert_not_called() + + +def test_on_disconnect_projector_one_item(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_disconnect_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ] + ) + + # THEN: Only t_3.disconnect_from_host() should be called + t_1.disconnect_from_host.assert_not_called() + t_2.disconnect_from_host.assert_not_called() + t_3.disconnect_from_host.assert_called_once() + + +def test_on_disconnect_projector_multiple_items(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with more than one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_disconnect_projector, + effect=[{'item': t_1, 'select': True}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ] + ) + + # THEN: t_1 and t_3 disconnect_from_host() should be called + t_1.disconnect_from_host.assert_called_once() + t_2.disconnect_from_host.assert_not_called() + t_3.disconnect_from_host.assert_called_once() + + +def test_on_poweroff_projector_direct(projector_manager_mtdb): + """ + Test calling method directly - projector_list_widget should not be called + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_poweroff_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ], + test_item=t_1 + ) + + # THEN: Only t_1.set_power_off() should be called + t_1.set_power_off.assert_called_once() + t_2.set_power_off.assert_not_called() + t_3.set_power_off.assert_not_called() + + +def test_on_poweroff_projector_one_item(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_poweroff_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ] + ) + + # THEN: Only t_3.set_power_off() should be called + t_1.set_power_off.assert_not_called() + t_2.set_power_off.assert_not_called() + t_3.set_power_off.assert_called_once() + + +def test_on_poweroff_projector_multiple_items(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with more than one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_poweroff_projector, + effect=[{'item': t_1, 'select': True}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ] + ) + + # THEN: t_1 and t_3 set_power_off() should be called + t_1.set_power_off.assert_called_once() + t_2.set_power_off.assert_not_called() + t_3.set_power_off.assert_called_once() + + +def test_on_poweron_projector_direct(projector_manager_mtdb): + """ + Test calling method directly - projector_list_widget should not be called + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_poweron_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ], + test_item=t_1 + ) + + # THEN: Only t_1.set_power_on() should be called + t_1.set_power_on.assert_called_once() + t_2.set_power_on.assert_not_called() + t_3.set_power_on.assert_not_called() + + +def test_on_poweron_projector_one_item(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_poweron_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ] + ) + + # THEN: Only t_3.set_power_on() should be called + t_1.set_power_on.assert_not_called() + t_2.set_power_on.assert_not_called() + t_3.set_power_on.assert_called_once() + + +def test_on_poweron_projector_multiple_items(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with more than one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_poweron_projector, + effect=[{'item': t_1, 'select': True}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ] + ) + + # THEN: t_1 and t_3 set_power_on() should be called + t_1.set_power_on.assert_called_once() + t_2.set_power_on.assert_not_called() + t_3.set_power_on.assert_called_once() + + +def test_on_show_projector_direct(projector_manager_mtdb): + """ + Test calling method directly - projector_list_widget should not be called + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_show_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ], + test_item=t_1 + ) + + # THEN: Only t_1.set_shutter_open() should be called + t_1.set_shutter_open.assert_called_once() + t_2.set_shutter_open.assert_not_called() + t_3.set_shutter_open.assert_not_called() + + +def test_on_show_projector_one_item(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_show_projector, + effect=[{'item': t_1, 'select': False}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ] + ) + + # THEN: Only t_3.set_shutter_open() should be called + t_1.set_shutter_open.assert_not_called() + t_2.set_shutter_open.assert_not_called() + t_3.set_shutter_open.assert_called_once() + + +def test_on_show_projector_multiple_items(projector_manager_mtdb): + """ + Test calling method using projector_list_widget with more than one item selected + """ + # GIVEN: Test setup + t_1 = FakePJLink(Projector(**TEST1_DATA)) + t_2 = FakePJLink(Projector(**TEST2_DATA)) + t_3 = FakePJLink(Projector(**TEST3_DATA)) + + # WHEN: called + helper_method(test_fixture=projector_manager_mtdb, + method=projector_manager_mtdb.on_show_projector, + effect=[{'item': t_1, 'select': True}, + {'item': t_2, 'select': False}, + {'item': t_3, 'select': True} + ] + ) + + # THEN: t_1 and t_3 set_shutter_open() should be called + t_1.set_shutter_open.assert_called_once() + t_2.set_shutter_open.assert_not_called() + t_3.set_shutter_open.assert_called_once() diff --git a/tests/openlp_core/projectors/manager/test_toolbar_triggers-basic.py b/tests/openlp_core/projectors/manager/test_toolbar_triggers-basic.py new file mode 100644 index 000000000..5504455cc --- /dev/null +++ b/tests/openlp_core/projectors/manager/test_toolbar_triggers-basic.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2022 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +########################################################################## +""" +Test methods called by toolbar icons with minimal flow paths and tests + +on_add_projector() +on_doubleclick_item() +on_edit_input() + +""" + +import logging +import openlp.core.projectors.manager + +from unittest.mock import DEFAULT, patch + +from openlp.core.projectors.constants import QSOCKET_STATE, \ + S_CONNECTED, S_NOT_CONNECTED + +from tests.helpers.projector import FakePJLink + +test_module = openlp.core.projectors.manager.__name__ + + +def test_on_add_projector(projector_manager): + """ + Test add new projector edit GUI is called properly + """ + # GIVEN: Test environment + # Mock to keep from getting event not registered error in Registry() + # during bootstrap_post_set_up() + with patch.multiple(projector_manager, + udp_listen_add=DEFAULT, + udp_listen_delete=DEFAULT): + projector_manager.bootstrap_initialise() + projector_manager.bootstrap_post_set_up() + + # Have to wait for projector_manager.bootstrap_post_set_up() before projector_form is initialized + with patch.object(projector_manager, 'projector_form') as mock_form: + + # WHEN called + projector_manager.on_add_projector() + + # THEN: projector form called + mock_form.exec.assert_called_once() + + +def test_on_edit_input(projector_manager, pjlink): + """ + Test on_edit_input calls on_select_input properly + """ + # GIVEN: Test setup + with patch.object(projector_manager, 'on_select_input') as mock_input: + + # WHEN: Called with pjlink instance + projector_manager.on_edit_input(opt=pjlink) + + # THEN: appropriate call made + mock_input.assert_called_with(opt=pjlink, edit=True) + + +def test_on_doubleclick_item_connected(projector_manager_mtdb, caplog): + """ + Test projector.connect_to_host() not called when status is S_CONNECTED + """ + t_1 = FakePJLink() + caplog.set_level(logging.DEBUG) + logs = [(test_module, logging.DEBUG, + f'ProjectorManager: "{t_1.pjlink.name}" already connected - skipping')] + + with patch.multiple(projector_manager_mtdb, + udp_listen_add=DEFAULT, + udp_listen_delete=DEFAULT, + update_icons=DEFAULT, + _add_projector=DEFAULT) as mock_manager: + + projector_manager_mtdb.bootstrap_initialise() + # projector_list_widget created here + projector_manager_mtdb.bootstrap_post_set_up() + + # Add ProjectorItem instances to projector_list_widget + mock_manager['_add_projector'].return_value = t_1 + projector_manager_mtdb.add_projector(projector=t_1) + + # WHEN: Called + t_1.state.return_value = QSOCKET_STATE[S_CONNECTED] + caplog.clear() + projector_manager_mtdb.on_doubleclick_item(projector_manager_mtdb.projector_list_widget.item(0)) + + assert caplog.record_tuples == logs, 'Invalid log entries' + t_1.connect_to_host.assert_not_called() + + +def test_on_doubleclick_item_not_connected(projector_manager_mtdb, caplog): + """ + Test projector.connect_to_host() called + """ + t_1 = FakePJLink() + caplog.set_level(logging.DEBUG) + logs = [(test_module, logging.DEBUG, + f'ProjectorManager: "{t_1.pjlink.name}" calling connect_to_host()')] + + with patch.multiple(projector_manager_mtdb, + udp_listen_add=DEFAULT, + udp_listen_delete=DEFAULT, + update_icons=DEFAULT, + _add_projector=DEFAULT) as mock_manager: + + projector_manager_mtdb.bootstrap_initialise() + # projector_list_widget created here + projector_manager_mtdb.bootstrap_post_set_up() + + # Add ProjectorItem instances to projector_list_widget + mock_manager['_add_projector'].return_value = t_1 + projector_manager_mtdb.add_projector(projector=t_1) + + # WHEN: Called + t_1.state.return_value = QSOCKET_STATE[S_NOT_CONNECTED] + caplog.clear() + projector_manager_mtdb.on_doubleclick_item(projector_manager_mtdb.projector_list_widget.item(0)) + + assert caplog.record_tuples == logs, 'Invalid log entries' + t_1.connect_to_host.assert_called_once() diff --git a/tests/resources/projector/data.py b/tests/resources/projector/data.py index 33e208eaa..7910bcb05 100644 --- a/tests/resources/projector/data.py +++ b/tests/resources/projector/data.py @@ -35,7 +35,8 @@ TEST_HASH = '5d8409bc1c3fa39749434aa3a5c38682' TEST_CONNECT_AUTHENTICATE = 'PJLink 1 {salt}'.format(salt=TEST_SALT) -TEST1_DATA = dict(ip='111.111.111.111', +TEST1_DATA = dict(id=1, + ip='111.111.111.111', port='1111', pin='1111', name='___TEST_ONE___', @@ -47,7 +48,8 @@ TEST1_DATA = dict(ip='111.111.111.111', model_lamp='Lamp type 1', mac_adx='11:11:11:11:11:11') -TEST2_DATA = dict(ip='222.222.222.222', +TEST2_DATA = dict(id=2, + ip='222.222.222.222', port='2222', pin='2222', name='___TEST_TWO___', @@ -59,7 +61,8 @@ TEST2_DATA = dict(ip='222.222.222.222', model_lamp='Lamp type 2', mac_adx='22:22:22:22:22:22') -TEST3_DATA = dict(ip='333.333.333.333', +TEST3_DATA = dict(id=3, + ip='333.333.333.333', port='3333', pin='3333', name='___TEST_THREE___',