diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 5d11f0c1f..94a0e19f1 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -36,6 +36,7 @@ from openlp.core.common import md5_hash from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import translate from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.path import Path from openlp.core.common.settings import Settings from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, expand_chords @@ -427,13 +428,13 @@ class ServiceItem(RegistryProperties): self.has_original_files = True if 'background_audio' in header: self.background_audio = [] - for filename in header['background_audio']: - # Give them real file paths. - filepath = str(filename) - if path: + for file_path in header['background_audio']: + # In OpenLP 3.0 we switched to storing Path objects in JSON files + if isinstance(file_path, str): + # Handle service files prior to OpenLP 3.0 # Windows can handle both forward and backward slashes, so we use ntpath to get the basename - filepath = os.path.join(path, ntpath.basename(str(filename))) - self.background_audio.append(filepath) + file_path = Path(path, ntpath.basename(file_path)) + self.background_audio.append(file_path) self.theme_overwritten = header.get('theme_overwritten', False) if self.service_item_type == ServiceItemType.Text: for slide in service_item['serviceitem']['data']: @@ -444,8 +445,8 @@ class ServiceItem(RegistryProperties): if path: self.has_original_files = False for text_image in service_item['serviceitem']['data']: - filename = os.path.join(path, text_image) - self.add_from_image(filename, text_image, background) + file_path = os.path.join(path, text_image) + self.add_from_image(file_path, text_image, background) else: for text_image in service_item['serviceitem']['data']: self.add_from_image(text_image['path'], text_image['title'], background) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 631ae4dfc..2b9e70abf 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -400,9 +400,9 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): if item: item.setIcon(build_icon(Path(gettempdir(), 'openlp', screenshot))) - def _download_progress(self, count, block_size): + def update_progress(self, count, block_size): """ - Calculate and display the download progress. + Calculate and display the download progress. This method is called by download_file(). """ increment = (count * block_size) - self.previous_size self._increment_progress_bar(None, increment) diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 9f9261015..faa481455 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -63,7 +63,7 @@ class UiFirstTimeWizard(object): first_time_wizard.setOptions(QtWidgets.QWizard.IndependentPages | QtWidgets.QWizard.NoBackButtonOnStartPage | QtWidgets.QWizard.NoBackButtonOnLastPage | QtWidgets.QWizard.HaveCustomButton1 | QtWidgets.QWizard.HaveCustomButton2) - if is_macosx(): + if is_macosx(): # pragma: nocover first_time_wizard.setPixmap(QtWidgets.QWizard.BackgroundPixmap, QtGui.QPixmap(':/wizards/openlp-osx-wizard.png')) first_time_wizard.resize(634, 386) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 4acb72ae2..0c16d24b2 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -1308,11 +1308,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert self.load_progress_bar.setValue(0) self.application.process_events() - def increment_progress_bar(self): + def increment_progress_bar(self, increment=1): """ - Increase the Progress Bar value by 1 + Increase the Progress Bar by the value in increment. + + :param int increment: The value you to increase the progress bar by. """ - self.load_progress_bar.setValue(self.load_progress_bar.value() + 1) + self.load_progress_bar.setValue(self.load_progress_bar.value() + increment) self.application.process_events() def finished_progress_bar(self): @@ -1380,4 +1382,4 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert if not isinstance(filename, str): filename = str(filename, sys.getfilesystemencoding()) if filename.endswith(('.osz', '.oszl')): - self.service_manager_contents.load_file(filename) + self.service_manager_contents.load_file(Path(filename)) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 2b079cdf7..fc19ab4fa 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -27,8 +27,9 @@ import json import os import shutil import zipfile +from contextlib import suppress from datetime import datetime, timedelta -from tempfile import mkstemp +from tempfile import NamedTemporaryFile from PyQt5 import QtCore, QtGui, QtWidgets @@ -36,11 +37,13 @@ from openlp.core.common import ThemeLevel, delete_file from openlp.core.common.actions import ActionList, CategoryOrder from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings, format_time, translate +from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder from openlp.core.common.mixins import LogMixin, RegistryProperties -from openlp.core.common.path import Path, create_paths, str_to_path +from openlp.core.common.path import Path, str_to_path from openlp.core.common.registry import Registry, RegistryBase from openlp.core.common.settings import Settings from openlp.core.lib import ServiceItem, ItemCapabilities, PluginStatus, build_icon +from openlp.core.lib.exceptions import ValidationError from openlp.core.lib.ui import critical_error_message_box, create_widget_action, find_and_set_in_combo_box from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm from openlp.core.widgets.dialogs import FileDialog @@ -449,7 +452,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi else: file_path = str_to_path(load_file) Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', file_path.parent) - self.load_file(str(file_path)) + self.load_file(file_path) def save_modified_service(self): """ @@ -475,7 +478,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi elif result == QtWidgets.QMessageBox.Save: self.decide_save_method() sender = self.sender() - self.load_file(sender.data()) + self.load_file(Path(sender.data())) def new_file(self): """ @@ -503,6 +506,31 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi service.append({'openlp_core': core}) return service + def get_write_file_list(self): + """ + Get a list of files used in the service and files that are missing. + + :return: A list of files used in the service that exist, and a list of files that don't. + :rtype: (list[openlp.core.common.path.Path], list[openlp.core.common.path.Path]) + """ + write_list = [] + missing_list = [] + for item in self.service_items: + if item['service_item'].uses_file(): + for frame in item['service_item'].get_frames(): + path_from = item['service_item'].get_frame_path(frame=frame) + if path_from in write_list or path_from in missing_list: + continue + if not os.path.exists(path_from): + missing_list.append(Path(path_from)) + else: + write_list.append(Path(path_from)) + for audio_path in item['service_item'].background_audio: + if audio_path in write_list: + continue + write_list.append(audio_path) + return write_list, missing_list + def save_file(self): """ Save the current service file. @@ -511,178 +539,74 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi there be an error when saving. Audio files are also copied into the service manager directory, and then packaged into the zip file. """ - if not self.file_name(): - return self.save_file_as() - temp_file, temp_file_name = mkstemp('.osz', 'openlp_') - # We don't need the file handle. - os.close(temp_file) - self.log_debug(temp_file_name) - path_file_name = str(self.file_name()) - path, file_name = os.path.split(path_file_name) - base_name = os.path.splitext(file_name)[0] - service_file_name = '{name}.osj'.format(name=base_name) - self.log_debug('ServiceManager.save_file - {name}'.format(name=path_file_name)) - Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', Path(path)) + file_path = self.file_name() + self.log_debug('ServiceManager.save_file - {name}'.format(name=file_path)) + self.application.set_busy_cursor() + service = self.create_basic_service() + write_list = [] missing_list = [] - audio_files = [] - total_size = 0 - self.application.set_busy_cursor() - # Number of items + 1 to zip it - self.main_window.display_progress_bar(len(self.service_items) + 1) - # Get list of missing files, and list of files to write - for item in self.service_items: - if not item['service_item'].uses_file(): - continue - for frame in item['service_item'].get_frames(): - path_from = item['service_item'].get_frame_path(frame=frame) - if path_from in write_list or path_from in missing_list: - continue - if not os.path.exists(path_from): - missing_list.append(path_from) - else: - write_list.append(path_from) - if missing_list: - self.application.set_normal_cursor() - title = translate('OpenLP.ServiceManager', 'Service File(s) Missing') - message = translate('OpenLP.ServiceManager', - 'The following file(s) in the service are missing: {name}\n\n' - 'These files will be removed if you continue to save.' - ).format(name="\n\t".join(missing_list)) - answer = QtWidgets.QMessageBox.critical(self, title, message, - QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok | - QtWidgets.QMessageBox.Cancel)) - if answer == QtWidgets.QMessageBox.Cancel: - self.main_window.finished_progress_bar() - return False + + if not self._save_lite: + write_list, missing_list = self.get_write_file_list() + + if missing_list: + self.application.set_normal_cursor() + title = translate('OpenLP.ServiceManager', 'Service File(s) Missing') + message = translate('OpenLP.ServiceManager', + 'The following file(s) in the service are missing: {name}\n\n' + 'These files will be removed if you continue to save.' + ).format(name='\n\t'.join(missing_list)) + answer = QtWidgets.QMessageBox.critical(self, title, message, + QtWidgets.QMessageBox.StandardButtons( + QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)) + if answer == QtWidgets.QMessageBox.Cancel: + return False # Check if item contains a missing file. for item in list(self.service_items): - self.main_window.increment_progress_bar() - item['service_item'].remove_invalid_frames(missing_list) - if item['service_item'].missing_frames(): - self.service_items.remove(item) - else: - service_item = item['service_item'].get_service_repr(self._save_lite) - if service_item['header']['background_audio']: - for i, file_name in enumerate(service_item['header']['background_audio']): - new_file = os.path.join('audio', item['service_item'].unique_identifier, str(file_name)) - audio_files.append((file_name, new_file)) - service_item['header']['background_audio'][i] = new_file - # Add the service item to the service. - service.append({'serviceitem': service_item}) - self.repaint_service_list(-1, -1) - for file_item in write_list: - file_size = os.path.getsize(file_item) - total_size += file_size - self.log_debug('ServiceManager.save_file - ZIP contents size is %i bytes' % total_size) - service_content = json.dumps(service) - # Usual Zip file cannot exceed 2GiB, file with Zip64 cannot be extracted using unzip in UNIX. - allow_zip_64 = (total_size > 2147483648 + len(service_content)) - self.log_debug('ServiceManager.save_file - allowZip64 is {text}'.format(text=allow_zip_64)) - zip_file = None - success = True - self.main_window.increment_progress_bar() - try: - zip_file = zipfile.ZipFile(temp_file_name, 'w', zipfile.ZIP_STORED, allow_zip_64) - # First we add service contents.. - zip_file.writestr(service_file_name, service_content) - # Finally add all the listed media files. - for write_from in write_list: - zip_file.write(write_from, write_from) - for audio_from, audio_to in audio_files: - audio_from = str(audio_from) - audio_to = str(audio_to) - if audio_from.startswith('audio'): - # When items are saved, they get new unique_identifier. Let's copy the file to the new location. - # Unused files can be ignored, OpenLP automatically cleans up the service manager dir on exit. - audio_from = os.path.join(self.service_path, audio_from) - save_file = os.path.join(self.service_path, audio_to) - save_path = os.path.split(save_file)[0] - create_paths(Path(save_path)) - if not os.path.exists(save_file): - shutil.copy(audio_from, save_file) - zip_file.write(audio_from, audio_to) - except OSError: - self.log_exception('Failed to save service to disk: {name}'.format(name=temp_file_name)) - self.main_window.error_message(translate('OpenLP.ServiceManager', 'Error Saving File'), - translate('OpenLP.ServiceManager', 'There was an error saving your file.')) - success = False - finally: - if zip_file: - zip_file.close() - self.main_window.finished_progress_bar() - self.application.set_normal_cursor() - if success: - try: - shutil.copy(temp_file_name, path_file_name) - except (shutil.Error, PermissionError): - return self.save_file_as() - except OSError as ose: - QtWidgets.QMessageBox.critical(self, translate('OpenLP.ServiceManager', 'Error Saving File'), - translate('OpenLP.ServiceManager', 'An error occurred while writing the ' - 'service file: {error}').format(error=ose.strerror), - QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok)) - success = False - self.main_window.add_recent_file(path_file_name) - self.set_modified(False) - delete_file(Path(temp_file_name)) - return success - - def save_local_file(self): - """ - Save the current service file but leave all the file references alone to point to the current machine. - This format is not transportable as it will not contain any files. - """ - if not self.file_name(): - return self.save_file_as() - temp_file, temp_file_name = mkstemp('.oszl', 'openlp_') - # We don't need the file handle. - os.close(temp_file) - self.log_debug(temp_file_name) - path_file_name = str(self.file_name()) - path, file_name = os.path.split(path_file_name) - base_name = os.path.splitext(file_name)[0] - service_file_name = '{name}.osj'.format(name=base_name) - self.log_debug('ServiceManager.save_file - {name}'.format(name=path_file_name)) - Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', Path(path)) - service = self.create_basic_service() - self.application.set_busy_cursor() - # Number of items + 1 to zip it - self.main_window.display_progress_bar(len(self.service_items) + 1) - for item in self.service_items: - self.main_window.increment_progress_bar() + if not self._save_lite: + item['service_item'].remove_invalid_frames(missing_list) + if item['service_item'].missing_frames(): + self.service_items.remove(item) + continue service_item = item['service_item'].get_service_repr(self._save_lite) - # TODO: check for file item on save. + # Add the service item to the service. service.append({'serviceitem': service_item}) - self.main_window.increment_progress_bar() - service_content = json.dumps(service) - zip_file = None - success = True - self.main_window.increment_progress_bar() + self.repaint_service_list(-1, -1) + service_content = json.dumps(service, cls=OpenLPJsonEncoder) + service_content_size = len(bytes(service_content, encoding='utf-8')) + total_size = service_content_size + for file_item in write_list: + total_size += file_item.stat().st_size + self.log_debug('ServiceManager.save_file - ZIP contents size is %i bytes' % total_size) + self.main_window.display_progress_bar(total_size) try: - zip_file = zipfile.ZipFile(temp_file_name, 'w', zipfile.ZIP_STORED, True) - # First we add service contents. - zip_file.writestr(service_file_name, service_content) - except OSError: - self.log_exception('Failed to save service to disk: {name}'.format(name=temp_file_name)) - self.main_window.error_message(translate('OpenLP.ServiceManager', 'Error Saving File'), - translate('OpenLP.ServiceManager', 'There was an error saving your file.')) - success = False - finally: - if zip_file: - zip_file.close() + with NamedTemporaryFile(dir=str(file_path.parent), prefix='.') as temp_file, \ + zipfile.ZipFile(temp_file, 'w') as zip_file: + # First we add service contents.. + zip_file.writestr('service_data.osj', service_content) + self.main_window.increment_progress_bar(service_content_size) + # Finally add all the listed media files. + for write_path in write_list: + zip_file.write(str(write_path), str(write_path)) + self.main_window.increment_progress_bar(write_path.stat().st_size) + with suppress(FileNotFoundError): + file_path.unlink() + os.link(temp_file.name, str(file_path)) + Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', file_path.parent) + except (PermissionError, OSError) as error: + self.log_exception('Failed to save service to disk: {name}'.format(name=file_path)) + self.main_window.error_message( + translate('OpenLP.ServiceManager', 'Error Saving File'), + translate('OpenLP.ServiceManager', + 'There was an error saving your file.\n\n{error}').format(error=error)) + return self.save_file_as() self.main_window.finished_progress_bar() self.application.set_normal_cursor() - if success: - try: - shutil.copy(temp_file_name, path_file_name) - except (shutil.Error, PermissionError): - return self.save_file_as() - self.main_window.add_recent_file(path_file_name) - self.set_modified(False) - delete_file(Path(temp_file_name)) - return success + self.main_window.add_recent_file(file_path) + self.set_modified(False) + return True def save_file_as(self): """ @@ -743,87 +667,49 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi """ if not self.file_name(): return self.save_file_as() - if self._save_lite: - return self.save_local_file() - else: - return self.save_file() + return self.save_file() - def load_file(self, file_name): + def load_file(self, file_path): """ Load an existing service file - :param file_name: + :param file_path: """ - if not file_name: + if not file_path.exists(): return False - file_name = str(file_name) - if not os.path.exists(file_name): - return False - zip_file = None - file_to = None + service_data = None self.application.set_busy_cursor() try: - zip_file = zipfile.ZipFile(file_name) - for zip_info in zip_file.infolist(): - try: - ucs_file = zip_info.filename - except UnicodeDecodeError: - self.log_exception('file_name "{name}" is not valid UTF-8'.format(name=zip_info.file_name)) - critical_error_message_box(message=translate('OpenLP.ServiceManager', - 'File is not a valid service.\n The content encoding is not UTF-8.')) - continue - os_file = ucs_file.replace('/', os.path.sep) - os_file = os.path.basename(os_file) - self.log_debug('Extract file: {name}'.format(name=os_file)) - zip_info.filename = os_file - zip_file.extract(zip_info, self.service_path) - if os_file.endswith('osj') or os_file.endswith('osd'): - p_file = os.path.join(self.service_path, os_file) - if 'p_file' in locals(): - file_to = open(p_file, 'r') - if p_file.endswith('osj'): - items = json.load(file_to) - else: - critical_error_message_box(message=translate('OpenLP.ServiceManager', - 'The service file you are trying to open is in an old ' - 'format.\n Please save it using OpenLP 2.0.2 or ' - 'greater.')) - return - file_to.close() + with zipfile.ZipFile(str(file_path)) as zip_file: + compressed_size = 0 + for zip_info in zip_file.infolist(): + compressed_size += zip_info.compress_size + self.main_window.display_progress_bar(compressed_size) + for zip_info in zip_file.infolist(): + self.log_debug('Extract file: {name}'.format(name=zip_info.filename)) + # The json file has been called 'service_data.osj' since OpenLP 3.0 + if zip_info.filename == 'service_data.osj' or zip_info.filename.endswith('osj'): + with zip_file.open(zip_info, 'r') as json_file: + service_data = json_file.read() + else: + zip_info.filename = os.path.basename(zip_info.filename) + zip_file.extract(zip_info, str(self.service_path)) + self.main_window.increment_progress_bar(zip_info.compress_size) + if service_data: + items = json.loads(service_data, cls=OpenLPJsonDecoder) self.new_file() - self.set_file_name(str_to_path(file_name)) - self.main_window.display_progress_bar(len(items)) self.process_service_items(items) - delete_file(Path(p_file)) - self.main_window.add_recent_file(file_name) + self.set_file_name(file_path) + self.main_window.add_recent_file(file_path) self.set_modified(False) - Settings().setValue('servicemanager/last file', Path(file_name)) + Settings().setValue('servicemanager/last file', file_path) else: - critical_error_message_box(message=translate('OpenLP.ServiceManager', 'File is not a valid service.')) - self.log_error('File contains no service data') - except (OSError, NameError): - self.log_exception('Problem loading service file {name}'.format(name=file_name)) - critical_error_message_box(message=translate('OpenLP.ServiceManager', - 'File could not be opened because it is corrupt.')) - except zipfile.BadZipFile: - if os.path.getsize(file_name) == 0: - self.log_exception('Service file is zero sized: {name}'.format(name=file_name)) - QtWidgets.QMessageBox.information(self, translate('OpenLP.ServiceManager', 'Empty File'), - translate('OpenLP.ServiceManager', - 'This service file does not contain ' - 'any data.')) - else: - self.log_exception('Service file is cannot be extracted as zip: {name}'.format(name=file_name)) - QtWidgets.QMessageBox.information(self, translate('OpenLP.ServiceManager', 'Corrupt File'), - translate('OpenLP.ServiceManager', - 'This file is either corrupt or it is not an OpenLP 2 ' - 'service file.')) - self.application.set_normal_cursor() - return - finally: - if file_to: - file_to.close() - if zip_file: - zip_file.close() + raise ValidationError(msg='No service data found') + except (NameError, OSError, ValidationError, zipfile.BadZipFile) as e: + self.log_exception('Problem loading service file {name}'.format(name=file_path)) + critical_error_message_box( + message=translate('OpenLP.ServiceManager', + 'The service file {file_path} could not be loaded because it is either corrupt, or ' + 'not a valid OpenLP 2 or OpenLP 3 service file.'.format(file_path=file_path))) self.main_window.finished_progress_bar() self.application.set_normal_cursor() self.repaint_service_list(-1, -1) @@ -838,7 +724,8 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.main_window.increment_progress_bar() service_item = ServiceItem() if 'openlp_core' in item: - item = item.get('openlp_core') + item = item['openlp_core'] + self._save_lite = item.get('lite-service', False) theme = item.get('service-theme', None) if theme: find_and_set_in_combo_box(self.theme_combo_box, theme, set_missing=False) @@ -861,9 +748,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi Load the last service item from the service manager when the service was last closed. Can be blank if there was no service present. """ - file_name = str_to_path(Settings().value('servicemanager/last file')) - if file_name: - self.load_file(file_name) + file_path = Settings().value('servicemanager/last file') + if file_path: + self.load_file(file_path) def context_menu(self, point): """ diff --git a/tests/functional/openlp_core/api/endpoint/__init__.py b/tests/functional/openlp_core/api/endpoint/test_remote.py similarity index 67% rename from tests/functional/openlp_core/api/endpoint/__init__.py rename to tests/functional/openlp_core/api/endpoint/test_remote.py index 711ded4ae..c330f64d6 100644 --- a/tests/functional/openlp_core/api/endpoint/__init__.py +++ b/tests/functional/openlp_core/api/endpoint/test_remote.py @@ -19,3 +19,26 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### +""" +Functional tests to test the remote index +""" +from unittest.mock import MagicMock, patch + +from openlp.core.api.endpoint.remote import index +from openlp.core.api.endpoint.core import TRANSLATED_STRINGS + + +@patch('openlp.core.api.endpoint.remote.remote_endpoint') +def test_index(mocked_endpoint): + """ + Test the index method of the remote + """ + # GIVEN: A mocked Endpoint + mocked_endpoint.render_template.return_value = 'test template' + + # WHEN: index is called + result = index(MagicMock(), 'index') + + # THEN: The result should be "test template" and the right methods should have been called + mocked_endpoint.render_template.assert_called_once_with('index.mako', **TRANSLATED_STRINGS) + assert result == 'test template' diff --git a/tests/functional/openlp_core/lib/test_pluginmanager.py b/tests/functional/openlp_core/lib/test_pluginmanager.py index 553221ff2..a2e981276 100644 --- a/tests/functional/openlp_core/lib/test_pluginmanager.py +++ b/tests/functional/openlp_core/lib/test_pluginmanager.py @@ -23,7 +23,7 @@ Package to test the openlp.core.lib.pluginmanager package. """ from unittest import TestCase -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings @@ -50,6 +50,32 @@ class TestPluginManager(TestCase): Registry().register('main_window', self.mocked_main_window) Registry().register('settings_form', self.mocked_settings_form) + def test_bootstrap_initialise(self): + """ + Test the PluginManager.bootstrap_initialise() method + """ + # GIVEN: A plugin manager with some mocked out methods + manager = PluginManager() + + with patch.object(manager, 'find_plugins') as mocked_find_plugins, \ + patch.object(manager, 'hook_settings_tabs') as mocked_hook_settings_tabs, \ + patch.object(manager, 'hook_media_manager') as mocked_hook_media_manager, \ + patch.object(manager, 'hook_import_menu') as mocked_hook_import_menu, \ + patch.object(manager, 'hook_export_menu') as mocked_hook_export_menu, \ + patch.object(manager, 'hook_tools_menu') as mocked_hook_tools_menu, \ + patch.object(manager, 'initialise_plugins') as mocked_initialise_plugins: + # WHEN: bootstrap_initialise() is called + manager.bootstrap_initialise() + + # THEN: The hook methods should have been called + mocked_find_plugins.assert_called_with() + mocked_hook_settings_tabs.assert_called_with() + mocked_hook_media_manager.assert_called_with() + mocked_hook_import_menu.assert_called_with() + mocked_hook_export_menu.assert_called_with() + mocked_hook_tools_menu.assert_called_with() + mocked_initialise_plugins.assert_called_with() + def test_hook_media_manager_with_disabled_plugin(self): """ Test running the hook_media_manager() method with a disabled plugin diff --git a/tests/functional/openlp_core/lib/test_serviceitem.py b/tests/functional/openlp_core/lib/test_serviceitem.py index 63544458a..85d3b1050 100644 --- a/tests/functional/openlp_core/lib/test_serviceitem.py +++ b/tests/functional/openlp_core/lib/test_serviceitem.py @@ -27,6 +27,7 @@ from unittest import TestCase from unittest.mock import MagicMock, patch from openlp.core.common import md5_hash +from openlp.core.common.path import Path from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import ItemCapabilities, ServiceItem, ServiceItemType, FormattingTags @@ -351,5 +352,5 @@ class TestServiceItem(TestCase, TestMixin): '"Amazing Grace! how sweet the s" has been returned as the title' assert '’Twas grace that taught my hea' == service_item.get_frame_title(1), \ '"’Twas grace that taught my hea" has been returned as the title' - assert '/test/amazing_grace.mp3' == service_item.background_audio[0], \ + assert Path('/test/amazing_grace.mp3') == service_item.background_audio[0], \ '"/test/amazing_grace.mp3" should be in the background_audio list' diff --git a/tests/functional/openlp_core/ui/test_mainwindow.py b/tests/functional/openlp_core/ui/test_mainwindow.py index fb00b3522..888ac435d 100644 --- a/tests/functional/openlp_core/ui/test_mainwindow.py +++ b/tests/functional/openlp_core/ui/test_mainwindow.py @@ -23,6 +23,7 @@ Package to test openlp.core.ui.mainwindow package. """ import os +from pathlib import Path from unittest import TestCase from unittest.mock import MagicMock, patch @@ -84,14 +85,13 @@ class TestMainWindow(TestCase, TestMixin): """ # GIVEN a service as an argument to openlp service = os.path.join(TEST_RESOURCES_PATH, 'service', 'test.osz') - self.main_window.arguments = [service] # WHEN the argument is processed with patch.object(self.main_window.service_manager, 'load_file') as mocked_load_file: self.main_window.open_cmd_line_files(service) # THEN the service from the arguments is loaded - mocked_load_file.assert_called_with(service) + mocked_load_file.assert_called_with(Path(service)) @patch('openlp.core.ui.servicemanager.ServiceManager.load_file') def test_cmd_line_arg(self, mocked_load_file): @@ -242,3 +242,30 @@ class TestMainWindow(TestCase, TestMixin): # THEN: projector_manager_dock.setVisible should had been called once mocked_dock.setVisible.assert_called_once_with(False) + + def test_increment_progress_bar_default_increment(self): + """ + Test that increment_progress_bar increments the progress bar by 1 when called without the `increment` arg. + """ + # GIVEN: A mocked progress bar + with patch.object(self.main_window, 'load_progress_bar', **{'value.return_value': 0}) as mocked_progress_bar: + + # WHEN: Calling increment_progress_bar without the `increment` arg + self.main_window.increment_progress_bar() + + # THEN: The progress bar value should have been incremented by 1 + mocked_progress_bar.setValue.assert_called_once_with(1) + + def test_increment_progress_bar_custom_increment(self): + """ + Test that increment_progress_bar increments the progress bar by the `increment` arg when called with the + `increment` arg with a set value. + """ + # GIVEN: A mocked progress bar + with patch.object(self.main_window, 'load_progress_bar', **{'value.return_value': 0}) as mocked_progress_bar: + + # WHEN: Calling increment_progress_bar with `increment` set to 10 + self.main_window.increment_progress_bar(increment=10) + + # THEN: The progress bar value should have been incremented by 10 + mocked_progress_bar.setValue.assert_called_once_with(10) diff --git a/tests/functional/openlp_core/ui/test_servicemanager.py b/tests/functional/openlp_core/ui/test_servicemanager.py index 191bd236e..53c612698 100644 --- a/tests/functional/openlp_core/ui/test_servicemanager.py +++ b/tests/functional/openlp_core/ui/test_servicemanager.py @@ -623,10 +623,10 @@ class TestServiceManager(TestCase): # THEN: make_preview() should not have been called assert mocked_make_preview.call_count == 0, 'ServiceManager.make_preview() should not be called' - @patch('openlp.core.ui.servicemanager.shutil.copy') @patch('openlp.core.ui.servicemanager.zipfile') @patch('openlp.core.ui.servicemanager.ServiceManager.save_file_as') - def test_save_file_raises_permission_error(self, mocked_save_file_as, mocked_zipfile, mocked_shutil_copy): + @patch('openlp.core.ui.servicemanager.os') + def test_save_file_raises_permission_error(self, mocked_os, mocked_save_file_as, mocked_zipfile): """ Test that when a PermissionError is raised when trying to save a file, it is handled correctly """ @@ -636,50 +636,22 @@ class TestServiceManager(TestCase): Registry().register('main_window', mocked_main_window) Registry().register('application', MagicMock()) service_manager = ServiceManager(None) - service_manager._service_path = os.path.join('temp', 'filename.osz') + service_manager._service_path = MagicMock() service_manager._save_lite = False service_manager.service_items = [] service_manager.service_theme = 'Default' service_manager.service_manager_list = MagicMock() mocked_save_file_as.return_value = True mocked_zipfile.ZipFile.return_value = MagicMock() - mocked_shutil_copy.side_effect = PermissionError + mocked_os.link.side_effect = PermissionError - # WHEN: The service is saved and a PermissionError is thrown + # WHEN: The service is saved and a PermissionError is raised result = service_manager.save_file() # THEN: The "save_as" method is called to save the service assert result is True mocked_save_file_as.assert_called_with() - @patch('openlp.core.ui.servicemanager.shutil.copy') - @patch('openlp.core.ui.servicemanager.zipfile') - @patch('openlp.core.ui.servicemanager.ServiceManager.save_file_as') - def test_save_local_file_raises_permission_error(self, mocked_save_file_as, mocked_zipfile, mocked_shutil_copy): - """ - Test that when a PermissionError is raised when trying to save a local file, it is handled correctly - """ - # GIVEN: A service manager, a service to save - mocked_main_window = MagicMock() - mocked_main_window.service_manager_settings_section = 'servicemanager' - Registry().register('main_window', mocked_main_window) - Registry().register('application', MagicMock()) - service_manager = ServiceManager(None) - service_manager._service_path = os.path.join('temp', 'filename.osz') - service_manager._save_lite = False - service_manager.service_items = [] - service_manager.service_theme = 'Default' - mocked_save_file_as.return_value = True - mocked_zipfile.ZipFile.return_value = MagicMock() - mocked_shutil_copy.side_effect = PermissionError - - # WHEN: The service is saved and a PermissionError is thrown - result = service_manager.save_local_file() - - # THEN: The "save_as" method is called to save the service - assert result is True - mocked_save_file_as.assert_called_with() - @patch('openlp.core.ui.servicemanager.ServiceManager.regenerate_service_items') def test_theme_change_global(self, mocked_regenerate_service_items): """ diff --git a/tests/functional/openlp_core/api/endpoint/test_controller.py b/tests/interfaces/openlp_core/ui/test_init.py similarity index 50% rename from tests/functional/openlp_core/api/endpoint/test_controller.py rename to tests/interfaces/openlp_core/ui/test_init.py index 08b1f5d69..8bfc08b9e 100644 --- a/tests/functional/openlp_core/api/endpoint/test_controller.py +++ b/tests/interfaces/openlp_core/ui/test_init.py @@ -19,36 +19,46 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from unittest import TestCase +""" +Test the openlp.core.ui package. +""" from unittest.mock import MagicMock, patch -from openlp.core.common.registry import Registry -from openlp.core.api.endpoint.controller import controller_text +from openlp.core.ui import SingleColumnTableWidget -class TestController(TestCase): +def test_single_col_widget_create(): """ - Test the Remote plugin deploy functions + Test creating the SingleColumnTableWidget object """ + # GIVEN: the SingleColumnTableWidget class + # WHEN: An object is created + widget = SingleColumnTableWidget(None) - def setUp(self): - """ - Setup for tests - """ - Registry.create() - self.registry = Registry() - self.mocked_live_controller = MagicMock() - Registry().register('live_controller', self.mocked_live_controller) + # THEN: The object should have 1 column and no visible header + assert widget.columnCount() == 1, 'There should be only 1 column' + assert widget.horizontalHeader().isVisible() is False, 'The horizontal header should not be visible' - def test_controller_text(self): - """ - Remote Deploy tests - test the dummy zip file is processed correctly - """ - # GIVEN: A mocked service with a dummy service item - self.mocked_live_controller.service_item = MagicMock() - # WHEN: I trigger the method - ret = controller_text("SomeText") - # THEN: I get a basic set of results - results = ret['results'] - assert isinstance(results['item'], MagicMock) - assert len(results['slides']) == 0 + +@patch('openlp.core.ui.QtWidgets.QTableWidget') +def test_single_col_widget_resize_event(MockQTableWidget): + """ + Test that the resizeEvent method does the right thing + """ + # GIVEN: An instance of a SingleColumnTableWidget and a mocked event + widget = SingleColumnTableWidget(None) + mocked_event = MagicMock() + mocked_event.size.return_value.width.return_value = 10 + + # WHEN: resizeEvent() is called + with patch.object(widget, 'columnCount') as mocked_column_count, \ + patch.object(widget, 'setColumnWidth') as mocked_set_column_width, \ + patch.object(widget, 'resizeRowsToContents') as mocked_resize_rows_to_contents: + mocked_column_count.return_value = 1 + widget.resizeEvent(mocked_event) + + # THEN: The correct calls should have been made + MockQTableWidget.resizeEvent.assert_called_once_with(widget, mocked_event) + mocked_column_count.assert_called_once_with() + mocked_set_column_width.assert_called_once_with(0, 10) + mocked_resize_rows_to_contents.assert_called_once_with()