From 779e95b52342df84330c367814b87cf7999a7640 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 18:33:03 +0100 Subject: [PATCH 01/96] Fix for samuel_m issue and bug #1672229 --- openlp/core/common/applocation.py | 2 +- openlp/core/lib/mediamanageritem.py | 4 +++- openlp/core/ui/lib/listwidgetwithdnd.py | 2 +- openlp/plugins/images/lib/mediaitem.py | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/openlp/core/common/applocation.py b/openlp/core/common/applocation.py index 02a872303..a0c927666 100644 --- a/openlp/core/common/applocation.py +++ b/openlp/core/common/applocation.py @@ -85,7 +85,7 @@ class AppLocation(object): """ # Check if we have a different data location. if Settings().contains('advanced/data path'): - path = Settings().value('advanced/data path') + path = Path(Settings().value('advanced/data path')) else: path = AppLocation.get_directory(AppLocation.DataDir) check_directory_exists(path) diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 1c7a5b4ef..ce106854a 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -341,7 +341,9 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): else: new_files.append(file_name) if new_files: - self.validate_and_load(new_files, data['target']) + if 'target' in data: + self.validate_and_load(new_files, data['target']) + self.validate_and_load(new_files) def dnd_move_internal(self, target): """ diff --git a/openlp/core/ui/lib/listwidgetwithdnd.py b/openlp/core/ui/lib/listwidgetwithdnd.py index 1758d46fb..e8f0b729f 100755 --- a/openlp/core/ui/lib/listwidgetwithdnd.py +++ b/openlp/core/ui/lib/listwidgetwithdnd.py @@ -121,7 +121,7 @@ class ListWidgetWithDnD(QtWidgets.QListWidget): for file in listing: files.append(os.path.join(local_file, file)) Registry().execute('{mime_data}_dnd'.format(mime_data=self.mime_data_text), - {'files': files, 'target': self.itemAt(event.pos())}) + {'files': files}) else: event.ignore() diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index b436d2708..5eac4b3b5 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -364,7 +364,7 @@ class ImageMediaItem(MediaManagerItem): if validate_thumb(image.file_path, thumbnail_path): icon = build_icon(thumbnail_path) else: - icon = create_thumb(image.file_path, thumbnail_path) + icon = create_thumb(str(image.file_path), str(thumbnail_path)) item_name = QtWidgets.QTreeWidgetItem([file_name]) item_name.setText(0, file_name) item_name.setIcon(0, icon) @@ -388,6 +388,7 @@ class ImageMediaItem(MediaManagerItem): :param files: A List of strings containing the filenames of the files to be loaded :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files """ + file_paths = [Path(file) for file in file_paths] self.application.set_normal_cursor() self.load_list(file_paths, target_group) last_dir = file_paths[0].parent From 98d5f0ec670940fc7b010f76220a8560a7c25642 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 19:38:56 +0100 Subject: [PATCH 02/96] fix 1660473, shamlessly ported from 2.4.5 --- openlp/core/ui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 25fe818ee..4a1200084 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -175,7 +175,7 @@ class Ui_MainWindow(object): triggers=self.service_manager_contents.on_load_service_clicked) self.file_save_item = create_action(main_window, 'fileSaveItem', icon=':/general/general_save.png', can_shortcuts=True, category=UiStrings().File, - triggers=self.service_manager_contents.save_file) + triggers=self.service_manager_contents.decide_save_method) self.file_save_as_item = create_action(main_window, 'fileSaveAsItem', can_shortcuts=True, category=UiStrings().File, triggers=self.service_manager_contents.save_file_as) From 71ad60d038f27747d691edafca9e956f7836b281 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 20:01:09 +0100 Subject: [PATCH 03/96] fixes #1660478 - Opening recent file does not prompt to save changes --- openlp/core/ui/servicemanager.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 153034e33..2bee2cae0 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -469,6 +469,12 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa Load a recent file as the service triggered by mainwindow recent service list. :param field: """ + if self.is_modified(): + result = self.save_modified_service() + if result == QtWidgets.QMessageBox.Cancel: + return False + elif result == QtWidgets.QMessageBox.Save: + self.decide_save_method() sender = self.sender() self.load_file(sender.data()) From 37da12affd0953d9824caf5e9bd43b4695af6256 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 20:29:54 +0100 Subject: [PATCH 04/96] fixes #1715125 - Missing .osz file extension on save service --- openlp/core/ui/servicemanager.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 2bee2cae0..42c8dea6d 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -713,18 +713,23 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa default_file_path = directory_path / default_file_path # SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in # the long term. + lite_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files - lite (*.oszl)') + packaged_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)') + if self._file_name.endswith('oszl') or self.service_has_all_original_files: file_path, filter_used = FileDialog.getSaveFileName( self.main_window, UiStrings().SaveService, default_file_path, - translate('OpenLP.ServiceManager', - 'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)')) + '{packaged};; {lite}'.format(packaged=packaged_filter, lite=lite_filter)) else: file_path, filter_used = FileDialog.getSaveFileName( - self.main_window, UiStrings().SaveService, file_path, - translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz);;')) + self.main_window, UiStrings().SaveService, default_file_path, + '{packaged};;'.format(packaged=packaged_filter)) if not file_path: return False - file_path.with_suffix('.osz') + if filter_used == lite_filter: + file_path = file_path.with_suffix('.oszl') + else: + file_path = file_path.with_suffix('.osz') self.set_file_name(file_path) self.decide_save_method() From 2be17b5c6fda2490fa5e44cd235a65ded29a3b45 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 20:41:07 +0100 Subject: [PATCH 05/96] fixes #1532193 - Typos in songusageplugin.py annother fix shamelessly ported from 2.4 --- openlp/core/common/settings.py | 6 ++++++ openlp/plugins/songusage/songusageplugin.py | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index ca59d4e6f..48d8710a0 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -270,6 +270,12 @@ class Settings(QtCore.QSettings): ('media/last directory', 'media/last directory', [(str_to_path, None)]) ] + __setting_upgrade_3__ = [ + ('songuasge/db password', 'songusage/db password', []) + ('songuasge/db hostname', 'songusage/db hostname', []) + ('songuasge/db database', 'songusage/db database', []) + ] + @staticmethod def extend_default_settings(default_values): """ diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py index d0c2f7fe7..8d5e08322 100644 --- a/openlp/plugins/songusage/songusageplugin.py +++ b/openlp/plugins/songusage/songusageplugin.py @@ -44,9 +44,9 @@ if QtCore.QDate().currentDate().month() < 9: __default_settings__ = { 'songusage/db type': 'sqlite', 'songusage/db username': '', - 'songuasge/db password': '', - 'songuasge/db hostname': '', - 'songuasge/db database': '', + 'songusage/db password': '', + 'songusage/db hostname': '', + 'songusage/db database': '', 'songusage/active': False, 'songusage/to date': QtCore.QDate(YEAR, 8, 31), 'songusage/from date': QtCore.QDate(YEAR - 1, 9, 1), From cecd2298a74ec36613eb7d719ea92d10f5f5dfd8 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 8 Oct 2017 21:36:11 +0100 Subject: [PATCH 06/96] fixes #1660486 - Dragging item in service manager without changes triggeres 'unsaved' --- openlp/core/common/settings.py | 4 ++-- openlp/core/ui/servicemanager.py | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 48d8710a0..6f681b7d5 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -271,8 +271,8 @@ class Settings(QtCore.QSettings): ] __setting_upgrade_3__ = [ - ('songuasge/db password', 'songusage/db password', []) - ('songuasge/db hostname', 'songusage/db hostname', []) + ('songuasge/db password', 'songusage/db password', []), + ('songuasge/db hostname', 'songusage/db hostname', []), ('songuasge/db database', 'songusage/db database', []) ] diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 42c8dea6d..42a45cafd 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -1663,14 +1663,15 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa if start_pos == -1: return if item is None: - end_pos = len(self.service_items) + end_pos = len(self.service_items) - 1 else: end_pos = get_parent_item_data(item) - 1 service_item = self.service_items[start_pos] - self.service_items.remove(service_item) - self.service_items.insert(end_pos, service_item) - self.repaint_service_list(end_pos, child) - self.set_modified() + if start_pos != end_pos: + self.service_items.remove(service_item) + self.service_items.insert(end_pos, service_item) + self.repaint_service_list(end_pos, child) + self.set_modified() else: # we are not over anything so drop replace = False From 66049f0c96985391f73822182109b5c75e127dbf Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Tue, 10 Oct 2017 20:09:20 +0100 Subject: [PATCH 07/96] bible pathlib changes --- openlp/core/common/applocation.py | 2 +- openlp/core/ui/lib/pathedit.py | 2 +- .../plugins/bibles/forms/bibleimportform.py | 286 ++++++------------ openlp/plugins/bibles/lib/bibleimport.py | 28 +- openlp/plugins/bibles/lib/biblestab.py | 4 +- openlp/plugins/bibles/lib/db.py | 34 ++- .../plugins/bibles/lib/importers/csvbible.py | 23 +- .../plugins/bibles/lib/importers/opensong.py | 11 +- openlp/plugins/bibles/lib/importers/osis.py | 8 +- openlp/plugins/bibles/lib/importers/sword.py | 2 +- .../bibles/lib/importers/wordproject.py | 27 +- .../plugins/bibles/lib/importers/zefania.py | 8 +- openlp/plugins/bibles/lib/manager.py | 19 +- .../openlp_plugins/bibles/test_bibleimport.py | 43 +-- .../openlp_plugins/bibles/test_csvimport.py | 49 +-- .../bibles/test_opensongimport.py | 35 +-- .../openlp_plugins/bibles/test_osisimport.py | 51 ++-- .../openlp_plugins/bibles/test_swordimport.py | 4 +- .../bibles/test_wordprojectimport.py | 55 ++-- .../bibles/test_zefaniaimport.py | 11 +- 20 files changed, 307 insertions(+), 395 deletions(-) diff --git a/openlp/core/common/applocation.py b/openlp/core/common/applocation.py index 02a872303..a0c927666 100644 --- a/openlp/core/common/applocation.py +++ b/openlp/core/common/applocation.py @@ -85,7 +85,7 @@ class AppLocation(object): """ # Check if we have a different data location. if Settings().contains('advanced/data path'): - path = Settings().value('advanced/data path') + path = Path(Settings().value('advanced/data path')) else: path = AppLocation.get_directory(AppLocation.DataDir) check_directory_exists(path) diff --git a/openlp/core/ui/lib/pathedit.py b/openlp/core/ui/lib/pathedit.py index d5b9272fa..2089772dd 100644 --- a/openlp/core/ui/lib/pathedit.py +++ b/openlp/core/ui/lib/pathedit.py @@ -84,7 +84,7 @@ class PathEdit(QtWidgets.QWidget): self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished) self.update_button_tool_tips() - @property + @QtCore.pyqtProperty('QVariant') def path(self): """ A property getter method to return the selected path. diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index fd270fced..346492c40 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -23,7 +23,6 @@ The bible import functions for OpenLP """ import logging -import os import urllib.error from lxml import etree @@ -39,6 +38,7 @@ from openlp.core.common.languagemanager import get_locale_key from openlp.core.lib.db import delete_database from openlp.core.lib.exceptions import ValidationError from openlp.core.lib.ui import critical_error_message_box +from openlp.core.ui.lib.pathedit import PathEdit from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings from openlp.plugins.bibles.lib.db import clean_filename from openlp.plugins.bibles.lib.importers.http import CWExtract, BGExtract, BSExtract @@ -120,15 +120,9 @@ class BibleImportForm(OpenLPWizard): Set up the signals used in the bible importer. """ self.web_source_combo_box.currentIndexChanged.connect(self.on_web_source_combo_box_index_changed) - self.osis_browse_button.clicked.connect(self.on_osis_browse_button_clicked) - self.csv_books_button.clicked.connect(self.on_csv_books_browse_button_clicked) - self.csv_verses_button.clicked.connect(self.on_csv_verses_browse_button_clicked) - self.open_song_browse_button.clicked.connect(self.on_open_song_browse_button_clicked) - self.zefania_browse_button.clicked.connect(self.on_zefania_browse_button_clicked) - self.wordproject_browse_button.clicked.connect(self.on_wordproject_browse_button_clicked) self.web_update_button.clicked.connect(self.on_web_update_button_clicked) - self.sword_browse_button.clicked.connect(self.on_sword_browse_button_clicked) - self.sword_zipbrowse_button.clicked.connect(self.on_sword_zipbrowse_button_clicked) + self.sword_folder_path_edit.pathChanged.connect(self.on_sword_folder_path_edit_path_changed) + self.sword_zipfile_path_edit.pathChanged.connect(self.on_sword_zipfile_path_edit_path_changed) def add_custom_pages(self): """ @@ -159,17 +153,12 @@ class BibleImportForm(OpenLPWizard): self.osis_layout.setObjectName('OsisLayout') self.osis_file_label = QtWidgets.QLabel(self.osis_widget) self.osis_file_label.setObjectName('OsisFileLabel') - self.osis_file_layout = QtWidgets.QHBoxLayout() - self.osis_file_layout.setObjectName('OsisFileLayout') - self.osis_file_edit = QtWidgets.QLineEdit(self.osis_widget) - self.osis_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.osis_file_edit.setObjectName('OsisFileEdit') - self.osis_file_layout.addWidget(self.osis_file_edit) - self.osis_browse_button = QtWidgets.QToolButton(self.osis_widget) - self.osis_browse_button.setIcon(self.open_icon) - self.osis_browse_button.setObjectName('OsisBrowseButton') - self.osis_file_layout.addWidget(self.osis_browse_button) - self.osis_layout.addRow(self.osis_file_label, self.osis_file_layout) + self.osis_path_edit = PathEdit( + self.osis_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.OSIS), + show_revert=False) + self.osis_layout.addRow(self.osis_file_label, self.osis_path_edit) self.osis_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer) self.select_stack.addWidget(self.osis_widget) self.csv_widget = QtWidgets.QWidget(self.select_page) @@ -179,30 +168,27 @@ class BibleImportForm(OpenLPWizard): self.csv_layout.setObjectName('CsvLayout') self.csv_books_label = QtWidgets.QLabel(self.csv_widget) self.csv_books_label.setObjectName('CsvBooksLabel') - self.csv_books_layout = QtWidgets.QHBoxLayout() - self.csv_books_layout.setObjectName('CsvBooksLayout') - self.csv_books_edit = QtWidgets.QLineEdit(self.csv_widget) - self.csv_books_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.csv_books_edit.setObjectName('CsvBooksEdit') - self.csv_books_layout.addWidget(self.csv_books_edit) - self.csv_books_button = QtWidgets.QToolButton(self.csv_widget) - self.csv_books_button.setIcon(self.open_icon) - self.csv_books_button.setObjectName('CsvBooksButton') - self.csv_books_layout.addWidget(self.csv_books_button) - self.csv_layout.addRow(self.csv_books_label, self.csv_books_layout) + self.csv_books_path_edit = PathEdit( + self.csv_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.CSV), + show_revert=False, + ) + self.csv_books_path_edit.filters = \ + '{name} (*.csv)'.format(name=translate('BiblesPlugin.ImportWizardForm', 'CSV File')) + self.csv_layout.addRow(self.csv_books_label, self.csv_books_path_edit) self.csv_verses_label = QtWidgets.QLabel(self.csv_widget) self.csv_verses_label.setObjectName('CsvVersesLabel') - self.csv_verses_layout = QtWidgets.QHBoxLayout() - self.csv_verses_layout.setObjectName('CsvVersesLayout') - self.csv_verses_edit = QtWidgets.QLineEdit(self.csv_widget) - self.csv_verses_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.csv_verses_edit.setObjectName('CsvVersesEdit') - self.csv_verses_layout.addWidget(self.csv_verses_edit) - self.csv_verses_button = QtWidgets.QToolButton(self.csv_widget) - self.csv_verses_button.setIcon(self.open_icon) - self.csv_verses_button.setObjectName('CsvVersesButton') - self.csv_verses_layout.addWidget(self.csv_verses_button) - self.csv_layout.addRow(self.csv_verses_label, self.csv_verses_layout) + self.csv_verses_path_edit = PathEdit( + self.csv_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.CSV), + show_revert=False, + ) + self.csv_verses_path_edit.filters = \ + '{name} (*.csv)'.format(name=translate('BiblesPlugin.ImportWizardForm', 'CSV File')) + self.csv_layout.addRow(self.csv_books_label, self.csv_books_path_edit) + self.csv_layout.addRow(self.csv_verses_label, self.csv_verses_path_edit) self.csv_layout.setItem(3, QtWidgets.QFormLayout.LabelRole, self.spacer) self.select_stack.addWidget(self.csv_widget) self.open_song_widget = QtWidgets.QWidget(self.select_page) @@ -212,17 +198,13 @@ class BibleImportForm(OpenLPWizard): self.open_song_layout.setObjectName('OpenSongLayout') self.open_song_file_label = QtWidgets.QLabel(self.open_song_widget) self.open_song_file_label.setObjectName('OpenSongFileLabel') - self.open_song_file_layout = QtWidgets.QHBoxLayout() - self.open_song_file_layout.setObjectName('OpenSongFileLayout') - self.open_song_file_edit = QtWidgets.QLineEdit(self.open_song_widget) - self.open_song_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.open_song_file_edit.setObjectName('OpenSongFileEdit') - self.open_song_file_layout.addWidget(self.open_song_file_edit) - self.open_song_browse_button = QtWidgets.QToolButton(self.open_song_widget) - self.open_song_browse_button.setIcon(self.open_icon) - self.open_song_browse_button.setObjectName('OpenSongBrowseButton') - self.open_song_file_layout.addWidget(self.open_song_browse_button) - self.open_song_layout.addRow(self.open_song_file_label, self.open_song_file_layout) + self.open_song_path_edit = PathEdit( + self.open_song_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.OS), + show_revert=False, + ) + self.open_song_layout.addRow(self.open_song_file_label, self.open_song_path_edit) self.open_song_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer) self.select_stack.addWidget(self.open_song_widget) self.web_tab_widget = QtWidgets.QTabWidget(self.select_page) @@ -290,17 +272,14 @@ class BibleImportForm(OpenLPWizard): self.zefania_layout.setObjectName('ZefaniaLayout') self.zefania_file_label = QtWidgets.QLabel(self.zefania_widget) self.zefania_file_label.setObjectName('ZefaniaFileLabel') - self.zefania_file_layout = QtWidgets.QHBoxLayout() - self.zefania_file_layout.setObjectName('ZefaniaFileLayout') - self.zefania_file_edit = QtWidgets.QLineEdit(self.zefania_widget) - self.zefania_file_edit.setObjectName('ZefaniaFileEdit') - self.zefania_file_layout.addWidget(self.zefania_file_edit) - self.zefania_browse_button = QtWidgets.QToolButton(self.zefania_widget) - self.zefania_browse_button.setIcon(self.open_icon) - self.zefania_browse_button.setObjectName('ZefaniaBrowseButton') - self.zefania_file_layout.addWidget(self.zefania_browse_button) - self.zefania_layout.addRow(self.zefania_file_label, self.zefania_file_layout) - self.zefania_layout.setItem(5, QtWidgets.QFormLayout.LabelRole, self.spacer) + self.zefania_path_edit = PathEdit( + self.zefania_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.ZEF), + show_revert=False, + ) + self.zefania_layout.addRow(self.zefania_file_label, self.zefania_path_edit) + self.zefania_layout.setItem(2, QtWidgets.QFormLayout.LabelRole, self.spacer) self.select_stack.addWidget(self.zefania_widget) self.sword_widget = QtWidgets.QWidget(self.select_page) self.sword_widget.setObjectName('SwordWidget') @@ -316,16 +295,13 @@ class BibleImportForm(OpenLPWizard): self.sword_folder_label = QtWidgets.QLabel(self.sword_folder_tab) self.sword_folder_label.setObjectName('SwordSourceLabel') self.sword_folder_label.setObjectName('SwordFolderLabel') - self.sword_folder_edit = QtWidgets.QLineEdit(self.sword_folder_tab) - self.sword_folder_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.sword_folder_edit.setObjectName('SwordFolderEdit') - self.sword_browse_button = QtWidgets.QToolButton(self.sword_folder_tab) - self.sword_browse_button.setIcon(self.open_icon) - self.sword_browse_button.setObjectName('SwordBrowseButton') - self.sword_folder_layout = QtWidgets.QHBoxLayout() - self.sword_folder_layout.addWidget(self.sword_folder_edit) - self.sword_folder_layout.addWidget(self.sword_browse_button) - self.sword_folder_tab_layout.addRow(self.sword_folder_label, self.sword_folder_layout) + self.sword_folder_path_edit = PathEdit( + self.sword_folder_tab, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.SWORD), + show_revert=False, + ) + self.sword_folder_tab_layout.addRow(self.sword_folder_label, self.sword_folder_path_edit) self.sword_bible_label = QtWidgets.QLabel(self.sword_folder_tab) self.sword_bible_label.setObjectName('SwordBibleLabel') self.sword_bible_combo_box = QtWidgets.QComboBox(self.sword_folder_tab) @@ -340,16 +316,13 @@ class BibleImportForm(OpenLPWizard): self.sword_zip_layout.setObjectName('SwordZipLayout') self.sword_zipfile_label = QtWidgets.QLabel(self.sword_zip_tab) self.sword_zipfile_label.setObjectName('SwordZipFileLabel') - self.sword_zipfile_edit = QtWidgets.QLineEdit(self.sword_zip_tab) - self.sword_zipfile_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.sword_zipfile_edit.setObjectName('SwordZipFileEdit') - self.sword_zipbrowse_button = QtWidgets.QToolButton(self.sword_zip_tab) - self.sword_zipbrowse_button.setIcon(self.open_icon) - self.sword_zipbrowse_button.setObjectName('SwordZipBrowseButton') - self.sword_zipfile_layout = QtWidgets.QHBoxLayout() - self.sword_zipfile_layout.addWidget(self.sword_zipfile_edit) - self.sword_zipfile_layout.addWidget(self.sword_zipbrowse_button) - self.sword_zip_layout.addRow(self.sword_zipfile_label, self.sword_zipfile_layout) + self.sword_zipfile_path_edit = PathEdit( + self.sword_zip_tab, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.SWORD), + show_revert=False, + ) + self.sword_zip_layout.addRow(self.sword_zipfile_label, self.sword_zipfile_path_edit) self.sword_zipbible_label = QtWidgets.QLabel(self.sword_folder_tab) self.sword_zipbible_label.setObjectName('SwordZipBibleLabel') self.sword_zipbible_combo_box = QtWidgets.QComboBox(self.sword_zip_tab) @@ -370,18 +343,13 @@ class BibleImportForm(OpenLPWizard): self.wordproject_layout.setObjectName('WordProjectLayout') self.wordproject_file_label = QtWidgets.QLabel(self.wordproject_widget) self.wordproject_file_label.setObjectName('WordProjectFileLabel') - self.wordproject_file_layout = QtWidgets.QHBoxLayout() - self.wordproject_file_layout.setObjectName('WordProjectFileLayout') - self.wordproject_file_edit = QtWidgets.QLineEdit(self.wordproject_widget) - self.wordproject_file_edit.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - self.wordproject_file_edit.setObjectName('WordProjectFileEdit') - self.wordproject_file_layout.addWidget(self.wordproject_file_edit) - self.wordproject_browse_button = QtWidgets.QToolButton(self.wordproject_widget) - self.wordproject_browse_button.setIcon(self.open_icon) - self.wordproject_browse_button.setObjectName('WordProjectBrowseButton') - self.wordproject_file_layout.addWidget(self.wordproject_browse_button) - self.wordproject_layout.addRow(self.wordproject_file_label, self.wordproject_file_layout) - self.wordproject_layout.setItem(5, QtWidgets.QFormLayout.LabelRole, self.spacer) + self.wordproject_path_edit = PathEdit( + self.wordproject_widget, + default_path=Settings().value('bibles/last directory import'), + dialog_caption=WizardStrings.OpenTypeFile.format(file_type=WizardStrings.WordProject), + show_revert=False) + self.wordproject_layout.addRow(self.wordproject_file_label, self.wordproject_path_edit) + self.wordproject_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer) self.select_stack.addWidget(self.wordproject_widget) self.select_page_layout.addLayout(self.select_stack) self.addPage(self.select_page) @@ -466,8 +434,6 @@ class BibleImportForm(OpenLPWizard): self.sword_bible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:')) self.sword_folder_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD data folder:')) self.sword_zipfile_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD zip-file:')) - self.sword_folder_edit.setPlaceholderText(translate('BiblesPlugin.ImportWizardForm', - 'Defaults to the standard SWORD data folder')) self.sword_zipbible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:')) self.sword_tab_widget.setTabText(self.sword_tab_widget.indexOf(self.sword_folder_tab), translate('BiblesPlugin.ImportWizardForm', 'Import from folder')) @@ -516,7 +482,7 @@ class BibleImportForm(OpenLPWizard): if self.field('source_format') == BibleFormat.OSIS: if not self.field('osis_location'): critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.OSIS) - self.osis_file_edit.setFocus() + self.osis_path_edit.setFocus() return False elif self.field('source_format') == BibleFormat.CSV: if not self.field('csv_booksfile'): @@ -536,18 +502,18 @@ class BibleImportForm(OpenLPWizard): elif self.field('source_format') == BibleFormat.OpenSong: if not self.field('opensong_file'): critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.OS) - self.open_song_file_edit.setFocus() + self.open_song_path_edit.setFocus() return False elif self.field('source_format') == BibleFormat.Zefania: if not self.field('zefania_file'): critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.ZEF) - self.zefania_file_edit.setFocus() + self.zefania_path_edit.setFocus() return False elif self.field('source_format') == BibleFormat.WordProject: if not self.field('wordproject_file'): critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.WordProject) - self.wordproject_file_edit.setFocus() + self.wordproject_path_edit.setFocus() return False elif self.field('source_format') == BibleFormat.WebDownload: # If count is 0 the bible list has not yet been downloaded @@ -561,7 +527,7 @@ class BibleImportForm(OpenLPWizard): if not self.field('sword_folder_path') and self.sword_bible_combo_box.count() == 0: critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFolder % WizardStrings.SWORD) - self.sword_folder_edit.setFocus() + self.sword_folder_path_edit.setFocus() return False key = self.sword_bible_combo_box.itemData(self.sword_bible_combo_box.currentIndex()) if 'description' in self.pysword_folder_modules_json[key]: @@ -573,7 +539,7 @@ class BibleImportForm(OpenLPWizard): elif self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_zip_tab): if not self.field('sword_zip_path'): critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.SWORD) - self.sword_zipfile_edit.setFocus() + self.sword_zipfile_path_edit.setFocus() return False key = self.sword_zipbible_combo_box.itemData(self.sword_zipbible_combo_box.currentIndex()) if 'description' in self.pysword_zip_modules_json[key]: @@ -584,7 +550,6 @@ class BibleImportForm(OpenLPWizard): elif self.currentPage() == self.license_details_page: license_version = self.field('license_version') license_copyright = self.field('license_copyright') - path = str(AppLocation.get_section_data_path('bibles')) if not license_version: critical_error_message_box( UiStrings().EmptyField, @@ -606,7 +571,7 @@ class BibleImportForm(OpenLPWizard): 'existing one.')) self.version_name_edit.setFocus() return False - elif os.path.exists(os.path.join(path, clean_filename(license_version))): + elif (AppLocation.get_section_data_path('bibles') / clean_filename(license_version)).exists(): critical_error_message_box( translate('BiblesPlugin.ImportWizardForm', 'Bible Exists'), translate('BiblesPlugin.ImportWizardForm', 'This Bible already exists. Please import ' @@ -629,57 +594,6 @@ class BibleImportForm(OpenLPWizard): bibles.sort(key=get_locale_key) self.web_translation_combo_box.addItems(bibles) - def on_osis_browse_button_clicked(self): - """ - Show the file open dialog for the OSIS file. - """ - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.OSIS, self.osis_file_edit, - 'last directory import') - - def on_csv_books_browse_button_clicked(self): - """ - Show the file open dialog for the books CSV file. - """ - # TODO: Verify format() with varible template - self.get_file_name( - WizardStrings.OpenTypeFile % WizardStrings.CSV, - self.csv_books_edit, - 'last directory import', - '{name} (*.csv)'.format(name=translate('BiblesPlugin.ImportWizardForm', 'CSV File'))) - - def on_csv_verses_browse_button_clicked(self): - """ - Show the file open dialog for the verses CSV file. - """ - # TODO: Verify format() with variable template - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.CSV, self.csv_verses_edit, - 'last directory import', - '{name} (*.csv)'.format(name=translate('BiblesPlugin.ImportWizardForm', 'CSV File'))) - - def on_open_song_browse_button_clicked(self): - """ - Show the file open dialog for the OpenSong file. - """ - # TODO: Verify format() with variable template - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.OS, self.open_song_file_edit, - 'last directory import') - - def on_zefania_browse_button_clicked(self): - """ - Show the file open dialog for the Zefania file. - """ - # TODO: Verify format() with variable template - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.ZEF, self.zefania_file_edit, - 'last directory import') - - def on_wordproject_browse_button_clicked(self): - """ - Show the file open dialog for the WordProject file. - """ - # TODO: Verify format() with variable template - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.WordProject, self.wordproject_file_edit, - 'last directory import') - def on_web_update_button_clicked(self): """ Download list of bibles from Crosswalk, BibleServer and BibleGateway. @@ -716,15 +630,13 @@ class BibleImportForm(OpenLPWizard): self.web_update_button.setEnabled(True) self.web_progress_bar.setVisible(False) - def on_sword_browse_button_clicked(self): + def on_sword_folder_path_edit_path_changed(self, new_path): """ Show the file open dialog for the SWORD folder. """ - self.get_folder(WizardStrings.OpenTypeFolder % WizardStrings.SWORD, self.sword_folder_edit, - 'last directory import') - if self.sword_folder_edit.text(): + if new_path: try: - self.pysword_folder_modules = modules.SwordModules(self.sword_folder_edit.text()) + self.pysword_folder_modules = modules.SwordModules(str(new_path)) self.pysword_folder_modules_json = self.pysword_folder_modules.parse_modules() bible_keys = self.pysword_folder_modules_json.keys() self.sword_bible_combo_box.clear() @@ -733,15 +645,13 @@ class BibleImportForm(OpenLPWizard): except: self.sword_bible_combo_box.clear() - def on_sword_zipbrowse_button_clicked(self): + def on_sword_zipfile_path_edit_path_changed(self, new_path): """ Show the file open dialog for a SWORD zip-file. """ - self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.SWORD, self.sword_zipfile_edit, - 'last directory import') - if self.sword_zipfile_edit.text(): + if new_path: try: - self.pysword_zip_modules = modules.SwordModules(self.sword_zipfile_edit.text()) + self.pysword_zip_modules = modules.SwordModules(str(new_path)) self.pysword_zip_modules_json = self.pysword_zip_modules.parse_modules() bible_keys = self.pysword_zip_modules_json.keys() self.sword_zipbible_combo_box.clear() @@ -755,16 +665,16 @@ class BibleImportForm(OpenLPWizard): Register the bible import wizard fields. """ self.select_page.registerField('source_format', self.format_combo_box) - self.select_page.registerField('osis_location', self.osis_file_edit) - self.select_page.registerField('csv_booksfile', self.csv_books_edit) - self.select_page.registerField('csv_versefile', self.csv_verses_edit) - self.select_page.registerField('opensong_file', self.open_song_file_edit) - self.select_page.registerField('zefania_file', self.zefania_file_edit) - self.select_page.registerField('wordproject_file', self.wordproject_file_edit) + self.select_page.registerField('osis_location', self.osis_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('csv_booksfile', self.csv_books_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('csv_versefile', self.csv_verses_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('opensong_file', self.open_song_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('zefania_file', self.zefania_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('wordproject_file', self.wordproject_path_edit, 'path', PathEdit.pathChanged) self.select_page.registerField('web_location', self.web_source_combo_box) self.select_page.registerField('web_biblename', self.web_translation_combo_box) - self.select_page.registerField('sword_folder_path', self.sword_folder_edit) - self.select_page.registerField('sword_zip_path', self.sword_zipfile_edit) + self.select_page.registerField('sword_folder_path', self.sword_folder_path_edit, 'path', PathEdit.pathChanged) + self.select_page.registerField('sword_zip_path', self.sword_zipfile_path_edit, 'path', PathEdit.pathChanged) self.select_page.registerField('proxy_server', self.web_server_edit) self.select_page.registerField('proxy_username', self.web_user_edit) self.select_page.registerField('proxy_password', self.web_password_edit) @@ -783,13 +693,13 @@ class BibleImportForm(OpenLPWizard): self.finish_button.setVisible(False) self.cancel_button.setVisible(True) self.setField('source_format', 0) - self.setField('osis_location', '') - self.setField('csv_booksfile', '') - self.setField('csv_versefile', '') - self.setField('opensong_file', '') - self.setField('zefania_file', '') - self.setField('sword_folder_path', '') - self.setField('sword_zip_path', '') + self.setField('osis_location', None) + self.setField('csv_booksfile', None) + self.setField('csv_versefile', None) + self.setField('opensong_file', None) + self.setField('zefania_file', None) + self.setField('sword_folder_path', None) + self.setField('sword_zip_path', None) self.setField('web_location', WebDownload.Crosswalk) self.setField('web_biblename', self.web_translation_combo_box.currentIndex()) self.setField('proxy_server', settings.value('proxy address')) @@ -831,16 +741,16 @@ class BibleImportForm(OpenLPWizard): if bible_type == BibleFormat.OSIS: # Import an OSIS bible. importer = self.manager.import_bible(BibleFormat.OSIS, name=license_version, - filename=self.field('osis_location')) + file_path=self.field('osis_location')) elif bible_type == BibleFormat.CSV: # Import a CSV bible. importer = self.manager.import_bible(BibleFormat.CSV, name=license_version, - booksfile=self.field('csv_booksfile'), - versefile=self.field('csv_versefile')) + books_path=self.field('csv_booksfile'), + verse_path=self.field('csv_versefile')) elif bible_type == BibleFormat.OpenSong: # Import an OpenSong bible. importer = self.manager.import_bible(BibleFormat.OpenSong, name=license_version, - filename=self.field('opensong_file')) + file_path=self.field('opensong_file')) elif bible_type == BibleFormat.WebDownload: # Import a bible from the web. self.progress_bar.setMaximum(1) @@ -859,11 +769,11 @@ class BibleImportForm(OpenLPWizard): elif bible_type == BibleFormat.Zefania: # Import a Zefania bible. importer = self.manager.import_bible(BibleFormat.Zefania, name=license_version, - filename=self.field('zefania_file')) + file_path=self.field('zefania_file')) elif bible_type == BibleFormat.WordProject: # Import a WordProject bible. importer = self.manager.import_bible(BibleFormat.WordProject, name=license_version, - filename=self.field('wordproject_file')) + file_path=self.field('wordproject_file')) elif bible_type == BibleFormat.SWORD: # Import a SWORD bible. if self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_folder_tab): diff --git a/openlp/plugins/bibles/lib/bibleimport.py b/openlp/plugins/bibles/lib/bibleimport.py index 00c7262a0..9fd6d937e 100644 --- a/openlp/plugins/bibles/lib/bibleimport.py +++ b/openlp/plugins/bibles/lib/bibleimport.py @@ -35,23 +35,23 @@ class BibleImport(OpenLPMixin, RegistryProperties, BibleDB): """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.filename = kwargs['filename'] if 'filename' in kwargs else None + self.file_path = kwargs.get('file_path') self.wizard = None self.stop_import_flag = False Registry().register_function('openlp_stop_wizard', self.stop_import) @staticmethod - def is_compressed(file): + def is_compressed(file_path): """ Check if the supplied file is compressed - :param file: A path to the file to check + :param file_path: A path to the file to check """ - if is_zipfile(file): + if is_zipfile(str(file_path)): critical_error_message_box( message=translate('BiblesPlugin.BibleImport', 'The file "{file}" you supplied is compressed. You must decompress it before import.' - ).format(file=file)) + ).format(file=file_path)) return True return False @@ -139,24 +139,24 @@ class BibleImport(OpenLPMixin, RegistryProperties, BibleDB): self.log_debug('No book name supplied. Falling back to guess_id') book_ref_id = guess_id if not book_ref_id: - raise ValidationError(msg='Could not resolve book_ref_id in "{}"'.format(self.filename)) + raise ValidationError(msg='Could not resolve book_ref_id in "{}"'.format(self.file_path)) book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) if book_details is None: raise ValidationError(msg='book_ref_id: {book_ref} Could not be found in the BibleResourcesDB while ' - 'importing {file}'.format(book_ref=book_ref_id, file=self.filename)) + 'importing {file}'.format(book_ref=book_ref_id, file=self.file_path)) return self.create_book(name, book_ref_id, book_details['testament_id']) - def parse_xml(self, filename, use_objectify=False, elements=None, tags=None): + def parse_xml(self, file_path, use_objectify=False, elements=None, tags=None): """ Parse and clean the supplied file by removing any elements or tags we don't use. - :param filename: The filename of the xml file to parse. Str + :param file_path: The filename of the xml file to parse. Str :param use_objectify: Use the objectify parser rather than the etree parser. (Bool) :param elements: A tuple of element names (Str) to remove along with their content. :param tags: A tuple of element names (Str) to remove, preserving their content. :return: The root element of the xml document """ try: - with open(filename, 'rb') as import_file: + with file_path.open('rb') as import_file: # NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own # encoding detection, and the two mechanisms together interfere with each other. if not use_objectify: @@ -203,17 +203,17 @@ class BibleImport(OpenLPMixin, RegistryProperties, BibleDB): self.log_debug('Stopping import') self.stop_import_flag = True - def validate_xml_file(self, filename, tag): + def validate_xml_file(self, file_path, tag): """ Validate the supplied file - :param filename: The supplied file + :param file_path: The supplied file :param tag: The expected root tag type :return: True if valid. ValidationError is raised otherwise. """ - if BibleImport.is_compressed(filename): + if BibleImport.is_compressed(file_path): raise ValidationError(msg='Compressed file') - bible = self.parse_xml(filename, use_objectify=True) + bible = self.parse_xml(file_path, use_objectify=True) if bible is None: raise ValidationError(msg='Error when opening file') root_tag = bible.tag.lower() diff --git a/openlp/plugins/bibles/lib/biblestab.py b/openlp/plugins/bibles/lib/biblestab.py index 1420dcc33..59f8655eb 100644 --- a/openlp/plugins/bibles/lib/biblestab.py +++ b/openlp/plugins/bibles/lib/biblestab.py @@ -39,11 +39,11 @@ class BiblesTab(SettingsTab): """ log.info('Bible Tab loaded') - def _init_(self, parent, title, visible_title, icon_path): + def _init_(self, *args, **kwargs): self.paragraph_style = True self.show_new_chapters = False self.display_style = 0 - super(BiblesTab, self).__init__(parent, title, visible_title, icon_path) + super().__init__(*args, **kwargs) def setupUi(self): self.setObjectName('BiblesTab') diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 64744113b..7487ab2a9 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -34,6 +34,7 @@ from sqlalchemy.orm import class_mapper, mapper, relation from sqlalchemy.orm.exc import UnmappedClassError from openlp.core.common import AppLocation, translate, clean_filename +from openlp.core.common.path import Path from openlp.core.lib.db import BaseModel, init_db, Manager from openlp.core.lib.ui import critical_error_message_box from openlp.plugins.bibles.lib import BibleStrings, LanguageSelection, upgrade @@ -128,10 +129,15 @@ class BibleDB(Manager): :param parent: :param kwargs: ``path`` - The path to the bible database file. + The path to the bible database file. Type: openlp.core.common.path.Path ``name`` The name of the database. This is also used as the file name for SQLite databases. + + ``file`` + Type: openlp.core.common.path.Path + + :rtype: None """ log.info('BibleDB loaded') self._setup(parent, **kwargs) @@ -144,20 +150,20 @@ class BibleDB(Manager): self.session = None if 'path' not in kwargs: raise KeyError('Missing keyword argument "path".') + self.path = kwargs['path'] if 'name' not in kwargs and 'file' not in kwargs: raise KeyError('Missing keyword argument "name" or "file".') if 'name' in kwargs: self.name = kwargs['name'] if not isinstance(self.name, str): self.name = str(self.name, 'utf-8') - self.file = clean_filename(self.name) + '.sqlite' + # TODO: To path object + file_path = Path(clean_filename(self.name) + '.sqlite') if 'file' in kwargs: - self.file = kwargs['file'] - Manager.__init__(self, 'bibles', init_schema, self.file, upgrade) + file_path = kwargs['file'] + Manager.__init__(self, 'bibles', init_schema, file_path, upgrade) if self.session and 'file' in kwargs: self.get_name() - if 'path' in kwargs: - self.path = kwargs['path'] self._is_web_bible = None def get_name(self): @@ -470,9 +476,9 @@ class BiblesResourcesDB(QtCore.QObject, Manager): Return the cursor object. Instantiate one if it doesn't exist yet. """ if BiblesResourcesDB.cursor is None: - file_path = os.path.join(str(AppLocation.get_directory(AppLocation.PluginsDir)), - 'bibles', 'resources', 'bibles_resources.sqlite') - conn = sqlite3.connect(file_path) + file_path = \ + AppLocation.get_directory(AppLocation.PluginsDir) / 'bibles' / 'resources' / 'bibles_resources.sqlite' + conn = sqlite3.connect(str(file_path)) BiblesResourcesDB.cursor = conn.cursor() return BiblesResourcesDB.cursor @@ -758,17 +764,13 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager): If necessary loads up the database and creates the tables if the database doesn't exist. """ if AlternativeBookNamesDB.cursor is None: - file_path = os.path.join( - str(AppLocation.get_directory(AppLocation.DataDir)), 'bibles', 'alternative_book_names.sqlite') - if not os.path.exists(file_path): + file_path = AppLocation.get_directory(AppLocation.DataDir) / 'bibles' / 'alternative_book_names.sqlite' + AlternativeBookNamesDB.conn = sqlite3.connect(str(file_path)) + if not file_path.exists(): # create new DB, create table alternative_book_names - AlternativeBookNamesDB.conn = sqlite3.connect(file_path) AlternativeBookNamesDB.conn.execute( 'CREATE TABLE alternative_book_names(id INTEGER NOT NULL, ' 'book_reference_id INTEGER, language_id INTEGER, name VARCHAR(50), PRIMARY KEY (id))') - else: - # use existing DB - AlternativeBookNamesDB.conn = sqlite3.connect(file_path) AlternativeBookNamesDB.cursor = AlternativeBookNamesDB.conn.cursor() return AlternativeBookNamesDB.cursor diff --git a/openlp/plugins/bibles/lib/importers/csvbible.py b/openlp/plugins/bibles/lib/importers/csvbible.py index e319bdf36..42b1dd51d 100644 --- a/openlp/plugins/bibles/lib/importers/csvbible.py +++ b/openlp/plugins/bibles/lib/importers/csvbible.py @@ -73,8 +73,8 @@ class CSVBible(BibleImport): """ super().__init__(*args, **kwargs) self.log_info(self.__class__.__name__) - self.books_file = kwargs['booksfile'] - self.verses_file = kwargs['versefile'] + self.books_path = kwargs['books_path'] + self.verses_path = kwargs['verse_path'] @staticmethod def get_book_name(name, books): @@ -92,21 +92,22 @@ class CSVBible(BibleImport): return book_name @staticmethod - def parse_csv_file(filename, results_tuple): + def parse_csv_file(file_path, results_tuple): """ Parse the supplied CSV file. - :param filename: The name of the file to parse. Str - :param results_tuple: The namedtuple to use to store the results. namedtuple - :return: An iterable yielding namedtuples of type results_tuple + :param openlp.core.common.path.Path file_path: The name of the file to parse. + :param namedtuple results_tuple: The namedtuple to use to store the results. + :return: An list of namedtuples of type results_tuple + :rtype: list[namedtuple] """ try: - encoding = get_file_encoding(Path(filename))['encoding'] - with open(filename, 'r', encoding=encoding, newline='') as csv_file: + encoding = get_file_encoding(file_path)['encoding'] + with file_path.open('r', encoding=encoding, newline='') as csv_file: csv_reader = csv.reader(csv_file, delimiter=',', quotechar='"') return [results_tuple(*line) for line in csv_reader] except (OSError, csv.Error): - raise ValidationError(msg='Parsing "{file}" failed'.format(file=filename)) + raise ValidationError(msg='Parsing "{file}" failed'.format(file=file_path)) def process_books(self, books): """ @@ -159,12 +160,12 @@ class CSVBible(BibleImport): self.language_id = self.get_language(bible_name) if not self.language_id: return False - books = self.parse_csv_file(self.books_file, Book) + books = self.parse_csv_file(self.books_path, Book) self.wizard.progress_bar.setValue(0) self.wizard.progress_bar.setMinimum(0) self.wizard.progress_bar.setMaximum(len(books)) book_list = self.process_books(books) - verses = self.parse_csv_file(self.verses_file, Verse) + verses = self.parse_csv_file(self.verses_path, Verse) self.wizard.progress_bar.setValue(0) self.wizard.progress_bar.setMaximum(len(books) + 1) self.process_verses(verses, book_list) diff --git a/openlp/plugins/bibles/lib/importers/opensong.py b/openlp/plugins/bibles/lib/importers/opensong.py index 2ac72503d..8400c9bf7 100644 --- a/openlp/plugins/bibles/lib/importers/opensong.py +++ b/openlp/plugins/bibles/lib/importers/opensong.py @@ -46,7 +46,8 @@ def parse_chapter_number(number, previous_number): :param number: The raw data from the xml :param previous_number: The previous chapter number - :return: Number of current chapter. (Int) + :return: Number of current chapter. + :rtype: int """ if number: return int(number.split()[-1]) @@ -132,13 +133,13 @@ class OpenSongBible(BibleImport): :param bible_name: The name of the bible being imported :return: True if import completed, False if import was unsuccessful """ - self.log_debug('Starting OpenSong import from "{name}"'.format(name=self.filename)) - self.validate_xml_file(self.filename, 'bible') - bible = self.parse_xml(self.filename, use_objectify=True) + self.log_debug('Starting OpenSong import from "{name}"'.format(name=self.file_path)) + self.validate_xml_file(self.file_path, 'bible') + bible = self.parse_xml(self.file_path, use_objectify=True) if bible is None: return False # No language info in the opensong format, so ask the user - self.language_id = self.get_language_id(bible_name=self.filename) + self.language_id = self.get_language_id(bible_name=str(self.file_path)) if not self.language_id: return False self.process_books(bible.b) diff --git a/openlp/plugins/bibles/lib/importers/osis.py b/openlp/plugins/bibles/lib/importers/osis.py index f10e084bc..cde57954a 100644 --- a/openlp/plugins/bibles/lib/importers/osis.py +++ b/openlp/plugins/bibles/lib/importers/osis.py @@ -159,14 +159,14 @@ class OSISBible(BibleImport): """ Loads a Bible from file. """ - self.log_debug('Starting OSIS import from "{name}"'.format(name=self.filename)) - self.validate_xml_file(self.filename, '{http://www.bibletechnologies.net/2003/osis/namespace}osis') - bible = self.parse_xml(self.filename, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS) + self.log_debug('Starting OSIS import from "{name}"'.format(name=self.file_path)) + self.validate_xml_file(self.file_path, '{http://www.bibletechnologies.net/2003/osis/namespace}osis') + bible = self.parse_xml(self.file_path, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS) if bible is None: return False # Find bible language language = bible.xpath("//ns:osisText/@xml:lang", namespaces=NS) - self.language_id = self.get_language_id(language[0] if language else None, bible_name=self.filename) + self.language_id = self.get_language_id(language[0] if language else None, bible_name=str(self.file_path)) if not self.language_id: return False self.process_books(bible) diff --git a/openlp/plugins/bibles/lib/importers/sword.py b/openlp/plugins/bibles/lib/importers/sword.py index e01783087..c43208ae5 100644 --- a/openlp/plugins/bibles/lib/importers/sword.py +++ b/openlp/plugins/bibles/lib/importers/sword.py @@ -60,7 +60,7 @@ class SwordBible(BibleImport): bible = pysword_modules.get_bible_from_module(self.sword_key) language = pysword_module_json['lang'] language = language[language.find('.') + 1:] - language_id = self.get_language_id(language, bible_name=self.filename) + language_id = self.get_language_id(language, bible_name=str(self.file_path)) if not language_id: return False books = bible.get_structure().get_books() diff --git a/openlp/plugins/bibles/lib/importers/wordproject.py b/openlp/plugins/bibles/lib/importers/wordproject.py index bf5c5fc75..90d23a2b5 100644 --- a/openlp/plugins/bibles/lib/importers/wordproject.py +++ b/openlp/plugins/bibles/lib/importers/wordproject.py @@ -19,15 +19,14 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -import os -import re import logging -from codecs import open as copen +import re from tempfile import TemporaryDirectory from zipfile import ZipFile from bs4 import BeautifulSoup, Tag, NavigableString +from openlp.core.common.path import Path from openlp.plugins.bibles.lib.bibleimport import BibleImport BOOK_NUMBER_PATTERN = re.compile(r'\[(\d+)\]') @@ -51,9 +50,9 @@ class WordProjectBible(BibleImport): Unzip the file to a temporary directory """ self.tmp = TemporaryDirectory() - zip_file = ZipFile(os.path.abspath(self.filename)) - zip_file.extractall(self.tmp.name) - self.base_dir = os.path.join(self.tmp.name, os.path.splitext(os.path.basename(self.filename))[0]) + with ZipFile(str(self.file_path)) as zip_file: + zip_file.extractall(self.tmp.name) + self.base_path = Path(self.tmp.name, self.file_path.stem) def process_books(self): """ @@ -62,8 +61,7 @@ class WordProjectBible(BibleImport): :param bible_data: parsed xml :return: None """ - with copen(os.path.join(self.base_dir, 'index.htm'), encoding='utf-8', errors='ignore') as index_file: - page = index_file.read() + page = (self.base_path / 'index.htm').read_text(encoding='utf-8', errors='ignore') soup = BeautifulSoup(page, 'lxml') bible_books = soup.find('div', 'textOptions').find_all('li') book_count = len(bible_books) @@ -93,9 +91,7 @@ class WordProjectBible(BibleImport): :return: None """ log.debug(book_link) - book_file = os.path.join(self.base_dir, os.path.normpath(book_link)) - with copen(book_file, encoding='utf-8', errors='ignore') as f: - page = f.read() + page = (self.base_path / book_link).read_text(encoding='utf-8', errors='ignore') soup = BeautifulSoup(page, 'lxml') header_div = soup.find('div', 'textHeader') chapters_p = header_div.find('p') @@ -114,9 +110,8 @@ class WordProjectBible(BibleImport): """ Get the verses for a particular book """ - chapter_file_name = os.path.join(self.base_dir, '{:02d}'.format(book_number), '{}.htm'.format(chapter_number)) - with copen(chapter_file_name, encoding='utf-8', errors='ignore') as chapter_file: - page = chapter_file.read() + chapter_file_path = self.base_path / '{:02d}'.format(book_number) / '{}.htm'.format(chapter_number) + page = chapter_file_path.read_text(encoding='utf-8', errors='ignore') soup = BeautifulSoup(page, 'lxml') text_body = soup.find('div', 'textBody') if text_body: @@ -158,9 +153,9 @@ class WordProjectBible(BibleImport): """ Loads a Bible from file. """ - self.log_debug('Starting WordProject import from "{name}"'.format(name=self.filename)) + self.log_debug('Starting WordProject import from "{name}"'.format(name=self.file_path)) self._unzip_file() - self.language_id = self.get_language_id(None, bible_name=self.filename) + self.language_id = self.get_language_id(None, bible_name=str(self.file_path)) result = False if self.language_id: self.process_books() diff --git a/openlp/plugins/bibles/lib/importers/zefania.py b/openlp/plugins/bibles/lib/importers/zefania.py index 221d57cb3..be32d08ed 100644 --- a/openlp/plugins/bibles/lib/importers/zefania.py +++ b/openlp/plugins/bibles/lib/importers/zefania.py @@ -45,13 +45,13 @@ class ZefaniaBible(BibleImport): """ Loads a Bible from file. """ - log.debug('Starting Zefania import from "{name}"'.format(name=self.filename)) + log.debug('Starting Zefania import from "{name}"'.format(name=self.file_path)) success = True try: - xmlbible = self.parse_xml(self.filename, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS) + xmlbible = self.parse_xml(self.file_path, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS) # Find bible language language = xmlbible.xpath("/XMLBIBLE/INFORMATION/language/text()") - language_id = self.get_language_id(language[0] if language else None, bible_name=self.filename) + language_id = self.get_language_id(language[0] if language else None, bible_name=str(self.file_path)) if not language_id: return False no_of_books = int(xmlbible.xpath('count(//BIBLEBOOK)')) @@ -69,7 +69,7 @@ class ZefaniaBible(BibleImport): log.debug('Could not find a name, will use number, basically a guess.') book_ref_id = int(bnumber) if not book_ref_id: - log.error('Importing books from "{name}" failed'.format(name=self.filename)) + log.error('Importing books from "{name}" failed'.format(name=self.file_path)) return False book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) db_book = self.create_book(book_details['name'], book_ref_id, book_details['testament_id']) diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 5c8728e58..06f26c016 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -19,7 +19,6 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### - import logging from openlp.core.common import AppLocation, OpenLPMixin, RegistryProperties, Settings, translate, delete_file, UiStrings @@ -111,7 +110,7 @@ class BibleManager(OpenLPMixin, RegistryProperties): self.settings_section = 'bibles' self.web = 'Web' self.db_cache = None - self.path = str(AppLocation.get_section_data_path(self.settings_section)) + self.path = AppLocation.get_section_data_path(self.settings_section) self.proxy_name = Settings().value(self.settings_section + '/proxy name') self.suffix = '.sqlite' self.import_wizard = None @@ -124,20 +123,20 @@ class BibleManager(OpenLPMixin, RegistryProperties): of HTTPBible is loaded instead of the BibleDB class. """ log.debug('Reload bibles') - files = [str(file) for file in AppLocation.get_files(self.settings_section, self.suffix)] - if 'alternative_book_names.sqlite' in files: - files.remove('alternative_book_names.sqlite') - log.debug('Bible Files {text}'.format(text=files)) + file_paths = AppLocation.get_files(self.settings_section, self.suffix) + if Path('alternative_book_names.sqlite') in file_paths: + file_paths.remove(Path('alternative_book_names.sqlite')) + log.debug('Bible Files {text}'.format(text=file_paths)) self.db_cache = {} - for filename in files: - bible = BibleDB(self.parent, path=self.path, file=filename) + for file_path in file_paths: + bible = BibleDB(self.parent, path=self.path, file=file_path) if not bible.session: continue name = bible.get_name() # Remove corrupted files. if name is None: bible.session.close_all() - delete_file(Path(self.path, filename)) + delete_file(self.path / file_path) continue log.debug('Bible Name: "{name}"'.format(name=name)) self.db_cache[name] = bible @@ -146,7 +145,7 @@ class BibleManager(OpenLPMixin, RegistryProperties): source = self.db_cache[name].get_object(BibleMeta, 'download_source') download_name = self.db_cache[name].get_object(BibleMeta, 'download_name').value meta_proxy = self.db_cache[name].get_object(BibleMeta, 'proxy_server') - web_bible = HTTPBible(self.parent, path=self.path, file=filename, download_source=source.value, + web_bible = HTTPBible(self.parent, path=self.path, file=file_path, download_source=source.value, download_name=download_name) if meta_proxy: web_bible.proxy_server = meta_proxy.value diff --git a/tests/functional/openlp_plugins/bibles/test_bibleimport.py b/tests/functional/openlp_plugins/bibles/test_bibleimport.py index b28ab0dfa..56f44e533 100644 --- a/tests/functional/openlp_plugins/bibles/test_bibleimport.py +++ b/tests/functional/openlp_plugins/bibles/test_bibleimport.py @@ -30,6 +30,7 @@ from lxml import etree, objectify from PyQt5.QtWidgets import QDialog from openlp.core.common.languages import Language +from openlp.core.common.path import Path from openlp.core.lib.exceptions import ValidationError from openlp.plugins.bibles.lib.bibleimport import BibleImport from openlp.plugins.bibles.lib.db import BibleDB @@ -48,7 +49,7 @@ class TestBibleImport(TestCase): b' Testdatatodiscard\n' b'' ) - self.open_patcher = patch('builtins.open') + self.open_patcher = patch.object(Path, 'open') self.addCleanup(self.open_patcher.stop) self.mocked_open = self.open_patcher.start() self.critical_error_message_box_patcher = \ @@ -75,7 +76,7 @@ class TestBibleImport(TestCase): instance = BibleImport(MagicMock()) # THEN: The filename attribute should be None - self.assertIsNone(instance.filename) + self.assertIsNone(instance.file_path) self.assertIsInstance(instance, BibleDB) def init_kwargs_set_test(self): @@ -84,11 +85,11 @@ class TestBibleImport(TestCase): """ # GIVEN: A patched BibleDB._setup, BibleImport class and mocked parent # WHEN: Creating an instance of BibleImport with selected key word arguments - kwargs = {'filename': 'bible.xml'} + kwargs = {'file_path': 'bible.xml'} instance = BibleImport(MagicMock(), **kwargs) # THEN: The filename keyword should be set to bible.xml - self.assertEqual(instance.filename, 'bible.xml') + self.assertEqual(instance.file_path, 'bible.xml') self.assertIsInstance(instance, BibleDB) def get_language_canceled_test(self): @@ -359,7 +360,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml - result = instance.parse_xml('file.tst') + result = instance.parse_xml(Path('file.tst')) # THEN: The result returned should contain the correct data, and should be an instance of eetree_Element self.assertEqual(etree.tostring(result), @@ -377,7 +378,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml - result = instance.parse_xml('file.tst', use_objectify=True) + result = instance.parse_xml(Path('file.tst'), use_objectify=True) # THEN: The result returned should contain the correct data, and should be an instance of ObjectifiedElement self.assertEqual(etree.tostring(result), @@ -396,7 +397,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml, with a test file - result = instance.parse_xml('file.tst', elements=elements) + result = instance.parse_xml(Path('file.tst'), elements=elements) # THEN: The result returned should contain the correct data self.assertEqual(etree.tostring(result), @@ -413,7 +414,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml, with a test file - result = instance.parse_xml('file.tst', tags=tags) + result = instance.parse_xml(Path('file.tst'), tags=tags) # THEN: The result returned should contain the correct data self.assertEqual(etree.tostring(result), b'\n Testdatatokeep\n Test' @@ -431,7 +432,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml, with a test file - result = instance.parse_xml('file.tst', elements=elements, tags=tags) + result = instance.parse_xml(Path('file.tst'), elements=elements, tags=tags) # THEN: The result returned should contain the correct data self.assertEqual(etree.tostring(result), b'\n Testdatatokeep\n \n') @@ -446,10 +447,10 @@ class TestBibleImport(TestCase): exception.filename = 'file.tst' exception.strerror = 'No such file or directory' self.mocked_open.side_effect = exception - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_xml - result = importer.parse_xml('file.tst') + result = importer.parse_xml(Path('file.tst')) # THEN: parse_xml should have caught the error, informed the user and returned None mocked_log_exception.assert_called_once_with('Opening file.tst failed.') @@ -468,10 +469,10 @@ class TestBibleImport(TestCase): exception.filename = 'file.tst' exception.strerror = 'Permission denied' self.mocked_open.side_effect = exception - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_xml - result = importer.parse_xml('file.tst') + result = importer.parse_xml(Path('file.tst')) # THEN: parse_xml should have caught the error, informed the user and returned None mocked_log_exception.assert_called_once_with('Opening file.tst failed.') @@ -485,7 +486,7 @@ class TestBibleImport(TestCase): Test set_current_chapter """ # GIVEN: An instance of BibleImport and a mocked wizard - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) importer.wizard = MagicMock() # WHEN: Calling set_current_chapter @@ -500,7 +501,7 @@ class TestBibleImport(TestCase): """ # GIVEN: A mocked parse_xml which returns None with patch.object(BibleImport, 'is_compressed', return_value=True): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling is_compressed # THEN: ValidationError should be raised, with the message 'Compressed file' @@ -515,7 +516,7 @@ class TestBibleImport(TestCase): # GIVEN: A mocked parse_xml which returns None with patch.object(BibleImport, 'parse_xml', return_value=None), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file # THEN: ValidationError should be raised, with the message 'Error when opening file' @@ -531,7 +532,7 @@ class TestBibleImport(TestCase): # GIVEN: Some test data with an OpenSong Bible "bible" root tag with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file result = importer.validate_xml_file('file.name', 'bible') @@ -546,7 +547,7 @@ class TestBibleImport(TestCase): # GIVEN: Some test data with an Zefania root tag and an instance of BibleImport with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file # THEN: ValidationError should be raised, and the critical error message box should was called informing @@ -565,7 +566,7 @@ class TestBibleImport(TestCase): with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring( '')), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file # THEN: ValidationError should be raised, and the critical error message box should was called informing @@ -583,7 +584,7 @@ class TestBibleImport(TestCase): # GIVEN: Some test data with an Zefania root tag and an instance of BibleImport with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file # THEN: ValidationError should be raised, and the critical error message box should was called informing @@ -602,7 +603,7 @@ class TestBibleImport(TestCase): with patch.object( BibleImport, 'parse_xml', return_value=objectify.fromstring('')), \ patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling validate_xml_file # THEN: ValidationError should be raised, and the critical error message box should was called informing diff --git a/tests/functional/openlp_plugins/bibles/test_csvimport.py b/tests/functional/openlp_plugins/bibles/test_csvimport.py index 63d3d5282..0ff9a7dcf 100644 --- a/tests/functional/openlp_plugins/bibles/test_csvimport.py +++ b/tests/functional/openlp_plugins/bibles/test_csvimport.py @@ -29,6 +29,7 @@ from collections import namedtuple from unittest import TestCase from unittest.mock import ANY, MagicMock, PropertyMock, call, patch +from openlp.core.common.path import Path from openlp.core.lib.exceptions import ValidationError from openlp.plugins.bibles.lib.bibleimport import BibleImport from openlp.plugins.bibles.lib.importers.csvbible import Book, CSVBible, Verse @@ -59,12 +60,13 @@ class TestCSVImport(TestCase): mocked_manager = MagicMock() # WHEN: An importer object is created - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = \ + CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), verse_path=Path('verse.csv')) # THEN: The importer should be an instance of BibleImport self.assertIsInstance(importer, BibleImport) - self.assertEqual(importer.books_file, 'books.csv') - self.assertEqual(importer.verses_file, 'verse.csv') + self.assertEqual(importer.books_path, Path('books.csv')) + self.assertEqual(importer.verses_path, Path('verse.csv')) def book_namedtuple_test(self): """ @@ -134,17 +136,17 @@ class TestCSVImport(TestCase): with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding', return_value={'encoding': 'utf-8', 'confidence': 0.99}),\ - patch('openlp.plugins.bibles.lib.importers.csvbible.open', create=True) as mocked_open,\ + patch('openlp.plugins.bibles.lib.importers.csvbible.Path.open', create=True) as mocked_open,\ patch('openlp.plugins.bibles.lib.importers.csvbible.csv.reader', return_value=iter(test_data)) as mocked_reader: # WHEN: Calling the CSVBible parse_csv_file method with a file name and TestTuple - result = CSVBible.parse_csv_file('file.csv', TestTuple) + result = CSVBible.parse_csv_file(Path('file.csv'), TestTuple) # THEN: A list of TestTuple instances with the parsed data should be returned self.assertEqual(result, [TestTuple('1', 'Line 1', 'Data 1'), TestTuple('2', 'Line 2', 'Data 2'), TestTuple('3', 'Line 3', 'Data 3')]) - mocked_open.assert_called_once_with('file.csv', 'r', encoding='utf-8', newline='') + mocked_open.assert_called_once_with('r', encoding='utf-8', newline='') mocked_reader.assert_called_once_with(ANY, delimiter=',', quotechar='"') def parse_csv_file_oserror_test(self): @@ -154,12 +156,12 @@ class TestCSVImport(TestCase): # GIVEN: Mocked a mocked open object which raises an OSError with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding', return_value={'encoding': 'utf-8', 'confidence': 0.99}),\ - patch('openlp.plugins.bibles.lib.importers.csvbible.open', side_effect=OSError, create=True): + patch('openlp.plugins.bibles.lib.importers.csvbible.Path.open', side_effect=OSError, create=True): # WHEN: Calling CSVBible.parse_csv_file # THEN: A ValidationError should be raised with self.assertRaises(ValidationError) as context: - CSVBible.parse_csv_file('file.csv', None) + CSVBible.parse_csv_file(Path('file.csv'), None) self.assertEqual(context.exception.msg, 'Parsing "file.csv" failed') def parse_csv_file_csverror_test(self): @@ -169,13 +171,13 @@ class TestCSVImport(TestCase): # GIVEN: Mocked a csv.reader which raises an csv.Error with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding', return_value={'encoding': 'utf-8', 'confidence': 0.99}),\ - patch('openlp.plugins.bibles.lib.importers.csvbible.open', create=True),\ + patch('openlp.plugins.bibles.lib.importers.csvbible.Path.open', create=True),\ patch('openlp.plugins.bibles.lib.importers.csvbible.csv.reader', side_effect=csv.Error): # WHEN: Calling CSVBible.parse_csv_file # THEN: A ValidationError should be raised with self.assertRaises(ValidationError) as context: - CSVBible.parse_csv_file('file.csv', None) + CSVBible.parse_csv_file(Path('file.csv'), None) self.assertEqual(context.exception.msg, 'Parsing "file.csv" failed') def process_books_stopped_import_test(self): @@ -185,7 +187,8 @@ class TestCSVImport(TestCase): # GIVEN: An instance of CSVBible with the stop_import_flag set to True mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verse.csv')) type(importer).application = PropertyMock() importer.stop_import_flag = True importer.wizard = MagicMock() @@ -205,7 +208,8 @@ class TestCSVImport(TestCase): mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\ patch('openlp.plugins.bibles.lib.importers.csvbible.translate'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verse.csv')) importer.find_and_create_book = MagicMock() importer.language_id = 10 importer.stop_import_flag = False @@ -229,7 +233,8 @@ class TestCSVImport(TestCase): # GIVEN: An instance of CSVBible with the stop_import_flag set to True mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verse.csv')) importer.get_book_name = MagicMock() importer.session = MagicMock() importer.stop_import_flag = True @@ -250,7 +255,8 @@ class TestCSVImport(TestCase): mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\ patch('openlp.plugins.bibles.lib.importers.csvbible.translate'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verse.csv')) importer.create_verse = MagicMock() importer.get_book = MagicMock(return_value=Book('1', '1', '1. Mosebog', '1Mos')) importer.get_book_name = MagicMock(return_value='1. Mosebog') @@ -281,7 +287,8 @@ class TestCSVImport(TestCase): # GIVEN: An instance of CSVBible and a mocked get_language which simulates the user cancelling the language box mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verse.csv')) importer.get_language = MagicMock(return_value=None) # WHEN: Calling do_import @@ -298,7 +305,8 @@ class TestCSVImport(TestCase): # GIVEN: An instance of CSVBible mocked_manager = MagicMock() with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'): - importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verses.csv') + importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), + verse_path=Path('verses.csv')) importer.get_language = MagicMock(return_value=10) importer.parse_csv_file = MagicMock(side_effect=[['Book 1'], ['Verse 1']]) importer.process_books = MagicMock(return_value=['Book 1']) @@ -312,7 +320,8 @@ class TestCSVImport(TestCase): # THEN: parse_csv_file should be called twice, # and True should be returned. - self.assertEqual(importer.parse_csv_file.mock_calls, [call('books.csv', Book), call('verses.csv', Verse)]) + self.assertEqual(importer.parse_csv_file.mock_calls, + [call(Path('books.csv'), Book), call(Path('verses.csv'), Verse)]) importer.process_books.assert_called_once_with(['Book 1']) importer.process_verses.assert_called_once_with(['Verse 1'], ['Book 1']) self.assertTrue(result) @@ -325,12 +334,12 @@ class TestCSVImport(TestCase): # get_book_ref_id_by_name, create_verse, create_book, session and get_language. result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb') test_data = json.loads(result_file.read().decode()) - books_file = os.path.join(TEST_PATH, 'dk1933-books.csv') - verses_file = os.path.join(TEST_PATH, 'dk1933-verses.csv') + books_file = Path(TEST_PATH, 'dk1933-books.csv') + verses_file = Path(TEST_PATH, 'dk1933-verses.csv') with patch('openlp.plugins.bibles.lib.importers.csvbible.CSVBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = CSVBible(mocked_manager, path='.', name='.', booksfile=books_file, versefile=verses_file) + importer = CSVBible(mocked_manager, path='.', name='.', books_path=books_file, verse_path=verses_file) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() diff --git a/tests/functional/openlp_plugins/bibles/test_opensongimport.py b/tests/functional/openlp_plugins/bibles/test_opensongimport.py index 4842e9c5f..885d1e27a 100644 --- a/tests/functional/openlp_plugins/bibles/test_opensongimport.py +++ b/tests/functional/openlp_plugins/bibles/test_opensongimport.py @@ -30,6 +30,7 @@ from unittest.mock import MagicMock, patch, call from lxml import objectify from openlp.core.common import Registry +from openlp.core.common.path import Path from openlp.plugins.bibles.lib.importers.opensong import OpenSongBible, get_text, parse_chapter_number from openlp.plugins.bibles.lib.bibleimport import BibleImport @@ -64,7 +65,7 @@ class TestOpenSongImport(TestCase, TestMixin): mocked_manager = MagicMock() # WHEN: An importer object is created - importer = OpenSongBible(mocked_manager, path='.', name='.', filename='') + importer = OpenSongBible(mocked_manager, path='.', name='.', file_path=None) # THEN: The importer should be an instance of BibleDB self.assertIsInstance(importer, BibleImport) @@ -126,7 +127,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test parse_verse_number when supplied with a valid verse number """ # GIVEN: An instance of OpenSongBible, the number 15 represented as a string and an instance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_verse_number result = importer.parse_verse_number('15', 0) @@ -139,7 +140,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test parse_verse_number when supplied with a verse range """ # GIVEN: An instance of OpenSongBible, and the range 24-26 represented as a string - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_verse_number result = importer.parse_verse_number('24-26', 0) @@ -152,7 +153,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test parse_verse_number when supplied with a invalid verse number """ # GIVEN: An instance of OpenSongBible, a non numeric string represented as a string - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_verse_number result = importer.parse_verse_number('invalid', 41) @@ -165,7 +166,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test parse_verse_number when the verse number is an empty string. (Bug #1074727) """ # GIVEN: An instance of OpenSongBible, an empty string, and the previous verse number set as 14 - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_verse_number result = importer.parse_verse_number('', 14) @@ -178,7 +179,7 @@ class TestOpenSongImport(TestCase, TestMixin): """ with patch.object(OpenSongBible, 'log_warning')as mocked_log_warning: # GIVEN: An instanceofOpenSongBible, a Tuple, and the previous verse number set as 12 - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling parse_verse_number result = importer.parse_verse_number((1, 2, 3), 12) @@ -193,7 +194,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test process_books when stop_import is set to True """ # GIVEN: An instance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: stop_import_flag is set to True importer.stop_import_flag = True @@ -209,7 +210,7 @@ class TestOpenSongImport(TestCase, TestMixin): # GIVEN: An instance of OpenSongBible Importer and two mocked books self.mocked_find_and_create_book.side_effect = ['db_book1', 'db_book2'] with patch.object(OpenSongBible, 'process_chapters') as mocked_process_chapters: - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) book1 = MagicMock() book1.attrib = {'n': 'Name1'} @@ -236,7 +237,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test process_chapters when stop_import is set to True """ # GIVEN: An isntance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) importer.parse_chapter_number = MagicMock() # WHEN: stop_import_flag is set to True @@ -252,7 +253,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test process_chapters when it completes """ # GIVEN: An instance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) importer.wizard = MagicMock() # WHEN: called with some valid data @@ -284,7 +285,7 @@ class TestOpenSongImport(TestCase, TestMixin): Test process_verses when stop_import is set to True """ # GIVEN: An isntance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) importer.parse_verse_number = MagicMock() # WHEN: stop_import_flag is set to True @@ -303,7 +304,7 @@ class TestOpenSongImport(TestCase, TestMixin): patch.object(OpenSongBible, 'parse_verse_number', **{'side_effect': [1, 2]}) as mocked_parse_verse_number: # GIVEN: An instance of OpenSongBible - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) importer.wizard = MagicMock() # WHEN: called with some valid data @@ -338,7 +339,7 @@ class TestOpenSongImport(TestCase, TestMixin): patch.object(OpenSongBible, 'validate_xml_file'), \ patch.object(OpenSongBible, 'parse_xml', return_value=None), \ patch.object(OpenSongBible, 'get_language_id') as mocked_language_id: - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -357,7 +358,7 @@ class TestOpenSongImport(TestCase, TestMixin): patch.object(OpenSongBible, 'parse_xml'), \ patch.object(OpenSongBible, 'get_language_id', return_value=False), \ patch.object(OpenSongBible, 'process_books') as mocked_process_books: - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -376,7 +377,7 @@ class TestOpenSongImport(TestCase, TestMixin): patch.object(OpenSongBible, 'parse_xml'), \ patch.object(OpenSongBible, 'get_language_id', return_value=10), \ patch.object(OpenSongBible, 'process_books'): - importer = OpenSongBible(MagicMock(), path='.', name='.', filename='') + importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -406,7 +407,7 @@ class TestOpenSongImportFileImports(TestCase, TestMixin): with patch('openlp.plugins.bibles.lib.importers.opensong.OpenSongBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = OpenSongBible(mocked_manager, path='.', name='.', filename='') + importer = OpenSongBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() @@ -416,7 +417,7 @@ class TestOpenSongImportFileImports(TestCase, TestMixin): importer.get_language.return_value = 'Danish' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. diff --git a/tests/functional/openlp_plugins/bibles/test_osisimport.py b/tests/functional/openlp_plugins/bibles/test_osisimport.py index 5be6c459c..de8a3c384 100644 --- a/tests/functional/openlp_plugins/bibles/test_osisimport.py +++ b/tests/functional/openlp_plugins/bibles/test_osisimport.py @@ -27,6 +27,7 @@ import json from unittest import TestCase from unittest.mock import MagicMock, call, patch +from openlp.core.common.path import Path from openlp.plugins.bibles.lib.bibleimport import BibleImport from openlp.plugins.bibles.lib.db import BibleDB from openlp.plugins.bibles.lib.importers.osis import OSISBible @@ -63,7 +64,7 @@ class TestOsisImport(TestCase): mocked_manager = MagicMock() # WHEN: An importer object is created - importer = OSISBible(mocked_manager, path='.', name='.', filename='') + importer = OSISBible(mocked_manager, path='.', name='.', file_path=None) # THEN: The importer should be an instance of BibleDB self.assertIsInstance(importer, BibleDB) @@ -73,7 +74,7 @@ class TestOsisImport(TestCase): Test process_books when stop_import is set to True """ # GIVEN: An instance of OSISBible adn some mocked data - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) mocked_data = MagicMock(**{'xpath.return_value': ['Book']}) # WHEN: stop_import_flag is set to True and process_books is called @@ -90,7 +91,7 @@ class TestOsisImport(TestCase): # GIVEN: An instance of OSISBible Importer and two mocked books self.mocked_find_and_create_book.side_effect = ['db_book1', 'db_book2'] with patch.object(OSISBible, 'process_chapters') as mocked_process_chapters: - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) book1 = MagicMock() book1.get.return_value = 'Name1' @@ -128,7 +129,7 @@ class TestOsisImport(TestCase): test_chapter = MagicMock() test_chapter.__iter__.return_value = [test_verse] test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_chapters importer.process_chapters(test_book, [test_chapter]) @@ -155,7 +156,7 @@ class TestOsisImport(TestCase): test_chapter = MagicMock() test_chapter.__iter__.return_value = [test_verse] test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_chapters importer.process_chapters(test_book, [test_chapter]) @@ -180,7 +181,7 @@ class TestOsisImport(TestCase): test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4'}.get(x) # WHEN: Calling process_chapters - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) importer.process_chapters(test_book, [test_chapter]) # THEN: neither set_current_chapter or process_verse should have been called @@ -201,7 +202,7 @@ class TestOsisImport(TestCase): test_chapter = MagicMock() test_chapter.tag = '{http://www.bibletechnologies.net/2003/OSIS/namespace}chapter' test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_chapters importer.process_chapters(test_book, [test_chapter]) @@ -228,7 +229,7 @@ class TestOsisImport(TestCase): test_verse.text = 'Verse Text' # WHEN: Calling process_chapters - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) importer.process_chapters(test_book, [test_verse]) # THEN: process_verse should have been called with the test data @@ -245,7 +246,7 @@ class TestOsisImport(TestCase): test_verse.get.side_effect = lambda x: {}.get(x) test_verse.tail = 'Verse Text' test_verse.text = None - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse) @@ -264,7 +265,7 @@ class TestOsisImport(TestCase): test_verse.get.side_effect = lambda x: {}.get(x) test_verse.tail = 'Verse Text' test_verse.text = None - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse) @@ -282,7 +283,7 @@ class TestOsisImport(TestCase): test_verse.tail = None test_verse.text = None test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse, use_milestones=True) @@ -301,7 +302,7 @@ class TestOsisImport(TestCase): test_verse.tail = 'Verse Text' test_verse.text = None test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse, use_milestones=True) @@ -320,7 +321,7 @@ class TestOsisImport(TestCase): test_verse.tail = '\n ' # Whitespace test_verse.text = None test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse) @@ -339,7 +340,7 @@ class TestOsisImport(TestCase): test_verse.tail = '\n ' # Whitespace test_verse.text = 'Verse Text' test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x) - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling process_verse with the test data importer.process_verse(test_book, 2, test_verse) @@ -356,7 +357,7 @@ class TestOsisImport(TestCase): patch.object(OSISBible, 'validate_xml_file'), \ patch.object(OSISBible, 'parse_xml', return_value=None), \ patch.object(OSISBible, 'get_language_id') as mocked_language_id: - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -375,7 +376,7 @@ class TestOsisImport(TestCase): patch.object(OSISBible, 'parse_xml'), \ patch.object(OSISBible, 'get_language_id', **{'return_value': False}), \ patch.object(OSISBible, 'process_books') as mocked_process_books: - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -394,7 +395,7 @@ class TestOsisImport(TestCase): patch.object(OSISBible, 'parse_xml'), \ patch.object(OSISBible, 'get_language_id', **{'return_value': 10}), \ patch.object(OSISBible, 'process_books'): - importer = OSISBible(MagicMock(), path='.', name='.', filename='') + importer = OSISBible(MagicMock(), path='.', name='.', file_path=None) # WHEN: Calling do_import result = importer.do_import() @@ -427,7 +428,7 @@ class TestOsisImportFileImports(TestCase): with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = OSISBible(mocked_manager, path='.', name='.', filename='') + importer = OSISBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() @@ -437,7 +438,7 @@ class TestOsisImportFileImports(TestCase): importer.get_language.return_value = 'Danish' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. @@ -457,7 +458,7 @@ class TestOsisImportFileImports(TestCase): with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = OSISBible(mocked_manager, path='.', name='.', filename='') + importer = OSISBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() @@ -467,7 +468,7 @@ class TestOsisImportFileImports(TestCase): importer.get_language.return_value = 'English' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. @@ -487,7 +488,7 @@ class TestOsisImportFileImports(TestCase): with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = OSISBible(mocked_manager, path='.', name='.', filename='') + importer = OSISBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() @@ -497,7 +498,7 @@ class TestOsisImportFileImports(TestCase): importer.get_language.return_value = 'English' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. @@ -517,7 +518,7 @@ class TestOsisImportFileImports(TestCase): with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = OSISBible(mocked_manager, path='.', name='.', filename='') + importer = OSISBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.get_book_ref_id_by_name = MagicMock() importer.create_verse = MagicMock() @@ -527,7 +528,7 @@ class TestOsisImportFileImports(TestCase): importer.get_language.return_value = 'Danish' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. diff --git a/tests/functional/openlp_plugins/bibles/test_swordimport.py b/tests/functional/openlp_plugins/bibles/test_swordimport.py index 235beea58..34e011498 100644 --- a/tests/functional/openlp_plugins/bibles/test_swordimport.py +++ b/tests/functional/openlp_plugins/bibles/test_swordimport.py @@ -64,7 +64,7 @@ class TestSwordImport(TestCase): mocked_manager = MagicMock() # WHEN: An importer object is created - importer = SwordBible(mocked_manager, path='.', name='.', filename='', sword_key='', sword_path='') + importer = SwordBible(mocked_manager, path='.', name='.', file_path=None, sword_key='', sword_path='') # THEN: The importer should be an instance of BibleDB self.assertIsInstance(importer, BibleDB) @@ -80,7 +80,7 @@ class TestSwordImport(TestCase): # Also mocked pysword structures mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = SwordBible(mocked_manager, path='.', name='.', filename='', sword_key='', sword_path='') + importer = SwordBible(mocked_manager, path='.', name='.', file_path=None, sword_key='', sword_path='') result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb') test_data = json.loads(result_file.read().decode()) importer.wizard = mocked_import_wizard diff --git a/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py b/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py index 6e62dae9e..fbf5b0412 100644 --- a/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py +++ b/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py @@ -26,6 +26,7 @@ import os from unittest import TestCase from unittest.mock import MagicMock, patch, call +from openlp.core.common.path import Path from openlp.plugins.bibles.lib.importers.wordproject import WordProjectBible @@ -48,19 +49,17 @@ class TestWordProjectImport(TestCase): self.addCleanup(self.manager_patcher.stop) self.manager_patcher.start() - @patch('openlp.plugins.bibles.lib.importers.wordproject.os') - @patch('openlp.plugins.bibles.lib.importers.wordproject.copen') - def test_process_books(self, mocked_open, mocked_os): + @patch.object(Path, 'read_text') + def test_process_books(self, mocked_read_text): """ Test the process_books() method """ # GIVEN: A WordProject importer and a bunch of mocked things - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') - importer.base_dir = '' + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip')) + importer.base_path = Path() importer.stop_import_flag = False importer.language_id = 'en' - mocked_open.return_value.__enter__.return_value.read.return_value = INDEX_PAGE - mocked_os.path.join.side_effect = lambda *x: ''.join(x) + mocked_read_text.return_value = INDEX_PAGE # WHEN: process_books() is called with patch.object(importer, 'find_and_create_book') as mocked_find_and_create_book, \ @@ -69,26 +68,22 @@ class TestWordProjectImport(TestCase): importer.process_books() # THEN: The right methods should have been called - mocked_os.path.join.assert_called_once_with('', 'index.htm') - mocked_open.assert_called_once_with('index.htm', encoding='utf-8', errors='ignore') + mocked_read_text.assert_called_once_with(encoding='utf-8', errors='ignore') assert mocked_find_and_create_book.call_count == 66, 'There should be 66 books' assert mocked_process_chapters.call_count == 66, 'There should be 66 books' assert mocked_session.commit.call_count == 66, 'There should be 66 books' - @patch('openlp.plugins.bibles.lib.importers.wordproject.os') - @patch('openlp.plugins.bibles.lib.importers.wordproject.copen') - def test_process_chapters(self, mocked_open, mocked_os): + @patch.object(Path, 'read_text') + def test_process_chapters(self, mocked_read_text): """ Test the process_chapters() method """ # GIVEN: A WordProject importer and a bunch of mocked things - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') - importer.base_dir = '' + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip')) + importer.base_path = Path() importer.stop_import_flag = False importer.language_id = 'en' - mocked_open.return_value.__enter__.return_value.read.return_value = CHAPTER_PAGE - mocked_os.path.join.side_effect = lambda *x: ''.join(x) - mocked_os.path.normpath.side_effect = lambda x: x + mocked_read_text.return_value = CHAPTER_PAGE mocked_db_book = MagicMock() mocked_db_book.name = 'Genesis' book_id = 1 @@ -102,24 +97,21 @@ class TestWordProjectImport(TestCase): # THEN: The right methods should have been called expected_set_current_chapter_calls = [call('Genesis', ch) for ch in range(1, 51)] expected_process_verses_calls = [call(mocked_db_book, 1, ch) for ch in range(1, 51)] - mocked_os.path.join.assert_called_once_with('', '01/1.htm') - mocked_open.assert_called_once_with('01/1.htm', encoding='utf-8', errors='ignore') + mocked_read_text.assert_called_once_with(encoding='utf-8', errors='ignore') assert mocked_set_current_chapter.call_args_list == expected_set_current_chapter_calls assert mocked_process_verses.call_args_list == expected_process_verses_calls - @patch('openlp.plugins.bibles.lib.importers.wordproject.os') - @patch('openlp.plugins.bibles.lib.importers.wordproject.copen') - def test_process_verses(self, mocked_open, mocked_os): + @patch.object(Path, 'read_text') + def test_process_verses(self, mocked_read_text): """ Test the process_verses() method """ # GIVEN: A WordProject importer and a bunch of mocked things - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') - importer.base_dir = '' + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip')) + importer.base_path = Path() importer.stop_import_flag = False importer.language_id = 'en' - mocked_open.return_value.__enter__.return_value.read.return_value = CHAPTER_PAGE - mocked_os.path.join.side_effect = lambda *x: '/'.join(x) + mocked_read_text.return_value = CHAPTER_PAGE mocked_db_book = MagicMock() mocked_db_book.name = 'Genesis' book_number = 1 @@ -130,8 +122,7 @@ class TestWordProjectImport(TestCase): importer.process_verses(mocked_db_book, book_number, chapter_number) # THEN: All the right methods should have been called - mocked_os.path.join.assert_called_once_with('', '01', '1.htm') - mocked_open.assert_called_once_with('/01/1.htm', encoding='utf-8', errors='ignore') + mocked_read_text.assert_called_once_with(encoding='utf-8', errors='ignore') assert mocked_process_verse.call_count == 31 def test_process_verse(self): @@ -139,7 +130,7 @@ class TestWordProjectImport(TestCase): Test the process_verse() method """ # GIVEN: An importer and a mocked method - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip')) mocked_db_book = MagicMock() mocked_db_book.id = 1 chapter_number = 1 @@ -158,7 +149,7 @@ class TestWordProjectImport(TestCase): Test the process_verse() method when there's no text """ # GIVEN: An importer and a mocked method - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip')) mocked_db_book = MagicMock() mocked_db_book.id = 1 chapter_number = 1 @@ -177,7 +168,7 @@ class TestWordProjectImport(TestCase): Test the do_import() method """ # GIVEN: An importer and mocked methods - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path='kj.zip') # WHEN: do_import() is called with patch.object(importer, '_unzip_file') as mocked_unzip_file, \ @@ -199,7 +190,7 @@ class TestWordProjectImport(TestCase): Test the do_import() method when the language is not available """ # GIVEN: An importer and mocked methods - importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip') + importer = WordProjectBible(MagicMock(), path='.', name='.', file_path='kj.zip') # WHEN: do_import() is called with patch.object(importer, '_unzip_file') as mocked_unzip_file, \ diff --git a/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py b/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py index 8e43d55b5..d423a2153 100644 --- a/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py +++ b/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py @@ -27,6 +27,7 @@ import json from unittest import TestCase from unittest.mock import MagicMock, patch +from openlp.core.common.path import Path from openlp.plugins.bibles.lib.importers.zefania import ZefaniaBible from openlp.plugins.bibles.lib.db import BibleDB @@ -55,7 +56,7 @@ class TestZefaniaImport(TestCase): mocked_manager = MagicMock() # WHEN: An importer object is created - importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='') + importer = ZefaniaBible(mocked_manager, path='.', name='.', file_path=None) # THEN: The importer should be an instance of BibleDB self.assertIsInstance(importer, BibleDB) @@ -72,7 +73,7 @@ class TestZefaniaImport(TestCase): with patch('openlp.plugins.bibles.lib.importers.zefania.ZefaniaBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='') + importer = ZefaniaBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.create_verse = MagicMock() importer.create_book = MagicMock() @@ -81,7 +82,7 @@ class TestZefaniaImport(TestCase): importer.get_language.return_value = 'Danish' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. @@ -102,7 +103,7 @@ class TestZefaniaImport(TestCase): with patch('openlp.plugins.bibles.lib.importers.zefania.ZefaniaBible.application'): mocked_manager = MagicMock() mocked_import_wizard = MagicMock() - importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='') + importer = ZefaniaBible(mocked_manager, path='.', name='.', file_path=None) importer.wizard = mocked_import_wizard importer.create_verse = MagicMock() importer.create_book = MagicMock() @@ -111,7 +112,7 @@ class TestZefaniaImport(TestCase): importer.get_language.return_value = 'Russian' # WHEN: Importing bible file - importer.filename = os.path.join(TEST_PATH, bible_file) + importer.file_path = Path(TEST_PATH, bible_file) importer.do_import() # THEN: The create_verse() method should have been called with each verse in the file. From eb107b72373dcfefd99c6cfb4a960d4d7a221eb4 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Tue, 10 Oct 2017 22:16:04 +0100 Subject: [PATCH 08/96] test fixes --- openlp/core/ui/lib/pathedit.py | 2 +- .../plugins/bibles/lib/importers/__init__.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 openlp/plugins/bibles/lib/importers/__init__.py diff --git a/openlp/core/ui/lib/pathedit.py b/openlp/core/ui/lib/pathedit.py index 1953e47ab..47a70505d 100644 --- a/openlp/core/ui/lib/pathedit.py +++ b/openlp/core/ui/lib/pathedit.py @@ -192,5 +192,5 @@ class PathEdit(QtWidgets.QWidget): :rtype: None """ if self._path != path: - self.path = path + self._path = path self.pathChanged.emit(path) diff --git a/openlp/plugins/bibles/lib/importers/__init__.py b/openlp/plugins/bibles/lib/importers/__init__.py new file mode 100644 index 000000000..f83bbd595 --- /dev/null +++ b/openlp/plugins/bibles/lib/importers/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +The :mod:`~openlp.plugins.bibles.lib.importers` module contains importers for the Bibles plugin. +""" From d5ab3340eb39b2ad2059aab8187f59ede165014d Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 23 Oct 2017 15:09:57 -0700 Subject: [PATCH 09/96] Move widgets to a widgets module --- openlp.py | 1 + openlp/core/api/http/server.py | 6 +- openlp/core/api/poll.py | 2 +- openlp/core/api/websockets.py | 6 +- openlp/core/app.py | 4 +- openlp/core/common/mixins.py | 153 ++++- openlp/core/common/registry.py | 145 +---- openlp/core/display/renderer.py | 6 +- openlp/core/lib/db.py | 1 + .../core/lib/json}/__init__.py | 0 openlp/core/lib/mediamanageritem.py | 11 +- openlp/core/lib/plugin.py | 5 +- openlp/core/lib/pluginmanager.py | 6 +- openlp/core/lib/projector/db.py | 12 +- openlp/core/lib/projector/pjlink.py | 2 +- openlp/core/lib/searchedit.py | 175 ------ openlp/core/lib/serviceitem.py | 2 +- openlp/core/lib/settingstab.py | 2 +- openlp/core/ui/advancedtab.py | 5 +- openlp/core/ui/exceptionform.py | 4 +- openlp/core/ui/filerenameform.py | 3 +- openlp/core/ui/firsttimeform.py | 3 +- openlp/core/ui/generaltab.py | 3 +- openlp/core/ui/lib/historycombobox.py | 84 --- openlp/core/ui/lib/listwidgetwithdnd.py | 153 ----- openlp/core/ui/lib/pathedit.py | 196 ------ openlp/core/ui/lib/spelltextedit.py | 205 ------- openlp/core/ui/lib/treewidgetwithdnd.py | 145 ----- openlp/core/ui/maindisplay.py | 8 +- openlp/core/ui/mainwindow.py | 8 +- openlp/core/ui/media/mediacontroller.py | 8 +- openlp/core/ui/media/mediaplayer.py | 2 +- openlp/core/ui/media/playertab.py | 3 +- openlp/core/ui/pluginform.py | 2 +- openlp/core/ui/printservicedialog.py | 2 +- openlp/core/ui/printserviceform.py | 3 +- openlp/core/ui/projector/manager.py | 14 +- openlp/core/ui/serviceitemeditform.py | 3 +- openlp/core/ui/servicemanager.py | 80 +-- openlp/core/ui/servicenoteform.py | 5 +- openlp/core/ui/settingsform.py | 3 +- openlp/core/ui/shortcutlistform.py | 2 +- openlp/core/ui/slidecontroller.py | 32 +- openlp/core/ui/starttimeform.py | 3 +- openlp/core/ui/themeform.py | 3 +- openlp/core/ui/thememanager.py | 10 +- openlp/core/ui/themewizard.py | 3 +- .../core/widgets}/__init__.py | 0 .../lib/colorbutton.py => widgets/buttons.py} | 0 .../lib/filedialog.py => widgets/dialogs.py} | 0 .../mediadockmanager.py => widgets/docks.py} | 30 +- openlp/core/widgets/edits.py | 573 ++++++++++++++++++ .../{ui/lib/__init__.py => widgets/enums.py} | 20 +- openlp/core/{ui/lib => widgets}/toolbar.py | 2 +- .../listpreviewwidget.py => widgets/views.py} | 244 +++++++- openlp/core/{ui/lib => widgets}/wizard.py | 5 +- openlp/plugins/alerts/lib/alertsmanager.py | 6 +- openlp/plugins/alerts/lib/alertstab.py | 2 +- .../plugins/bibles/forms/bibleimportform.py | 2 +- openlp/plugins/bibles/forms/editbibleform.py | 2 +- openlp/plugins/bibles/lib/bibleimport.py | 6 +- openlp/plugins/bibles/lib/db.py | 1 - .../plugins/bibles/lib/importers}/__init__.py | 0 openlp/plugins/bibles/lib/importers/http.py | 3 +- openlp/plugins/bibles/lib/manager.py | 5 +- openlp/plugins/bibles/lib/mediaitem.py | 2 +- .../custom/forms/editcustomslidedialog.py | 2 +- openlp/plugins/images/lib/imagetab.py | 2 +- openlp/plugins/images/lib/mediaitem.py | 2 +- .../media/forms/mediaclipselectorform.py | 2 +- openlp/plugins/media/lib/mediaitem.py | 3 +- .../presentations/lib/presentationtab.py | 2 +- .../songs/forms/duplicatesongremovalform.py | 5 +- openlp/plugins/songs/forms/editsongform.py | 5 +- openlp/plugins/songs/forms/editversedialog.py | 2 +- openlp/plugins/songs/forms/songexportform.py | 7 +- openlp/plugins/songs/forms/songimportform.py | 13 +- .../songs/forms/songmaintenanceform.py | 3 +- .../plugins/songs/forms/songselectdialog.py | 2 +- openlp/plugins/songs/lib/importer.py | 2 +- .../songs/lib/importers/easyworship.py | 2 +- .../songs/lib/importers/foilpresenter.py | 2 +- openlp/plugins/songs/lib/importers/openlp.py | 2 +- .../plugins/songs/lib/importers/openlyrics.py | 2 +- .../songs/lib/importers/powerpraise.py | 4 +- .../lib/importers/presentationmanager.py | 4 +- .../songs/lib/importers/propresenter.py | 4 +- .../plugins/songs/lib/importers/songimport.py | 2 +- .../songs/lib/importers/songshowplus.py | 2 +- openlp/plugins/songs/lib/openlyricsexport.py | 2 +- openlp/plugins/songs/reporting.py | 2 +- .../songusage/forms/songusagedeleteform.py | 2 +- .../songusage/forms/songusagedetaildialog.py | 5 +- .../songusage/forms/songusagedetailform.py | 2 +- .../openlp_core/common/test_mixins.py | 91 ++- .../openlp_core/common/test_registry.py | 92 +-- tests/functional/openlp_core/test_app.py | 73 +-- .../ui/lib/test_listwidgetwithdnd.py | 137 ----- .../openlp_core/ui/test_mainwindow.py | 130 ++-- .../openlp_core/ui/test_servicemanager.py | 2 +- .../openlp_core/ui/test_thememanager.py | 135 ++--- .../openlp_core/widgets}/__init__.py | 0 .../test_buttons.py} | 82 +-- .../test_dialogs.py} | 6 +- .../test_edits.py} | 72 +-- .../test_views.py} | 209 +++++-- .../__init__.py | 0 .../interfaces/openlp_core/api/__init__.py | 36 -- .../interfaces/openlp_core/common/__init__.py | 21 + .../common}/test_utils.py | 0 tests/interfaces/openlp_core/lib/__init__.py | 21 + .../lib}/test_pluginmanager.py | 0 .../ui}/__init__.py | 0 .../interfaces/openlp_core/ui/lib/__init__.py | 21 + .../ui/lib}/test_historycombobox.py | 0 .../openlp_core/ui/media/__init__.py | 21 + .../openlp_core/ui/media/vendor/__init__.py | 21 + .../ui/media/vendor}/test_mediainfoWrapper.py | 2 +- .../ui}/test_filerenamedialog.py | 0 .../ui}/test_mainwindow.py | 0 .../ui}/test_projectoreditform.py | 0 .../ui}/test_projectormanager.py | 0 .../ui}/test_projectorsourceform.py | 0 .../ui}/test_servicemanager.py | 115 ++-- .../ui}/test_servicenotedialog.py | 0 .../ui}/test_settings_form.py | 0 .../ui}/test_shortcutlistform.py | 0 .../ui}/test_starttimedialog.py | 0 .../ui}/test_thememanager.py | 0 .../openlp_core/widgets/__init__.py | 21 + .../widgets/test_edits.py} | 36 +- .../widgets/test_views.py} | 4 +- 132 files changed, 1928 insertions(+), 1946 deletions(-) rename {tests/interfaces/openlp_core_api => openlp/core/lib/json}/__init__.py (100%) delete mode 100644 openlp/core/lib/searchedit.py delete mode 100644 openlp/core/ui/lib/historycombobox.py delete mode 100755 openlp/core/ui/lib/listwidgetwithdnd.py delete mode 100644 openlp/core/ui/lib/pathedit.py delete mode 100644 openlp/core/ui/lib/spelltextedit.py delete mode 100644 openlp/core/ui/lib/treewidgetwithdnd.py rename {tests/interfaces/openlp_core_common => openlp/core/widgets}/__init__.py (100%) rename openlp/core/{ui/lib/colorbutton.py => widgets/buttons.py} (100%) rename openlp/core/{ui/lib/filedialog.py => widgets/dialogs.py} (100%) rename openlp/core/{ui/lib/mediadockmanager.py => widgets/docks.py} (76%) create mode 100644 openlp/core/widgets/edits.py rename openlp/core/{ui/lib/__init__.py => widgets/enums.py} (69%) rename openlp/core/{ui/lib => widgets}/toolbar.py (98%) rename openlp/core/{ui/lib/listpreviewwidget.py => widgets/views.py} (56%) rename openlp/core/{ui/lib => widgets}/wizard.py (98%) rename {tests/interfaces/openlp_core_lib => openlp/plugins/bibles/lib/importers}/__init__.py (100%) delete mode 100755 tests/functional/openlp_core/ui/lib/test_listwidgetwithdnd.py rename tests/{interfaces/openlp_core_ui_lib => functional/openlp_core/widgets}/__init__.py (100%) rename tests/functional/openlp_core/{ui/lib/test_colorbutton.py => widgets/test_buttons.py} (70%) rename tests/functional/openlp_core/{ui/lib/test_filedialog.py => widgets/test_dialogs.py} (95%) rename tests/functional/openlp_core/{ui/lib/test_pathedit.py => widgets/test_edits.py} (81%) rename tests/functional/openlp_core/{ui/lib/test_listpreviewwidget.py => widgets/test_views.py} (75%) rename tests/interfaces/{openlp_core_ul_media_vendor => openlp_core}/__init__.py (100%) rename openlp/core/ui/lib/dockwidget.py => tests/interfaces/openlp_core/api/__init__.py (60%) create mode 100644 tests/interfaces/openlp_core/common/__init__.py rename tests/interfaces/{openlp_core_common => openlp_core/common}/test_utils.py (100%) create mode 100644 tests/interfaces/openlp_core/lib/__init__.py rename tests/interfaces/{openlp_core_lib => openlp_core/lib}/test_pluginmanager.py (100%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/__init__.py (100%) create mode 100644 tests/interfaces/openlp_core/ui/lib/__init__.py rename tests/interfaces/{openlp_core_ui_lib => openlp_core/ui/lib}/test_historycombobox.py (100%) create mode 100644 tests/interfaces/openlp_core/ui/media/__init__.py create mode 100644 tests/interfaces/openlp_core/ui/media/vendor/__init__.py rename tests/interfaces/{openlp_core_ul_media_vendor => openlp_core/ui/media/vendor}/test_mediainfoWrapper.py (97%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/test_filerenamedialog.py (100%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/test_mainwindow.py (100%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/test_projectoreditform.py (100%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/test_projectormanager.py (100%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/test_projectorsourceform.py (100%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/test_servicemanager.py (87%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/test_servicenotedialog.py (100%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/test_settings_form.py (100%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/test_shortcutlistform.py (100%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/test_starttimedialog.py (100%) rename tests/interfaces/{openlp_core_ui => openlp_core/ui}/test_thememanager.py (100%) create mode 100644 tests/interfaces/openlp_core/widgets/__init__.py rename tests/interfaces/{openlp_core_lib/test_searchedit.py => openlp_core/widgets/test_edits.py} (84%) rename tests/interfaces/{openlp_core_ui_lib/test_listpreviewwidget.py => openlp_core/widgets/test_views.py} (97%) diff --git a/openlp.py b/openlp.py index 02287cd5e..9bccc526f 100755 --- a/openlp.py +++ b/openlp.py @@ -46,6 +46,7 @@ if __name__ == '__main__': """ Instantiate and run the application. """ + faulthandler.enable() set_up_fault_handling() # Add support for using multiprocessing from frozen Windows executable (built using PyInstaller), # see https://docs.python.org/3/library/multiprocessing.html#multiprocessing.freeze_support diff --git a/openlp/core/api/http/server.py b/openlp/core/api/http/server.py index fad135f2b..782940f2d 100644 --- a/openlp/core/api/http/server.py +++ b/openlp/core/api/http/server.py @@ -39,9 +39,9 @@ from openlp.core.api.http import application from openlp.core.api.poll import Poller from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings -from openlp.core.common.mixins import OpenLPMixin, RegistryMixin +from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.path import create_paths -from openlp.core.common.registry import RegistryProperties, Registry +from openlp.core.common.registry import Registry, RegistryBase from openlp.core.common.settings import Settings from openlp.core.common.i18n import translate @@ -73,7 +73,7 @@ class HttpWorker(QtCore.QObject): pass -class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin): +class HttpServer(RegistryBase, RegistryProperties, LogMixin): """ Wrapper round a server instance """ diff --git a/openlp/core/api/poll.py b/openlp/core/api/poll.py index 5b3fb33c4..d2d36f60a 100644 --- a/openlp/core/api/poll.py +++ b/openlp/core/api/poll.py @@ -23,7 +23,7 @@ import json from openlp.core.common.httputils import get_web_page -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.core.common.settings import Settings diff --git a/openlp/core/api/websockets.py b/openlp/core/api/websockets.py index d64fdf3cc..90dca8208 100644 --- a/openlp/core/api/websockets.py +++ b/openlp/core/api/websockets.py @@ -31,8 +31,8 @@ import time from PyQt5 import QtCore -from openlp.core.common.mixins import OpenLPMixin -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import LogMixin, RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings log = logging.getLogger(__name__) @@ -61,7 +61,7 @@ class WebSocketWorker(QtCore.QObject): self.ws_server.stop = True -class WebSocketServer(RegistryProperties, OpenLPMixin): +class WebSocketServer(RegistryProperties, LogMixin): """ Wrapper round a server instance """ diff --git a/openlp/core/app.py b/openlp/core/app.py index 7c3938cfe..19943e3f0 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -38,7 +38,7 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common import is_macosx, is_win from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import LanguageManager, UiStrings, translate -from openlp.core.common.mixins import OpenLPMixin +from openlp.core.common.mixins import LogMixin from openlp.core.common.path import create_paths, copytree from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings @@ -59,7 +59,7 @@ __all__ = ['OpenLP', 'main'] log = logging.getLogger() -class OpenLP(OpenLPMixin, QtWidgets.QApplication): +class OpenLP(QtWidgets.QApplication, LogMixin): """ The core application class. This class inherits from Qt's QApplication class in order to provide the core of the application. diff --git a/openlp/core/common/mixins.py b/openlp/core/common/mixins.py index 6a39b8f2c..1bc6907a0 100644 --- a/openlp/core/common/mixins.py +++ b/openlp/core/common/mixins.py @@ -25,25 +25,29 @@ Provide Error Handling and login Services import logging import inspect -from openlp.core.common import trace_error_handler, de_hump +from openlp.core.common import is_win, trace_error_handler from openlp.core.common.registry import Registry DO_NOT_TRACE_EVENTS = ['timerEvent', 'paintEvent', 'drag_enter_event', 'drop_event', 'on_controller_size_changed', 'preview_size_changed', 'resizeEvent'] -class OpenLPMixin(object): +class LogMixin(object): """ Base Calling object for OpenLP classes. """ - def __init__(self, *args, **kwargs): - super(OpenLPMixin, self).__init__(*args, **kwargs) - self.logger = logging.getLogger("%s.%s" % (self.__module__, self.__class__.__name__)) - if self.logger.getEffectiveLevel() == logging.DEBUG: - for name, m in inspect.getmembers(self, inspect.ismethod): - if name not in DO_NOT_TRACE_EVENTS: - if not name.startswith("_") and not name.startswith("log"): - setattr(self, name, self.logging_wrapper(m, self)) + @property + def logger(self): + if hasattr(self, '_logger') and self._logger: + return self._logger + else: + self._logger = logging.getLogger("%s.%s" % (self.__module__, self.__class__.__name__)) + if self._logger.getEffectiveLevel() == logging.DEBUG: + for name, m in inspect.getmembers(self, inspect.ismethod): + if name not in DO_NOT_TRACE_EVENTS: + if not name.startswith("_") and not name.startswith("log"): + setattr(self, name, self.logging_wrapper(m, self)) + return self._logger def logging_wrapper(self, func, parent): """ @@ -93,30 +97,127 @@ class OpenLPMixin(object): self.logger.exception(message) -class RegistryMixin(object): +class RegistryProperties(object): """ This adds registry components to classes to use at run time. """ - def __init__(self, parent): + @property + def application(self): """ - Register the class and bootstrap hooks. + Adds the openlp to the class dynamically. + Windows needs to access the application in a dynamic manner. """ - try: - super(RegistryMixin, self).__init__(parent) - except TypeError: - super(RegistryMixin, self).__init__() - Registry().register(de_hump(self.__class__.__name__), self) - Registry().register_function('bootstrap_initialise', self.bootstrap_initialise) - Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up) + if is_win(): + return Registry().get('application') + else: + if not hasattr(self, '_application') or not self._application: + self._application = Registry().get('application') + return self._application - def bootstrap_initialise(self): + @property + def plugin_manager(self): """ - Dummy method to be overridden + Adds the plugin manager to the class dynamically """ - pass + if not hasattr(self, '_plugin_manager') or not self._plugin_manager: + self._plugin_manager = Registry().get('plugin_manager') + return self._plugin_manager - def bootstrap_post_set_up(self): + @property + def image_manager(self): """ - Dummy method to be overridden + Adds the image manager to the class dynamically """ - pass + if not hasattr(self, '_image_manager') or not self._image_manager: + self._image_manager = Registry().get('image_manager') + return self._image_manager + + @property + def media_controller(self): + """ + Adds the media controller to the class dynamically + """ + if not hasattr(self, '_media_controller') or not self._media_controller: + self._media_controller = Registry().get('media_controller') + return self._media_controller + + @property + def service_manager(self): + """ + Adds the service manager to the class dynamically + """ + if not hasattr(self, '_service_manager') or not self._service_manager: + self._service_manager = Registry().get('service_manager') + return self._service_manager + + @property + def preview_controller(self): + """ + Adds the preview controller to the class dynamically + """ + if not hasattr(self, '_preview_controller') or not self._preview_controller: + self._preview_controller = Registry().get('preview_controller') + return self._preview_controller + + @property + def live_controller(self): + """ + Adds the live controller to the class dynamically + """ + if not hasattr(self, '_live_controller') or not self._live_controller: + self._live_controller = Registry().get('live_controller') + return self._live_controller + + @property + def main_window(self): + """ + Adds the main window to the class dynamically + """ + if not hasattr(self, '_main_window') or not self._main_window: + self._main_window = Registry().get('main_window') + return self._main_window + + @property + def renderer(self): + """ + Adds the Renderer to the class dynamically + """ + if not hasattr(self, '_renderer') or not self._renderer: + self._renderer = Registry().get('renderer') + return self._renderer + + @property + def theme_manager(self): + """ + Adds the theme manager to the class dynamically + """ + if not hasattr(self, '_theme_manager') or not self._theme_manager: + self._theme_manager = Registry().get('theme_manager') + return self._theme_manager + + @property + def settings_form(self): + """ + Adds the settings form to the class dynamically + """ + if not hasattr(self, '_settings_form') or not self._settings_form: + self._settings_form = Registry().get('settings_form') + return self._settings_form + + @property + def alerts_manager(self): + """ + Adds the alerts manager to the class dynamically + """ + if not hasattr(self, '_alerts_manager') or not self._alerts_manager: + self._alerts_manager = Registry().get('alerts_manager') + return self._alerts_manager + + @property + def projector_manager(self): + """ + Adds the projector manager to the class dynamically + """ + if not hasattr(self, '_projector_manager') or not self._projector_manager: + self._projector_manager = Registry().get('projector_manager') + return self._projector_manager diff --git a/openlp/core/common/registry.py b/openlp/core/common/registry.py index 5cbe881ba..71978ae5d 100644 --- a/openlp/core/common/registry.py +++ b/openlp/core/common/registry.py @@ -25,7 +25,7 @@ Provide Registry Services import logging import sys -from openlp.core.common import is_win, trace_error_handler +from openlp.core.common import de_hump, trace_error_handler log = logging.getLogger(__name__) @@ -61,6 +61,15 @@ class Registry(object): registry.initialising = True return registry + @classmethod + def destroy(cls): + """ + Destroy the Registry. + """ + if cls.__instance__.running_under_test: + del cls.__instance__ + cls.__instance__ = None + def get(self, key): """ Extracts the registry value from the list based on the key passed in @@ -178,128 +187,30 @@ class Registry(object): del self.working_flags[key] -class RegistryProperties(object): +class RegistryBase(object): """ This adds registry components to classes to use at run time. """ + def __init__(self, *args, **kwargs): + """ + Register the class and bootstrap hooks. + """ + try: + super().__init__(*args, **kwargs) + except TypeError: + super().__init__() + Registry().register(de_hump(self.__class__.__name__), self) + Registry().register_function('bootstrap_initialise', self.bootstrap_initialise) + Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up) - @property - def application(self): + def bootstrap_initialise(self): """ - Adds the openlp to the class dynamically. - Windows needs to access the application in a dynamic manner. + Dummy method to be overridden """ - if is_win(): - return Registry().get('application') - else: - if not hasattr(self, '_application') or not self._application: - self._application = Registry().get('application') - return self._application + pass - @property - def plugin_manager(self): + def bootstrap_post_set_up(self): """ - Adds the plugin manager to the class dynamically + Dummy method to be overridden """ - if not hasattr(self, '_plugin_manager') or not self._plugin_manager: - self._plugin_manager = Registry().get('plugin_manager') - return self._plugin_manager - - @property - def image_manager(self): - """ - Adds the image manager to the class dynamically - """ - if not hasattr(self, '_image_manager') or not self._image_manager: - self._image_manager = Registry().get('image_manager') - return self._image_manager - - @property - def media_controller(self): - """ - Adds the media controller to the class dynamically - """ - if not hasattr(self, '_media_controller') or not self._media_controller: - self._media_controller = Registry().get('media_controller') - return self._media_controller - - @property - def service_manager(self): - """ - Adds the service manager to the class dynamically - """ - if not hasattr(self, '_service_manager') or not self._service_manager: - self._service_manager = Registry().get('service_manager') - return self._service_manager - - @property - def preview_controller(self): - """ - Adds the preview controller to the class dynamically - """ - if not hasattr(self, '_preview_controller') or not self._preview_controller: - self._preview_controller = Registry().get('preview_controller') - return self._preview_controller - - @property - def live_controller(self): - """ - Adds the live controller to the class dynamically - """ - if not hasattr(self, '_live_controller') or not self._live_controller: - self._live_controller = Registry().get('live_controller') - return self._live_controller - - @property - def main_window(self): - """ - Adds the main window to the class dynamically - """ - if not hasattr(self, '_main_window') or not self._main_window: - self._main_window = Registry().get('main_window') - return self._main_window - - @property - def renderer(self): - """ - Adds the Renderer to the class dynamically - """ - if not hasattr(self, '_renderer') or not self._renderer: - self._renderer = Registry().get('renderer') - return self._renderer - - @property - def theme_manager(self): - """ - Adds the theme manager to the class dynamically - """ - if not hasattr(self, '_theme_manager') or not self._theme_manager: - self._theme_manager = Registry().get('theme_manager') - return self._theme_manager - - @property - def settings_form(self): - """ - Adds the settings form to the class dynamically - """ - if not hasattr(self, '_settings_form') or not self._settings_form: - self._settings_form = Registry().get('settings_form') - return self._settings_form - - @property - def alerts_manager(self): - """ - Adds the alerts manager to the class dynamically - """ - if not hasattr(self, '_alerts_manager') or not self._alerts_manager: - self._alerts_manager = Registry().get('alerts_manager') - return self._alerts_manager - - @property - def projector_manager(self): - """ - Adds the projector manager to the class dynamically - """ - if not hasattr(self, '_projector_manager') or not self._projector_manager: - self._projector_manager = Registry().get('projector_manager') - return self._projector_manager + pass diff --git a/openlp/core/display/renderer.py b/openlp/core/display/renderer.py index fd040d9c6..9dd7f3d1f 100644 --- a/openlp/core/display/renderer.py +++ b/openlp/core/display/renderer.py @@ -25,9 +25,9 @@ import re from string import Template from PyQt5 import QtGui, QtCore, QtWebKitWidgets -from openlp.core.common.mixins import OpenLPMixin, RegistryMixin +from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.path import path_to_str -from openlp.core.common.registry import Registry, 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.lib import FormattingTags, ImageSource, ItemCapabilities, ServiceItem, expand_tags, build_chords_css, \ @@ -46,7 +46,7 @@ VERSE_FOR_LINE_COUNT = '\n'.join(map(str, range(100))) FOOTER = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456'] -class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties): +class Renderer(RegistryBase, LogMixin, RegistryProperties): """ Class to pull all Renderer interactions into one place. The plugins will call helper methods to do the rendering but this class will provide display defense code. diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index 698b1d73e..9e3c5aff9 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -350,6 +350,7 @@ class Manager(object): resulting in the plugin_name being used. :param upgrade_mod: The upgrade_schema function for this database """ + super().__init__() self.is_dirty = False self.session = None self.db_url = None diff --git a/tests/interfaces/openlp_core_api/__init__.py b/openlp/core/lib/json/__init__.py similarity index 100% rename from tests/interfaces/openlp_core_api/__init__.py rename to openlp/core/lib/json/__init__.py diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index dda12e10e..425fa0523 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -30,14 +30,15 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.path import Path, path_to_str, str_to_path -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import ServiceItem, StringContent, ServiceItemContext -from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.ui import create_widget_action, critical_error_message_box -from openlp.core.ui.lib.filedialog import FileDialog -from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD -from openlp.core.ui.lib.toolbar import OpenLPToolbar +from openlp.core.widgets.dialogs import FileDialog +from openlp.core.widgets.edits import SearchEdit +from openlp.core.widgets.toolbar import OpenLPToolbar +from openlp.core.widgets.views import ListWidgetWithDnD log = logging.getLogger(__name__) diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index 059b7a314..f155b3ce7 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -26,9 +26,10 @@ import logging from PyQt5 import QtCore -from openlp.core.common.registry import Registry, RegistryProperties -from openlp.core.common.settings import Settings from openlp.core.common.i18n import UiStrings +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry +from openlp.core.common.settings import Settings from openlp.core.version import get_version log = logging.getLogger(__name__) diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index 1bdb020aa..061788b25 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -26,12 +26,12 @@ import os from openlp.core.common import extension_loader from openlp.core.common.applocation import AppLocation -from openlp.core.common.registry import RegistryProperties -from openlp.core.common.mixins import OpenLPMixin, RegistryMixin +from openlp.core.common.mixins import LogMixin, RegistryProperties +from openlp.core.common.registry import RegistryBase from openlp.core.lib import Plugin, PluginStatus -class PluginManager(RegistryMixin, OpenLPMixin, RegistryProperties): +class PluginManager(RegistryBase, LogMixin, RegistryProperties): """ This is the Plugin manager, which loads all the plugins, and executes all the hooks, as and when necessary. diff --git a/openlp/core/lib/projector/db.py b/openlp/core/lib/projector/db.py index 223159a51..fa8934ae2 100644 --- a/openlp/core/lib/projector/db.py +++ b/openlp/core/lib/projector/db.py @@ -49,7 +49,7 @@ from openlp.core.lib.projector import upgrade Base = declarative_base(MetaData()) -class CommonBase(object): +class CommonMixin(object): """ Base class to automate table name and ID column. """ @@ -60,7 +60,7 @@ class CommonBase(object): id = Column(Integer, primary_key=True) -class Manufacturer(CommonBase, Base): +class Manufacturer(Base, CommonMixin): """ Projector manufacturer table. @@ -85,7 +85,7 @@ class Manufacturer(CommonBase, Base): lazy='joined') -class Model(CommonBase, Base): +class Model(Base, CommonMixin): """ Projector model table. @@ -113,7 +113,7 @@ class Model(CommonBase, Base): lazy='joined') -class Source(CommonBase, Base): +class Source(Base, CommonMixin): """ Projector video source table. @@ -140,7 +140,7 @@ class Source(CommonBase, Base): text = Column(String(30)) -class Projector(CommonBase, Base): +class Projector(Base, CommonMixin): """ Projector table. @@ -213,7 +213,7 @@ class Projector(CommonBase, Base): lazy='joined') -class ProjectorSource(CommonBase, Base): +class ProjectorSource(Base, CommonMixin): """ Projector local source table This table allows mapping specific projector source input to a local diff --git a/openlp/core/lib/projector/pjlink.py b/openlp/core/lib/projector/pjlink.py index 2eb4da32c..2272b971f 100644 --- a/openlp/core/lib/projector/pjlink.py +++ b/openlp/core/lib/projector/pjlink.py @@ -514,7 +514,7 @@ class PJLinkCommands(object): self.sw_version_received = data -class PJLink(PJLinkCommands, QtNetwork.QTcpSocket): +class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ Socket service for PJLink TCP socket. """ diff --git a/openlp/core/lib/searchedit.py b/openlp/core/lib/searchedit.py deleted file mode 100644 index db45486f9..000000000 --- a/openlp/core/lib/searchedit.py +++ /dev/null @@ -1,175 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2017 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; version 2 of the License. # -# # -# 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, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### - -import logging - -from PyQt5 import QtCore, QtWidgets - -from openlp.core.common.settings import Settings -from openlp.core.lib import build_icon -from openlp.core.lib.ui import create_widget_action - -log = logging.getLogger(__name__) - - -class SearchEdit(QtWidgets.QLineEdit): - """ - This is a specialised QLineEdit with a "clear" button inside for searches. - """ - searchTypeChanged = QtCore.pyqtSignal(QtCore.QVariant) - cleared = QtCore.pyqtSignal() - - def __init__(self, parent, settings_section): - """ - Constructor. - """ - super().__init__(parent) - self.settings_section = settings_section - self._current_search_type = -1 - self.clear_button = QtWidgets.QToolButton(self) - self.clear_button.setIcon(build_icon(':/system/clear_shortcut.png')) - self.clear_button.setCursor(QtCore.Qt.ArrowCursor) - self.clear_button.setStyleSheet('QToolButton { border: none; padding: 0px; }') - self.clear_button.resize(18, 18) - self.clear_button.hide() - self.clear_button.clicked.connect(self._on_clear_button_clicked) - self.textChanged.connect(self._on_search_edit_text_changed) - self._update_style_sheet() - self.setAcceptDrops(False) - - def _update_style_sheet(self): - """ - Internal method to update the stylesheet depending on which widgets are available and visible. - """ - frame_width = self.style().pixelMetric(QtWidgets.QStyle.PM_DefaultFrameWidth) - right_padding = self.clear_button.width() + frame_width - if hasattr(self, 'menu_button'): - left_padding = self.menu_button.width() - stylesheet = 'QLineEdit {{ padding-left:{left}px; padding-right: {right}px; }} '.format(left=left_padding, - right=right_padding) - else: - stylesheet = 'QLineEdit {{ padding-right: {right}px; }} '.format(right=right_padding) - self.setStyleSheet(stylesheet) - msz = self.minimumSizeHint() - self.setMinimumSize(max(msz.width(), self.clear_button.width() + (frame_width * 2) + 2), - max(msz.height(), self.clear_button.height() + (frame_width * 2) + 2)) - - def resizeEvent(self, event): - """ - Reimplemented method to react to resizing of the widget. - - :param event: The event that happened. - """ - size = self.clear_button.size() - frame_width = self.style().pixelMetric(QtWidgets.QStyle.PM_DefaultFrameWidth) - self.clear_button.move(self.rect().right() - frame_width - size.width(), - (self.rect().bottom() + 1 - size.height()) // 2) - if hasattr(self, 'menu_button'): - size = self.menu_button.size() - self.menu_button.move(self.rect().left() + frame_width + 2, (self.rect().bottom() + 1 - size.height()) // 2) - - def current_search_type(self): - """ - Readonly property to return the current search type. - """ - return self._current_search_type - - def set_current_search_type(self, identifier): - """ - Set a new current search type. - - :param identifier: The search type identifier (int). - """ - menu = self.menu_button.menu() - for action in menu.actions(): - if identifier == action.data(): - self.setPlaceholderText(action.placeholder_text) - self.menu_button.setDefaultAction(action) - self._current_search_type = identifier - Settings().setValue('{section}/last used search type'.format(section=self.settings_section), identifier) - self.searchTypeChanged.emit(identifier) - return True - - def set_search_types(self, items): - """ - A list of tuples to be used in the search type menu. The first item in the list will be preselected as the - default. - - :param items: The list of tuples to use. The tuples should contain an integer identifier, an icon (QIcon - instance or string) and a title for the item in the menu. In short, they should look like this:: - - (, , , <place holder text>) - - For instance:: - - (1, <QIcon instance>, "Titles", "Search Song Titles...") - - Or:: - - (2, ":/songs/authors.png", "Authors", "Search Authors...") - """ - menu = QtWidgets.QMenu(self) - for identifier, icon, title, placeholder in items: - action = create_widget_action( - menu, text=title, icon=icon, data=identifier, triggers=self._on_menu_action_triggered) - action.placeholder_text = placeholder - if not hasattr(self, 'menu_button'): - self.menu_button = QtWidgets.QToolButton(self) - self.menu_button.setIcon(build_icon(':/system/clear_shortcut.png')) - self.menu_button.setCursor(QtCore.Qt.ArrowCursor) - self.menu_button.setPopupMode(QtWidgets.QToolButton.InstantPopup) - self.menu_button.setStyleSheet('QToolButton { border: none; padding: 0px 10px 0px 0px; }') - self.menu_button.resize(QtCore.QSize(28, 18)) - self.menu_button.setMenu(menu) - self.set_current_search_type( - Settings().value('{section}/last used search type'.format(section=self.settings_section))) - self.menu_button.show() - self._update_style_sheet() - - def _on_search_edit_text_changed(self, text): - """ - Internally implemented slot to react to when the text in the line edit has changed so that we can show or hide - the clear button. - - :param text: A :class:`~PyQt5.QtCore.QString` instance which represents the text in the line edit. - """ - self.clear_button.setVisible(bool(text)) - - def _on_clear_button_clicked(self): - """ - Internally implemented slot to react to the clear button being clicked to clear the line edit. Once it has - cleared the line edit, it emits the ``cleared()`` signal so that an application can react to the clearing of the - line edit. - """ - self.clear() - self.cleared.emit() - - def _on_menu_action_triggered(self): - """ - Internally implemented slot to react to the select of one of the search types in the menu. Once it has set the - correct action on the button, and set the current search type (using the list of identifiers provided by the - developer), the ``searchTypeChanged(int)`` signal is emitted with the identifier. - """ - for action in self.menu_button.menu().actions(): - # Why is this needed? - action.setChecked(False) - self.set_current_search_type(self.sender().data()) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 22b1f9b5f..3a824d424 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -35,7 +35,7 @@ from PyQt5 import QtGui 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.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.core.common.settings import Settings from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, expand_chords diff --git a/openlp/core/lib/settingstab.py b/openlp/core/lib/settingstab.py index 4b259465f..06009ee3d 100644 --- a/openlp/core/lib/settingstab.py +++ b/openlp/core/lib/settingstab.py @@ -25,7 +25,7 @@ own tab to the settings dialog. """ from PyQt5 import QtWidgets -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties class SettingsTab(QtWidgets.QWidget, RegistryProperties): diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index 9581dc6f9..2d434c0c6 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -32,8 +32,9 @@ from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings, format_time, translate from openlp.core.common.settings import Settings from openlp.core.lib import SettingsTab, build_icon -from openlp.core.ui.lib import PathEdit, PathType from openlp.core.ui.style import HAS_DARK_STYLE +from openlp.core.widgets.edits import PathEdit +from openlp.core.widgets.enums import PathEditType log = logging.getLogger(__name__) @@ -122,7 +123,7 @@ class AdvancedTab(SettingsTab): self.data_directory_layout.setObjectName('data_directory_layout') self.data_directory_new_label = QtWidgets.QLabel(self.data_directory_group_box) self.data_directory_new_label.setObjectName('data_directory_current_label') - self.data_directory_path_edit = PathEdit(self.data_directory_group_box, path_type=PathType.Directories, + self.data_directory_path_edit = PathEdit(self.data_directory_group_box, path_type=PathEditType.Directories, default_path=AppLocation.get_directory(AppLocation.DataDir)) self.data_directory_layout.addRow(self.data_directory_new_label, self.data_directory_path_edit) self.new_data_directory_has_files_label = QtWidgets.QLabel(self.data_directory_group_box) diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index 1ceeed989..70fe2c416 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.py @@ -72,10 +72,10 @@ except ImportError: from openlp.core.common import is_linux from openlp.core.common.i18n import UiStrings, translate -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.core.common.settings import Settings from openlp.core.ui.exceptiondialog import Ui_ExceptionDialog -from openlp.core.ui.lib.filedialog import FileDialog +from openlp.core.widgets.dialogs import FileDialog from openlp.core.version import get_version diff --git a/openlp/core/ui/filerenameform.py b/openlp/core/ui/filerenameform.py index d6a519240..249ea1f10 100644 --- a/openlp/core/ui/filerenameform.py +++ b/openlp/core/ui/filerenameform.py @@ -25,7 +25,8 @@ The file rename dialog. from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import translate -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.ui.filerenamedialog import Ui_FileRenameDialog diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index ea98577c8..37fac2dd4 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -38,7 +38,8 @@ from openlp.core.common import clean_button_text, trace_error_handler from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import translate from openlp.core.common.path import Path, create_paths -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import PluginStatus, build_icon from openlp.core.lib.ui import critical_error_message_box diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py index 8488f13a1..a908ac91d 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.py @@ -33,7 +33,8 @@ from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.display.screens import ScreenList from openlp.core.lib import SettingsTab -from openlp.core.ui.lib import ColorButton, PathEdit +from openlp.core.widgets.buttons import ColorButton +from openlp.core.widgets.edits import PathEdit log = logging.getLogger(__name__) diff --git a/openlp/core/ui/lib/historycombobox.py b/openlp/core/ui/lib/historycombobox.py deleted file mode 100644 index 6320bc383..000000000 --- a/openlp/core/ui/lib/historycombobox.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2017 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; version 2 of the License. # -# # -# 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, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### -""" -The :mod:`~openlp.core.ui.lib.historycombobox` module contains the HistoryComboBox widget -""" - -from PyQt5 import QtCore, QtWidgets - - -class HistoryComboBox(QtWidgets.QComboBox): - """ - The :class:`~openlp.core.common.historycombobox.HistoryComboBox` widget emulates the QLineEdit ``returnPressed`` - signal for when the :kbd:`Enter` or :kbd:`Return` keys are pressed, and saves anything that is typed into the edit - box into its list. - """ - returnPressed = QtCore.pyqtSignal() - - def __init__(self, parent=None): - """ - Initialise the combo box, setting duplicates to False and the insert policy to insert items at the top. - - :param parent: The parent widget - """ - super().__init__(parent) - self.setDuplicatesEnabled(False) - self.setEditable(True) - self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) - self.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop) - - def keyPressEvent(self, event): - """ - Override the inherited keyPressEvent method to emit the ``returnPressed`` signal and to save the current text to - the dropdown list. - - :param event: The keyboard event - """ - # Handle Enter and Return ourselves - if event.key() == QtCore.Qt.Key_Enter or event.key() == QtCore.Qt.Key_Return: - # Emit the returnPressed signal - self.returnPressed.emit() - # Save the current text to the dropdown list - if self.currentText() and self.findText(self.currentText()) == -1: - self.insertItem(0, self.currentText()) - # Let the parent handle any keypress events - super().keyPressEvent(event) - - def focusOutEvent(self, event): - """ - Override the inherited focusOutEvent to save the current text to the dropdown list. - - :param event: The focus event - """ - # Save the current text to the dropdown list - if self.currentText() and self.findText(self.currentText()) == -1: - self.insertItem(0, self.currentText()) - # Let the parent handle any keypress events - super().focusOutEvent(event) - - def getItems(self): - """ - Get all the items from the history - - :return: A list of strings - """ - return [self.itemText(i) for i in range(self.count())] diff --git a/openlp/core/ui/lib/listwidgetwithdnd.py b/openlp/core/ui/lib/listwidgetwithdnd.py deleted file mode 100755 index 5648ff8f4..000000000 --- a/openlp/core/ui/lib/listwidgetwithdnd.py +++ /dev/null @@ -1,153 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2017 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; version 2 of the License. # -# # -# 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, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### -""" -Extend QListWidget to handle drag and drop functionality -""" -import os - -from PyQt5 import QtCore, QtGui, QtWidgets - -from openlp.core.common.i18n import UiStrings -from openlp.core.common.registry import Registry - - -class ListWidgetWithDnD(QtWidgets.QListWidget): - """ - Provide a list widget to store objects and handle drag and drop events - """ - def __init__(self, parent=None, name=''): - """ - Initialise the list widget - """ - super().__init__(parent) - self.mime_data_text = name - self.no_results_text = UiStrings().NoResults - self.setSpacing(1) - self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.setAlternatingRowColors(True) - self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - - def activateDnD(self): - """ - Activate DnD of widget - """ - self.setAcceptDrops(True) - self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) - Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file) - - def clear(self, search_while_typing=False): - """ - Re-implement clear, so that we can customise feedback when using 'Search as you type' - - :param search_while_typing: True if we want to display the customised message - :return: None - """ - if search_while_typing: - self.no_results_text = UiStrings().ShortResults - else: - self.no_results_text = UiStrings().NoResults - super().clear() - - def mouseMoveEvent(self, event): - """ - Drag and drop event does not care what data is selected as the recipient will use events to request the data - move just tell it what plugin to call - """ - if event.buttons() != QtCore.Qt.LeftButton: - event.ignore() - return - if not self.selectedItems(): - event.ignore() - return - drag = QtGui.QDrag(self) - mime_data = QtCore.QMimeData() - drag.setMimeData(mime_data) - mime_data.setText(self.mime_data_text) - drag.exec(QtCore.Qt.CopyAction) - - def dragEnterEvent(self, event): - """ - When something is dragged into this object, check if you should be able to drop it in here. - """ - if event.mimeData().hasUrls(): - event.accept() - else: - event.ignore() - - def dragMoveEvent(self, event): - """ - Make an object droppable, and set it to copy the contents of the object, not move it. - """ - if event.mimeData().hasUrls(): - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - else: - event.ignore() - - def dropEvent(self, event): - """ - Receive drop event check if it is a file and process it if it is. - - :param event: Handle of the event pint passed - """ - if event.mimeData().hasUrls(): - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - files = [] - for url in event.mimeData().urls(): - local_file = os.path.normpath(url.toLocalFile()) - if os.path.isfile(local_file): - files.append(local_file) - elif os.path.isdir(local_file): - listing = os.listdir(local_file) - for file in listing: - files.append(os.path.join(local_file, file)) - Registry().execute('{mime_data}_dnd'.format(mime_data=self.mime_data_text), - {'files': files, 'target': self.itemAt(event.pos())}) - else: - event.ignore() - - def allItems(self): - """ - An generator to list all the items in the widget - - :return: a generator - """ - for row in range(self.count()): - yield self.item(row) - - def paintEvent(self, event): - """ - Re-implement paintEvent so that we can add 'No Results' text when the listWidget is empty. - - :param event: A QPaintEvent - :return: None - """ - super().paintEvent(event) - if not self.count(): - viewport = self.viewport() - painter = QtGui.QPainter(viewport) - font = QtGui.QFont() - font.setItalic(True) - painter.setFont(font) - painter.drawText(QtCore.QRect(0, 0, viewport.width(), viewport.height()), - (QtCore.Qt.AlignHCenter | QtCore.Qt.TextWordWrap), self.no_results_text) diff --git a/openlp/core/ui/lib/pathedit.py b/openlp/core/ui/lib/pathedit.py deleted file mode 100644 index 7b28c47ba..000000000 --- a/openlp/core/ui/lib/pathedit.py +++ /dev/null @@ -1,196 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2017 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; version 2 of the License. # -# # -# 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, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### -from enum import Enum - -from PyQt5 import QtCore, QtWidgets - -from openlp.core.common.i18n import UiStrings, translate -from openlp.core.common.path import Path, path_to_str, str_to_path -from openlp.core.lib import build_icon -from openlp.core.ui.lib.filedialog import FileDialog - - -class PathType(Enum): - Files = 1 - Directories = 2 - - -class PathEdit(QtWidgets.QWidget): - """ - The :class:`~openlp.core.ui.lib.pathedit.PathEdit` class subclasses QWidget to create a custom widget for use when - a file or directory needs to be selected. - """ - pathChanged = QtCore.pyqtSignal(Path) - - def __init__(self, parent=None, path_type=PathType.Files, default_path=None, dialog_caption=None, show_revert=True): - """ - Initialise the PathEdit widget - - :param QtWidget.QWidget | None: The parent of the widget. This is just passed to the super method. - :param str dialog_caption: Used to customise the caption in the QFileDialog. - :param openlp.core.common.path.Path default_path: The default path. This is set as the path when the revert - button is clicked - :param bool show_revert: Used to determine if the 'revert button' should be visible. - :rtype: None - """ - super().__init__(parent) - self.default_path = default_path - self.dialog_caption = dialog_caption - self._path_type = path_type - self._path = None - self.filters = '{all_files} (*)'.format(all_files=UiStrings().AllFiles) - self._setup(show_revert) - - def _setup(self, show_revert): - """ - Set up the widget - :param bool show_revert: Show or hide the revert button - :rtype: None - """ - widget_layout = QtWidgets.QHBoxLayout() - widget_layout.setContentsMargins(0, 0, 0, 0) - self.line_edit = QtWidgets.QLineEdit(self) - widget_layout.addWidget(self.line_edit) - self.browse_button = QtWidgets.QToolButton(self) - self.browse_button.setIcon(build_icon(':/general/general_open.png')) - widget_layout.addWidget(self.browse_button) - self.revert_button = QtWidgets.QToolButton(self) - self.revert_button.setIcon(build_icon(':/general/general_revert.png')) - self.revert_button.setVisible(show_revert) - widget_layout.addWidget(self.revert_button) - self.setLayout(widget_layout) - # Signals and Slots - self.browse_button.clicked.connect(self.on_browse_button_clicked) - self.revert_button.clicked.connect(self.on_revert_button_clicked) - self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished) - self.update_button_tool_tips() - - @property - def path(self): - """ - A property getter method to return the selected path. - - :return: The selected path - :rtype: openlp.core.common.path.Path - """ - return self._path - - @path.setter - def path(self, path): - """ - A Property setter method to set the selected path - - :param openlp.core.common.path.Path path: The path to set the widget to - :rtype: None - """ - self._path = path - text = path_to_str(path) - self.line_edit.setText(text) - self.line_edit.setToolTip(text) - - @property - def path_type(self): - """ - A property getter method to return the path_type. Path type allows you to sepecify if the user is restricted to - selecting a file or directory. - - :return: The type selected - :rtype: PathType - """ - return self._path_type - - @path_type.setter - def path_type(self, path_type): - """ - A Property setter method to set the path type - - :param PathType path_type: The type of path to select - :rtype: None - """ - self._path_type = path_type - self.update_button_tool_tips() - - def update_button_tool_tips(self): - """ - Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised - - :rtype: None - """ - if self._path_type == PathType.Directories: - self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.')) - self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default directory.')) - else: - self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for file.')) - self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default file.')) - - def on_browse_button_clicked(self): - """ - A handler to handle a click on the browse button. - - Show the QFileDialog and process the input from the user - - :rtype: None - """ - caption = self.dialog_caption - path = None - if self._path_type == PathType.Directories: - if not caption: - caption = translate('OpenLP.PathEdit', 'Select Directory') - path = FileDialog.getExistingDirectory(self, caption, self._path, FileDialog.ShowDirsOnly) - elif self._path_type == PathType.Files: - if not caption: - caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File') - path, filter_used = FileDialog.getOpenFileName(self, caption, self._path, self.filters) - if path: - self.on_new_path(path) - - def on_revert_button_clicked(self): - """ - A handler to handle a click on the revert button. - - Set the new path to the value of the default_path instance variable. - - :rtype: None - """ - self.on_new_path(self.default_path) - - def on_line_edit_editing_finished(self): - """ - A handler to handle when the line edit has finished being edited. - - :rtype: None - """ - path = str_to_path(self.line_edit.text()) - self.on_new_path(path) - - def on_new_path(self, path): - """ - A method called to validate and set a new path. - - Emits the pathChanged Signal - - :param openlp.core.common.path.Path path: The new path - :rtype: None - """ - if self._path != path: - self.path = path - self.pathChanged.emit(path) diff --git a/openlp/core/ui/lib/spelltextedit.py b/openlp/core/ui/lib/spelltextedit.py deleted file mode 100644 index d0fc25af2..000000000 --- a/openlp/core/ui/lib/spelltextedit.py +++ /dev/null @@ -1,205 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2017 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; version 2 of the License. # -# # -# 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, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### -""" -The :mod:`~openlp.core.lib.spelltextedit` module contains a classes to add spell checking to an edit widget. -""" - -import logging -import re - -try: - import enchant - from enchant import DictNotFoundError - from enchant.errors import Error - ENCHANT_AVAILABLE = True -except ImportError: - ENCHANT_AVAILABLE = False - -# based on code from http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check - -from PyQt5 import QtCore, QtGui, QtWidgets - -from openlp.core.common.i18n import translate -from openlp.core.lib import FormattingTags -from openlp.core.lib.ui import create_action - -log = logging.getLogger(__name__) - - -class SpellTextEdit(QtWidgets.QPlainTextEdit): - """ - Spell checking widget based on QPlanTextEdit. - """ - def __init__(self, parent=None, formatting_tags_allowed=True): - """ - Constructor. - """ - global ENCHANT_AVAILABLE - super(SpellTextEdit, self).__init__(parent) - self.formatting_tags_allowed = formatting_tags_allowed - # Default dictionary based on the current locale. - if ENCHANT_AVAILABLE: - try: - self.dictionary = enchant.Dict() - self.highlighter = Highlighter(self.document()) - self.highlighter.spelling_dictionary = self.dictionary - except (Error, DictNotFoundError): - ENCHANT_AVAILABLE = False - log.debug('Could not load default dictionary') - - def mousePressEvent(self, event): - """ - Handle mouse clicks within the text edit region. - """ - if event.button() == QtCore.Qt.RightButton: - # Rewrite the mouse event to a left button event so the cursor is moved to the location of the pointer. - event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, - event.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier) - QtWidgets.QPlainTextEdit.mousePressEvent(self, event) - - def contextMenuEvent(self, event): - """ - Provide the context menu for the text edit region. - """ - popup_menu = self.createStandardContextMenu() - # Select the word under the cursor. - cursor = self.textCursor() - # only select text if not already selected - if not cursor.hasSelection(): - cursor.select(QtGui.QTextCursor.WordUnderCursor) - self.setTextCursor(cursor) - # Add menu with available languages. - if ENCHANT_AVAILABLE: - lang_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Language:')) - for lang in enchant.list_languages(): - action = create_action(lang_menu, lang, text=lang, checked=lang == self.dictionary.tag) - lang_menu.addAction(action) - popup_menu.insertSeparator(popup_menu.actions()[0]) - popup_menu.insertMenu(popup_menu.actions()[0], lang_menu) - lang_menu.triggered.connect(self.set_language) - # Check if the selected word is misspelled and offer spelling suggestions if it is. - if ENCHANT_AVAILABLE and self.textCursor().hasSelection(): - text = self.textCursor().selectedText() - if not self.dictionary.check(text): - spell_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Spelling Suggestions')) - for word in self.dictionary.suggest(text): - action = SpellAction(word, spell_menu) - action.correct.connect(self.correct_word) - spell_menu.addAction(action) - # Only add the spelling suggests to the menu if there are suggestions. - if spell_menu.actions(): - popup_menu.insertMenu(popup_menu.actions()[0], spell_menu) - tag_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Formatting Tags')) - if self.formatting_tags_allowed: - for html in FormattingTags.get_html_tags(): - action = SpellAction(html['desc'], tag_menu) - action.correct.connect(self.html_tag) - tag_menu.addAction(action) - popup_menu.insertSeparator(popup_menu.actions()[0]) - popup_menu.insertMenu(popup_menu.actions()[0], tag_menu) - popup_menu.exec(event.globalPos()) - - def set_language(self, action): - """ - Changes the language for this spelltextedit. - - :param action: The action. - """ - self.dictionary = enchant.Dict(action.text()) - self.highlighter.spelling_dictionary = self.dictionary - self.highlighter.highlightBlock(self.toPlainText()) - self.highlighter.rehighlight() - - def correct_word(self, word): - """ - Replaces the selected text with word. - """ - cursor = self.textCursor() - cursor.beginEditBlock() - cursor.removeSelectedText() - cursor.insertText(word) - cursor.endEditBlock() - - def html_tag(self, tag): - """ - Replaces the selected text with word. - """ - tag = tag.replace('&', '') - for html in FormattingTags.get_html_tags(): - if tag == html['desc']: - cursor = self.textCursor() - if self.textCursor().hasSelection(): - text = cursor.selectedText() - cursor.beginEditBlock() - cursor.removeSelectedText() - cursor.insertText(html['start tag']) - cursor.insertText(text) - cursor.insertText(html['end tag']) - cursor.endEditBlock() - else: - cursor = self.textCursor() - cursor.insertText(html['start tag']) - cursor.insertText(html['end tag']) - - -class Highlighter(QtGui.QSyntaxHighlighter): - """ - Provides a text highlighter for pointing out spelling errors in text. - """ - WORDS = r'(?iu)[\w\']+' - - def __init__(self, *args): - """ - Constructor - """ - super(Highlighter, self).__init__(*args) - self.spelling_dictionary = None - - def highlightBlock(self, text): - """ - Highlight mis spelt words in a block of text. - - Note, this is a Qt hook. - """ - if not self.spelling_dictionary: - return - text = str(text) - char_format = QtGui.QTextCharFormat() - char_format.setUnderlineColor(QtCore.Qt.red) - char_format.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline) - for word_object in re.finditer(self.WORDS, text): - if not self.spelling_dictionary.check(word_object.group()): - self.setFormat(word_object.start(), word_object.end() - word_object.start(), char_format) - - -class SpellAction(QtWidgets.QAction): - """ - A special QAction that returns the text in a signal. - """ - correct = QtCore.pyqtSignal(str) - - def __init__(self, *args): - """ - Constructor - """ - super(SpellAction, self).__init__(*args) - self.triggered.connect(lambda x: self.correct.emit(self.text())) diff --git a/openlp/core/ui/lib/treewidgetwithdnd.py b/openlp/core/ui/lib/treewidgetwithdnd.py deleted file mode 100644 index 792fa8ab8..000000000 --- a/openlp/core/ui/lib/treewidgetwithdnd.py +++ /dev/null @@ -1,145 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2017 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; version 2 of the License. # -# # -# 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, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### -""" -Extend QTreeWidget to handle drag and drop functionality -""" -import os - -from PyQt5 import QtCore, QtGui, QtWidgets - -from openlp.core.common import is_win -from openlp.core.common.registry import Registry - - -class TreeWidgetWithDnD(QtWidgets.QTreeWidget): - """ - Provide a tree widget to store objects and handle drag and drop events - """ - def __init__(self, parent=None, name=''): - """ - Initialise the tree widget - """ - super(TreeWidgetWithDnD, self).__init__(parent) - self.mime_data_text = name - self.allow_internal_dnd = False - self.header().close() - self.default_indentation = self.indentation() - self.setIndentation(0) - self.setAnimated(True) - - def activateDnD(self): - """ - Activate DnD of widget - """ - self.setAcceptDrops(True) - self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) - Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file) - Registry().register_function(('%s_dnd_internal' % self.mime_data_text), self.parent().dnd_move_internal) - - def mouseMoveEvent(self, event): - """ - Drag and drop event does not care what data is selected as the recipient will use events to request the data - move just tell it what plugin to call - - :param event: The event that occurred - """ - if event.buttons() != QtCore.Qt.LeftButton: - event.ignore() - return - if not self.selectedItems(): - event.ignore() - return - drag = QtGui.QDrag(self) - mime_data = QtCore.QMimeData() - drag.setMimeData(mime_data) - mime_data.setText(self.mime_data_text) - drag.exec(QtCore.Qt.CopyAction) - - def dragEnterEvent(self, event): - """ - Receive drag enter event, check if it is a file or internal object and allow it if it is. - - :param event: The event that occurred - """ - if event.mimeData().hasUrls(): - event.accept() - elif self.allow_internal_dnd: - event.accept() - else: - event.ignore() - - def dragMoveEvent(self, event): - """ - Receive drag move event, check if it is a file or internal object and allow it if it is. - - :param event: The event that occurred - """ - QtWidgets.QTreeWidget.dragMoveEvent(self, event) - if event.mimeData().hasUrls(): - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - elif self.allow_internal_dnd: - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - else: - event.ignore() - - def dropEvent(self, event): - """ - Receive drop event, check if it is a file or internal object and process it if it is. - - :param event: Handle of the event pint passed - """ - # If we are on Windows, OpenLP window will not be set on top. For example, user can drag images to Library and - # the folder stays on top of the group creation box. This piece of code fixes this issue. - if is_win(): - self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) - self.setWindowState(QtCore.Qt.WindowNoState) - if event.mimeData().hasUrls(): - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - files = [] - for url in event.mimeData().urls(): - local_file = url.toLocalFile() - if os.path.isfile(local_file): - files.append(local_file) - elif os.path.isdir(local_file): - listing = os.listdir(local_file) - for file_name in listing: - files.append(os.path.join(local_file, file_name)) - Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())}) - elif self.allow_internal_dnd: - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - Registry().execute('%s_dnd_internal' % self.mime_data_text, self.itemAt(event.pos())) - else: - event.ignore() - - # Convenience methods for emulating a QListWidget. This helps keeping MediaManagerItem simple. - def addItem(self, item): - self.addTopLevelItem(item) - - def count(self): - return self.topLevelItemCount() - - def item(self, index): - return self.topLevelItem(index) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index d7f2264ee..ca634910e 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -36,9 +36,9 @@ from PyQt5 import QtCore, QtWidgets, QtWebKit, QtWebKitWidgets, QtGui, QtMultime from openlp.core.common import is_macosx, is_win from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import translate -from openlp.core.common.mixins import OpenLPMixin +from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.path import path_to_str -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.display.screens import ScreenList from openlp.core.lib import ServiceItem, ImageSource, build_html, expand_tags, image_to_byte @@ -131,7 +131,7 @@ class Display(QtWidgets.QGraphicsView): self.web_loaded = True -class MainDisplay(OpenLPMixin, Display, RegistryProperties): +class MainDisplay(Display, LogMixin, RegistryProperties): """ This is the display screen as a specialized class from the Display class """ @@ -603,7 +603,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): self.web_view.setGeometry(0, 0, self.width(), self.height()) -class AudioPlayer(OpenLPMixin, QtCore.QObject): +class AudioPlayer(LogMixin, QtCore.QObject): """ This Class will play audio only allowing components to work with a soundtrack independent of the user interface. """ diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 26a8b921a..5fa9b2a2f 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -41,7 +41,8 @@ from openlp.core.common.actions import ActionList, CategoryOrder from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import LanguageManager, UiStrings, translate from openlp.core.common.path import Path, copyfile, create_paths, path_to_str, str_to_path -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.display.screens import ScreenList from openlp.core.display.renderer import Renderer @@ -50,9 +51,8 @@ from openlp.core.lib.ui import create_action from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \ ShortcutListForm, FormattingTagForm, PreviewController from openlp.core.ui.firsttimeform import FirstTimeForm -from openlp.core.ui.lib.dockwidget import OpenLPDockWidget -from openlp.core.ui.lib.filedialog import FileDialog -from openlp.core.ui.lib.mediadockmanager import MediaDockManager +from openlp.core.widgets.dialogs import FileDialog +from openlp.core.widgets.docks import OpenLPDockWidget, MediaDockManager from openlp.core.ui.media import MediaController from openlp.core.ui.printserviceform import PrintServiceForm from openlp.core.ui.projector.manager import ProjectorManager diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index bdc315556..c72d6669d 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -31,8 +31,8 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.api.http import register_endpoint from openlp.core.common import extension_loader from openlp.core.common.i18n import UiStrings, translate -from openlp.core.common.mixins import OpenLPMixin, RegistryMixin -from openlp.core.common.registry import Registry, RegistryProperties +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.lib import ItemCapabilities from openlp.core.lib.ui import critical_error_message_box @@ -42,7 +42,7 @@ from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper from openlp.core.ui.media.mediaplayer import MediaPlayer from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\ parse_optical_path -from openlp.core.ui.lib.toolbar import OpenLPToolbar +from openlp.core.widgets.toolbar import OpenLPToolbar log = logging.getLogger(__name__) @@ -91,7 +91,7 @@ class MediaSlider(QtWidgets.QSlider): QtWidgets.QSlider.mouseReleaseEvent(self, event) -class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): +class MediaController(RegistryBase, LogMixin, RegistryProperties): """ The implementation of the Media Controller. The Media Controller adds an own class for every Player. Currently these are QtWebkit, Phonon and Vlc. display_controllers are an array of controllers keyed on the diff --git a/openlp/core/ui/media/mediaplayer.py b/openlp/core/ui/media/mediaplayer.py index e4d210513..77d089c89 100644 --- a/openlp/core/ui/media/mediaplayer.py +++ b/openlp/core/ui/media/mediaplayer.py @@ -22,7 +22,7 @@ """ The :mod:`~openlp.core.ui.media.mediaplayer` module contains the MediaPlayer class. """ -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.core.ui.media import MediaState diff --git a/openlp/core/ui/media/playertab.py b/openlp/core/ui/media/playertab.py index f719a167b..28d7798ee 100644 --- a/openlp/core/ui/media/playertab.py +++ b/openlp/core/ui/media/playertab.py @@ -23,6 +23,7 @@ The :mod:`~openlp.core.ui.media.playertab` module holds the configuration tab for the media stuff. """ import platform + from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import UiStrings, translate @@ -31,7 +32,7 @@ from openlp.core.common.settings import Settings from openlp.core.lib import SettingsTab from openlp.core.lib.ui import create_button from openlp.core.ui.media import get_media_players, set_media_players -from openlp.core.ui.lib.colorbutton import ColorButton +from openlp.core.widgets.buttons import ColorButton class MediaQCheckBox(QtWidgets.QCheckBox): diff --git a/openlp/core/ui/pluginform.py b/openlp/core/ui/pluginform.py index b34d79714..43fa5e68d 100644 --- a/openlp/core/ui/pluginform.py +++ b/openlp/core/ui/pluginform.py @@ -27,7 +27,7 @@ import logging from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import translate -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.core.lib import PluginStatus from openlp.core.ui.plugindialog import Ui_PluginViewDialog diff --git a/openlp/core/ui/printservicedialog.py b/openlp/core/ui/printservicedialog.py index d71b42cdb..ee3b07080 100644 --- a/openlp/core/ui/printservicedialog.py +++ b/openlp/core/ui/printservicedialog.py @@ -26,7 +26,7 @@ from PyQt5 import QtCore, QtWidgets, QtPrintSupport from openlp.core.common.i18n import UiStrings, translate from openlp.core.lib import build_icon -from openlp.core.ui.lib import SpellTextEdit +from openlp.core.widgets.edits import SpellTextEdit class ZoomSize(object): diff --git a/openlp/core/ui/printserviceform.py b/openlp/core/ui/printserviceform.py index 482d0f084..07ab24496 100644 --- a/openlp/core/ui/printserviceform.py +++ b/openlp/core/ui/printserviceform.py @@ -30,7 +30,8 @@ from PyQt5 import QtCore, QtGui, QtWidgets, QtPrintSupport from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings, translate -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import get_text_file_string from openlp.core.ui.printservicedialog import Ui_PrintServiceDialog, ZoomSize diff --git a/openlp/core/ui/projector/manager.py b/openlp/core/ui/projector/manager.py index 47bbb832e..0770886e4 100644 --- a/openlp/core/ui/projector/manager.py +++ b/openlp/core/ui/projector/manager.py @@ -20,9 +20,9 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ - :mod: openlp.core.ui.projector.manager` module +:mod: openlp.core.ui.projector.manager` module - Provides the functions for the display/control of Projectors. +Provides the functions for the display/control of Projectors. """ import logging @@ -30,8 +30,8 @@ import logging from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common.i18n import translate -from openlp.core.common.mixins import OpenLPMixin, RegistryMixin -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import LogMixin, RegistryProperties +from openlp.core.common.registry import RegistryBase from openlp.core.common.settings import Settings from openlp.core.lib.ui import create_widget_action from openlp.core.lib.projector import DialogSourceStyle @@ -40,9 +40,9 @@ from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHE S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP from openlp.core.lib.projector.db import ProjectorDB from openlp.core.lib.projector.pjlink import PJLink, PJLinkUDP -from openlp.core.ui.lib import OpenLPToolbar from openlp.core.ui.projector.editform import ProjectorEditForm from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle +from openlp.core.widgets.toolbar import OpenLPToolbar log = logging.getLogger(__name__) log.debug('projectormanager loaded') @@ -276,7 +276,7 @@ class UiProjectorManager(object): self.update_icons() -class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjectorManager, RegistryProperties): +class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogMixin, RegistryProperties): """ Manage the projectors. """ @@ -288,7 +288,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto :param projectordb: Database session inherited from superclass. """ log.debug('__init__()') - super().__init__(parent) + super(ProjectorManager, self).__init__(parent) self.settings_section = 'projector' self.projectordb = projectordb self.projector_list = [] diff --git a/openlp/core/ui/serviceitemeditform.py b/openlp/core/ui/serviceitemeditform.py index 17f648ecc..0a4b7cab6 100644 --- a/openlp/core/ui/serviceitemeditform.py +++ b/openlp/core/ui/serviceitemeditform.py @@ -24,7 +24,8 @@ The service item edit dialog """ from PyQt5 import QtCore, QtWidgets -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.ui.serviceitemeditdialog import Ui_ServiceItemEditDialog diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index ff6ab9a47..d13d7879d 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -36,15 +36,15 @@ from openlp.core.common import ThemeLevel, split_filename, 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.mixins import OpenLPMixin, RegistryMixin +from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.path import Path, create_paths, path_to_str, str_to_path -from openlp.core.common.registry import Registry, RegistryProperties +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.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.ui.lib import OpenLPToolbar -from openlp.core.ui.lib.filedialog import FileDialog +from openlp.core.widgets.dialogs import FileDialog +from openlp.core.widgets.toolbar import OpenLPToolbar class ServiceManagerList(QtWidgets.QTreeWidget): @@ -56,8 +56,24 @@ class ServiceManagerList(QtWidgets.QTreeWidget): Constructor """ super(ServiceManagerList, self).__init__(parent) + self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) + self.setAlternatingRowColors(True) + self.setHeaderHidden(True) + self.setExpandsOnDoubleClick(False) self.service_manager = service_manager + def dragEnterEvent(self, event): + """ + React to a drag enter event + """ + event.accept() + + def dragMoveEvent(self, event): + """ + React to a drage move event + """ + event.accept() + def keyPressEvent(self, event): """ Capture Key press and respond accordingly. @@ -117,7 +133,7 @@ class Ui_ServiceManager(object): self.layout.setSpacing(0) self.layout.setContentsMargins(0, 0, 0, 0) # Create the top toolbar - self.toolbar = OpenLPToolbar(widget) + self.toolbar = OpenLPToolbar(self) self.toolbar.add_toolbar_action('newService', text=UiStrings().NewService, icon=':/general/general_new.png', tooltip=UiStrings().CreateService, triggers=self.on_new_service_clicked) self.toolbar.add_toolbar_action('openService', text=UiStrings().OpenService, @@ -147,73 +163,67 @@ class Ui_ServiceManager(object): QtWidgets.QAbstractItemView.CurrentChanged | QtWidgets.QAbstractItemView.DoubleClicked | QtWidgets.QAbstractItemView.EditKeyPressed) - self.service_manager_list.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) - self.service_manager_list.setAlternatingRowColors(True) - self.service_manager_list.setHeaderHidden(True) - self.service_manager_list.setExpandsOnDoubleClick(False) self.service_manager_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.service_manager_list.customContextMenuRequested.connect(self.context_menu) self.service_manager_list.setObjectName('service_manager_list') # enable drop - self.service_manager_list.__class__.dragEnterEvent = lambda x, event: event.accept() - self.service_manager_list.__class__.dragMoveEvent = lambda x, event: event.accept() - self.service_manager_list.__class__.dropEvent = self.drop_event + self.service_manager_list.dropEvent = self.drop_event self.layout.addWidget(self.service_manager_list) # Add the bottom toolbar self.order_toolbar = OpenLPToolbar(widget) action_list = ActionList.get_instance() action_list.add_category(UiStrings().Service, CategoryOrder.standard_toolbar) - self.service_manager_list.move_top = self.order_toolbar.add_toolbar_action( + self.move_top_action = self.order_toolbar.add_toolbar_action( 'moveTop', text=translate('OpenLP.ServiceManager', 'Move to &top'), icon=':/services/service_top.png', tooltip=translate('OpenLP.ServiceManager', 'Move item to the top of the service.'), can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_top) - self.service_manager_list.move_up = self.order_toolbar.add_toolbar_action( + self.move_up_action = self.order_toolbar.add_toolbar_action( 'moveUp', text=translate('OpenLP.ServiceManager', 'Move &up'), icon=':/services/service_up.png', tooltip=translate('OpenLP.ServiceManager', 'Move item up one position in the service.'), can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_up) - self.service_manager_list.move_down = self.order_toolbar.add_toolbar_action( + self.move_down_action = self.order_toolbar.add_toolbar_action( 'moveDown', text=translate('OpenLP.ServiceManager', 'Move &down'), icon=':/services/service_down.png', tooltip=translate('OpenLP.ServiceManager', 'Move item down one position in the service.'), can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_down) - self.service_manager_list.move_bottom = self.order_toolbar.add_toolbar_action( + self.move_bottom_action = self.order_toolbar.add_toolbar_action( 'moveBottom', text=translate('OpenLP.ServiceManager', 'Move to &bottom'), icon=':/services/service_bottom.png', tooltip=translate('OpenLP.ServiceManager', 'Move item to the end of the service.'), can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_end) - self.service_manager_list.down = self.order_toolbar.add_toolbar_action( + self.down_action = self.order_toolbar.add_toolbar_action( 'down', text=translate('OpenLP.ServiceManager', 'Move &down'), can_shortcuts=True, tooltip=translate('OpenLP.ServiceManager', 'Moves the selection down the window.'), visible=False, triggers=self.on_move_selection_down) - action_list.add_action(self.service_manager_list.down) - self.service_manager_list.up = self.order_toolbar.add_toolbar_action( + action_list.add_action(self.down_action) + self.up_action = self.order_toolbar.add_toolbar_action( 'up', text=translate('OpenLP.ServiceManager', 'Move up'), can_shortcuts=True, tooltip=translate('OpenLP.ServiceManager', 'Moves the selection up the window.'), visible=False, triggers=self.on_move_selection_up) - action_list.add_action(self.service_manager_list.up) + action_list.add_action(self.up_action) self.order_toolbar.addSeparator() - self.service_manager_list.delete = self.order_toolbar.add_toolbar_action( + self.delete_action = self.order_toolbar.add_toolbar_action( 'delete', can_shortcuts=True, text=translate('OpenLP.ServiceManager', '&Delete From Service'), icon=':/general/general_delete.png', tooltip=translate('OpenLP.ServiceManager', 'Delete the selected item from the service.'), triggers=self.on_delete_from_service) self.order_toolbar.addSeparator() - self.service_manager_list.expand = self.order_toolbar.add_toolbar_action( + self.expand_action = self.order_toolbar.add_toolbar_action( 'expand', can_shortcuts=True, text=translate('OpenLP.ServiceManager', '&Expand all'), icon=':/services/service_expand_all.png', tooltip=translate('OpenLP.ServiceManager', 'Expand all the service items.'), category=UiStrings().Service, triggers=self.on_expand_all) - self.service_manager_list.collapse = self.order_toolbar.add_toolbar_action( + self.collapse_action = self.order_toolbar.add_toolbar_action( 'collapse', can_shortcuts=True, text=translate('OpenLP.ServiceManager', '&Collapse all'), icon=':/services/service_collapse_all.png', tooltip=translate('OpenLP.ServiceManager', 'Collapse all the service items.'), category=UiStrings().Service, triggers=self.on_collapse_all) self.order_toolbar.addSeparator() - self.service_manager_list.make_live = self.order_toolbar.add_toolbar_action( + self.make_live_action = self.order_toolbar.add_toolbar_action( 'make_live', can_shortcuts=True, text=translate('OpenLP.ServiceManager', 'Go Live'), icon=':/general/general_live.png', tooltip=translate('OpenLP.ServiceManager', 'Send the selected item to Live.'), @@ -254,7 +264,7 @@ class Ui_ServiceManager(object): icon=':/media/auto-start_active.png', triggers=self.on_auto_start) # Add already existing delete action to the menu. - self.menu.addAction(self.service_manager_list.delete) + self.menu.addAction(self.delete_action) self.create_custom_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', 'Create New &Custom ' 'Slide'), @@ -285,28 +295,20 @@ class Ui_ServiceManager(object): self.preview_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', 'Show &Preview'), icon=':/general/general_preview.png', triggers=self.make_preview) # Add already existing make live action to the menu. - self.menu.addAction(self.service_manager_list.make_live) + self.menu.addAction(self.make_live_action) self.menu.addSeparator() self.theme_menu = QtWidgets.QMenu(translate('OpenLP.ServiceManager', '&Change Item Theme')) self.menu.addMenu(self.theme_menu) - self.service_manager_list.addActions( - [self.service_manager_list.move_down, - self.service_manager_list.move_up, - self.service_manager_list.make_live, - self.service_manager_list.move_top, - self.service_manager_list.move_bottom, - self.service_manager_list.up, - self.service_manager_list.down, - self.service_manager_list.expand, - self.service_manager_list.collapse - ]) + self.service_manager_list.addActions([self.move_down_action, self.move_up_action, self.make_live_action, + self.move_top_action, self.move_bottom_action, self.up_action, + self.down_action, self.expand_action, self.collapse_action]) Registry().register_function('theme_update_list', self.update_theme_list) Registry().register_function('config_screen_changed', self.regenerate_service_items) Registry().register_function('theme_update_global', self.theme_change) Registry().register_function('mediaitem_suffix_reset', self.reset_supported_suffixes) -class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceManager, RegistryProperties): +class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixin, RegistryProperties): """ Manages the services. This involves taking text strings from plugins and adding them to the service. This service can then be zipped up with all the resources used into one OSZ or oszl file for use on any OpenLP v2 installation. @@ -320,7 +322,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa """ Sets up the service manager, toolbars, list view, et al. """ - super(ServiceManager, self).__init__(parent) + super().__init__(parent) self.active = build_icon(':/media/auto-start_active.png') self.inactive = build_icon(':/media/auto-start_inactive.png') self.service_items = [] diff --git a/openlp/core/ui/servicenoteform.py b/openlp/core/ui/servicenoteform.py index 998431636..179248570 100644 --- a/openlp/core/ui/servicenoteform.py +++ b/openlp/core/ui/servicenoteform.py @@ -25,9 +25,10 @@ The :mod:`~openlp.core.ui.servicenoteform` module contains the `ServiceNoteForm` from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import translate -from openlp.core.common.registry import Registry, RegistryProperties -from openlp.core.ui.lib import SpellTextEdit +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.lib.ui import create_button_box +from openlp.core.widgets.edits import SpellTextEdit class ServiceNoteForm(QtWidgets.QDialog, RegistryProperties): diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index 5524b89ad..e7a86bac2 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -27,7 +27,8 @@ import logging from PyQt5 import QtCore, QtWidgets from openlp.core.api import ApiTab -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.lib import build_icon from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab from openlp.core.ui.media import PlayerTab diff --git a/openlp/core/ui/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py index 3db127d75..92a0e789f 100644 --- a/openlp/core/ui/shortcutlistform.py +++ b/openlp/core/ui/shortcutlistform.py @@ -29,7 +29,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common.actions import ActionList from openlp.core.common.i18n import translate -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.core.common.settings import Settings from openlp.core.ui.shortcutlistdialog import Ui_ShortcutListDialog diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 52e200886..80eb155e0 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -22,7 +22,6 @@ """ The :mod:`slidecontroller` module contains the most important part of OpenLP - the slide controller """ - import copy import os from collections import deque @@ -33,15 +32,15 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import SlideLimits from openlp.core.common.actions import ActionList, CategoryOrder from openlp.core.common.i18n import UiStrings, translate -from openlp.core.common.mixins import OpenLPMixin, RegistryMixin -from openlp.core.common.registry import Registry, RegistryProperties +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.lib import ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, build_icon, build_html from openlp.core.lib.ui import create_action -from openlp.core.ui.lib.toolbar import OpenLPToolbar -from openlp.core.ui.lib.listpreviewwidget import ListPreviewWidget from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType +from openlp.core.widgets.toolbar import OpenLPToolbar +from openlp.core.widgets.views import ListPreviewWidget # Threshold which has to be trespassed to toggle. @@ -82,11 +81,11 @@ class DisplayController(QtWidgets.QWidget): """ Controller is a general display controller widget. """ - def __init__(self, parent): + def __init__(self, *args, **kwargs): """ Set up the general Controller. """ - super(DisplayController, self).__init__(parent) + super().__init__(*args, **kwargs) self.is_live = False self.display = None self.controller_type = None @@ -133,16 +132,16 @@ class InfoLabel(QtWidgets.QLabel): super().setText(text) -class SlideController(DisplayController, RegistryProperties): +class SlideController(DisplayController, LogMixin, RegistryProperties): """ SlideController is the slide controller widget. This widget is what the user uses to control the displaying of verses/slides/etc on the screen. """ - def __init__(self, parent): + def __init__(self, *args, **kwargs): """ Set up the Slide Controller. """ - super(SlideController, self).__init__(parent) + super().__init__(*args, **kwargs) def post_set_up(self): """ @@ -1505,7 +1504,7 @@ class SlideController(DisplayController, RegistryProperties): self.display.audio_player.go_to(action.data()) -class PreviewController(RegistryMixin, OpenLPMixin, SlideController): +class PreviewController(RegistryBase, SlideController): """ Set up the Preview Controller. """ @@ -1513,11 +1512,12 @@ class PreviewController(RegistryMixin, OpenLPMixin, SlideController): slidecontroller_preview_next = QtCore.pyqtSignal() slidecontroller_preview_previous = QtCore.pyqtSignal() - def __init__(self, parent): + def __init__(self, *args, **kwargs): """ Set up the base Controller as a preview. """ - super(PreviewController, self).__init__(parent) + self.__registry_name = 'preview_slidecontroller' + super().__init__(*args, **kwargs) self.split = 0 self.type_prefix = 'preview' self.category = 'Preview Toolbar' @@ -1529,7 +1529,7 @@ class PreviewController(RegistryMixin, OpenLPMixin, SlideController): self.post_set_up() -class LiveController(RegistryMixin, OpenLPMixin, SlideController): +class LiveController(RegistryBase, SlideController): """ Set up the Live Controller. """ @@ -1541,11 +1541,11 @@ class LiveController(RegistryMixin, OpenLPMixin, SlideController): mediacontroller_live_pause = QtCore.pyqtSignal() mediacontroller_live_stop = QtCore.pyqtSignal() - def __init__(self, parent): + def __init__(self, *args, **kwargs): """ Set up the base Controller as a live. """ - super(LiveController, self).__init__(parent) + super().__init__(*args, **kwargs) self.is_live = True self.split = 1 self.type_prefix = 'live' diff --git a/openlp/core/ui/starttimeform.py b/openlp/core/ui/starttimeform.py index e1ad9d9a6..00d0bff40 100644 --- a/openlp/core/ui/starttimeform.py +++ b/openlp/core/ui/starttimeform.py @@ -25,7 +25,8 @@ The actual start time form. from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import UiStrings, translate -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.lib.ui import critical_error_message_box from openlp.core.ui.starttimedialog import Ui_StartTimeDialog diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index c15110569..f8449986a 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -28,7 +28,8 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import get_images_filter, is_not_image_file from openlp.core.common.i18n import UiStrings, translate -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.lib.theme import BackgroundType, BackgroundGradientType from openlp.core.lib.ui import critical_error_message_box from openlp.core.ui import ThemeLayoutForm diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index f27eb6abb..5b4c5cbb9 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -31,17 +31,17 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import delete_file from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings, translate, get_locale_key -from openlp.core.common.mixins import OpenLPMixin, RegistryMixin +from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.path import Path, copyfile, create_paths, path_to_str, rmtree -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.registry import Registry, RegistryBase from openlp.core.common.settings import Settings from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \ check_item_selected, create_thumb, validate_thumb from openlp.core.lib.theme import Theme, BackgroundType from openlp.core.lib.ui import critical_error_message_box, create_widget_action from openlp.core.ui import FileRenameForm, ThemeForm -from openlp.core.ui.lib import OpenLPToolbar -from openlp.core.ui.lib.filedialog import FileDialog +from openlp.core.widgets.dialogs import FileDialog +from openlp.core.widgets.toolbar import OpenLPToolbar class Ui_ThemeManager(object): @@ -125,7 +125,7 @@ class Ui_ThemeManager(object): self.theme_list_widget.currentItemChanged.connect(self.check_list_state) -class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManager, RegistryProperties): +class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, RegistryProperties): """ Manages the orders of Theme. """ diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py index c50cd1a85..d7bc5f822 100644 --- a/openlp/core/ui/themewizard.py +++ b/openlp/core/ui/themewizard.py @@ -29,7 +29,8 @@ from openlp.core.common.i18n import UiStrings, translate from openlp.core.lib import build_icon from openlp.core.lib.theme import HorizontalType, BackgroundType, BackgroundGradientType from openlp.core.lib.ui import add_welcome_page, create_valign_selection_widgets -from openlp.core.ui.lib import ColorButton, PathEdit +from openlp.core.widgets.buttons import ColorButton +from openlp.core.widgets.edits import PathEdit class Ui_ThemeWizard(object): diff --git a/tests/interfaces/openlp_core_common/__init__.py b/openlp/core/widgets/__init__.py similarity index 100% rename from tests/interfaces/openlp_core_common/__init__.py rename to openlp/core/widgets/__init__.py diff --git a/openlp/core/ui/lib/colorbutton.py b/openlp/core/widgets/buttons.py similarity index 100% rename from openlp/core/ui/lib/colorbutton.py rename to openlp/core/widgets/buttons.py diff --git a/openlp/core/ui/lib/filedialog.py b/openlp/core/widgets/dialogs.py similarity index 100% rename from openlp/core/ui/lib/filedialog.py rename to openlp/core/widgets/dialogs.py diff --git a/openlp/core/ui/lib/mediadockmanager.py b/openlp/core/widgets/docks.py similarity index 76% rename from openlp/core/ui/lib/mediadockmanager.py rename to openlp/core/widgets/docks.py index 8fee1d55d..a1b4e9789 100644 --- a/openlp/core/ui/lib/mediadockmanager.py +++ b/openlp/core/widgets/docks.py @@ -20,15 +20,41 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The media manager dock. +The :mod:`~openlp.core.widgets.docks` module contains a customised base dock widget and dock widgets """ import logging -from openlp.core.lib import StringContent +from PyQt5 import QtWidgets + +from openlp.core.display.screens import ScreenList +from openlp.core.lib import StringContent, build_icon log = logging.getLogger(__name__) +class OpenLPDockWidget(QtWidgets.QDockWidget): + """ + Custom DockWidget class to handle events + """ + def __init__(self, parent=None, name=None, icon=None): + """ + Initialise the DockWidget + """ + log.debug('Initialise the %s widget' % name) + super(OpenLPDockWidget, self).__init__(parent) + if name: + self.setObjectName(name) + if icon: + self.setWindowIcon(build_icon(icon)) + # Sort out the minimum width. + screens = ScreenList() + main_window_docbars = screens.current['size'].width() // 5 + if main_window_docbars > 300: + self.setMinimumWidth(300) + else: + self.setMinimumWidth(main_window_docbars) + + class MediaDockManager(object): """ Provide a repository for MediaManagerItems diff --git a/openlp/core/widgets/edits.py b/openlp/core/widgets/edits.py new file mode 100644 index 000000000..c2396810f --- /dev/null +++ b/openlp/core/widgets/edits.py @@ -0,0 +1,573 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +The :mod:`~openlp.core.widgets.edits` module contains all the customised edit widgets used in OpenLP +""" +import logging +import re + +from PyQt5 import QtCore, QtGui, QtWidgets + +from openlp.core.common.i18n import UiStrings, translate +from openlp.core.common.path import Path, path_to_str, str_to_path +from openlp.core.common.settings import Settings +from openlp.core.lib import FormattingTags, build_icon +from openlp.core.lib.ui import create_widget_action, create_action +from openlp.core.widgets.dialogs import FileDialog +from openlp.core.widgets.enums import PathEditType + +try: + import enchant + from enchant import DictNotFoundError + from enchant.errors import Error + ENCHANT_AVAILABLE = True +except ImportError: + ENCHANT_AVAILABLE = False + +log = logging.getLogger(__name__) + + +class SearchEdit(QtWidgets.QLineEdit): + """ + This is a specialised QLineEdit with a "clear" button inside for searches. + """ + searchTypeChanged = QtCore.pyqtSignal(QtCore.QVariant) + cleared = QtCore.pyqtSignal() + + def __init__(self, parent, settings_section): + """ + Constructor. + """ + super().__init__(parent) + self.settings_section = settings_section + self._current_search_type = -1 + self.clear_button = QtWidgets.QToolButton(self) + self.clear_button.setIcon(build_icon(':/system/clear_shortcut.png')) + self.clear_button.setCursor(QtCore.Qt.ArrowCursor) + self.clear_button.setStyleSheet('QToolButton { border: none; padding: 0px; }') + self.clear_button.resize(18, 18) + self.clear_button.hide() + self.clear_button.clicked.connect(self._on_clear_button_clicked) + self.textChanged.connect(self._on_search_edit_text_changed) + self._update_style_sheet() + self.setAcceptDrops(False) + + def _update_style_sheet(self): + """ + Internal method to update the stylesheet depending on which widgets are available and visible. + """ + frame_width = self.style().pixelMetric(QtWidgets.QStyle.PM_DefaultFrameWidth) + right_padding = self.clear_button.width() + frame_width + if hasattr(self, 'menu_button'): + left_padding = self.menu_button.width() + stylesheet = 'QLineEdit {{ padding-left:{left}px; padding-right: {right}px; }} '.format(left=left_padding, + right=right_padding) + else: + stylesheet = 'QLineEdit {{ padding-right: {right}px; }} '.format(right=right_padding) + self.setStyleSheet(stylesheet) + msz = self.minimumSizeHint() + self.setMinimumSize(max(msz.width(), self.clear_button.width() + (frame_width * 2) + 2), + max(msz.height(), self.clear_button.height() + (frame_width * 2) + 2)) + + def resizeEvent(self, event): + """ + Reimplemented method to react to resizing of the widget. + + :param event: The event that happened. + """ + size = self.clear_button.size() + frame_width = self.style().pixelMetric(QtWidgets.QStyle.PM_DefaultFrameWidth) + self.clear_button.move(self.rect().right() - frame_width - size.width(), + (self.rect().bottom() + 1 - size.height()) // 2) + if hasattr(self, 'menu_button'): + size = self.menu_button.size() + self.menu_button.move(self.rect().left() + frame_width + 2, (self.rect().bottom() + 1 - size.height()) // 2) + + def current_search_type(self): + """ + Readonly property to return the current search type. + """ + return self._current_search_type + + def set_current_search_type(self, identifier): + """ + Set a new current search type. + + :param identifier: The search type identifier (int). + """ + menu = self.menu_button.menu() + for action in menu.actions(): + if identifier == action.data(): + self.setPlaceholderText(action.placeholder_text) + self.menu_button.setDefaultAction(action) + self._current_search_type = identifier + Settings().setValue('{section}/last used search type'.format(section=self.settings_section), identifier) + self.searchTypeChanged.emit(identifier) + return True + + def set_search_types(self, items): + """ + A list of tuples to be used in the search type menu. The first item in the list will be preselected as the + default. + + :param items: The list of tuples to use. The tuples should contain an integer identifier, an icon (QIcon + instance or string) and a title for the item in the menu. In short, they should look like this:: + + (<identifier>, <icon>, <title>, <place holder text>) + + For instance:: + + (1, <QIcon instance>, "Titles", "Search Song Titles...") + + Or:: + + (2, ":/songs/authors.png", "Authors", "Search Authors...") + """ + menu = QtWidgets.QMenu(self) + for identifier, icon, title, placeholder in items: + action = create_widget_action( + menu, text=title, icon=icon, data=identifier, triggers=self._on_menu_action_triggered) + action.placeholder_text = placeholder + if not hasattr(self, 'menu_button'): + self.menu_button = QtWidgets.QToolButton(self) + self.menu_button.setIcon(build_icon(':/system/clear_shortcut.png')) + self.menu_button.setCursor(QtCore.Qt.ArrowCursor) + self.menu_button.setPopupMode(QtWidgets.QToolButton.InstantPopup) + self.menu_button.setStyleSheet('QToolButton { border: none; padding: 0px 10px 0px 0px; }') + self.menu_button.resize(QtCore.QSize(28, 18)) + self.menu_button.setMenu(menu) + self.set_current_search_type( + Settings().value('{section}/last used search type'.format(section=self.settings_section))) + self.menu_button.show() + self._update_style_sheet() + + def _on_search_edit_text_changed(self, text): + """ + Internally implemented slot to react to when the text in the line edit has changed so that we can show or hide + the clear button. + + :param text: A :class:`~PyQt5.QtCore.QString` instance which represents the text in the line edit. + """ + self.clear_button.setVisible(bool(text)) + + def _on_clear_button_clicked(self): + """ + Internally implemented slot to react to the clear button being clicked to clear the line edit. Once it has + cleared the line edit, it emits the ``cleared()`` signal so that an application can react to the clearing of the + line edit. + """ + self.clear() + self.cleared.emit() + + def _on_menu_action_triggered(self): + """ + Internally implemented slot to react to the select of one of the search types in the menu. Once it has set the + correct action on the button, and set the current search type (using the list of identifiers provided by the + developer), the ``searchTypeChanged(int)`` signal is emitted with the identifier. + """ + for action in self.menu_button.menu().actions(): + # Why is this needed? + action.setChecked(False) + self.set_current_search_type(self.sender().data()) + + +class PathEdit(QtWidgets.QWidget): + """ + The :class:`~openlp.core.widgets.edits.PathEdit` class subclasses QWidget to create a custom widget for use when + a file or directory needs to be selected. + """ + pathChanged = QtCore.pyqtSignal(Path) + + def __init__(self, parent=None, path_type=PathEditType.Files, default_path=None, dialog_caption=None, + show_revert=True): + """ + Initialise the PathEdit widget + + :param QtWidget.QWidget | None: The parent of the widget. This is just passed to the super method. + :param str dialog_caption: Used to customise the caption in the QFileDialog. + :param openlp.core.common.path.Path default_path: The default path. This is set as the path when the revert + button is clicked + :param bool show_revert: Used to determine if the 'revert button' should be visible. + :rtype: None + """ + super().__init__(parent) + self.default_path = default_path + self.dialog_caption = dialog_caption + self._path_type = path_type + self._path = None + self.filters = '{all_files} (*)'.format(all_files=UiStrings().AllFiles) + self._setup(show_revert) + + def _setup(self, show_revert): + """ + Set up the widget + :param bool show_revert: Show or hide the revert button + :rtype: None + """ + widget_layout = QtWidgets.QHBoxLayout() + widget_layout.setContentsMargins(0, 0, 0, 0) + self.line_edit = QtWidgets.QLineEdit(self) + widget_layout.addWidget(self.line_edit) + self.browse_button = QtWidgets.QToolButton(self) + self.browse_button.setIcon(build_icon(':/general/general_open.png')) + widget_layout.addWidget(self.browse_button) + self.revert_button = QtWidgets.QToolButton(self) + self.revert_button.setIcon(build_icon(':/general/general_revert.png')) + self.revert_button.setVisible(show_revert) + widget_layout.addWidget(self.revert_button) + self.setLayout(widget_layout) + # Signals and Slots + self.browse_button.clicked.connect(self.on_browse_button_clicked) + self.revert_button.clicked.connect(self.on_revert_button_clicked) + self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished) + self.update_button_tool_tips() + + @property + def path(self): + """ + A property getter method to return the selected path. + + :return: The selected path + :rtype: openlp.core.common.path.Path + """ + return self._path + + @path.setter + def path(self, path): + """ + A Property setter method to set the selected path + + :param openlp.core.common.path.Path path: The path to set the widget to + :rtype: None + """ + self._path = path + text = path_to_str(path) + self.line_edit.setText(text) + self.line_edit.setToolTip(text) + + @property + def path_type(self): + """ + A property getter method to return the path_type. Path type allows you to sepecify if the user is restricted to + selecting a file or directory. + + :return: The type selected + :rtype: PathType + """ + return self._path_type + + @path_type.setter + def path_type(self, path_type): + """ + A Property setter method to set the path type + + :param PathType path_type: The type of path to select + :rtype: None + """ + self._path_type = path_type + self.update_button_tool_tips() + + def update_button_tool_tips(self): + """ + Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised + + :rtype: None + """ + if self._path_type == PathEditType.Directories: + self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.')) + self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default directory.')) + else: + self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for file.')) + self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default file.')) + + def on_browse_button_clicked(self): + """ + A handler to handle a click on the browse button. + + Show the QFileDialog and process the input from the user + + :rtype: None + """ + caption = self.dialog_caption + path = None + if self._path_type == PathEditType.Directories: + if not caption: + caption = translate('OpenLP.PathEdit', 'Select Directory') + path = FileDialog.getExistingDirectory(self, caption, self._path, FileDialog.ShowDirsOnly) + elif self._path_type == PathEditType.Files: + if not caption: + caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File') + path, filter_used = FileDialog.getOpenFileName(self, caption, self._path, self.filters) + if path: + self.on_new_path(path) + + def on_revert_button_clicked(self): + """ + A handler to handle a click on the revert button. + + Set the new path to the value of the default_path instance variable. + + :rtype: None + """ + self.on_new_path(self.default_path) + + def on_line_edit_editing_finished(self): + """ + A handler to handle when the line edit has finished being edited. + + :rtype: None + """ + path = str_to_path(self.line_edit.text()) + self.on_new_path(path) + + def on_new_path(self, path): + """ + A method called to validate and set a new path. + + Emits the pathChanged Signal + + :param openlp.core.common.path.Path path: The new path + :rtype: None + """ + if self._path != path: + self.path = path + self.pathChanged.emit(path) + + +class SpellTextEdit(QtWidgets.QPlainTextEdit): + """ + Spell checking widget based on QPlanTextEdit. + + Based on code from http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check + """ + def __init__(self, parent=None, formatting_tags_allowed=True): + """ + Constructor. + """ + global ENCHANT_AVAILABLE + super(SpellTextEdit, self).__init__(parent) + self.formatting_tags_allowed = formatting_tags_allowed + # Default dictionary based on the current locale. + if ENCHANT_AVAILABLE: + try: + self.dictionary = enchant.Dict() + self.highlighter = Highlighter(self.document()) + self.highlighter.spelling_dictionary = self.dictionary + except (Error, DictNotFoundError): + ENCHANT_AVAILABLE = False + log.debug('Could not load default dictionary') + + def mousePressEvent(self, event): + """ + Handle mouse clicks within the text edit region. + """ + if event.button() == QtCore.Qt.RightButton: + # Rewrite the mouse event to a left button event so the cursor is moved to the location of the pointer. + event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, + event.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier) + QtWidgets.QPlainTextEdit.mousePressEvent(self, event) + + def contextMenuEvent(self, event): + """ + Provide the context menu for the text edit region. + """ + popup_menu = self.createStandardContextMenu() + # Select the word under the cursor. + cursor = self.textCursor() + # only select text if not already selected + if not cursor.hasSelection(): + cursor.select(QtGui.QTextCursor.WordUnderCursor) + self.setTextCursor(cursor) + # Add menu with available languages. + if ENCHANT_AVAILABLE: + lang_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Language:')) + for lang in enchant.list_languages(): + action = create_action(lang_menu, lang, text=lang, checked=lang == self.dictionary.tag) + lang_menu.addAction(action) + popup_menu.insertSeparator(popup_menu.actions()[0]) + popup_menu.insertMenu(popup_menu.actions()[0], lang_menu) + lang_menu.triggered.connect(self.set_language) + # Check if the selected word is misspelled and offer spelling suggestions if it is. + if ENCHANT_AVAILABLE and self.textCursor().hasSelection(): + text = self.textCursor().selectedText() + if not self.dictionary.check(text): + spell_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Spelling Suggestions')) + for word in self.dictionary.suggest(text): + action = SpellAction(word, spell_menu) + action.correct.connect(self.correct_word) + spell_menu.addAction(action) + # Only add the spelling suggests to the menu if there are suggestions. + if spell_menu.actions(): + popup_menu.insertMenu(popup_menu.actions()[0], spell_menu) + tag_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Formatting Tags')) + if self.formatting_tags_allowed: + for html in FormattingTags.get_html_tags(): + action = SpellAction(html['desc'], tag_menu) + action.correct.connect(self.html_tag) + tag_menu.addAction(action) + popup_menu.insertSeparator(popup_menu.actions()[0]) + popup_menu.insertMenu(popup_menu.actions()[0], tag_menu) + popup_menu.exec(event.globalPos()) + + def set_language(self, action): + """ + Changes the language for this spelltextedit. + + :param action: The action. + """ + self.dictionary = enchant.Dict(action.text()) + self.highlighter.spelling_dictionary = self.dictionary + self.highlighter.highlightBlock(self.toPlainText()) + self.highlighter.rehighlight() + + def correct_word(self, word): + """ + Replaces the selected text with word. + """ + cursor = self.textCursor() + cursor.beginEditBlock() + cursor.removeSelectedText() + cursor.insertText(word) + cursor.endEditBlock() + + def html_tag(self, tag): + """ + Replaces the selected text with word. + """ + tag = tag.replace('&', '') + for html in FormattingTags.get_html_tags(): + if tag == html['desc']: + cursor = self.textCursor() + if self.textCursor().hasSelection(): + text = cursor.selectedText() + cursor.beginEditBlock() + cursor.removeSelectedText() + cursor.insertText(html['start tag']) + cursor.insertText(text) + cursor.insertText(html['end tag']) + cursor.endEditBlock() + else: + cursor = self.textCursor() + cursor.insertText(html['start tag']) + cursor.insertText(html['end tag']) + + +class Highlighter(QtGui.QSyntaxHighlighter): + """ + Provides a text highlighter for pointing out spelling errors in text. + """ + WORDS = r'(?iu)[\w\']+' + + def __init__(self, *args): + """ + Constructor + """ + super(Highlighter, self).__init__(*args) + self.spelling_dictionary = None + + def highlightBlock(self, text): + """ + Highlight mis spelt words in a block of text. + + Note, this is a Qt hook. + """ + if not self.spelling_dictionary: + return + text = str(text) + char_format = QtGui.QTextCharFormat() + char_format.setUnderlineColor(QtCore.Qt.red) + char_format.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline) + for word_object in re.finditer(self.WORDS, text): + if not self.spelling_dictionary.check(word_object.group()): + self.setFormat(word_object.start(), word_object.end() - word_object.start(), char_format) + + +class SpellAction(QtWidgets.QAction): + """ + A special QAction that returns the text in a signal. + """ + correct = QtCore.pyqtSignal(str) + + def __init__(self, *args): + """ + Constructor + """ + super(SpellAction, self).__init__(*args) + self.triggered.connect(lambda x: self.correct.emit(self.text())) + + +class HistoryComboBox(QtWidgets.QComboBox): + """ + The :class:`~openlp.core.common.historycombobox.HistoryComboBox` widget emulates the QLineEdit ``returnPressed`` + signal for when the :kbd:`Enter` or :kbd:`Return` keys are pressed, and saves anything that is typed into the edit + box into its list. + """ + returnPressed = QtCore.pyqtSignal() + + def __init__(self, parent=None): + """ + Initialise the combo box, setting duplicates to False and the insert policy to insert items at the top. + + :param parent: The parent widget + """ + super().__init__(parent) + self.setDuplicatesEnabled(False) + self.setEditable(True) + self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + self.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop) + + def keyPressEvent(self, event): + """ + Override the inherited keyPressEvent method to emit the ``returnPressed`` signal and to save the current text to + the dropdown list. + + :param event: The keyboard event + """ + # Handle Enter and Return ourselves + if event.key() == QtCore.Qt.Key_Enter or event.key() == QtCore.Qt.Key_Return: + # Emit the returnPressed signal + self.returnPressed.emit() + # Save the current text to the dropdown list + if self.currentText() and self.findText(self.currentText()) == -1: + self.insertItem(0, self.currentText()) + # Let the parent handle any keypress events + super().keyPressEvent(event) + + def focusOutEvent(self, event): + """ + Override the inherited focusOutEvent to save the current text to the dropdown list. + + :param event: The focus event + """ + # Save the current text to the dropdown list + if self.currentText() and self.findText(self.currentText()) == -1: + self.insertItem(0, self.currentText()) + # Let the parent handle any keypress events + super().focusOutEvent(event) + + def getItems(self): + """ + Get all the items from the history + + :return: A list of strings + """ + return [self.itemText(i) for i in range(self.count())] diff --git a/openlp/core/ui/lib/__init__.py b/openlp/core/widgets/enums.py similarity index 69% rename from openlp/core/ui/lib/__init__.py rename to openlp/core/widgets/enums.py index cf55b9d20..f79dd775c 100644 --- a/openlp/core/ui/lib/__init__.py +++ b/openlp/core/widgets/enums.py @@ -19,18 +19,12 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### +""" +The :mod:`~openlp.core.widgets.enums` module contains enumerations used by the widgets +""" +from enum import Enum -from .colorbutton import ColorButton -from .listpreviewwidget import ListPreviewWidget -from .listwidgetwithdnd import ListWidgetWithDnD -from .mediadockmanager import MediaDockManager -from .dockwidget import OpenLPDockWidget -from .toolbar import OpenLPToolbar -from .wizard import OpenLPWizard, WizardStrings -from .pathedit import PathEdit, PathType -from .spelltextedit import SpellTextEdit -from .treewidgetwithdnd import TreeWidgetWithDnD -__all__ = ['ColorButton', 'ListPreviewWidget', 'ListWidgetWithDnD', 'MediaDockManager', 'OpenLPDockWidget', - 'OpenLPToolbar', 'OpenLPWizard', 'PathEdit', 'PathType', 'SpellTextEdit', 'TreeWidgetWithDnD', - 'WizardStrings'] +class PathEditType(Enum): + Files = 1 + Directories = 2 diff --git a/openlp/core/ui/lib/toolbar.py b/openlp/core/widgets/toolbar.py similarity index 98% rename from openlp/core/ui/lib/toolbar.py rename to openlp/core/widgets/toolbar.py index 68343889f..8c76ce50e 100644 --- a/openlp/core/ui/lib/toolbar.py +++ b/openlp/core/widgets/toolbar.py @@ -40,7 +40,7 @@ class OpenLPToolbar(QtWidgets.QToolBar): """ Initialise the toolbar. """ - super(OpenLPToolbar, self).__init__(parent) + super().__init__(parent) # useful to be able to reuse button icons... self.setIconSize(QtCore.QSize(20, 20)) self.actions = {} diff --git a/openlp/core/ui/lib/listpreviewwidget.py b/openlp/core/widgets/views.py similarity index 56% rename from openlp/core/ui/lib/listpreviewwidget.py rename to openlp/core/widgets/views.py index d03261e8c..f2a7d4498 100644 --- a/openlp/core/ui/lib/listpreviewwidget.py +++ b/openlp/core/widgets/views.py @@ -23,10 +23,14 @@ The :mod:`listpreviewwidget` is a widget that lists the slides in the slide controller. It is based on a QTableWidget but represents its contents in list form. """ +import os from PyQt5 import QtCore, QtGui, QtWidgets -from openlp.core.common.registry import RegistryProperties +from openlp.core.common import is_win +from openlp.core.common.i18n import UiStrings +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import ImageSource, ItemCapabilities, ServiceItem @@ -238,3 +242,241 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties): Returns the number of slides this widget holds. """ return super(ListPreviewWidget, self).rowCount() + + +class ListWidgetWithDnD(QtWidgets.QListWidget): + """ + Provide a list widget to store objects and handle drag and drop events + """ + def __init__(self, parent=None, name=''): + """ + Initialise the list widget + """ + super().__init__(parent) + self.mime_data_text = name + self.no_results_text = UiStrings().NoResults + self.setSpacing(1) + self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.setAlternatingRowColors(True) + self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + + def activateDnD(self): + """ + Activate DnD of widget + """ + self.setAcceptDrops(True) + self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) + Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file) + + def clear(self, search_while_typing=False): + """ + Re-implement clear, so that we can customise feedback when using 'Search as you type' + + :param search_while_typing: True if we want to display the customised message + :return: None + """ + if search_while_typing: + self.no_results_text = UiStrings().ShortResults + else: + self.no_results_text = UiStrings().NoResults + super().clear() + + def mouseMoveEvent(self, event): + """ + Drag and drop event does not care what data is selected as the recipient will use events to request the data + move just tell it what plugin to call + """ + if event.buttons() != QtCore.Qt.LeftButton: + event.ignore() + return + if not self.selectedItems(): + event.ignore() + return + drag = QtGui.QDrag(self) + mime_data = QtCore.QMimeData() + drag.setMimeData(mime_data) + mime_data.setText(self.mime_data_text) + drag.exec(QtCore.Qt.CopyAction) + + def dragEnterEvent(self, event): + """ + When something is dragged into this object, check if you should be able to drop it in here. + """ + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + + def dragMoveEvent(self, event): + """ + Make an object droppable, and set it to copy the contents of the object, not move it. + """ + if event.mimeData().hasUrls(): + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + else: + event.ignore() + + def dropEvent(self, event): + """ + Receive drop event check if it is a file and process it if it is. + + :param event: Handle of the event pint passed + """ + if event.mimeData().hasUrls(): + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + files = [] + for url in event.mimeData().urls(): + local_file = os.path.normpath(url.toLocalFile()) + if os.path.isfile(local_file): + files.append(local_file) + elif os.path.isdir(local_file): + listing = os.listdir(local_file) + for file in listing: + files.append(os.path.join(local_file, file)) + Registry().execute('{mime_data}_dnd'.format(mime_data=self.mime_data_text), + {'files': files, 'target': self.itemAt(event.pos())}) + else: + event.ignore() + + def allItems(self): + """ + An generator to list all the items in the widget + + :return: a generator + """ + for row in range(self.count()): + yield self.item(row) + + def paintEvent(self, event): + """ + Re-implement paintEvent so that we can add 'No Results' text when the listWidget is empty. + + :param event: A QPaintEvent + :return: None + """ + super().paintEvent(event) + if not self.count(): + viewport = self.viewport() + painter = QtGui.QPainter(viewport) + font = QtGui.QFont() + font.setItalic(True) + painter.setFont(font) + painter.drawText(QtCore.QRect(0, 0, viewport.width(), viewport.height()), + (QtCore.Qt.AlignHCenter | QtCore.Qt.TextWordWrap), self.no_results_text) + + +class TreeWidgetWithDnD(QtWidgets.QTreeWidget): + """ + Provide a tree widget to store objects and handle drag and drop events + """ + def __init__(self, parent=None, name=''): + """ + Initialise the tree widget + """ + super(TreeWidgetWithDnD, self).__init__(parent) + self.mime_data_text = name + self.allow_internal_dnd = False + self.header().close() + self.default_indentation = self.indentation() + self.setIndentation(0) + self.setAnimated(True) + + def activateDnD(self): + """ + Activate DnD of widget + """ + self.setAcceptDrops(True) + self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) + Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file) + Registry().register_function(('%s_dnd_internal' % self.mime_data_text), self.parent().dnd_move_internal) + + def mouseMoveEvent(self, event): + """ + Drag and drop event does not care what data is selected as the recipient will use events to request the data + move just tell it what plugin to call + + :param event: The event that occurred + """ + if event.buttons() != QtCore.Qt.LeftButton: + event.ignore() + return + if not self.selectedItems(): + event.ignore() + return + drag = QtGui.QDrag(self) + mime_data = QtCore.QMimeData() + drag.setMimeData(mime_data) + mime_data.setText(self.mime_data_text) + drag.exec(QtCore.Qt.CopyAction) + + def dragEnterEvent(self, event): + """ + Receive drag enter event, check if it is a file or internal object and allow it if it is. + + :param event: The event that occurred + """ + if event.mimeData().hasUrls(): + event.accept() + elif self.allow_internal_dnd: + event.accept() + else: + event.ignore() + + def dragMoveEvent(self, event): + """ + Receive drag move event, check if it is a file or internal object and allow it if it is. + + :param event: The event that occurred + """ + QtWidgets.QTreeWidget.dragMoveEvent(self, event) + if event.mimeData().hasUrls(): + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + elif self.allow_internal_dnd: + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + else: + event.ignore() + + def dropEvent(self, event): + """ + Receive drop event, check if it is a file or internal object and process it if it is. + + :param event: Handle of the event pint passed + """ + # If we are on Windows, OpenLP window will not be set on top. For example, user can drag images to Library and + # the folder stays on top of the group creation box. This piece of code fixes this issue. + if is_win(): + self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) + self.setWindowState(QtCore.Qt.WindowNoState) + if event.mimeData().hasUrls(): + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + files = [] + for url in event.mimeData().urls(): + local_file = url.toLocalFile() + if os.path.isfile(local_file): + files.append(local_file) + elif os.path.isdir(local_file): + listing = os.listdir(local_file) + for file_name in listing: + files.append(os.path.join(local_file, file_name)) + Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())}) + elif self.allow_internal_dnd: + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + Registry().execute('%s_dnd_internal' % self.mime_data_text, self.itemAt(event.pos())) + else: + event.ignore() + + # Convenience methods for emulating a QListWidget. This helps keeping MediaManagerItem simple. + def addItem(self, item): + self.addTopLevelItem(item) + + def count(self): + return self.topLevelItemCount() + + def item(self, index): + return self.topLevelItem(index) diff --git a/openlp/core/ui/lib/wizard.py b/openlp/core/widgets/wizard.py similarity index 98% rename from openlp/core/ui/lib/wizard.py rename to openlp/core/widgets/wizard.py index 1da24eab3..dc8288bc9 100644 --- a/openlp/core/ui/lib/wizard.py +++ b/openlp/core/widgets/wizard.py @@ -28,11 +28,12 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import is_macosx from openlp.core.common.i18n import UiStrings, translate -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import build_icon from openlp.core.lib.ui import add_welcome_page -from openlp.core.ui.lib.filedialog import FileDialog +from openlp.core.widgets.dialogs import FileDialog log = logging.getLogger(__name__) diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index 6eb26ff57..e5ae93093 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -26,12 +26,12 @@ displaying of alerts. from PyQt5 import QtCore from openlp.core.common.i18n import translate -from openlp.core.common.mixins import OpenLPMixin, RegistryMixin -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import LogMixin, RegistryProperties +from openlp.core.common.registry import Registry, RegistryBase from openlp.core.common.settings import Settings -class AlertsManager(OpenLPMixin, RegistryMixin, QtCore.QObject, RegistryProperties): +class AlertsManager(QtCore.QObject, RegistryBase, LogMixin, RegistryProperties): """ AlertsManager manages the settings of Alerts. """ diff --git a/openlp/plugins/alerts/lib/alertstab.py b/openlp/plugins/alerts/lib/alertstab.py index 406e35607..51d92cf06 100644 --- a/openlp/plugins/alerts/lib/alertstab.py +++ b/openlp/plugins/alerts/lib/alertstab.py @@ -26,7 +26,7 @@ from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.settings import Settings from openlp.core.lib import SettingsTab from openlp.core.lib.ui import create_valign_selection_widgets -from openlp.core.ui.lib.colorbutton import ColorButton +from openlp.core.widgets.buttons import ColorButton class AlertsTab(SettingsTab): diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index 4cb9ee453..111e361f8 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -41,7 +41,7 @@ from openlp.core.common.settings import Settings from openlp.core.lib.db import delete_database from openlp.core.lib.exceptions import ValidationError from openlp.core.lib.ui import critical_error_message_box -from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings +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 CWExtract, BGExtract, BSExtract from openlp.plugins.bibles.lib.manager import BibleFormat diff --git a/openlp/plugins/bibles/forms/editbibleform.py b/openlp/plugins/bibles/forms/editbibleform.py index 365d98a4e..8899087ba 100644 --- a/openlp/plugins/bibles/forms/editbibleform.py +++ b/openlp/plugins/bibles/forms/editbibleform.py @@ -26,7 +26,7 @@ import re from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import UiStrings, translate -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.core.lib.ui import critical_error_message_box from .editbibledialog import Ui_EditBibleDialog from openlp.plugins.bibles.lib import BibleStrings diff --git a/openlp/plugins/bibles/lib/bibleimport.py b/openlp/plugins/bibles/lib/bibleimport.py index 61aa35a3e..5083d7f6b 100644 --- a/openlp/plugins/bibles/lib/bibleimport.py +++ b/openlp/plugins/bibles/lib/bibleimport.py @@ -23,15 +23,15 @@ from lxml import etree, objectify from zipfile import is_zipfile -from openlp.core.common.mixins import OpenLPMixin -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import LogMixin, RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.common.i18n import get_language, translate from openlp.core.lib import ValidationError from openlp.core.lib.ui import critical_error_message_box from openlp.plugins.bibles.lib.db import AlternativeBookNamesDB, BibleDB, BiblesResourcesDB -class BibleImport(OpenLPMixin, RegistryProperties, BibleDB): +class BibleImport(BibleDB, LogMixin, RegistryProperties): """ Helper class to import bibles from a third party source into OpenLP """ diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 5d82fe613..bc8ce4150 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -19,7 +19,6 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### - import chardet import logging import os diff --git a/tests/interfaces/openlp_core_lib/__init__.py b/openlp/plugins/bibles/lib/importers/__init__.py similarity index 100% rename from tests/interfaces/openlp_core_lib/__init__.py rename to openlp/plugins/bibles/lib/importers/__init__.py diff --git a/openlp/plugins/bibles/lib/importers/http.py b/openlp/plugins/bibles/lib/importers/http.py index 5d3098b4d..b88dbe7a9 100644 --- a/openlp/plugins/bibles/lib/importers/http.py +++ b/openlp/plugins/bibles/lib/importers/http.py @@ -32,7 +32,8 @@ from bs4 import BeautifulSoup, NavigableString, Tag from openlp.core.common.httputils import get_web_page from openlp.core.common.i18n import translate -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.lib.ui import critical_error_message_box from openlp.plugins.bibles.lib import SearchResults from openlp.plugins.bibles.lib.bibleimport import BibleImport diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 55444f080..7290ca862 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -25,9 +25,8 @@ import logging from openlp.core.common import delete_file from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings, translate -from openlp.core.common.mixins import OpenLPMixin +from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.path import Path -from openlp.core.common.registry import RegistryProperties from openlp.core.common.settings import Settings from openlp.plugins.bibles.lib import LanguageSelection, parse_reference from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta @@ -98,7 +97,7 @@ class BibleFormat(object): ] -class BibleManager(OpenLPMixin, RegistryProperties): +class BibleManager(LogMixin, RegistryProperties): """ The Bible manager which holds and manages all the Bibles. """ diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 10bb8d6ec..fdfc4b51d 100755 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -30,9 +30,9 @@ from openlp.core.common.i18n import UiStrings, translate, get_locale_key from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext -from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.ui import set_case_insensitive_completer, create_horizontal_adjusting_combo_box, \ critical_error_message_box, find_and_set_in_combo_box, build_icon +from openlp.core.widgets.edits import SearchEdit from openlp.plugins.bibles.forms.bibleimportform import BibleImportForm from openlp.plugins.bibles.forms.editbibleform import EditBibleForm from openlp.plugins.bibles.lib import DisplayStyle, LayoutStyle, VerseReferenceList, \ diff --git a/openlp/plugins/custom/forms/editcustomslidedialog.py b/openlp/plugins/custom/forms/editcustomslidedialog.py index 452146feb..5ccc94cc6 100644 --- a/openlp/plugins/custom/forms/editcustomslidedialog.py +++ b/openlp/plugins/custom/forms/editcustomslidedialog.py @@ -25,7 +25,7 @@ from PyQt5 import QtWidgets from openlp.core.common.i18n import UiStrings, translate from openlp.core.lib import build_icon from openlp.core.lib.ui import create_button, create_button_box -from openlp.core.ui.lib import SpellTextEdit +from openlp.core.widgets.edits import SpellTextEdit class Ui_CustomSlideEditDialog(object): diff --git a/openlp/plugins/images/lib/imagetab.py b/openlp/plugins/images/lib/imagetab.py index 23b742bfb..e2b64a6cc 100644 --- a/openlp/plugins/images/lib/imagetab.py +++ b/openlp/plugins/images/lib/imagetab.py @@ -25,7 +25,7 @@ from PyQt5 import QtWidgets from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.settings import Settings from openlp.core.lib import SettingsTab -from openlp.core.ui.lib.colorbutton import ColorButton +from openlp.core.widgets.buttons import ColorButton class ImageTab(SettingsTab): diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 134dbe2eb..b030d26fd 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -33,7 +33,7 @@ from openlp.core.common.settings import Settings from openlp.core.lib import ItemCapabilities, MediaManagerItem, ServiceItemContext, StringContent, build_icon, \ check_item_selected, create_thumb, validate_thumb from openlp.core.lib.ui import create_widget_action, critical_error_message_box -from openlp.core.ui.lib.treewidgetwithdnd import TreeWidgetWithDnD +from openlp.core.widgets.views import TreeWidgetWithDnD from openlp.plugins.images.forms import AddGroupForm, ChooseGroupForm from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups diff --git a/openlp/plugins/media/forms/mediaclipselectorform.py b/openlp/plugins/media/forms/mediaclipselectorform.py index f48e9b09e..af08870df 100644 --- a/openlp/plugins/media/forms/mediaclipselectorform.py +++ b/openlp/plugins/media/forms/mediaclipselectorform.py @@ -29,7 +29,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import is_win, is_linux, is_macosx from openlp.core.common.i18n import translate -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.plugins.media.forms.mediaclipselectordialog import Ui_MediaClipSelector from openlp.core.lib.ui import critical_error_message_box from openlp.core.ui.media.vlcplayer import get_vlc diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index ab0cf4968..ed787166b 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -28,7 +28,8 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings, translate, get_locale_key from openlp.core.common.path import Path, path_to_str, create_paths -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import ItemCapabilities, MediaManagerItem, MediaType, ServiceItem, ServiceItemContext, \ build_icon, check_item_selected diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 96fcc573a..d6a4f5190 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -26,7 +26,7 @@ from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.settings import Settings from openlp.core.lib import SettingsTab from openlp.core.lib.ui import critical_error_message_box -from openlp.core.ui.lib import PathEdit +from openlp.core.widgets.edits import PathEdit from openlp.plugins.presentations.lib.pdfcontroller import PdfController diff --git a/openlp/plugins/songs/forms/duplicatesongremovalform.py b/openlp/plugins/songs/forms/duplicatesongremovalform.py index d2238d4b7..8e7724808 100644 --- a/openlp/plugins/songs/forms/duplicatesongremovalform.py +++ b/openlp/plugins/songs/forms/duplicatesongremovalform.py @@ -29,8 +29,9 @@ import multiprocessing from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import translate -from openlp.core.common.registry import Registry, RegistryProperties -from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry +from openlp.core.widgets.wizard import OpenLPWizard, WizardStrings from openlp.plugins.songs.lib import delete_song from openlp.plugins.songs.lib.db import Song from openlp.plugins.songs.forms.songreviewwidget import SongReviewWidget diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index bdb1a9353..fa475a63f 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -31,10 +31,11 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings, translate, get_natural_key from openlp.core.common.path import create_paths, copyfile -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.lib import PluginStatus, MediaType, create_separated_list from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box -from openlp.core.ui.lib.filedialog import FileDialog +from openlp.core.widgets.dialogs import FileDialog from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog from openlp.plugins.songs.forms.editverseform import EditVerseForm from openlp.plugins.songs.forms.mediafilesform import MediaFilesForm diff --git a/openlp/plugins/songs/forms/editversedialog.py b/openlp/plugins/songs/forms/editversedialog.py index 63ca0cf27..76dc70c17 100644 --- a/openlp/plugins/songs/forms/editversedialog.py +++ b/openlp/plugins/songs/forms/editversedialog.py @@ -26,7 +26,7 @@ from openlp.core.common.settings import Settings from openlp.core.common.i18n import UiStrings, translate from openlp.core.lib import build_icon from openlp.core.lib.ui import create_button_box -from openlp.core.ui.lib import SpellTextEdit +from openlp.core.widgets.edits import SpellTextEdit from openlp.plugins.songs.lib import VerseType diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 28ca8054c..c6446d74c 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -32,8 +32,9 @@ from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import create_separated_list from openlp.core.lib.ui import critical_error_message_box -from openlp.core.ui.lib import PathEdit, PathType -from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings +from openlp.core.widgets.edits import PathEdit +from openlp.core.widgets.enums import PathEditType +from openlp.core.widgets.wizard import OpenLPWizard, WizardStrings from openlp.plugins.songs.lib.db import Song from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport @@ -124,7 +125,7 @@ class SongExportForm(OpenLPWizard): self.selected_list_widget.setObjectName('selected_list_widget') self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 2) self.output_directory_path_edit = PathEdit( - self.export_song_page, PathType.Directories, + self.export_song_page, PathEditType.Directories, dialog_caption=translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'), show_revert=False) self.output_directory_path_edit.path = Settings().value('songs/last directory export') self.directory_label = QtWidgets.QLabel(self.export_song_page) diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index acfa4b5b8..af88bfb35 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -27,12 +27,13 @@ import logging from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import UiStrings, translate -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.core.common.settings import Settings from openlp.core.lib.ui import critical_error_message_box -from openlp.core.ui.lib import PathEdit, PathType -from openlp.core.ui.lib.filedialog import FileDialog -from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings +from openlp.core.widgets.dialogs import FileDialog +from openlp.core.widgets.edits import PathEdit +from openlp.core.widgets.enums import PathEditType +from openlp.core.widgets.wizard import OpenLPWizard, WizardStrings from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect log = logging.getLogger(__name__) @@ -383,10 +384,10 @@ class SongImportForm(OpenLPWizard, RegistryProperties): file_path_label = QtWidgets.QLabel(import_widget) file_path_layout.addWidget(file_path_label) if select_mode == SongFormatSelect.SingleFile: - path_type = PathType.Files + path_type = PathEditType.Files dialog_caption = WizardStrings.OpenTypeFile.format(file_type=format_name) else: - path_type = PathType.Directories + path_type = PathEditType.Directories dialog_caption = WizardStrings.OpenTypeFolder.format(folder_name=format_name) path_edit = PathEdit( parent=import_widget, path_type=path_type, dialog_caption=dialog_caption, show_revert=False) diff --git a/openlp/plugins/songs/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index 4dc485e24..7f0236d24 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -25,7 +25,8 @@ from PyQt5 import QtCore, QtWidgets from sqlalchemy.sql import and_ from openlp.core.common.i18n import UiStrings, translate, get_natural_key -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.registry import Registry from openlp.core.lib.ui import critical_error_message_box from openlp.plugins.songs.forms.authorsform import AuthorsForm from openlp.plugins.songs.forms.topicsform import TopicsForm diff --git a/openlp/plugins/songs/forms/songselectdialog.py b/openlp/plugins/songs/forms/songselectdialog.py index b784071bd..bc0bd7415 100644 --- a/openlp/plugins/songs/forms/songselectdialog.py +++ b/openlp/plugins/songs/forms/songselectdialog.py @@ -28,7 +28,7 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import translate from openlp.core.lib import build_icon from openlp.core.ui import SingleColumnTableWidget -from openlp.core.ui.lib.historycombobox import HistoryComboBox +from openlp.core.widgets.edits import HistoryComboBox class Ui_SongSelectDialog(object): diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 3b8081999..e306dae87 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -26,7 +26,7 @@ import logging from openlp.core.common import is_win from openlp.core.common.i18n import UiStrings, translate -from openlp.core.ui.lib.wizard import WizardStrings +from openlp.core.widgets.wizard import WizardStrings from .importers.opensong import OpenSongImport from .importers.easyslides import EasySlidesImport from .importers.openlp import OpenLPSongImport diff --git a/openlp/plugins/songs/lib/importers/easyworship.py b/openlp/plugins/songs/lib/importers/easyworship.py index c82a7e6ad..8f096099f 100644 --- a/openlp/plugins/songs/lib/importers/easyworship.py +++ b/openlp/plugins/songs/lib/importers/easyworship.py @@ -44,7 +44,7 @@ NOTE_REGEX = re.compile(r'\(.*?\)') log = logging.getLogger(__name__) -class FieldDescEntry: +class FieldDescEntry(object): def __init__(self, name, field_type, size): self.name = name self.field_type = field_type diff --git a/openlp/plugins/songs/lib/importers/foilpresenter.py b/openlp/plugins/songs/lib/importers/foilpresenter.py index 860177172..f164dfda2 100644 --- a/openlp/plugins/songs/lib/importers/foilpresenter.py +++ b/openlp/plugins/songs/lib/importers/foilpresenter.py @@ -88,7 +88,7 @@ import re from lxml import etree, objectify from openlp.core.common.i18n import translate -from openlp.core.ui.lib.wizard import WizardStrings +from openlp.core.widgets.wizard import WizardStrings from openlp.plugins.songs.lib import clean_song, VerseType from openlp.plugins.songs.lib.importers.songimport import SongImport from openlp.plugins.songs.lib.db import Author, Book, Song, Topic diff --git a/openlp/plugins/songs/lib/importers/openlp.py b/openlp/plugins/songs/lib/importers/openlp.py index 252b8fd8b..80a981d02 100644 --- a/openlp/plugins/songs/lib/importers/openlp.py +++ b/openlp/plugins/songs/lib/importers/openlp.py @@ -31,7 +31,7 @@ from sqlalchemy.orm.exc import UnmappedClassError from openlp.core.common.i18n import translate from openlp.core.lib.db import BaseModel -from openlp.core.ui.lib.wizard import WizardStrings +from openlp.core.widgets.wizard import WizardStrings from openlp.plugins.songs.lib import clean_song from openlp.plugins.songs.lib.db import Author, Book, Song, Topic, MediaFile from .songimport import SongImport diff --git a/openlp/plugins/songs/lib/importers/openlyrics.py b/openlp/plugins/songs/lib/importers/openlyrics.py index 44f5f96bf..f443cfb34 100644 --- a/openlp/plugins/songs/lib/importers/openlyrics.py +++ b/openlp/plugins/songs/lib/importers/openlyrics.py @@ -27,7 +27,7 @@ import logging from lxml import etree -from openlp.core.ui.lib.wizard import WizardStrings +from openlp.core.widgets.wizard import WizardStrings from openlp.plugins.songs.lib.importers.songimport import SongImport from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, OpenLyricsError diff --git a/openlp/plugins/songs/lib/importers/powerpraise.py b/openlp/plugins/songs/lib/importers/powerpraise.py index a08652e3f..fd6f64360 100644 --- a/openlp/plugins/songs/lib/importers/powerpraise.py +++ b/openlp/plugins/songs/lib/importers/powerpraise.py @@ -25,8 +25,8 @@ Powerpraise song files into the current database. """ from lxml import objectify -from openlp.core.ui.lib.wizard import WizardStrings -from .songimport import SongImport +from openlp.core.widgets.wizard import WizardStrings +from openlp.plugins.songs.lib.importers.songimport import SongImport class PowerPraiseImport(SongImport): diff --git a/openlp/plugins/songs/lib/importers/presentationmanager.py b/openlp/plugins/songs/lib/importers/presentationmanager.py index e7fec2a6c..c8f9a16f9 100644 --- a/openlp/plugins/songs/lib/importers/presentationmanager.py +++ b/openlp/plugins/songs/lib/importers/presentationmanager.py @@ -29,8 +29,8 @@ from lxml import objectify, etree from openlp.core.common.i18n import translate from openlp.core.common import get_file_encoding -from openlp.core.ui.lib.wizard import WizardStrings -from .songimport import SongImport +from openlp.core.widgets.wizard import WizardStrings +from openlp.plugins.songs.lib.importers.songimport import SongImport class PresentationManagerImport(SongImport): diff --git a/openlp/plugins/songs/lib/importers/propresenter.py b/openlp/plugins/songs/lib/importers/propresenter.py index 582b1a6ee..0de0e5300 100644 --- a/openlp/plugins/songs/lib/importers/propresenter.py +++ b/openlp/plugins/songs/lib/importers/propresenter.py @@ -27,9 +27,9 @@ import base64 import logging from lxml import objectify -from openlp.core.ui.lib.wizard import WizardStrings +from openlp.core.widgets.wizard import WizardStrings from openlp.plugins.songs.lib import strip_rtf -from .songimport import SongImport +from openlp.plugins.songs.lib.importers.songimport import SongImport log = logging.getLogger(__name__) diff --git a/openlp/plugins/songs/lib/importers/songimport.py b/openlp/plugins/songs/lib/importers/songimport.py index e0cc5220e..a67c17fe7 100644 --- a/openlp/plugins/songs/lib/importers/songimport.py +++ b/openlp/plugins/songs/lib/importers/songimport.py @@ -29,7 +29,7 @@ from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import translate from openlp.core.common.path import copyfile, create_paths from openlp.core.common.registry import Registry -from openlp.core.ui.lib.wizard import WizardStrings +from openlp.core.widgets.wizard import WizardStrings from openlp.plugins.songs.lib import clean_song, VerseType from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile from openlp.plugins.songs.lib.ui import SongStrings diff --git a/openlp/plugins/songs/lib/importers/songshowplus.py b/openlp/plugins/songs/lib/importers/songshowplus.py index 2fcf414dd..3109fae1d 100644 --- a/openlp/plugins/songs/lib/importers/songshowplus.py +++ b/openlp/plugins/songs/lib/importers/songshowplus.py @@ -27,7 +27,7 @@ import logging import re import struct -from openlp.core.ui.lib.wizard import WizardStrings +from openlp.core.widgets.wizard import WizardStrings from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding from openlp.plugins.songs.lib.importers.songimport import SongImport diff --git a/openlp/plugins/songs/lib/openlyricsexport.py b/openlp/plugins/songs/lib/openlyricsexport.py index ca7fe0bc7..b07298253 100644 --- a/openlp/plugins/songs/lib/openlyricsexport.py +++ b/openlp/plugins/songs/lib/openlyricsexport.py @@ -30,7 +30,7 @@ from lxml import etree from openlp.core.common import clean_filename from openlp.core.common.i18n import translate from openlp.core.common.path import create_paths -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics log = logging.getLogger(__name__) diff --git a/openlp/plugins/songs/reporting.py b/openlp/plugins/songs/reporting.py index 01fb0af6c..808c05b57 100644 --- a/openlp/plugins/songs/reporting.py +++ b/openlp/plugins/songs/reporting.py @@ -29,7 +29,7 @@ from openlp.core.common.i18n import translate from openlp.core.common.path import Path from openlp.core.common.registry import Registry from openlp.core.lib.ui import critical_error_message_box -from openlp.core.ui.lib.filedialog import FileDialog +from openlp.core.widgets.dialogs import FileDialog from openlp.plugins.songs.lib.db import Song log = logging.getLogger(__name__) diff --git a/openlp/plugins/songusage/forms/songusagedeleteform.py b/openlp/plugins/songusage/forms/songusagedeleteform.py index 4a619e64a..9042756d6 100644 --- a/openlp/plugins/songusage/forms/songusagedeleteform.py +++ b/openlp/plugins/songusage/forms/songusagedeleteform.py @@ -23,7 +23,7 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import translate -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.plugins.songusage.lib.db import SongUsageItem from openlp.plugins.songusage.forms.songusagedeletedialog import Ui_SongUsageDeleteDialog diff --git a/openlp/plugins/songusage/forms/songusagedetaildialog.py b/openlp/plugins/songusage/forms/songusagedetaildialog.py index 9d51b041a..7f6d1c16b 100644 --- a/openlp/plugins/songusage/forms/songusagedetaildialog.py +++ b/openlp/plugins/songusage/forms/songusagedetaildialog.py @@ -24,7 +24,8 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import translate from openlp.core.lib import build_icon from openlp.core.lib.ui import create_button_box -from openlp.core.ui.lib import PathEdit, PathType +from openlp.core.widgets.edits import PathEdit +from openlp.core.widgets.enums import PathEditType class Ui_SongUsageDetailDialog(object): @@ -68,7 +69,7 @@ class Ui_SongUsageDetailDialog(object): self.file_horizontal_layout.setSpacing(8) self.file_horizontal_layout.setContentsMargins(8, 8, 8, 8) self.file_horizontal_layout.setObjectName('file_horizontal_layout') - self.report_path_edit = PathEdit(self.file_group_box, path_type=PathType.Directories, show_revert=False) + self.report_path_edit = PathEdit(self.file_group_box, path_type=PathEditType.Directories, show_revert=False) self.file_horizontal_layout.addWidget(self.report_path_edit) self.vertical_layout.addWidget(self.file_group_box) self.button_box = create_button_box(song_usage_detail_dialog, 'button_box', ['cancel', 'ok']) diff --git a/openlp/plugins/songusage/forms/songusagedetailform.py b/openlp/plugins/songusage/forms/songusagedetailform.py index afc013bbd..147e26d10 100644 --- a/openlp/plugins/songusage/forms/songusagedetailform.py +++ b/openlp/plugins/songusage/forms/songusagedetailform.py @@ -25,7 +25,7 @@ from PyQt5 import QtCore, QtWidgets from sqlalchemy.sql import and_ from openlp.core.common.i18n import translate -from openlp.core.common.registry import RegistryProperties +from openlp.core.common.mixins import RegistryProperties from openlp.core.common.settings import Settings from openlp.core.common.path import create_paths from openlp.core.lib.ui import critical_error_message_box diff --git a/tests/functional/openlp_core/common/test_mixins.py b/tests/functional/openlp_core/common/test_mixins.py index 7cb8604af..8eee60c6f 100644 --- a/tests/functional/openlp_core/common/test_mixins.py +++ b/tests/functional/openlp_core/common/test_mixins.py @@ -23,45 +23,76 @@ Package to test the openlp.core.common package. """ from unittest import TestCase +from unittest.mock import MagicMock, patch -from openlp.core.common.mixins import RegistryMixin +from openlp.core.common.mixins import RegistryProperties from openlp.core.common.registry import Registry -class PlainStub(object): - def __init__(self): - pass - - -class MixinStub(RegistryMixin): - def __init__(self): - super().__init__(None) - - -class TestRegistryMixin(TestCase): - - def test_registry_mixin_missing(self): +class TestRegistryProperties(TestCase, RegistryProperties): + """ + Test the functions in the ThemeManager module + """ + def setUp(self): """ - Test the registry creation and its usage + Create the Register """ - # GIVEN: A new registry Registry.create() - # WHEN: I create an instance of a class that doesn't inherit from RegistryMixin - PlainStub() - - # THEN: Nothing is registered with the registry - self.assertEqual(len(Registry().functions_list), 0), 'The function should not be in the dict anymore.' - - def test_registry_mixin_present(self): + def test_no_application(self): """ - Test the registry creation and its usage + Test property if no registry value assigned """ - # GIVEN: A new registry - Registry.create() + # GIVEN an Empty Registry + # WHEN there is no Application + # THEN the application should be none + self.assertEqual(self.application, None, 'The application value should be None') - # WHEN: I create an instance of a class that inherits from RegistryMixin - MixinStub() + def test_application(self): + """ + Test property if registry value assigned + """ + # GIVEN an Empty Registry + application = MagicMock() - # THEN: The bootstrap methods should be registered - self.assertEqual(len(Registry().functions_list), 2), 'The bootstrap functions should be in the dict.' + # WHEN the application is registered + Registry().register('application', application) + + # THEN the application should be none + self.assertEqual(self.application, application, 'The application value should match') + + @patch('openlp.core.common.mixins.is_win') + def test_application_on_windows(self, mocked_is_win): + """ + Test property if registry value assigned on Windows + """ + # GIVEN an Empty Registry and we're on Windows + application = MagicMock() + mocked_is_win.return_value = True + + # WHEN the application is registered + Registry().register('application', application) + + # THEN the application should be none + self.assertEqual(self.application, application, 'The application value should match') + + @patch('openlp.core.common.mixins.is_win') + def test_get_application_on_windows(self, mocked_is_win): + """ + Set that getting the application object on Windows happens dynamically + """ + # GIVEN an Empty Registry and we're on Windows + mocked_is_win.return_value = True + mock_application = MagicMock() + reg_props = RegistryProperties() + registry = Registry() + + # WHEN the application is accessed + with patch.object(registry, 'get') as mocked_get: + mocked_get.return_value = mock_application + actual_application = reg_props.application + + # THEN the application should be the mock object, and the correct function should have been called + self.assertEqual(mock_application, actual_application, 'The application value should match') + mocked_is_win.assert_called_with() + mocked_get.assert_called_with('application') diff --git a/tests/functional/openlp_core/common/test_registry.py b/tests/functional/openlp_core/common/test_registry.py index 6428a27c3..a274243bd 100644 --- a/tests/functional/openlp_core/common/test_registry.py +++ b/tests/functional/openlp_core/common/test_registry.py @@ -24,9 +24,9 @@ Package to test the openlp.core.lib package. """ import os from unittest import TestCase -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock -from openlp.core.common.registry import Registry, RegistryProperties +from openlp.core.common.registry import Registry, RegistryBase TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../', '..', 'resources')) @@ -151,70 +151,40 @@ class TestRegistry(TestCase): return "function_2" -class TestRegistryProperties(TestCase, RegistryProperties): - """ - Test the functions in the ThemeManager module - """ - def setUp(self): +class PlainStub(object): + def __init__(self): + pass + + +class RegistryStub(RegistryBase): + def __init__(self): + super().__init__() + + +class TestRegistryBase(TestCase): + + def test_registry_mixin_missing(self): """ - Create the Register + Test the registry creation and its usage """ + # GIVEN: A new registry Registry.create() - def test_no_application(self): + # WHEN: I create an instance of a class that doesn't inherit from RegistryMixin + PlainStub() + + # THEN: Nothing is registered with the registry + self.assertEqual(len(Registry().functions_list), 0), 'The function should not be in the dict anymore.' + + def test_registry_mixin_present(self): """ - Test property if no registry value assigned + Test the registry creation and its usage """ - # GIVEN an Empty Registry - # WHEN there is no Application - # THEN the application should be none - self.assertEqual(self.application, None, 'The application value should be None') + # GIVEN: A new registry + Registry.create() - def test_application(self): - """ - Test property if registry value assigned - """ - # GIVEN an Empty Registry - application = MagicMock() + # WHEN: I create an instance of a class that inherits from RegistryMixin + RegistryStub() - # WHEN the application is registered - Registry().register('application', application) - - # THEN the application should be none - self.assertEqual(self.application, application, 'The application value should match') - - @patch('openlp.core.common.registry.is_win') - def test_application_on_windows(self, mocked_is_win): - """ - Test property if registry value assigned on Windows - """ - # GIVEN an Empty Registry and we're on Windows - application = MagicMock() - mocked_is_win.return_value = True - - # WHEN the application is registered - Registry().register('application', application) - - # THEN the application should be none - self.assertEqual(self.application, application, 'The application value should match') - - @patch('openlp.core.common.registry.is_win') - def test_get_application_on_windows(self, mocked_is_win): - """ - Set that getting the application object on Windows happens dynamically - """ - # GIVEN an Empty Registry and we're on Windows - mocked_is_win.return_value = True - mock_application = MagicMock() - reg_props = RegistryProperties() - registry = Registry() - - # WHEN the application is accessed - with patch.object(registry, 'get') as mocked_get: - mocked_get.return_value = mock_application - actual_application = reg_props.application - - # THEN the application should be the mock object, and the correct function should have been called - self.assertEqual(mock_application, actual_application, 'The application value should match') - mocked_is_win.assert_called_with() - mocked_get.assert_called_with('application') + # THEN: The bootstrap methods should be registered + self.assertEqual(len(Registry().functions_list), 2), 'The bootstrap functions should be in the dict.' diff --git a/tests/functional/openlp_core/test_app.py b/tests/functional/openlp_core/test_app.py index 0f5633c3e..eccb81447 100644 --- a/tests/functional/openlp_core/test_app.py +++ b/tests/functional/openlp_core/test_app.py @@ -29,8 +29,6 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.app import OpenLP, parse_options from openlp.core.common.settings import Settings -from tests.helpers.testmixin import TestMixin - TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'resources')) @@ -141,23 +139,33 @@ class TestOpenLP(TestCase): """ Test the OpenLP app class """ - @patch('openlp.core.app.QtWidgets.QApplication.exec') - def test_exec(self, mocked_exec): + def setUp(self): + self.build_settings() + self.qapplication_patcher = patch('openlp.core.app.QtGui.QApplication') + self.mocked_qapplication = self.qapplication_patcher.start() + self.openlp = OpenLP([]) + + def tearDown(self): + self.qapplication_patcher.stop() + self.destroy_settings() + del self.openlp + self.openlp = None + + def test_exec(self): """ Test the exec method """ # GIVEN: An app - app = OpenLP([]) - app.shared_memory = MagicMock() - mocked_exec.return_value = False + self.openlp.shared_memory = MagicMock() + self.mocked_qapplication.exec.return_value = False # WHEN: exec() is called - result = app.exec() + result = self.openlp.exec() # THEN: The right things should be called - assert app.is_event_loop_active is True - mocked_exec.assert_called_once_with() - app.shared_memory.detach.assert_called_once_with() + assert self.openlp.is_event_loop_active is True + self.mocked_qapplication.exec.assert_called_once_with() + self.openlp.shared_memory.detach.assert_called_once_with() assert result is False @patch('openlp.core.app.QtCore.QSharedMemory') @@ -169,10 +177,9 @@ class TestOpenLP(TestCase): mocked_shared_memory = MagicMock() mocked_shared_memory.attach.return_value = False MockedSharedMemory.return_value = mocked_shared_memory - app = OpenLP([]) # WHEN: is_already_running() is called - result = app.is_already_running() + result = self.openlp.is_already_running() # THEN: The result should be false MockedSharedMemory.assert_called_once_with('OpenLP') @@ -193,10 +200,9 @@ class TestOpenLP(TestCase): MockedSharedMemory.return_value = mocked_shared_memory MockedStandardButtons.return_value = 0 mocked_critical.return_value = QtWidgets.QMessageBox.Yes - app = OpenLP([]) # WHEN: is_already_running() is called - result = app.is_already_running() + result = self.openlp.is_already_running() # THEN: The result should be false MockedSharedMemory.assert_called_once_with('OpenLP') @@ -218,10 +224,9 @@ class TestOpenLP(TestCase): MockedSharedMemory.return_value = mocked_shared_memory MockedStandardButtons.return_value = 0 mocked_critical.return_value = QtWidgets.QMessageBox.No - app = OpenLP([]) # WHEN: is_already_running() is called - result = app.is_already_running() + result = self.openlp.is_already_running() # THEN: The result should be false MockedSharedMemory.assert_called_once_with('OpenLP') @@ -235,11 +240,9 @@ class TestOpenLP(TestCase): Test that the app.process_events() method simply calls the Qt method """ # GIVEN: An app - app = OpenLP([]) - # WHEN: process_events() is called - with patch.object(app, 'processEvents') as mocked_processEvents: - app.process_events() + with patch.object(self.openlp, 'processEvents') as mocked_processEvents: + self.openlp.process_events() # THEN: processEvents was called mocked_processEvents.assert_called_once_with() @@ -249,12 +252,10 @@ class TestOpenLP(TestCase): Test that the set_busy_cursor() method sets the cursor """ # GIVEN: An app - app = OpenLP([]) - # WHEN: set_busy_cursor() is called - with patch.object(app, 'setOverrideCursor') as mocked_setOverrideCursor, \ - patch.object(app, 'processEvents') as mocked_processEvents: - app.set_busy_cursor() + with patch.object(self.openlp, 'setOverrideCursor') as mocked_setOverrideCursor, \ + patch.object(self.openlp, 'processEvents') as mocked_processEvents: + self.openlp.set_busy_cursor() # THEN: The cursor should have been set mocked_setOverrideCursor.assert_called_once_with(QtCore.Qt.BusyCursor) @@ -265,29 +266,15 @@ class TestOpenLP(TestCase): Test that the set_normal_cursor() method resets the cursor """ # GIVEN: An app - app = OpenLP([]) - # WHEN: set_normal_cursor() is called - with patch.object(app, 'restoreOverrideCursor') as mocked_restoreOverrideCursor, \ - patch.object(app, 'processEvents') as mocked_processEvents: - app.set_normal_cursor() + with patch.object(self.openlp, 'restoreOverrideCursor') as mocked_restoreOverrideCursor, \ + patch.object(self.openlp, 'processEvents') as mocked_processEvents: + self.openlp.set_normal_cursor() # THEN: The cursor should have been set mocked_restoreOverrideCursor.assert_called_once_with() mocked_processEvents.assert_called_once_with() - -class TestInit(TestCase, TestMixin): - def setUp(self): - self.build_settings() - with patch('openlp.core.app.OpenLPMixin.__init__') as constructor: - constructor.return_value = None - self.openlp = OpenLP(list()) - - def tearDown(self): - self.destroy_settings() - del self.openlp - def test_event(self): """ Test the reimplemented event method diff --git a/tests/functional/openlp_core/ui/lib/test_listwidgetwithdnd.py b/tests/functional/openlp_core/ui/lib/test_listwidgetwithdnd.py deleted file mode 100755 index 2a4e039fa..000000000 --- a/tests/functional/openlp_core/ui/lib/test_listwidgetwithdnd.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2017 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; version 2 of the License. # -# # -# 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, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### -""" -This module contains tests for the openlp.core.lib.listwidgetwithdnd module -""" -from unittest import TestCase -from unittest.mock import MagicMock, patch -from types import GeneratorType - -from openlp.core.common.i18n import UiStrings -from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD - - -class TestListWidgetWithDnD(TestCase): - """ - Test the :class:`~openlp.core.lib.listwidgetwithdnd.ListWidgetWithDnD` class - """ - def test_clear(self): - """ - Test the clear method when called without any arguments. - """ - # GIVEN: An instance of ListWidgetWithDnD - widget = ListWidgetWithDnD() - - # WHEN: Calling clear with out any arguments - widget.clear() - - # THEN: The results text should be the standard 'no results' text. - self.assertEqual(widget.no_results_text, UiStrings().NoResults) - - def test_clear_search_while_typing(self): - """ - Test the clear method when called with the search_while_typing argument set to True - """ - # GIVEN: An instance of ListWidgetWithDnD - widget = ListWidgetWithDnD() - - # WHEN: Calling clear with search_while_typing set to True - widget.clear(search_while_typing=True) - - # THEN: The results text should be the 'short results' text. - self.assertEqual(widget.no_results_text, UiStrings().ShortResults) - - def test_all_items_no_list_items(self): - """ - Test allItems when there are no items in the list widget - """ - # GIVEN: An instance of ListWidgetWithDnD - widget = ListWidgetWithDnD() - with patch.object(widget, 'count', return_value=0), \ - patch.object(widget, 'item', side_effect=lambda x: [][x]): - - # WHEN: Calling allItems - result = widget.allItems() - - # THEN: An instance of a Generator object should be returned. The generator should not yeild any results - self.assertIsInstance(result, GeneratorType) - self.assertEqual(list(result), []) - - def test_all_items_list_items(self): - """ - Test allItems when the list widget contains some items. - """ - # GIVEN: An instance of ListWidgetWithDnD - widget = ListWidgetWithDnD() - with patch.object(widget, 'count', return_value=2), \ - patch.object(widget, 'item', side_effect=lambda x: [5, 3][x]): - - # WHEN: Calling allItems - result = widget.allItems() - - # THEN: An instance of a Generator object should be returned. The generator should not yeild any results - self.assertIsInstance(result, GeneratorType) - self.assertEqual(list(result), [5, 3]) - - def test_paint_event(self): - """ - Test the paintEvent method when the list is not empty - """ - # GIVEN: An instance of ListWidgetWithDnD with a mocked out count methode which returns 1 - # (i.e the list has an item) - widget = ListWidgetWithDnD() - with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.paintEvent') as mocked_paint_event, \ - patch.object(widget, 'count', return_value=1), \ - patch.object(widget, 'viewport') as mocked_viewport: - mocked_event = MagicMock() - - # WHEN: Calling paintEvent - widget.paintEvent(mocked_event) - - # THEN: The overridden paintEvnet should have been called - mocked_paint_event.assert_called_once_with(mocked_event) - self.assertFalse(mocked_viewport.called) - - def test_paint_event_no_items(self): - """ - Test the paintEvent method when the list is empty - """ - # GIVEN: An instance of ListWidgetWithDnD with a mocked out count methode which returns 0 - # (i.e the list is empty) - widget = ListWidgetWithDnD() - mocked_painter_instance = MagicMock() - mocked_qrect = MagicMock() - with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.paintEvent') as mocked_paint_event, \ - patch.object(widget, 'count', return_value=0), \ - patch.object(widget, 'viewport'), \ - patch('openlp.core.ui.lib.listwidgetwithdnd.QtGui.QPainter', - return_value=mocked_painter_instance) as mocked_qpainter, \ - patch('openlp.core.ui.lib.listwidgetwithdnd.QtCore.QRect', return_value=mocked_qrect): - mocked_event = MagicMock() - - # WHEN: Calling paintEvent - widget.paintEvent(mocked_event) - - # THEN: The overridden paintEvnet should have been called, and some text should be drawn. - mocked_paint_event.assert_called_once_with(mocked_event) - mocked_qpainter.assert_called_once_with(widget.viewport()) - mocked_painter_instance.drawText.assert_called_once_with(mocked_qrect, 4100, 'No Search Results') diff --git a/tests/functional/openlp_core/ui/test_mainwindow.py b/tests/functional/openlp_core/ui/test_mainwindow.py index 4bb1cee37..5e1a69cbc 100644 --- a/tests/functional/openlp_core/ui/test_mainwindow.py +++ b/tests/functional/openlp_core/ui/test_mainwindow.py @@ -26,10 +26,11 @@ import os from unittest import TestCase from unittest.mock import MagicMock, patch -from PyQt5 import QtWidgets +from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import UiStrings from openlp.core.common.registry import Registry +from openlp.core.display.screens import ScreenList from openlp.core.ui.mainwindow import MainWindow from tests.helpers.testmixin import TestMixin @@ -37,8 +38,23 @@ from tests.utils.constants import TEST_RESOURCES_PATH class TestMainWindow(TestCase, TestMixin): + """ + Test the main window + """ + def _create_mock_action(self, parent, name, **kwargs): + """ + Create a fake action with some "real" attributes + """ + action = QtWidgets.QAction(parent) + action.setObjectName(name) + if kwargs.get('triggers'): + action.triggered.connect(kwargs.pop('triggers')) + return action def setUp(self): + """ + Set up the objects we need for all of the tests + """ Registry.create() self.registry = Registry() self.setup_application() @@ -48,30 +64,18 @@ class TestMainWindow(TestCase, TestMixin): self.app.args = [] Registry().register('application', self.app) Registry().set_flag('no_web_server', False) - # Mock classes and methods used by mainwindow. - with patch('openlp.core.ui.mainwindow.SettingsForm') as mocked_settings_form, \ - patch('openlp.core.ui.mainwindow.ImageManager') as mocked_image_manager, \ - patch('openlp.core.ui.mainwindow.LiveController') as mocked_live_controller, \ - patch('openlp.core.ui.mainwindow.PreviewController') as mocked_preview_controller, \ - patch('openlp.core.ui.mainwindow.OpenLPDockWidget') as mocked_dock_widget, \ - patch('openlp.core.ui.mainwindow.QtWidgets.QToolBox') as mocked_q_tool_box_class, \ - patch('openlp.core.ui.mainwindow.QtWidgets.QMainWindow.addDockWidget') as mocked_add_dock_method, \ - patch('openlp.core.ui.mainwindow.ThemeManager') as mocked_theme_manager, \ - patch('openlp.core.ui.mainwindow.Renderer') as mocked_renderer, \ - patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \ - patch('openlp.core.ui.mainwindow.server.HttpServer'): - self.mocked_settings_form = mocked_settings_form - self.mocked_image_manager = mocked_image_manager - self.mocked_live_controller = mocked_live_controller - self.mocked_preview_controller = mocked_preview_controller - self.mocked_dock_widget = mocked_dock_widget - self.mocked_q_tool_box_class = mocked_q_tool_box_class - self.mocked_add_dock_method = mocked_add_dock_method - self.mocked_theme_manager = mocked_theme_manager - self.mocked_renderer = mocked_renderer + self.add_toolbar_action_patcher = patch('openlp.core.ui.mainwindow.create_action') + self.mocked_add_toolbar_action = self.add_toolbar_action_patcher.start() + self.mocked_add_toolbar_action.side_effect = self._create_mock_action + with patch('openlp.core.display.screens.ScreenList.__instance__', spec=ScreenList) as mocked_screen_list: + mocked_screen_list.current = {'number': 0, 'size': QtCore.QSize(600, 800), 'primary': True} self.main_window = MainWindow() def tearDown(self): + """ + Delete all the C++ objects and stop all the patchers + """ + self.add_toolbar_action_patcher.stop() del self.main_window def test_cmd_line_file(self): @@ -81,13 +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] - with patch('openlp.core.ui.servicemanager.ServiceManager.load_file') as mocked_load_path: - # WHEN the argument is processed + # 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_path.assert_called_with(service), 'load_path should have been called with the service\'s path' + # THEN the service from the arguments is loaded + mocked_load_file.assert_called_with(service) def test_cmd_line_arg(self): """ @@ -96,13 +100,13 @@ class TestMainWindow(TestCase, TestMixin): # GIVEN a non service file as an argument to openlp service = os.path.join('openlp.py') self.main_window.arguments = [service] - with patch('openlp.core.ui.servicemanager.ServiceManager.load_file') as mocked_load_path: + with patch('openlp.core.ui.servicemanager.ServiceManager.load_file') as mocked_load_file: # WHEN the argument is processed self.main_window.open_cmd_line_files("") # THEN the file should not be opened - assert not mocked_load_path.called, 'load_path should not have been called' + assert not mocked_load_file.called, 'load_file should not have been called' def test_main_window_title(self): """ @@ -151,14 +155,14 @@ class TestMainWindow(TestCase, TestMixin): # WHEN: you check the started functions # THEN: the following registry functions should have been registered - self.assertEqual(len(self.registry.service_list), 6, 'The registry should have 6 services.') - self.assertEqual(len(self.registry.functions_list), 18, 'The registry should have 18 functions') - self.assertTrue('application' in self.registry.service_list, 'The application should have been registered.') - self.assertTrue('main_window' in self.registry.service_list, 'The main_window should have been registered.') - self.assertTrue('media_controller' in self.registry.service_list, 'The media_controller should have been ' - 'registered.') - self.assertTrue('plugin_manager' in self.registry.service_list, - 'The plugin_manager should have been registered.') + assert len(self.registry.service_list) == 12, \ + 'The registry should have 12 services, got {}'.format(self.registry.service_list.keys()) + assert len(self.registry.functions_list) == 19, \ + 'The registry should have 19 functions, got {}'.format(self.registry.functions_list.keys()) + assert 'application' in self.registry.service_list, 'The application should have been registered.' + assert 'main_window' in self.registry.service_list, 'The main_window should have been registered.' + assert 'media_controller' in self.registry.service_list, 'The media_controller should have been registered.' + assert 'plugin_manager' in self.registry.service_list, 'The plugin_manager should have been registered.' def test_projector_manager_hidden_on_startup(self): """ @@ -167,7 +171,7 @@ class TestMainWindow(TestCase, TestMixin): # GIVEN: A built main window # WHEN: OpenLP is started # THEN: The projector manager should be hidden - self.main_window.projector_manager_dock.setVisible.assert_called_once_with(False) + assert self.main_window.projector_manager_dock.isVisible() is False def test_on_search_shortcut_triggered_shows_media_manager(self): """ @@ -203,56 +207,38 @@ class TestMainWindow(TestCase, TestMixin): self.assertEqual(0, mocked_media_manager_dock.setVisible.call_count) mocked_widget.on_focus.assert_called_with() - @patch('openlp.core.ui.mainwindow.MainWindow.plugin_manager') - @patch('openlp.core.ui.mainwindow.MainWindow.first_time') - @patch('openlp.core.ui.mainwindow.MainWindow.application') @patch('openlp.core.ui.mainwindow.FirstTimeForm') @patch('openlp.core.ui.mainwindow.QtWidgets.QMessageBox.warning') @patch('openlp.core.ui.mainwindow.Settings') - def test_on_first_time_wizard_clicked_show_projectors_after(self, mocked_Settings, mocked_warning, - mocked_FirstTimeForm, mocked_application, - mocked_first_time, - mocked_plugin_manager): + def test_on_first_time_wizard_clicked_show_projectors_after(self, MockSettings, mocked_warning, MockWizard): + """Test that the projector manager is shown after the FTW is run""" # GIVEN: Main_window, patched things, patched "Yes" as confirmation to re-run wizard, settings to True. - mocked_Settings_obj = MagicMock() - mocked_Settings_obj.value.return_value = True - mocked_Settings.return_value = mocked_Settings_obj + MockSettings.return_value.value.return_value = True mocked_warning.return_value = QtWidgets.QMessageBox.Yes - mocked_FirstTimeForm_obj = MagicMock() - mocked_FirstTimeForm_obj.was_cancelled = False - mocked_FirstTimeForm.return_value = mocked_FirstTimeForm_obj - mocked_plugin_manager.plugins = [] - self.main_window.projector_manager_dock = MagicMock() + MockWizard.return_value.was_cancelled = False - # WHEN: on_first_time_wizard_clicked is called - self.main_window.on_first_time_wizard_clicked() + with patch.object(self.main_window, 'projector_manager_dock') as mocked_dock, \ + patch.object(self.registry, 'execute'), patch.object(self.main_window, 'theme_manager_contents'): + # WHEN: on_first_time_wizard_clicked is called + self.main_window.on_first_time_wizard_clicked() # THEN: projector_manager_dock.setVisible should had been called once - self.main_window.projector_manager_dock.setVisible.assert_called_once_with(True) + mocked_dock.setVisible.assert_called_once_with(True) - @patch('openlp.core.ui.mainwindow.MainWindow.plugin_manager') - @patch('openlp.core.ui.mainwindow.MainWindow.first_time') - @patch('openlp.core.ui.mainwindow.MainWindow.application') @patch('openlp.core.ui.mainwindow.FirstTimeForm') @patch('openlp.core.ui.mainwindow.QtWidgets.QMessageBox.warning') @patch('openlp.core.ui.mainwindow.Settings') - def test_on_first_time_wizard_clicked_hide_projectors_after(self, mocked_Settings, mocked_warning, - mocked_FirstTimeForm, mocked_application, - mocked_first_time, - mocked_plugin_manager): + def test_on_first_time_wizard_clicked_hide_projectors_after(self, MockSettings, mocked_warning, MockWizard): + """Test that the projector manager is hidden after the FTW is run""" # GIVEN: Main_window, patched things, patched "Yes" as confirmation to re-run wizard, settings to False. - mocked_Settings_obj = MagicMock() - mocked_Settings_obj.value.return_value = False - mocked_Settings.return_value = mocked_Settings_obj + MockSettings.return_value.value.return_value = False mocked_warning.return_value = QtWidgets.QMessageBox.Yes - mocked_FirstTimeForm_obj = MagicMock() - mocked_FirstTimeForm_obj.was_cancelled = False - mocked_FirstTimeForm.return_value = mocked_FirstTimeForm_obj - mocked_plugin_manager.plugins = [] - self.main_window.projector_manager_dock = MagicMock() + MockWizard.return_value.was_cancelled = False # WHEN: on_first_time_wizard_clicked is called - self.main_window.on_first_time_wizard_clicked() + with patch.object(self.main_window, 'projector_manager_dock') as mocked_dock, \ + patch.object(self.registry, 'execute'), patch.object(self.main_window, 'theme_manager_contents'): + self.main_window.on_first_time_wizard_clicked() # THEN: projector_manager_dock.setVisible should had been called once - self.main_window.projector_manager_dock.setVisible.assert_called_once_with(False) + mocked_dock.setVisible.assert_called_once_with(False) diff --git a/tests/functional/openlp_core/ui/test_servicemanager.py b/tests/functional/openlp_core/ui/test_servicemanager.py index 487f56362..adf241d35 100644 --- a/tests/functional/openlp_core/ui/test_servicemanager.py +++ b/tests/functional/openlp_core/ui/test_servicemanager.py @@ -32,7 +32,7 @@ from openlp.core.common import ThemeLevel from openlp.core.common.registry import Registry from openlp.core.lib import ServiceItem, ServiceItemType, ItemCapabilities from openlp.core.ui import ServiceManager -from openlp.core.ui.lib.toolbar import OpenLPToolbar +from openlp.core.widgets.toolbar import OpenLPToolbar class TestServiceManager(TestCase): diff --git a/tests/functional/openlp_core/ui/test_thememanager.py b/tests/functional/openlp_core/ui/test_thememanager.py index c1ba80f1d..62bf980b4 100644 --- a/tests/functional/openlp_core/ui/test_thememanager.py +++ b/tests/functional/openlp_core/ui/test_thememanager.py @@ -52,25 +52,24 @@ class TestThemeManager(TestCase): """ shutil.rmtree(self.temp_folder) - def test_export_theme(self): + @patch('openlp.core.ui.thememanager.zipfile.ZipFile.__init__') + @patch('openlp.core.ui.thememanager.zipfile.ZipFile.write') + def test_export_theme(self, mocked_zipfile_write, mocked_zipfile_init): """ Test exporting a theme . """ # GIVEN: A new ThemeManager instance. theme_manager = ThemeManager() theme_manager.theme_path = Path(TEST_RESOURCES_PATH, 'themes') - with patch('zipfile.ZipFile.__init__') as mocked_zipfile_init, \ - patch('zipfile.ZipFile.write') as mocked_zipfile_write: - mocked_zipfile_init.return_value = None + mocked_zipfile_init.return_value = None - # WHEN: The theme is exported - theme_manager._export_theme(Path('some', 'path', 'Default.otz'), 'Default') + # WHEN: The theme is exported + theme_manager._export_theme(Path('some', 'path', 'Default.otz'), 'Default') - # THEN: The zipfile should be created at the given path - mocked_zipfile_init.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w') - mocked_zipfile_write.assert_called_with(os.path.join(TEST_RESOURCES_PATH, 'themes', - 'Default', 'Default.xml'), - os.path.join('Default', 'Default.xml')) + # THEN: The zipfile should be created at the given path + mocked_zipfile_init.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w') + mocked_zipfile_write.assert_called_with(os.path.join(TEST_RESOURCES_PATH, 'themes', 'Default', 'Default.xml'), + os.path.join('Default', 'Default.xml')) def test_initial_theme_manager(self): """ @@ -83,53 +82,53 @@ class TestThemeManager(TestCase): # THEN: The the controller should be registered in the registry. self.assertIsNotNone(Registry().get('theme_manager'), 'The base theme manager should be registered') - def test_write_theme_same_image(self): + @patch('openlp.core.ui.thememanager.copyfile') + @patch('openlp.core.ui.thememanager.create_paths') + def test_write_theme_same_image(self, mocked_create_paths, mocked_copyfile): """ Test that we don't try to overwrite a theme background image with itself """ # GIVEN: A new theme manager instance, with mocked builtins.open, copyfile, # theme, create_paths and thememanager-attributes. - with patch('openlp.core.ui.thememanager.copyfile') as mocked_copyfile, \ - patch('openlp.core.ui.thememanager.create_paths'): - theme_manager = ThemeManager(None) - theme_manager.old_background_image = None - theme_manager.generate_and_save_image = MagicMock() - theme_manager.theme_path = MagicMock() - mocked_theme = MagicMock() - mocked_theme.theme_name = 'themename' - mocked_theme.extract_formatted_xml = MagicMock() - mocked_theme.extract_formatted_xml.return_value = 'fake_theme_xml'.encode() + theme_manager = ThemeManager(None) + theme_manager.old_background_image = None + theme_manager.generate_and_save_image = MagicMock() + theme_manager.theme_path = MagicMock() + mocked_theme = MagicMock() + mocked_theme.theme_name = 'themename' + mocked_theme.extract_formatted_xml = MagicMock() + mocked_theme.extract_formatted_xml.return_value = 'fake_theme_xml'.encode() - # WHEN: Calling _write_theme with path to the same image, but the path written slightly different - file_name1 = Path(TEST_RESOURCES_PATH, 'church.jpg') - theme_manager._write_theme(mocked_theme, file_name1, file_name1) + # WHEN: Calling _write_theme with path to the same image, but the path written slightly different + file_name1 = Path(TEST_RESOURCES_PATH, 'church.jpg') + theme_manager._write_theme(mocked_theme, file_name1, file_name1) - # THEN: The mocked_copyfile should not have been called - self.assertFalse(mocked_copyfile.called, 'copyfile should not be called') + # THEN: The mocked_copyfile should not have been called + assert mocked_copyfile.called is False, 'copyfile should not be called' - def test_write_theme_diff_images(self): + @patch('openlp.core.ui.thememanager.copyfile') + @patch('openlp.core.ui.thememanager.create_paths') + def test_write_theme_diff_images(self, mocked_create_paths, mocked_copyfile): """ Test that we do overwrite a theme background image when a new is submitted """ # GIVEN: A new theme manager instance, with mocked builtins.open, copyfile, # theme, create_paths and thememanager-attributes. - with patch('openlp.core.ui.thememanager.copyfile') as mocked_copyfile, \ - patch('openlp.core.ui.thememanager.create_paths'): - theme_manager = ThemeManager(None) - theme_manager.old_background_image = None - theme_manager.generate_and_save_image = MagicMock() - theme_manager.theme_path = MagicMock() - mocked_theme = MagicMock() - mocked_theme.theme_name = 'themename' - mocked_theme.filename = "filename" + theme_manager = ThemeManager(None) + theme_manager.old_background_image = None + theme_manager.generate_and_save_image = MagicMock() + theme_manager.theme_path = MagicMock() + mocked_theme = MagicMock() + mocked_theme.theme_name = 'themename' + mocked_theme.filename = "filename" - # WHEN: Calling _write_theme with path to different images - file_name1 = Path(TEST_RESOURCES_PATH, 'church.jpg') - file_name2 = Path(TEST_RESOURCES_PATH, 'church2.jpg') - theme_manager._write_theme(mocked_theme, file_name1, file_name2) + # WHEN: Calling _write_theme with path to different images + file_name1 = Path(TEST_RESOURCES_PATH, 'church.jpg') + file_name2 = Path(TEST_RESOURCES_PATH, 'church2.jpg') + theme_manager._write_theme(mocked_theme, file_name1, file_name2) - # THEN: The mocked_copyfile should not have been called - self.assertTrue(mocked_copyfile.called, 'copyfile should be called') + # THEN: The mocked_copyfile should not have been called + assert mocked_copyfile.called is True, 'copyfile should be called' def test_write_theme_special_char_name(self): """ @@ -151,45 +150,43 @@ class TestThemeManager(TestCase): self.assertTrue(os.path.exists(os.path.join(self.temp_folder, 'theme 愛 name', 'theme 愛 name.json')), 'Theme with special characters should have been created!') - def test_over_write_message_box_yes(self): + @patch('openlp.core.ui.thememanager.QtWidgets.QMessageBox.question', return_value=QtWidgets.QMessageBox.Yes) + @patch('openlp.core.ui.thememanager.translate') + def test_over_write_message_box_yes(self, mocked_translate, mocked_qmessagebox_question): """ Test that theme_manager.over_write_message_box returns True when the user clicks yes. """ # GIVEN: A patched QMessageBox.question and an instance of ThemeManager - with patch('openlp.core.ui.thememanager.QtWidgets.QMessageBox.question', - return_value=QtWidgets.QMessageBox.Yes) as mocked_qmessagebox_question,\ - patch('openlp.core.ui.thememanager.translate') as mocked_translate: - mocked_translate.side_effect = lambda context, text: text - theme_manager = ThemeManager(None) + mocked_translate.side_effect = lambda context, text: text + theme_manager = ThemeManager(None) - # WHEN: Calling over_write_message_box with 'Theme Name' - result = theme_manager.over_write_message_box('Theme Name') + # WHEN: Calling over_write_message_box with 'Theme Name' + result = theme_manager.over_write_message_box('Theme Name') - # THEN: over_write_message_box should return True and the message box should contain the theme name - self.assertTrue(result) - mocked_qmessagebox_question.assert_called_once_with( - theme_manager, 'Theme Already Exists', 'Theme Theme Name already exists. Do you want to replace it?', - defaultButton=ANY) + # THEN: over_write_message_box should return True and the message box should contain the theme name + assert result is True + mocked_qmessagebox_question.assert_called_once_with( + theme_manager, 'Theme Already Exists', 'Theme Theme Name already exists. Do you want to replace it?', + defaultButton=ANY) - def test_over_write_message_box_no(self): + @patch('openlp.core.ui.thememanager.QtWidgets.QMessageBox.question', return_value=QtWidgets.QMessageBox.No) + @patch('openlp.core.ui.thememanager.translate') + def test_over_write_message_box_no(self, mocked_translate, mocked_qmessagebox_question): """ Test that theme_manager.over_write_message_box returns False when the user clicks no. """ # GIVEN: A patched QMessageBox.question and an instance of ThemeManager - with patch('openlp.core.ui.thememanager.QtWidgets.QMessageBox.question', return_value=QtWidgets.QMessageBox.No)\ - as mocked_qmessagebox_question,\ - patch('openlp.core.ui.thememanager.translate') as mocked_translate: - mocked_translate.side_effect = lambda context, text: text - theme_manager = ThemeManager(None) + mocked_translate.side_effect = lambda context, text: text + theme_manager = ThemeManager(None) - # WHEN: Calling over_write_message_box with 'Theme Name' - result = theme_manager.over_write_message_box('Theme Name') + # WHEN: Calling over_write_message_box with 'Theme Name' + result = theme_manager.over_write_message_box('Theme Name') - # THEN: over_write_message_box should return False and the message box should contain the theme name - self.assertFalse(result) - mocked_qmessagebox_question.assert_called_once_with( - theme_manager, 'Theme Already Exists', 'Theme Theme Name already exists. Do you want to replace it?', - defaultButton=ANY) + # THEN: over_write_message_box should return False and the message box should contain the theme name + assert result is False + mocked_qmessagebox_question.assert_called_once_with( + theme_manager, 'Theme Already Exists', 'Theme Theme Name already exists. Do you want to replace it?', + defaultButton=ANY) def test_unzip_theme(self): """ diff --git a/tests/interfaces/openlp_core_ui_lib/__init__.py b/tests/functional/openlp_core/widgets/__init__.py similarity index 100% rename from tests/interfaces/openlp_core_ui_lib/__init__.py rename to tests/functional/openlp_core/widgets/__init__.py diff --git a/tests/functional/openlp_core/ui/lib/test_colorbutton.py b/tests/functional/openlp_core/widgets/test_buttons.py similarity index 70% rename from tests/functional/openlp_core/ui/lib/test_colorbutton.py rename to tests/functional/openlp_core/widgets/test_buttons.py index b010cbbdc..4abd38da4 100644 --- a/tests/functional/openlp_core/ui/lib/test_colorbutton.py +++ b/tests/functional/openlp_core/widgets/test_buttons.py @@ -20,12 +20,12 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -This module contains tests for the openlp.core.ui.lib.colorbutton module +This module contains tests for the openlp.core.widgets.buttons module """ from unittest import TestCase from unittest.mock import MagicMock, call, patch -from openlp.core.ui.lib import ColorButton +from openlp.core.widgets.buttons import ColorButton class TestColorDialog(TestCase): @@ -33,11 +33,11 @@ class TestColorDialog(TestCase): Test the :class:`~openlp.core.lib.colorbutton.ColorButton` class """ def setUp(self): - self.change_color_patcher = patch('openlp.core.ui.lib.colorbutton.ColorButton.change_color') - self.clicked_patcher = patch('openlp.core.ui.lib.colorbutton.ColorButton.clicked') - self.color_changed_patcher = patch('openlp.core.ui.lib.colorbutton.ColorButton.colorChanged') - self.qt_gui_patcher = patch('openlp.core.ui.lib.colorbutton.QtWidgets') - self.translate_patcher = patch('openlp.core.ui.lib.colorbutton.translate', **{'return_value': 'Tool Tip Text'}) + self.change_color_patcher = patch('openlp.core.widgets.buttons.ColorButton.change_color') + self.clicked_patcher = patch('openlp.core.widgets.buttons.ColorButton.clicked') + self.color_changed_patcher = patch('openlp.core.widgets.buttons.ColorButton.colorChanged') + self.qt_gui_patcher = patch('openlp.core.widgets.buttons.QtWidgets') + self.translate_patcher = patch('openlp.core.widgets.buttons.translate', **{'return_value': 'Tool Tip Text'}) self.addCleanup(self.change_color_patcher.stop) self.addCleanup(self.clicked_patcher.stop) self.addCleanup(self.color_changed_patcher.stop) @@ -49,41 +49,40 @@ class TestColorDialog(TestCase): self.mocked_qt_widgets = self.qt_gui_patcher.start() self.mocked_translate = self.translate_patcher.start() - def test_constructor(self): + @patch('openlp.core.widgets.buttons.ColorButton.setToolTip') + def test_constructor(self, mocked_set_tool_tip): """ Test that constructing a ColorButton object works correctly """ # GIVEN: The ColorButton class, a mocked change_color, setToolTip methods and clicked signal - with patch('openlp.core.ui.lib.colorbutton.ColorButton.setToolTip') as mocked_set_tool_tip: + # WHEN: The ColorButton object is instantiated + widget = ColorButton() - # WHEN: The ColorButton object is instantiated - widget = ColorButton() + # THEN: The widget __init__ method should have the correct properties and methods called + self.assertEqual(widget.parent, None, + 'The parent should be the same as the one that the class was instianted with') + self.mocked_change_color.assert_called_once_with('#ffffff') + mocked_set_tool_tip.assert_called_once_with('Tool Tip Text') + self.mocked_clicked.connect.assert_called_once_with(widget.on_clicked) - # THEN: The widget __init__ method should have the correct properties and methods called - self.assertEqual(widget.parent, None, - 'The parent should be the same as the one that the class was instianted with') - self.mocked_change_color.assert_called_once_with('#ffffff') - mocked_set_tool_tip.assert_called_once_with('Tool Tip Text') - self.mocked_clicked.connect.assert_called_once_with(widget.on_clicked) - - def test_change_color(self): + @patch('openlp.core.widgets.buttons.ColorButton.setStyleSheet') + def test_change_color(self, mocked_set_style_sheet): """ Test that change_color sets the new color and the stylesheet """ self.change_color_patcher.stop() # GIVEN: An instance of the ColorButton object, and a mocked out setStyleSheet - with patch('openlp.core.ui.lib.colorbutton.ColorButton.setStyleSheet') as mocked_set_style_sheet: - widget = ColorButton() + widget = ColorButton() - # WHEN: Changing the color - widget.change_color('#000000') + # WHEN: Changing the color + widget.change_color('#000000') - # THEN: The _color attribute should be set to #000000 and setStyleSheet should have been called twice - self.assertEqual(widget._color, '#000000', '_color should have been set to #000000') - mocked_set_style_sheet.assert_has_calls( - [call('background-color: #ffffff'), call('background-color: #000000')]) + # THEN: The _color attribute should be set to #000000 and setStyleSheet should have been called twice + self.assertEqual(widget._color, '#000000', '_color should have been set to #000000') + mocked_set_style_sheet.assert_has_calls( + [call('background-color: #ffffff'), call('background-color: #000000')]) self.mocked_change_color = self.change_color_patcher.start() @@ -91,22 +90,6 @@ class TestColorDialog(TestCase): """ Test that the color property method returns the set color """ - - # GIVEN: An instance of ColorButton, with a set _color attribute - widget = ColorButton() - widget._color = '#000000' - - # WHEN: Accesing the color property - value = widget.color - - # THEN: The value set in _color should be returned - self.assertEqual(value, '#000000', 'The value returned should be equal to the one we set') - - def test_color(self): - """ - Test that the color property method returns the set color - """ - # GIVEN: An instance of ColorButton, with a set _color attribute widget = ColorButton() widget._color = '#000000' @@ -117,20 +100,19 @@ class TestColorDialog(TestCase): # THEN: The value set in _color should be returned self.assertEqual(value, '#000000', 'The value returned should be equal to the one we set') + # @patch('openlp.core.widgets.buttons.ColorButton.__init__', **{'return_value': None}) def test_color_setter(self): """ Test that the color property setter method sets the color """ - # GIVEN: An instance of ColorButton, with a mocked __init__ - with patch('openlp.core.ui.lib.colorbutton.ColorButton.__init__', **{'return_value': None}): - widget = ColorButton() + widget = ColorButton() - # WHEN: Setting the color property - widget.color = '#000000' + # WHEN: Setting the color property + widget.color = '#000000' - # THEN: Then change_color should have been called with the value we set - self.mocked_change_color.assert_called_once_with('#000000') + # THEN: Then change_color should have been called with the value we set + self.mocked_change_color.assert_called_with('#000000') def test_on_clicked_invalid_color(self): """ diff --git a/tests/functional/openlp_core/ui/lib/test_filedialog.py b/tests/functional/openlp_core/widgets/test_dialogs.py similarity index 95% rename from tests/functional/openlp_core/ui/lib/test_filedialog.py rename to tests/functional/openlp_core/widgets/test_dialogs.py index 777ff65ec..fd65de33e 100755 --- a/tests/functional/openlp_core/ui/lib/test_filedialog.py +++ b/tests/functional/openlp_core/widgets/test_dialogs.py @@ -5,12 +5,12 @@ from unittest.mock import patch from PyQt5 import QtWidgets from openlp.core.common.path import Path -from openlp.core.ui.lib.filedialog import FileDialog +from openlp.core.widgets.dialogs import FileDialog class TestFileDialogPatches(TestCase): """ - Tests for the :mod:`openlp.core.ui.lib.filedialogpatches` module + Tests for the :mod:`openlp.core.widgets.dialogs` module """ def test_file_dialog(self): @@ -55,7 +55,7 @@ class TestFileDialogPatches(TestCase): order """ # GIVEN: FileDialog - with patch('openlp.core.ui.lib.filedialog.QtWidgets.QFileDialog.getExistingDirectory', return_value='') \ + with patch('openlp.core.widgets.dialogs.QtWidgets.QFileDialog.getExistingDirectory', return_value='') \ as mocked_get_existing_directory: # WHEN: Calling the getExistingDirectory method with all parameters set diff --git a/tests/functional/openlp_core/ui/lib/test_pathedit.py b/tests/functional/openlp_core/widgets/test_edits.py similarity index 81% rename from tests/functional/openlp_core/ui/lib/test_pathedit.py rename to tests/functional/openlp_core/widgets/test_edits.py index 227a4317a..5ce6dc9df 100755 --- a/tests/functional/openlp_core/ui/lib/test_pathedit.py +++ b/tests/functional/openlp_core/widgets/test_edits.py @@ -20,23 +20,24 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -This module contains tests for the openlp.core.ui.lib.pathedit module +This module contains tests for the openlp.core.widgets.edits module """ import os from unittest import TestCase from unittest.mock import MagicMock, PropertyMock, patch from openlp.core.common.path import Path -from openlp.core.ui.lib import PathEdit, PathType -from openlp.core.ui.lib.filedialog import FileDialog +from openlp.core.widgets.edits import PathEdit +from openlp.core.widgets.enums import PathEditType +from openlp.core.widgets.dialogs import FileDialog class TestPathEdit(TestCase): """ - Test the :class:`~openlp.core.lib.pathedit.PathEdit` class + Test the :class:`~openlp.core.widgets.edits.PathEdit` class """ def setUp(self): - with patch('openlp.core.ui.lib.pathedit.PathEdit._setup'): + with patch('openlp.core.widgets.edits.PathEdit._setup'): self.widget = PathEdit() def test_path_getter(self): @@ -73,7 +74,7 @@ class TestPathEdit(TestCase): # GIVEN: An instance of PathEdit # WHEN: Reading the `path` property # THEN: The default value should be returned - self.assertEqual(self.widget.path_type, PathType.Files) + self.assertEqual(self.widget.path_type, PathEditType.Files) def test_path_type_setter(self): """ @@ -83,11 +84,11 @@ class TestPathEdit(TestCase): with patch.object(self.widget, 'update_button_tool_tips') as mocked_update_button_tool_tips: # WHEN: Writing to a different value than default to the `path_type` property - self.widget.path_type = PathType.Directories + self.widget.path_type = PathEditType.Directories # THEN: The `_path_type` instance variable should be set with the test data and not the default. The # update_button_tool_tips should have been called. - self.assertEqual(self.widget._path_type, PathType.Directories) + self.assertEqual(self.widget._path_type, PathEditType.Directories) mocked_update_button_tool_tips.assert_called_once_with() def test_update_button_tool_tips_directories(self): @@ -97,7 +98,7 @@ class TestPathEdit(TestCase): # GIVEN: An instance of PathEdit with the `path_type` set to `Directories` self.widget.browse_button = MagicMock() self.widget.revert_button = MagicMock() - self.widget._path_type = PathType.Directories + self.widget._path_type = PathEditType.Directories # WHEN: Calling update_button_tool_tips self.widget.update_button_tool_tips() @@ -112,7 +113,7 @@ class TestPathEdit(TestCase): # GIVEN: An instance of PathEdit with the `path_type` set to `Files` self.widget.browse_button = MagicMock() self.widget.revert_button = MagicMock() - self.widget._path_type = PathType.Files + self.widget._path_type = PathEditType.Files # WHEN: Calling update_button_tool_tips self.widget.update_button_tool_tips() @@ -120,26 +121,25 @@ class TestPathEdit(TestCase): self.widget.browse_button.setToolTip.assert_called_once_with('Browse for file.') self.widget.revert_button.setToolTip.assert_called_once_with('Revert to default file.') - def test_on_browse_button_clicked_directory(self): + @patch('openlp.core.widgets.edits.FileDialog.getExistingDirectory', return_value=None) + @patch('openlp.core.widgets.edits.FileDialog.getOpenFileName') + def test_on_browse_button_clicked_directory(self, mocked_get_open_file_name, mocked_get_existing_directory): """ Test the `browse_button` `clicked` handler on_browse_button_clicked when the `path_type` is set to Directories. """ # GIVEN: An instance of PathEdit with the `path_type` set to `Directories` and a mocked # QFileDialog.getExistingDirectory - with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory', return_value=None) as \ - mocked_get_existing_directory, \ - patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName') as mocked_get_open_file_name: - self.widget._path_type = PathType.Directories - self.widget._path = Path('test', 'path') + self.widget._path_type = PathEditType.Directories + self.widget._path = Path('test', 'path') - # WHEN: Calling on_browse_button_clicked - self.widget.on_browse_button_clicked() + # WHEN: Calling on_browse_button_clicked + self.widget.on_browse_button_clicked() - # THEN: The FileDialog.getExistingDirectory should have been called with the default caption - mocked_get_existing_directory.assert_called_once_with(self.widget, 'Select Directory', - Path('test', 'path'), - FileDialog.ShowDirsOnly) - self.assertFalse(mocked_get_open_file_name.called) + # THEN: The FileDialog.getExistingDirectory should have been called with the default caption + mocked_get_existing_directory.assert_called_once_with(self.widget, 'Select Directory', + Path('test', 'path'), + FileDialog.ShowDirsOnly) + self.assertFalse(mocked_get_open_file_name.called) def test_on_browse_button_clicked_directory_custom_caption(self): """ @@ -148,10 +148,10 @@ class TestPathEdit(TestCase): """ # GIVEN: An instance of PathEdit with the `path_type` set to `Directories` and a mocked # QFileDialog.getExistingDirectory with `default_caption` set. - with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory', return_value=None) as \ + with patch('openlp.core.widgets.edits.FileDialog.getExistingDirectory', return_value=None) as \ mocked_get_existing_directory, \ - patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName') as mocked_get_open_file_name: - self.widget._path_type = PathType.Directories + patch('openlp.core.widgets.edits.FileDialog.getOpenFileName') as mocked_get_open_file_name: + self.widget._path_type = PathEditType.Directories self.widget._path = Path('test', 'path') self.widget.dialog_caption = 'Directory Caption' @@ -169,10 +169,10 @@ class TestPathEdit(TestCase): Test the `browse_button` `clicked` handler on_browse_button_clicked when the `path_type` is set to Files. """ # GIVEN: An instance of PathEdit with the `path_type` set to `Files` and a mocked QFileDialog.getOpenFileName - with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory') as mocked_get_existing_directory, \ - patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \ + with patch('openlp.core.widgets.edits.FileDialog.getExistingDirectory') as mocked_get_existing_directory, \ + patch('openlp.core.widgets.edits.FileDialog.getOpenFileName', return_value=(None, '')) as \ mocked_get_open_file_name: - self.widget._path_type = PathType.Files + self.widget._path_type = PathEditType.Files self.widget._path = Path('test', 'pat.h') # WHEN: Calling on_browse_button_clicked @@ -190,10 +190,10 @@ class TestPathEdit(TestCase): """ # GIVEN: An instance of PathEdit with the `path_type` set to `Files` and a mocked QFileDialog.getOpenFileName # with `default_caption` set. - with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory') as mocked_get_existing_directory, \ - patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \ + with patch('openlp.core.widgets.edits.FileDialog.getExistingDirectory') as mocked_get_existing_directory, \ + patch('openlp.core.widgets.edits.FileDialog.getOpenFileName', return_value=(None, '')) as \ mocked_get_open_file_name: - self.widget._path_type = PathType.Files + self.widget._path_type = PathEditType.Files self.widget._path = Path('test', 'pat.h') self.widget.dialog_caption = 'File Caption' @@ -212,7 +212,7 @@ class TestPathEdit(TestCase): """ # GIVEN: An instance of PathEdit with a mocked QFileDialog.getOpenFileName which returns an empty str for the # file path. - with patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \ + with patch('openlp.core.widgets.edits.FileDialog.getOpenFileName', return_value=(None, '')) as \ mocked_get_open_file_name: # WHEN: Calling on_browse_button_clicked @@ -228,7 +228,7 @@ class TestPathEdit(TestCase): """ # GIVEN: An instance of PathEdit with a mocked QFileDialog.getOpenFileName which returns a str for the file # path. - with patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', + with patch('openlp.core.widgets.edits.FileDialog.getOpenFileName', return_value=(Path('test', 'pat.h'), '')) as mocked_get_open_file_name, \ patch.object(self.widget, 'on_new_path'): @@ -272,7 +272,7 @@ class TestPathEdit(TestCase): Test `on_new_path` when called with a path that is the same as the existing path. """ # GIVEN: An instance of PathEdit with a test path and mocked `pathChanged` signal - with patch('openlp.core.ui.lib.pathedit.PathEdit.path', new_callable=PropertyMock): + with patch('openlp.core.widgets.edits.PathEdit.path', new_callable=PropertyMock): self.widget._path = Path('/old', 'test', 'pat.h') self.widget.pathChanged = MagicMock() @@ -287,7 +287,7 @@ class TestPathEdit(TestCase): Test `on_new_path` when called with a path that is the different to the existing path. """ # GIVEN: An instance of PathEdit with a test path and mocked `pathChanged` signal - with patch('openlp.core.ui.lib.pathedit.PathEdit.path', new_callable=PropertyMock): + with patch('openlp.core.widgets.edits.PathEdit.path', new_callable=PropertyMock): self.widget._path = Path('/old', 'test', 'pat.h') self.widget.pathChanged = MagicMock() diff --git a/tests/functional/openlp_core/ui/lib/test_listpreviewwidget.py b/tests/functional/openlp_core/widgets/test_views.py similarity index 75% rename from tests/functional/openlp_core/ui/lib/test_listpreviewwidget.py rename to tests/functional/openlp_core/widgets/test_views.py index a64ae5e0e..d931a5ef5 100644 --- a/tests/functional/openlp_core/ui/lib/test_listpreviewwidget.py +++ b/tests/functional/openlp_core/widgets/test_views.py @@ -20,15 +20,17 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.ui.lib.listpreviewwidget package. +Package to test the openlp.core.widgets.views package. """ +from types import GeneratorType from unittest import TestCase from unittest.mock import MagicMock, patch, call from PyQt5 import QtGui -from openlp.core.ui.lib.listpreviewwidget import ListPreviewWidget +from openlp.core.common.i18n import UiStrings from openlp.core.lib import ImageSource +from openlp.core.widgets.views import ListPreviewWidget, ListWidgetWithDnD, TreeWidgetWithDnD class TestListPreviewWidget(TestCase): @@ -38,13 +40,13 @@ class TestListPreviewWidget(TestCase): Mock out stuff for all the tests """ # Mock self.parent().width() - self.parent_patcher = patch('openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.parent') + self.parent_patcher = patch('openlp.core.widgets.views.ListPreviewWidget.parent') self.mocked_parent = self.parent_patcher.start() self.mocked_parent.width.return_value = 100 self.addCleanup(self.parent_patcher.stop) # Mock Settings().value() - self.Settings_patcher = patch('openlp.core.ui.lib.listpreviewwidget.Settings') + self.Settings_patcher = patch('openlp.core.widgets.views.Settings') self.mocked_Settings = self.Settings_patcher.start() self.mocked_Settings_obj = MagicMock() self.mocked_Settings_obj.value.return_value = None @@ -52,7 +54,7 @@ class TestListPreviewWidget(TestCase): self.addCleanup(self.Settings_patcher.stop) # Mock self.viewport().width() - self.viewport_patcher = patch('openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.viewport') + self.viewport_patcher = patch('openlp.core.widgets.views.ListPreviewWidget.viewport') self.mocked_viewport = self.viewport_patcher.start() self.mocked_viewport_obj = MagicMock() self.mocked_viewport_obj.width.return_value = 200 @@ -72,9 +74,9 @@ class TestListPreviewWidget(TestCase): self.assertIsNotNone(list_preview_widget, 'The ListPreviewWidget object should not be None') self.assertEquals(list_preview_widget.screen_ratio, 1, 'Should not be called') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.image_manager') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.image_manager') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.resizeRowsToContents') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.setRowHeight') def test_replace_service_item_thumbs(self, mocked_setRowHeight, mocked_resizeRowsToContents, mocked_image_manager): """ @@ -119,8 +121,8 @@ class TestListPreviewWidget(TestCase): call('TEST3', ImageSource.CommandPlugins), call('TEST4', ImageSource.CommandPlugins)] mocked_image_manager.get_image.assert_has_calls(calls) - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.resizeRowsToContents') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.setRowHeight') def test_replace_recalculate_layout_text(self, mocked_setRowHeight, mocked_resizeRowsToContents): """ Test if "Max height for non-text slides..." enabled, txt slides unchanged in replace_service_item & __recalc... @@ -151,8 +153,8 @@ class TestListPreviewWidget(TestCase): self.assertEquals(mocked_resizeRowsToContents.call_count, 2, 'Should be called') self.assertEquals(mocked_setRowHeight.call_count, 0, 'Should not be called') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.resizeRowsToContents') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.setRowHeight') def test_replace_recalculate_layout_img(self, mocked_setRowHeight, mocked_resizeRowsToContents): """ Test if "Max height for non-text slides..." disabled, img slides unchanged in replace_service_item & __recalc... @@ -188,8 +190,8 @@ class TestListPreviewWidget(TestCase): calls = [call(0, 200), call(1, 200), call(0, 400), call(1, 400), call(0, 400), call(1, 400)] mocked_setRowHeight.assert_has_calls(calls) - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.resizeRowsToContents') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.setRowHeight') def test_replace_recalculate_layout_img_max(self, mocked_setRowHeight, mocked_resizeRowsToContents): """ Test if "Max height for non-text slides..." enabled, img slides resized in replace_service_item & __recalc... @@ -223,8 +225,8 @@ class TestListPreviewWidget(TestCase): calls = [call(0, 100), call(1, 100), call(0, 100), call(1, 100)] mocked_setRowHeight.assert_has_calls(calls) - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.resizeRowsToContents') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.setRowHeight') def test_replace_recalculate_layout_img_auto(self, mocked_setRowHeight, mocked_resizeRowsToContents): """ Test if "Max height for non-text slides..." auto, img slides resized in replace_service_item & __recalc... @@ -261,9 +263,9 @@ class TestListPreviewWidget(TestCase): calls = [call(0, 100), call(1, 100), call(0, 150), call(1, 150), call(0, 100), call(1, 100)] mocked_setRowHeight.assert_has_calls(calls) - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.resizeRowsToContents') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.setRowHeight') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.cellWidget') def test_row_resized_text(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents): """ Test if "Max height for non-text slides..." enabled, text-based slides not affected in row_resized. @@ -295,9 +297,9 @@ class TestListPreviewWidget(TestCase): # THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should not be called self.assertEquals(mocked_cellWidget_child.setMaximumWidth.call_count, 0, 'Should not be called') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.resizeRowsToContents') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.setRowHeight') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.cellWidget') def test_row_resized_img(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents): """ Test if "Max height for non-text slides..." disabled, image-based slides not affected in row_resized. @@ -332,9 +334,9 @@ class TestListPreviewWidget(TestCase): # THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should not be called self.assertEquals(mocked_cellWidget_child.setMaximumWidth.call_count, 0, 'Should not be called') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.resizeRowsToContents') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.setRowHeight') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.cellWidget') def test_row_resized_img_max(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents): """ Test if "Max height for non-text slides..." enabled, image-based slides are scaled in row_resized. @@ -367,9 +369,9 @@ class TestListPreviewWidget(TestCase): # THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should be called mocked_cellWidget_child.setMaximumWidth.assert_called_once_with(150) - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.resizeRowsToContents') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.setRowHeight') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.cellWidget') def test_row_resized_setting_changed(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents): """ Test if "Max height for non-text slides..." enabled while item live, program doesn't crash on row_resized. @@ -402,10 +404,10 @@ class TestListPreviewWidget(TestCase): # THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should fail self.assertRaises(Exception) - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.selectRow') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.scrollToItem') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.item') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.slide_count') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.selectRow') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.scrollToItem') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.item') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.slide_count') def test_autoscroll_setting_invalid(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow): """ Test if 'advanced/autoscrolling' setting None or invalid, that no autoscrolling occurs on change_slide(). @@ -438,10 +440,10 @@ class TestListPreviewWidget(TestCase): self.assertEquals(mocked_selectRow.call_count, 0, 'Should not be called') self.assertEquals(mocked_item.call_count, 0, 'Should not be called') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.selectRow') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.scrollToItem') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.item') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.slide_count') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.selectRow') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.scrollToItem') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.item') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.slide_count') def test_autoscroll_dist_bounds(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow): """ Test if 'advanced/autoscrolling' setting asks to scroll beyond list bounds, that it does not beyond. @@ -468,10 +470,10 @@ class TestListPreviewWidget(TestCase): calls = [call(0, 0), call(0, 0)] mocked_item.assert_has_calls(calls) - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.selectRow') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.scrollToItem') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.item') - @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.slide_count') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.selectRow') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.scrollToItem') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.item') + @patch(u'openlp.core.widgets.views.ListPreviewWidget.slide_count') def test_autoscroll_normal(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow): """ Test if 'advanced/autoscrolling' setting valid, autoscrolling called as expected. @@ -499,3 +501,130 @@ class TestListPreviewWidget(TestCase): self.assertEquals(mocked_item.call_count, 3, 'Should be called') calls = [call(0, 0), call(1, 0), call(2, 0)] mocked_item.assert_has_calls(calls) + + +class TestListWidgetWithDnD(TestCase): + """ + Test the :class:`~openlp.core.widgets.views.ListWidgetWithDnD` class + """ + def test_clear(self): + """ + Test the clear method when called without any arguments. + """ + # GIVEN: An instance of ListWidgetWithDnD + widget = ListWidgetWithDnD() + + # WHEN: Calling clear with out any arguments + widget.clear() + + # THEN: The results text should be the standard 'no results' text. + self.assertEqual(widget.no_results_text, UiStrings().NoResults) + + def test_clear_search_while_typing(self): + """ + Test the clear method when called with the search_while_typing argument set to True + """ + # GIVEN: An instance of ListWidgetWithDnD + widget = ListWidgetWithDnD() + + # WHEN: Calling clear with search_while_typing set to True + widget.clear(search_while_typing=True) + + # THEN: The results text should be the 'short results' text. + self.assertEqual(widget.no_results_text, UiStrings().ShortResults) + + def test_all_items_no_list_items(self): + """ + Test allItems when there are no items in the list widget + """ + # GIVEN: An instance of ListWidgetWithDnD + widget = ListWidgetWithDnD() + with patch.object(widget, 'count', return_value=0), \ + patch.object(widget, 'item', side_effect=lambda x: [][x]): + + # WHEN: Calling allItems + result = widget.allItems() + + # THEN: An instance of a Generator object should be returned. The generator should not yeild any results + self.assertIsInstance(result, GeneratorType) + self.assertEqual(list(result), []) + + def test_all_items_list_items(self): + """ + Test allItems when the list widget contains some items. + """ + # GIVEN: An instance of ListWidgetWithDnD + widget = ListWidgetWithDnD() + with patch.object(widget, 'count', return_value=2), \ + patch.object(widget, 'item', side_effect=lambda x: [5, 3][x]): + + # WHEN: Calling allItems + result = widget.allItems() + + # THEN: An instance of a Generator object should be returned. The generator should not yeild any results + self.assertIsInstance(result, GeneratorType) + self.assertEqual(list(result), [5, 3]) + + def test_paint_event(self): + """ + Test the paintEvent method when the list is not empty + """ + # GIVEN: An instance of ListWidgetWithDnD with a mocked out count methode which returns 1 + # (i.e the list has an item) + widget = ListWidgetWithDnD() + with patch('openlp.core.widgets.views.QtWidgets.QListWidget.paintEvent') as mocked_paint_event, \ + patch.object(widget, 'count', return_value=1), \ + patch.object(widget, 'viewport') as mocked_viewport: + mocked_event = MagicMock() + + # WHEN: Calling paintEvent + widget.paintEvent(mocked_event) + + # THEN: The overridden paintEvnet should have been called + mocked_paint_event.assert_called_once_with(mocked_event) + self.assertFalse(mocked_viewport.called) + + def test_paint_event_no_items(self): + """ + Test the paintEvent method when the list is empty + """ + # GIVEN: An instance of ListWidgetWithDnD with a mocked out count methode which returns 0 + # (i.e the list is empty) + widget = ListWidgetWithDnD() + mocked_painter_instance = MagicMock() + mocked_qrect = MagicMock() + with patch('openlp.core.widgets.views.QtWidgets.QListWidget.paintEvent') as mocked_paint_event, \ + patch.object(widget, 'count', return_value=0), \ + patch.object(widget, 'viewport'), \ + patch('openlp.core.widgets.views.QtGui.QPainter', + return_value=mocked_painter_instance) as mocked_qpainter, \ + patch('openlp.core.widgets.views.QtCore.QRect', return_value=mocked_qrect): + mocked_event = MagicMock() + + # WHEN: Calling paintEvent + widget.paintEvent(mocked_event) + + # THEN: The overridden paintEvnet should have been called, and some text should be drawn. + mocked_paint_event.assert_called_once_with(mocked_event) + mocked_qpainter.assert_called_once_with(widget.viewport()) + mocked_painter_instance.drawText.assert_called_once_with(mocked_qrect, 4100, 'No Search Results') + + +class TestTreeWidgetWithDnD(TestCase): + """ + Test the :class:`~openlp.core.widgets.views.TreeWidgetWithDnD` class + """ + def test_constructor(self): + """ + Test the constructor + """ + # GIVEN: A TreeWidgetWithDnD + # WHEN: An instance is created + widget = TreeWidgetWithDnD(name='Test') + + # THEN: It should be initialised correctly + assert widget.mime_data_text == 'Test' + assert widget.allow_internal_dnd is False + assert widget.indentation() == 0 + assert widget.isAnimated() is True + diff --git a/tests/interfaces/openlp_core_ul_media_vendor/__init__.py b/tests/interfaces/openlp_core/__init__.py similarity index 100% rename from tests/interfaces/openlp_core_ul_media_vendor/__init__.py rename to tests/interfaces/openlp_core/__init__.py diff --git a/openlp/core/ui/lib/dockwidget.py b/tests/interfaces/openlp_core/api/__init__.py similarity index 60% rename from openlp/core/ui/lib/dockwidget.py rename to tests/interfaces/openlp_core/api/__init__.py index 398d1e674..ea62548f4 100644 --- a/openlp/core/ui/lib/dockwidget.py +++ b/tests/interfaces/openlp_core/api/__init__.py @@ -19,39 +19,3 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### - -""" -Provide additional functionality required by OpenLP from the inherited QDockWidget. -""" - -import logging - -from PyQt5 import QtWidgets - -from openlp.core.display.screens import ScreenList -from openlp.core.lib import build_icon - -log = logging.getLogger(__name__) - - -class OpenLPDockWidget(QtWidgets.QDockWidget): - """ - Custom DockWidget class to handle events - """ - def __init__(self, parent=None, name=None, icon=None): - """ - Initialise the DockWidget - """ - log.debug('Initialise the %s widget' % name) - super(OpenLPDockWidget, self).__init__(parent) - if name: - self.setObjectName(name) - if icon: - self.setWindowIcon(build_icon(icon)) - # Sort out the minimum width. - screens = ScreenList() - main_window_docbars = screens.current['size'].width() // 5 - if main_window_docbars > 300: - self.setMinimumWidth(300) - else: - self.setMinimumWidth(main_window_docbars) diff --git a/tests/interfaces/openlp_core/common/__init__.py b/tests/interfaces/openlp_core/common/__init__.py new file mode 100644 index 000000000..ea62548f4 --- /dev/null +++ b/tests/interfaces/openlp_core/common/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### diff --git a/tests/interfaces/openlp_core_common/test_utils.py b/tests/interfaces/openlp_core/common/test_utils.py similarity index 100% rename from tests/interfaces/openlp_core_common/test_utils.py rename to tests/interfaces/openlp_core/common/test_utils.py diff --git a/tests/interfaces/openlp_core/lib/__init__.py b/tests/interfaces/openlp_core/lib/__init__.py new file mode 100644 index 000000000..ea62548f4 --- /dev/null +++ b/tests/interfaces/openlp_core/lib/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### diff --git a/tests/interfaces/openlp_core_lib/test_pluginmanager.py b/tests/interfaces/openlp_core/lib/test_pluginmanager.py similarity index 100% rename from tests/interfaces/openlp_core_lib/test_pluginmanager.py rename to tests/interfaces/openlp_core/lib/test_pluginmanager.py diff --git a/tests/interfaces/openlp_core_ui/__init__.py b/tests/interfaces/openlp_core/ui/__init__.py similarity index 100% rename from tests/interfaces/openlp_core_ui/__init__.py rename to tests/interfaces/openlp_core/ui/__init__.py diff --git a/tests/interfaces/openlp_core/ui/lib/__init__.py b/tests/interfaces/openlp_core/ui/lib/__init__.py new file mode 100644 index 000000000..ea62548f4 --- /dev/null +++ b/tests/interfaces/openlp_core/ui/lib/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### diff --git a/tests/interfaces/openlp_core_ui_lib/test_historycombobox.py b/tests/interfaces/openlp_core/ui/lib/test_historycombobox.py similarity index 100% rename from tests/interfaces/openlp_core_ui_lib/test_historycombobox.py rename to tests/interfaces/openlp_core/ui/lib/test_historycombobox.py diff --git a/tests/interfaces/openlp_core/ui/media/__init__.py b/tests/interfaces/openlp_core/ui/media/__init__.py new file mode 100644 index 000000000..ea62548f4 --- /dev/null +++ b/tests/interfaces/openlp_core/ui/media/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### diff --git a/tests/interfaces/openlp_core/ui/media/vendor/__init__.py b/tests/interfaces/openlp_core/ui/media/vendor/__init__.py new file mode 100644 index 000000000..ea62548f4 --- /dev/null +++ b/tests/interfaces/openlp_core/ui/media/vendor/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### diff --git a/tests/interfaces/openlp_core_ul_media_vendor/test_mediainfoWrapper.py b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py similarity index 97% rename from tests/interfaces/openlp_core_ul_media_vendor/test_mediainfoWrapper.py rename to tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py index d4c1891b1..f8fee253d 100644 --- a/tests/interfaces/openlp_core_ul_media_vendor/test_mediainfoWrapper.py +++ b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py @@ -28,7 +28,7 @@ from unittest import TestCase from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper -TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources', 'media')) +TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', 'resources', 'media')) TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]] diff --git a/tests/interfaces/openlp_core_ui/test_filerenamedialog.py b/tests/interfaces/openlp_core/ui/test_filerenamedialog.py similarity index 100% rename from tests/interfaces/openlp_core_ui/test_filerenamedialog.py rename to tests/interfaces/openlp_core/ui/test_filerenamedialog.py diff --git a/tests/interfaces/openlp_core_ui/test_mainwindow.py b/tests/interfaces/openlp_core/ui/test_mainwindow.py similarity index 100% rename from tests/interfaces/openlp_core_ui/test_mainwindow.py rename to tests/interfaces/openlp_core/ui/test_mainwindow.py diff --git a/tests/interfaces/openlp_core_ui/test_projectoreditform.py b/tests/interfaces/openlp_core/ui/test_projectoreditform.py similarity index 100% rename from tests/interfaces/openlp_core_ui/test_projectoreditform.py rename to tests/interfaces/openlp_core/ui/test_projectoreditform.py diff --git a/tests/interfaces/openlp_core_ui/test_projectormanager.py b/tests/interfaces/openlp_core/ui/test_projectormanager.py similarity index 100% rename from tests/interfaces/openlp_core_ui/test_projectormanager.py rename to tests/interfaces/openlp_core/ui/test_projectormanager.py diff --git a/tests/interfaces/openlp_core_ui/test_projectorsourceform.py b/tests/interfaces/openlp_core/ui/test_projectorsourceform.py similarity index 100% rename from tests/interfaces/openlp_core_ui/test_projectorsourceform.py rename to tests/interfaces/openlp_core/ui/test_projectorsourceform.py diff --git a/tests/interfaces/openlp_core_ui/test_servicemanager.py b/tests/interfaces/openlp_core/ui/test_servicemanager.py similarity index 87% rename from tests/interfaces/openlp_core_ui/test_servicemanager.py rename to tests/interfaces/openlp_core/ui/test_servicemanager.py index 1deb1a97f..3426fffcb 100644 --- a/tests/interfaces/openlp_core_ui/test_servicemanager.py +++ b/tests/interfaces/openlp_core/ui/test_servicemanager.py @@ -25,102 +25,103 @@ from unittest import TestCase from unittest.mock import MagicMock, patch +from PyQt5 import QtCore, QtGui, QtWidgets + from openlp.core.common.registry import Registry -from openlp.core.display.screens import ScreenList from openlp.core.lib import ServiceItem, ItemCapabilities -from openlp.core.ui.mainwindow import MainWindow +from openlp.core.ui.servicemanager import ServiceManager from tests.helpers.testmixin import TestMixin -from PyQt5 import QtCore, QtGui, QtWidgets - class TestServiceManager(TestCase, TestMixin): + """ + Test the service manager + """ + + def _create_mock_action(self, name, **kwargs): + """ + Create a fake action with some "real" attributes + """ + action = QtWidgets.QAction(self.service_manager) + action.setObjectName(name) + if kwargs.get('triggers'): + action.triggered.connect(kwargs.pop('triggers')) + self.service_manager.toolbar.actions[name] = action + return action def setUp(self): """ Create the UI """ Registry.create() - Registry().set_flag('no_web_server', False) self.setup_application() - ScreenList.create(self.app.desktop()) Registry().register('application', MagicMock()) - # Mock classes and methods used by mainwindow. - with patch('openlp.core.ui.mainwindow.SettingsForm'), \ - patch('openlp.core.ui.mainwindow.ImageManager'), \ - patch('openlp.core.ui.mainwindow.LiveController'), \ - patch('openlp.core.ui.mainwindow.PreviewController'), \ - patch('openlp.core.ui.mainwindow.OpenLPDockWidget'), \ - patch('openlp.core.ui.mainwindow.QtWidgets.QToolBox'), \ - patch('openlp.core.ui.mainwindow.QtWidgets.QMainWindow.addDockWidget'), \ - patch('openlp.core.ui.mainwindow.ThemeManager'), \ - patch('openlp.core.ui.mainwindow.ProjectorManager'), \ - patch('openlp.core.ui.mainwindow.Renderer'), \ - patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \ - patch('openlp.core.ui.mainwindow.server.HttpServer'): - self.main_window = MainWindow() - self.service_manager = Registry().get('service_manager') + Registry().register('main_window', MagicMock(service_manager_settings_section='servicemanager')) + self.service_manager = ServiceManager() + self.add_toolbar_action_patcher = patch('openlp.core.ui.servicemanager.OpenLPToolbar.add_toolbar_action') + self.mocked_add_toolbar_action = self.add_toolbar_action_patcher.start() + self.mocked_add_toolbar_action.side_effect = self._create_mock_action def tearDown(self): """ Delete all the C++ objects at the end so that we don't have a segfault """ - del self.main_window + self.add_toolbar_action_patcher.stop() + del self.service_manager def test_basic_service_manager(self): """ Test the Service Manager UI Functionality """ # GIVEN: A New Service Manager instance - # WHEN I have set up the display self.service_manager.setup_ui(self.service_manager) + # THEN the count of items should be zero self.assertEqual(self.service_manager.service_manager_list.topLevelItemCount(), 0, 'The service manager list should be empty ') - def test_default_context_menu(self): + @patch('openlp.core.ui.servicemanager.QtWidgets.QTreeWidget.itemAt') + @patch('openlp.core.ui.servicemanager.QtWidgets.QWidget.mapToGlobal') + @patch('openlp.core.ui.servicemanager.QtWidgets.QMenu.exec') + def test_default_context_menu(self, mocked_exec, mocked_mapToGlobal, mocked_item_at_method): """ Test the context_menu() method with a default service item """ # GIVEN: A service item added + mocked_item = MagicMock() + mocked_item.parent.return_value = None + mocked_item_at_method.return_value = mocked_item + mocked_item.data.return_value = 1 self.service_manager.setup_ui(self.service_manager) - with patch('PyQt5.QtWidgets.QTreeWidget.itemAt') as mocked_item_at_method, \ - patch('PyQt5.QtWidgets.QWidget.mapToGlobal'), \ - patch('PyQt5.QtWidgets.QMenu.exec'): - mocked_item = MagicMock() - mocked_item.parent.return_value = None - mocked_item_at_method.return_value = mocked_item - # We want 1 to be returned for the position - mocked_item.data.return_value = 1 - # A service item without capabilities. - service_item = ServiceItem() - self.service_manager.service_items = [{'service_item': service_item}] - q_point = None - # Mocked actions. - self.service_manager.edit_action.setVisible = MagicMock() - self.service_manager.create_custom_action.setVisible = MagicMock() - self.service_manager.maintain_action.setVisible = MagicMock() - self.service_manager.notes_action.setVisible = MagicMock() - self.service_manager.time_action.setVisible = MagicMock() - self.service_manager.auto_start_action.setVisible = MagicMock() + # A service item without capabilities. + service_item = ServiceItem() + self.service_manager.service_items = [{'service_item': service_item}] + q_point = None + # Mocked actions. + self.service_manager.edit_action.setVisible = MagicMock() + self.service_manager.create_custom_action.setVisible = MagicMock() + self.service_manager.maintain_action.setVisible = MagicMock() + self.service_manager.notes_action.setVisible = MagicMock() + self.service_manager.time_action.setVisible = MagicMock() + self.service_manager.auto_start_action.setVisible = MagicMock() - # WHEN: Show the context menu. - self.service_manager.context_menu(q_point) + # WHEN: Show the context menu. + self.service_manager.context_menu(q_point) - # THEN: The following actions should be not visible. - self.service_manager.edit_action.setVisible.assert_called_once_with(False), \ - 'The action should be set invisible.' - self.service_manager.create_custom_action.setVisible.assert_called_once_with(False), \ - 'The action should be set invisible.' - self.service_manager.maintain_action.setVisible.assert_called_once_with(False), \ - 'The action should be set invisible.' - self.service_manager.notes_action.setVisible.assert_called_with(True), 'The action should be set visible.' - self.service_manager.time_action.setVisible.assert_called_once_with(False), \ - 'The action should be set invisible.' - self.service_manager.auto_start_action.setVisible.assert_called_once_with(False), \ - 'The action should be set invisible.' + # THEN: The following actions should be not visible. + self.service_manager.edit_action.setVisible.assert_called_once_with(False), \ + 'The action should be set invisible.' + self.service_manager.create_custom_action.setVisible.assert_called_once_with(False), \ + 'The action should be set invisible.' + self.service_manager.maintain_action.setVisible.assert_called_once_with(False), \ + 'The action should be set invisible.' + self.service_manager.notes_action.setVisible.assert_called_with(True), 'The action should be set visible.' + self.service_manager.time_action.setVisible.assert_called_once_with(False), \ + 'The action should be set invisible.' + self.service_manager.auto_start_action.setVisible.assert_called_once_with(False), \ + 'The action should be set invisible.' def test_edit_context_menu(self): """ diff --git a/tests/interfaces/openlp_core_ui/test_servicenotedialog.py b/tests/interfaces/openlp_core/ui/test_servicenotedialog.py similarity index 100% rename from tests/interfaces/openlp_core_ui/test_servicenotedialog.py rename to tests/interfaces/openlp_core/ui/test_servicenotedialog.py diff --git a/tests/interfaces/openlp_core_ui/test_settings_form.py b/tests/interfaces/openlp_core/ui/test_settings_form.py similarity index 100% rename from tests/interfaces/openlp_core_ui/test_settings_form.py rename to tests/interfaces/openlp_core/ui/test_settings_form.py diff --git a/tests/interfaces/openlp_core_ui/test_shortcutlistform.py b/tests/interfaces/openlp_core/ui/test_shortcutlistform.py similarity index 100% rename from tests/interfaces/openlp_core_ui/test_shortcutlistform.py rename to tests/interfaces/openlp_core/ui/test_shortcutlistform.py diff --git a/tests/interfaces/openlp_core_ui/test_starttimedialog.py b/tests/interfaces/openlp_core/ui/test_starttimedialog.py similarity index 100% rename from tests/interfaces/openlp_core_ui/test_starttimedialog.py rename to tests/interfaces/openlp_core/ui/test_starttimedialog.py diff --git a/tests/interfaces/openlp_core_ui/test_thememanager.py b/tests/interfaces/openlp_core/ui/test_thememanager.py similarity index 100% rename from tests/interfaces/openlp_core_ui/test_thememanager.py rename to tests/interfaces/openlp_core/ui/test_thememanager.py diff --git a/tests/interfaces/openlp_core/widgets/__init__.py b/tests/interfaces/openlp_core/widgets/__init__.py new file mode 100644 index 000000000..ea62548f4 --- /dev/null +++ b/tests/interfaces/openlp_core/widgets/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### diff --git a/tests/interfaces/openlp_core_lib/test_searchedit.py b/tests/interfaces/openlp_core/widgets/test_edits.py similarity index 84% rename from tests/interfaces/openlp_core_lib/test_searchedit.py rename to tests/interfaces/openlp_core/widgets/test_edits.py index d4751ddf0..3951e5f80 100644 --- a/tests/interfaces/openlp_core_lib/test_searchedit.py +++ b/tests/interfaces/openlp_core/widgets/test_edits.py @@ -28,7 +28,7 @@ from unittest.mock import MagicMock, call, patch from PyQt5 import QtCore, QtGui, QtTest, QtWidgets from openlp.core.common.registry import Registry -from openlp.core.lib.searchedit import SearchEdit +from openlp.core.widgets.edits import SearchEdit, HistoryComboBox from tests.helpers.testmixin import TestMixin @@ -60,7 +60,7 @@ class TestSearchEdit(TestCase, TestMixin): Registry().register('main_window', self.main_window) settings_patcher = patch( - 'openlp.core.lib.searchedit.Settings', return_value=MagicMock(**{'value.return_value': SearchTypes.First})) + 'openlp.core.widgets.edits.Settings', return_value=MagicMock(**{'value.return_value': SearchTypes.First})) self.addCleanup(settings_patcher.stop) self.mocked_settings = settings_patcher.start() @@ -135,3 +135,35 @@ class TestSearchEdit(TestCase, TestMixin): # THEN: The search edit text should be cleared and the button be hidden. assert not self.search_edit.text(), "The search edit should not have any text." assert self.search_edit.clear_button.isHidden(), "The clear button should be hidden." + + +class TestHistoryComboBox(TestCase, TestMixin): + def setUp(self): + """ + Some pre-test setup required. + """ + Registry.create() + self.setup_application() + self.main_window = QtWidgets.QMainWindow() + Registry().register('main_window', self.main_window) + self.combo = HistoryComboBox(self.main_window) + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + del self.combo + del self.main_window + + def test_get_items(self): + """ + Test the getItems() method + """ + # GIVEN: The combo. + + # WHEN: Add two items. + self.combo.addItem('test1') + self.combo.addItem('test2') + + # THEN: The list of items should contain both strings. + self.assertEqual(self.combo.getItems(), ['test1', 'test2']) diff --git a/tests/interfaces/openlp_core_ui_lib/test_listpreviewwidget.py b/tests/interfaces/openlp_core/widgets/test_views.py similarity index 97% rename from tests/interfaces/openlp_core_ui_lib/test_listpreviewwidget.py rename to tests/interfaces/openlp_core/widgets/test_views.py index e22f9e9a4..f4a493f4d 100644 --- a/tests/interfaces/openlp_core_ui_lib/test_listpreviewwidget.py +++ b/tests/interfaces/openlp_core/widgets/test_views.py @@ -20,7 +20,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ - Package to test the openlp.core.ui.lib.listpreviewwidget. + Package to test the openlp.core.widgets.views. """ from unittest import TestCase from unittest.mock import MagicMock, patch @@ -29,7 +29,7 @@ from PyQt5 import QtGui, QtWidgets from openlp.core.common.registry import Registry from openlp.core.lib import ServiceItem -from openlp.core.ui.lib import ListWidgetWithDnD, ListPreviewWidget +from openlp.core.widgets.views import ListPreviewWidget from tests.utils.osdinteraction import read_service_from_file from tests.helpers.testmixin import TestMixin From fb7bff01bcd776fab6f1df4c8b6fd12304e07350 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Mon, 23 Oct 2017 15:23:05 -0700 Subject: [PATCH 10/96] Don't throw an exception when a function does not exist in the registry function list --- openlp/core/common/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/common/registry.py b/openlp/core/common/registry.py index 71978ae5d..252274d4d 100644 --- a/openlp/core/common/registry.py +++ b/openlp/core/common/registry.py @@ -128,7 +128,7 @@ class Registry(object): :param event: The function description.. :param function: The function to be called when the event happens. """ - if event in self.functions_list: + if event in self.functions_list and function in self.functions_list[event]: self.functions_list[event].remove(function) def execute(self, event, *args, **kwargs): From 93e0a35489c5443317d37ace58a0135c7ddcb675 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Thu, 26 Oct 2017 13:33:22 -0700 Subject: [PATCH 11/96] Update Jenkins script to use python-jenkins --- scripts/jenkins_script.py | 118 ++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 56 deletions(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 9a0e9a4ec..8ce6af90d 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -31,25 +31,16 @@ You probably want to create an alias. Add this to your ~/.bashrc file and then l You can look up the token in the Branch-01-Pull job configuration or ask in IRC. """ - +import os import re -import sys import time -from optparse import OptionParser +from argparse import ArgumentParser from subprocess import Popen, PIPE -import warnings -from requests.exceptions import HTTPError from jenkins import Jenkins - JENKINS_URL = 'https://ci.openlp.io/' REPO_REGEX = r'(.*/+)(~.*)' -# Allows us to black list token. So when we change the token, we can display a proper message to the user. -OLD_TOKENS = [] - -# Disable the InsecureRequestWarning we get from urllib3, because we're not verifying our own self-signed certificate -warnings.simplefilter('ignore') class OpenLPJobs(object): @@ -85,13 +76,22 @@ class JenkinsTrigger(object): :param token: The token we need to trigger the build. If you do not have this token, ask in IRC. """ - def __init__(self, token): + def __init__(self, username, password, can_use_colour): """ Create the JenkinsTrigger instance. """ - self.token = token + self.build_numbers = {} + self.can_use_colour = can_use_colour and not os.name.startswith('nt') self.repo_name = get_repo_name() - self.jenkins_instance = Jenkins(JENKINS_URL) + self.server = Jenkins(JENKINS_URL, username=username, password=password) + + def fetch_build_numbers(self): + """ + Get the next build number from all the jobs + """ + for job_name in OpenLPJobs.Jobs: + job_info = self.server.get_job_info(job_name) + self.build_number[job_name] = job_info['nextBuildNumber'] def trigger_build(self): """ @@ -102,8 +102,7 @@ class JenkinsTrigger(object): # We just want the name (not the email). name = ' '.join(raw_output.decode().split()[:-1]) cause = 'Build triggered by %s (%s)' % (name, self.repo_name) - self.jenkins_instance.job(OpenLPJobs.Branch_Pull).build({'BRANCH_NAME': self.repo_name, 'cause': cause}, - token=self.token) + self.server.build_job(OpenLPJobs.Branch_Pull, {'BRANCH_NAME': self.repo_name, 'cause': cause}) def print_output(self): """ @@ -129,6 +128,20 @@ class JenkinsTrigger(object): # Open the url Popen(('xdg-open', url), stderr=PIPE) + def _get_build_info(self, job_name, build_number): + """ + Get the build info from the server. This method will check the queue and wait for the build. + """ + queue_info = self.server.get_queue_info() + tries = 0 + while queue_info and tries < 50: + tries += 1 + time.sleep(0.5) + queue_info = self.server.get_queue_info() + if tries >= 50: + raise Exception('Build has not started yet, it may be stuck in the queue.') + return self.server.get_build_info(job_name, build_number) + def __print_build_info(self, job_name): """ This helper method prints the job information of the given ``job_name`` @@ -136,21 +149,21 @@ class JenkinsTrigger(object): :param job_name: The name of the job we want the information from. For example *Branch-01-Pull*. Use the class variables from the :class:`OpenLPJobs` class. """ + build_info = self._get_build_info(job_name, self.build_number[job_name]) + print('{} ... '.format(build_info['url']), end='', flush=True) is_success = False - job = self.jenkins_instance.job(job_name) - while job.info['inQueue']: - time.sleep(1) - build = job.last_build - build.wait() - if build.info['result'] == 'SUCCESS': + while build_info['building'] is True: + time.sleep(0.5) + build_info = self.server.get_build_info(job_name, self.build_number[job_name]) + result_string = build_info['result'] + if self.can_use_colour and result_string == 'SUCCESS': # Make 'SUCCESS' green. - result_string = '%s%s%s' % (Colour.GREEN_START, build.info['result'], Colour.GREEN_END) + result_string = '%s%s%s' % (Colour.GREEN_START, result_string, Colour.GREEN_END) is_success = True - else: + elif self.can_use_colour: # Make 'FAILURE' red. - result_string = '%s%s%s' % (Colour.RED_START, build.info['result'], Colour.RED_END) - url = build.info['url'] - print('[%s] %s' % (result_string, url)) + result_string = '%s%s%s' % (Colour.RED_START, result_string, Colour.RED_END) + print('[{}]'.format(result_string)) return is_success @@ -186,36 +199,29 @@ def get_repo_name(): def main(): - usage = 'Usage: python %prog TOKEN [options]' + """ + Run the script + """ + parser = ArgumentParser() + parser.add_argument('-d', '--disable-output', action='store_false', default=True, help='Disable output') + parser.add_argument('-b', '--open-browser', action='store_true', default=False, + help='Opens the jenkins page in your browser') + parser.add_argument('-c', '--enable-colour', action='store_false', default=True, + help='Enable coloured output. Disabled on Windows') + parser.add_argument('-u', '--username', required=True, help='Your Jenkins username') + parser.add_argument('-p', '--password', required=True, help='Your Jenkins password or personal token') + args = parser.parse_args() - parser = OptionParser(usage=usage) - parser.add_option('-d', '--disable-output', dest='enable_output', action='store_false', default=True, - help='Disable output.') - parser.add_option('-b', '--open-browser', dest='open_browser', action='store_true', default=False, - help='Opens the jenkins page in your browser.') - options, args = parser.parse_args(sys.argv) - - if len(args) == 2: - if not get_repo_name(): - print('Not a branch. Have you pushed it to launchpad? Did you cd to the branch?') - return - token = args[-1] - if token in OLD_TOKENS: - print('Your token is not valid anymore. Get the most recent one.') - return - jenkins_trigger = JenkinsTrigger(token) - try: - jenkins_trigger.trigger_build() - except HTTPError: - print('Wrong token.') - return - # Open the browser before printing the output. - if options.open_browser: - jenkins_trigger.open_browser() - if options.enable_output: - jenkins_trigger.print_output() - else: - parser.print_help() + if not get_repo_name(): + print('Not a branch. Have you pushed it to launchpad? Did you cd to the branch?') + return + jenkins_trigger = JenkinsTrigger(username=args.username, password=args.password) + jenkins_trigger.trigger_build() + # Open the browser before printing the output. + if args.open_browser: + jenkins_trigger.open_browser() + if not args.disable_output: + jenkins_trigger.print_output() if __name__ == '__main__': From f6243f6e8828edc534d70be969420f673e41fa3f Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Thu, 26 Oct 2017 15:05:10 -0700 Subject: [PATCH 12/96] Fix some bugs, line up the statuses, provide some failure feedback, flip the colour option --- scripts/jenkins_script.py | 43 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 8ce6af90d..2d93d53d3 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -80,7 +80,7 @@ class JenkinsTrigger(object): """ Create the JenkinsTrigger instance. """ - self.build_numbers = {} + self.build_number = {} self.can_use_colour = can_use_colour and not os.name.startswith('nt') self.repo_name = get_repo_name() self.server = Jenkins(JENKINS_URL, username=username, password=password) @@ -102,6 +102,7 @@ class JenkinsTrigger(object): # We just want the name (not the email). name = ' '.join(raw_output.decode().split()[:-1]) cause = 'Build triggered by %s (%s)' % (name, self.repo_name) + self.fetch_build_numbers() self.server.build_job(OpenLPJobs.Branch_Pull, {'BRANCH_NAME': self.repo_name, 'cause': cause}) def print_output(self): @@ -117,7 +118,10 @@ class JenkinsTrigger(object): for job in OpenLPJobs.Jobs: if not self.__print_build_info(job): - print('Stopping after failure') + if self.current_build: + print('Stopping after failure, see {}console for more details'.format(self.current_build['url'])) + else: + print('Stopping after failure') break def open_browser(self): @@ -149,21 +153,22 @@ class JenkinsTrigger(object): :param job_name: The name of the job we want the information from. For example *Branch-01-Pull*. Use the class variables from the :class:`OpenLPJobs` class. """ - build_info = self._get_build_info(job_name, self.build_number[job_name]) - print('{} ... '.format(build_info['url']), end='', flush=True) + self.current_build = self._get_build_info(job_name, self.build_number[job_name]) + print('{:<60} [RUNNING]'.format(self.current_build['url']), end='', flush=True) is_success = False - while build_info['building'] is True: + while self.current_build['building'] is True: time.sleep(0.5) - build_info = self.server.get_build_info(job_name, self.build_number[job_name]) - result_string = build_info['result'] - if self.can_use_colour and result_string == 'SUCCESS': - # Make 'SUCCESS' green. - result_string = '%s%s%s' % (Colour.GREEN_START, result_string, Colour.GREEN_END) - is_success = True - elif self.can_use_colour: - # Make 'FAILURE' red. - result_string = '%s%s%s' % (Colour.RED_START, result_string, Colour.RED_END) - print('[{}]'.format(result_string)) + self.current_build = self.server.get_build_info(job_name, self.build_number[job_name]) + result_string = self.current_build['result'] + is_success = result_string == 'SUCCESS' + if self.can_use_colour: + if is_success: + # Make 'SUCCESS' green. + result_string = '{}{}{}'.format(Colour.GREEN_START, result_string, Colour.GREEN_END) + else: + # Make 'FAILURE' red. + result_string = '{}{}{}'.format(Colour.RED_START, result_string, Colour.RED_END) + print('\b\b\b\b\b\b\b\b\b[{:>7}]'.format(result_string)) return is_success @@ -203,11 +208,11 @@ def main(): Run the script """ parser = ArgumentParser() - parser.add_argument('-d', '--disable-output', action='store_false', default=True, help='Disable output') + parser.add_argument('-d', '--disable-output', action='store_true', default=False, help='Disable output') parser.add_argument('-b', '--open-browser', action='store_true', default=False, help='Opens the jenkins page in your browser') - parser.add_argument('-c', '--enable-colour', action='store_false', default=True, - help='Enable coloured output. Disabled on Windows') + parser.add_argument('-n', '--no-colour', action='store_true', default=False, + help='Disable coloured output (always disabled on Windows)') parser.add_argument('-u', '--username', required=True, help='Your Jenkins username') parser.add_argument('-p', '--password', required=True, help='Your Jenkins password or personal token') args = parser.parse_args() @@ -215,7 +220,7 @@ def main(): if not get_repo_name(): print('Not a branch. Have you pushed it to launchpad? Did you cd to the branch?') return - jenkins_trigger = JenkinsTrigger(username=args.username, password=args.password) + jenkins_trigger = JenkinsTrigger(args.username, args.password, not args.no_colour) jenkins_trigger.trigger_build() # Open the browser before printing the output. if args.open_browser: From 0d486d051448546c5758ebe2bc678435b5447b7c Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Thu, 26 Oct 2017 15:32:20 -0700 Subject: [PATCH 13/96] Updated the documentation in the file --- scripts/jenkins_script.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 2d93d53d3..05c589ddd 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -21,15 +21,24 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -This script helps to trigger builds of branches. To use it you have to install the jenkins-webapi package: +This script helps to trigger builds of branches. To use it you have to install the python-jenkins module. On Fedora +and Ubuntu/Debian, it is available as the ``python3-jenkins`` package:: - pip3 install jenkins-webapi + $ sudo dnf/apt install python3-jenkins -You probably want to create an alias. Add this to your ~/.bashrc file and then logout and login (to apply the alias): +To make it easier to run you may want to create a shell script or an alias. To create an alias, add this to your +``~/.bashrc`` (or ``~/.zshrc``) file and then log out and log back in again (to apply the alias):: - alias ci="python3 ./scripts/jenkins_script.py TOKEN" + alias ci="python3 /path/to/openlp_root/scripts/jenkins_script.py -u USERNAME -p PASSWORD" -You can look up the token in the Branch-01-Pull job configuration or ask in IRC. +To create a shell script, create the following file in a location in your ``$PATH`` (I called mine ``ci``):: + + #!/bin/bash + python3 /path/to/openlp_root/scripts/jenkins_script.py -u USERNAME -p PASSWORD + +``USERNAME`` is your Jenkins username, and ``PASSWORD`` is your Jenkins password or personal token. + +An older version of this script used to use a shared TOKEN, but this has been replaced with the username and password. """ import os import re @@ -154,7 +163,7 @@ class JenkinsTrigger(object): variables from the :class:`OpenLPJobs` class. """ self.current_build = self._get_build_info(job_name, self.build_number[job_name]) - print('{:<60} [RUNNING]'.format(self.current_build['url']), end='', flush=True) + print('{:<70} [RUNNING]'.format(self.current_build['url']), end='', flush=True) is_success = False while self.current_build['building'] is True: time.sleep(0.5) From 960ddedc6f08744f3585c28e4e66c55345c75b19 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Sat, 28 Oct 2017 11:04:09 +0100 Subject: [PATCH 14/96] make tidy text part of core.\nRemove redundant (since py3) re.UNICODE flag Fixes: https://launchpad.net/bugs/1727517 --- openlp/core/common/__init__.py | 18 +++++++++++++-- openlp/core/common/i18n.py | 2 +- openlp/core/lib/__init__.py | 1 - openlp/core/lib/mediamanageritem.py | 2 +- openlp/core/ui/formattingtagcontroller.py | 2 +- openlp/plugins/bibles/forms/booknameform.py | 3 +-- openlp/plugins/bibles/lib/__init__.py | 6 ++--- openlp/plugins/bibles/lib/db.py | 3 +-- openlp/plugins/songs/forms/editsongform.py | 6 ++--- openlp/plugins/songs/lib/__init__.py | 5 ++-- .../plugins/songs/lib/importers/easyslides.py | 3 ++- .../plugins/songs/lib/importers/mediashout.py | 2 +- .../plugins/songs/lib/importers/openoffice.py | 4 ++-- .../plugins/songs/lib/importers/opensong.py | 3 ++- .../plugins/songs/lib/importers/songimport.py | 23 ++----------------- .../songs/lib/importers/songsoffellowship.py | 1 - openlp/plugins/songs/lib/openlyricsxml.py | 2 +- 17 files changed, 39 insertions(+), 47 deletions(-) diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index f8017fdbd..f4c22f490 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -43,9 +43,13 @@ log = logging.getLogger(__name__ + '.__init__') FIRST_CAMEL_REGEX = re.compile('(.)([A-Z][a-z]+)') SECOND_CAMEL_REGEX = re.compile('([a-z0-9])([A-Z])') -CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE) -INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE) +CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]') +INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]') IMAGES_FILTER = None +REPLACMENT_CHARS_MAP = str.maketrans({'\u2018': '\'', '\u2019': '\'', '\u201c': '"', '\u201d': '"', '\u2026': '...', + '\u2013': '-', '\u2014': '-', '\v': '\n\n', '\f': '\n\n'}) +NEW_LINE_REGEX = re.compile(r' ?(\r\n?|\n) ?') +WHITESPACE_REGEX = re.compile(r'[ \t]+') def trace_error_handler(logger): @@ -436,3 +440,13 @@ def get_file_encoding(file_path): return detector.result except OSError: log.exception('Error detecting file encoding') + +def normalize_str(irreg_str): + """ + + :param str irreg_str: + :return: + """ + irreg_str = irreg_str.translate(REPLACMENT_CHARS_MAP) + irreg_str = NEW_LINE_REGEX.sub('\n', irreg_str) + return WHITESPACE_REGEX.sub(' ', irreg_str) diff --git a/openlp/core/common/i18n.py b/openlp/core/common/i18n.py index 1f4357808..9149f3fe6 100644 --- a/openlp/core/common/i18n.py +++ b/openlp/core/common/i18n.py @@ -53,7 +53,7 @@ def translate(context, text, comment=None, qt_translate=QtCore.QCoreApplication. Language = namedtuple('Language', ['id', 'name', 'code']) ICU_COLLATOR = None -DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+', re.UNICODE) +DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+') LANGUAGES = sorted([ Language(1, translate('common.languages', '(Afan) Oromo', 'Language code: om'), 'om'), Language(2, translate('common.languages', 'Abkhazian', 'Language code: ab'), 'ab'), diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 0f4078420..f78065774 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -38,7 +38,6 @@ log = logging.getLogger(__name__ + '.__init__') SLIMCHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\' - class ServiceItemContext(object): """ The context in which a Service Item is being generated diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index c650ad80e..cc884279c 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -92,7 +92,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): Run some initial setup. This method is separate from __init__ in order to mock it out in tests. """ self.hide() - self.whitespace = re.compile(r'[\W_]+', re.UNICODE) + self.whitespace = re.compile(r'[\W_]+') visible_title = self.plugin.get_string(StringContent.VisibleName) self.title = str(visible_title['title']) Registry().register(self.plugin.name, self) diff --git a/openlp/core/ui/formattingtagcontroller.py b/openlp/core/ui/formattingtagcontroller.py index e92173fed..4b9d75fee 100644 --- a/openlp/core/ui/formattingtagcontroller.py +++ b/openlp/core/ui/formattingtagcontroller.py @@ -43,7 +43,7 @@ class FormattingTagController(object): r'(?P<tag>[^\s/!\?>]+)(?:\s+[^\s=]+="[^"]*")*\s*(?P<empty>/)?' r'|(?P<cdata>!\[CDATA\[(?:(?!\]\]>).)*\]\])' r'|(?P<procinst>\?(?:(?!\?>).)*\?)' - r'|(?P<comment>!--(?:(?!-->).)*--))>', re.UNICODE) + r'|(?P<comment>!--(?:(?!-->).)*--))>') self.html_regex = re.compile(r'^(?:[^<>]*%s)*[^<>]*$' % self.html_tag_regex.pattern) def pre_save(self): diff --git a/openlp/plugins/bibles/forms/booknameform.py b/openlp/plugins/bibles/forms/booknameform.py index f78559ce5..7c8a2c3cd 100644 --- a/openlp/plugins/bibles/forms/booknameform.py +++ b/openlp/plugins/bibles/forms/booknameform.py @@ -113,8 +113,7 @@ class BookNameForm(QDialog, Ui_BookNameDialog): cor_book = self.corresponding_combo_box.currentText() for character in '\\.^$*+?{}[]()': cor_book = cor_book.replace(character, '\\' + character) - books = [key for key in list(self.book_names.keys()) if re.match(cor_book, str(self.book_names[key]), - re.UNICODE)] + books = [key for key in list(self.book_names.keys()) if re.match(cor_book, str(self.book_names[key]))] books = [_f for _f in map(BiblesResourcesDB.get_book, books) if _f] if books: self.book_id = books[0]['id'] diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index 9247485c1..f9d93a43e 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -224,13 +224,13 @@ def update_reference_separators(): range_regex = '(?:(?P<from_chapter>[0-9]+){sep_v})?' \ '(?P<from_verse>[0-9]+)(?P<range_to>{sep_r}(?:(?:(?P<to_chapter>' \ '[0-9]+){sep_v})?(?P<to_verse>[0-9]+)|{sep_e})?)?'.format_map(REFERENCE_SEPARATORS) - REFERENCE_MATCHES['range'] = re.compile(r'^\s*{range}\s*$'.format(range=range_regex), re.UNICODE) - REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE) + REFERENCE_MATCHES['range'] = re.compile(r'^\s*{range}\s*$'.format(range=range_regex)) + REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l']) # full reference match: <book>(<range>(,(?!$)|(?=$)))+ REFERENCE_MATCHES['full'] = \ re.compile(r'^\s*(?!\s)(?P<book>[\d]*[.]?[^\d\.]+)\.*(?<!\s)\s*' r'(?P<ranges>(?:{range_regex}(?:{sep_l}(?!\s*$)|(?=\s*$)))+)\s*$'.format( - range_regex=range_regex, sep_l=REFERENCE_SEPARATORS['sep_l']), re.UNICODE) + range_regex=range_regex, sep_l=REFERENCE_SEPARATORS['sep_l'])) def get_reference_separator(separator_type): diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index bc8ce4150..995a9691a 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -307,8 +307,7 @@ class BibleDB(Manager): book_escaped = book for character in RESERVED_CHARACTERS: book_escaped = book_escaped.replace(character, '\\' + character) - regex_book = re.compile('\\s*{book}\\s*'.format(book='\\s*'.join(book_escaped.split())), - re.UNICODE | re.IGNORECASE) + regex_book = re.compile('\\s*{book}\\s*'.format(book='\\s*'.join(book_escaped.split())), re.IGNORECASE) if language_selection == LanguageSelection.Bible: db_book = self.get_book(book) if db_book: diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index fa475a63f..6e0772418 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -105,9 +105,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): self.topics_list_view.setSortingEnabled(False) self.topics_list_view.setAlternatingRowColors(True) self.audio_list_widget.setAlternatingRowColors(True) - self.find_verse_split = re.compile('---\[\]---\n', re.UNICODE) - self.whitespace = re.compile(r'\W+', re.UNICODE) - self.find_tags = re.compile(u'\{/?\w+\}', re.UNICODE) + self.find_verse_split = re.compile('---\[\]---\n') + self.whitespace = re.compile(r'\W+') + self.find_tags = re.compile(r'\{/?\w+\}') def _load_objects(self, cls, combo, cache): """ diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index f88aa8678..74334ef0d 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -24,7 +24,6 @@ The :mod:`~openlp.plugins.songs.lib` module contains a number of library functio """ import logging -import os import re from PyQt5 import QtWidgets @@ -39,8 +38,8 @@ from openlp.plugins.songs.lib.ui import SongStrings log = logging.getLogger(__name__) -WHITESPACE = re.compile(r'[\W_]+', re.UNICODE) -APOSTROPHE = re.compile('[\'`’ʻ′]', re.UNICODE) +WHITESPACE = re.compile(r'[\W_]+') +APOSTROPHE = re.compile(r'[\'`’ʻ′]') # PATTERN will look for the next occurence of one of these symbols: # \controlword - optionally preceded by \*, optionally followed by a number # \'## - where ## is a pair of hex digits, representing a single character diff --git a/openlp/plugins/songs/lib/importers/easyslides.py b/openlp/plugins/songs/lib/importers/easyslides.py index a1ffb7b7c..6d717bdb4 100644 --- a/openlp/plugins/songs/lib/importers/easyslides.py +++ b/openlp/plugins/songs/lib/importers/easyslides.py @@ -25,6 +25,7 @@ import re from lxml import etree, objectify +from openlp.core.common import normalize_str from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib.importers.songimport import SongImport @@ -225,7 +226,7 @@ class EasySlidesImport(SongImport): verses[reg].setdefault(vt, {}) verses[reg][vt].setdefault(vn, {}) verses[reg][vt][vn].setdefault(inst, []) - verses[reg][vt][vn][inst].append(self.tidy_text(line)) + verses[reg][vt][vn][inst].append(normalize_str(line)) # done parsing versetags = [] # we use our_verse_order to ensure, we insert lyrics in the same order diff --git a/openlp/plugins/songs/lib/importers/mediashout.py b/openlp/plugins/songs/lib/importers/mediashout.py index 67cf0d0fb..9df9baa0f 100644 --- a/openlp/plugins/songs/lib/importers/mediashout.py +++ b/openlp/plugins/songs/lib/importers/mediashout.py @@ -101,7 +101,7 @@ class MediaShoutImport(SongImport): self.song_book_name = song.SongID for verse in verses: tag = VERSE_TAGS[verse.Type] + str(verse.Number) if verse.Type < len(VERSE_TAGS) else 'O' - self.add_verse(self.tidy_text(verse.Text), tag) + self.add_verse(verse.Text, tag) for order in verse_order: if order.Type < len(VERSE_TAGS): self.verse_order_list.append(VERSE_TAGS[order.Type] + str(order.Number)) diff --git a/openlp/plugins/songs/lib/importers/openoffice.py b/openlp/plugins/songs/lib/importers/openoffice.py index a097d8b85..f2a8b2147 100644 --- a/openlp/plugins/songs/lib/importers/openoffice.py +++ b/openlp/plugins/songs/lib/importers/openoffice.py @@ -24,7 +24,7 @@ import time from PyQt5 import QtCore -from openlp.core.common import is_win, get_uno_command, get_uno_instance +from openlp.core.common import get_uno_command, get_uno_instance, is_win, normalize_str from openlp.core.common.i18n import translate from .songimport import SongImport @@ -241,7 +241,7 @@ class OpenOfficeImport(SongImport): :param text: The text. """ - song_texts = self.tidy_text(text).split('\f') + song_texts = normalize_str(text).split('\f') self.set_defaults() for song_text in song_texts: if song_text.strip(): diff --git a/openlp/plugins/songs/lib/importers/opensong.py b/openlp/plugins/songs/lib/importers/opensong.py index e6924e7b2..6cd690562 100644 --- a/openlp/plugins/songs/lib/importers/opensong.py +++ b/openlp/plugins/songs/lib/importers/opensong.py @@ -25,6 +25,7 @@ import re from lxml import objectify from lxml.etree import Error, LxmlError +from openlp.core.common import normalize_str from openlp.core.common.i18n import translate from openlp.core.common.settings import Settings from openlp.plugins.songs.lib import VerseType @@ -262,7 +263,7 @@ class OpenSongImport(SongImport): post=this_line[offset + column:]) offset += len(chord) + 2 # Tidy text and remove the ____s from extended words - this_line = self.tidy_text(this_line) + this_line = normalize_str(this_line) this_line = this_line.replace('_', '') this_line = this_line.replace('||', '\n[---]\n') this_line = this_line.strip() diff --git a/openlp/plugins/songs/lib/importers/songimport.py b/openlp/plugins/songs/lib/importers/songimport.py index a67c17fe7..2bd8c0e56 100644 --- a/openlp/plugins/songs/lib/importers/songimport.py +++ b/openlp/plugins/songs/lib/importers/songimport.py @@ -25,6 +25,7 @@ import re from PyQt5 import QtCore +from openlp.core.common import normalize_str from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import translate from openlp.core.common.path import copyfile, create_paths @@ -130,26 +131,6 @@ class SongImport(QtCore.QObject): def register(self, import_wizard): self.import_wizard = import_wizard - def tidy_text(self, text): - """ - Get rid of some dodgy unicode and formatting characters we're not interested in. Some can be converted to ascii. - """ - text = text.replace('\u2018', '\'') - text = text.replace('\u2019', '\'') - text = text.replace('\u201c', '"') - text = text.replace('\u201d', '"') - text = text.replace('\u2026', '...') - text = text.replace('\u2013', '-') - text = text.replace('\u2014', '-') - # Replace vertical tab with 2 linebreaks - text = text.replace('\v', '\n\n') - # Replace form feed (page break) with 2 linebreaks - text = text.replace('\f', '\n\n') - # Remove surplus blank lines, spaces, trailing/leading spaces - text = re.sub(r'[ \t]+', ' ', text) - text = re.sub(r' ?(\r\n?|\n) ?', '\n', text) - return text - def process_song_text(self, text): """ Process the song text from import @@ -368,7 +349,7 @@ class SongImport(QtCore.QObject): verse_tag = VerseType.tags[VerseType.Other] log.info('Versetype {old} changing to {new}'.format(old=verse_def, new=new_verse_def)) verse_def = new_verse_def - sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang) + sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], normalize_str(verse_text), lang) song.lyrics = str(sxml.extract_xml(), 'utf-8') if not self.verse_order_list and self.verse_order_list_generated_useful: self.verse_order_list = self.verse_order_list_generated diff --git a/openlp/plugins/songs/lib/importers/songsoffellowship.py b/openlp/plugins/songs/lib/importers/songsoffellowship.py index 13e073cc1..bbba654c9 100644 --- a/openlp/plugins/songs/lib/importers/songsoffellowship.py +++ b/openlp/plugins/songs/lib/importers/songsoffellowship.py @@ -194,7 +194,6 @@ class SongsOfFellowshipImport(OpenOfficeImport): :param text_portion: A Piece of text """ text = text_portion.getString() - text = self.tidy_text(text) if text.strip() == '': return text if text_portion.CharWeight == BOLD: diff --git a/openlp/plugins/songs/lib/openlyricsxml.py b/openlp/plugins/songs/lib/openlyricsxml.py index 74d91068c..ef47fa77b 100644 --- a/openlp/plugins/songs/lib/openlyricsxml.py +++ b/openlp/plugins/songs/lib/openlyricsxml.py @@ -281,7 +281,7 @@ class OpenLyrics(object): # Process the formatting tags. # Have we any tags in song lyrics? tags_element = None - match = re.search('\{/?\w+\}', song.lyrics, re.UNICODE) + match = re.search(r'\{/?\w+\}', song.lyrics) if match: # Named 'format_' - 'format' is built-in function in Python. format_ = etree.SubElement(song_xml, 'format') From a268c4476b11fd9f6cb50bf1c1541419b637b236 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Sat, 28 Oct 2017 19:58:34 +0100 Subject: [PATCH 15/96] Couple fixes for bugs caused by controll chars --- openlp/core/common/__init__.py | 8 ++++++-- openlp/core/widgets/edits.py | 12 ++++++++++- .../plugins/songs/lib/importers/zionworx.py | 20 +++++-------------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index f4c22f490..b7e579803 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -441,12 +441,16 @@ def get_file_encoding(file_path): except OSError: log.exception('Error detecting file encoding') + def normalize_str(irreg_str): """ + Normalize the supplied string. Remove unicode control chars and tidy up white space. - :param str irreg_str: - :return: + :param str irreg_str: The string to normalize. + :return: The normalized string + :rtype: str """ irreg_str = irreg_str.translate(REPLACMENT_CHARS_MAP) + irreg_str = CONTROL_CHARS.sub('', irreg_str) irreg_str = NEW_LINE_REGEX.sub('\n', irreg_str) return WHITESPACE_REGEX.sub(' ', irreg_str) diff --git a/openlp/core/widgets/edits.py b/openlp/core/widgets/edits.py index c2396810f..e64125833 100644 --- a/openlp/core/widgets/edits.py +++ b/openlp/core/widgets/edits.py @@ -27,6 +27,7 @@ import re from PyQt5 import QtCore, QtGui, QtWidgets +from openlp.core.common import CONTROL_CHARS from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.path import Path, path_to_str, str_to_path from openlp.core.common.settings import Settings @@ -470,12 +471,21 @@ class SpellTextEdit(QtWidgets.QPlainTextEdit): cursor.insertText(html['start tag']) cursor.insertText(html['end tag']) + def insertFromMimeData(self, source): + """ + Reimplement `insertFromMimeData` so that we can remove any control characters + + :param QtCore.QMimeData source: The mime data to insert + :rtype: None + """ + self.insertPlainText(CONTROL_CHARS.sub('', source.text())) + class Highlighter(QtGui.QSyntaxHighlighter): """ Provides a text highlighter for pointing out spelling errors in text. """ - WORDS = r'(?iu)[\w\']+' + WORDS = r'(?i)[\w\']+' def __init__(self, *args): """ diff --git a/openlp/plugins/songs/lib/importers/zionworx.py b/openlp/plugins/songs/lib/importers/zionworx.py index 5cfc0576d..23817c31a 100644 --- a/openlp/plugins/songs/lib/importers/zionworx.py +++ b/openlp/plugins/songs/lib/importers/zionworx.py @@ -30,9 +30,6 @@ from openlp.plugins.songs.lib.importers.songimport import SongImport log = logging.getLogger(__name__) -# Used to strip control chars (except 10=LF, 13=CR) -CONTROL_CHARS_MAP = dict.fromkeys(list(range(10)) + [11, 12] + list(range(14, 32)) + [127]) - class ZionWorxImport(SongImport): """ @@ -95,12 +92,12 @@ class ZionWorxImport(SongImport): return self.set_defaults() try: - self.title = self._decode(record['Title1']) + self.title = record['Title1'] if record['Title2']: - self.alternate_title = self._decode(record['Title2']) - self.parse_author(self._decode(record['Writer'])) - self.add_copyright(self._decode(record['Copyright'])) - lyrics = self._decode(record['Lyrics']) + self.alternate_title = record['Title2'] + self.parse_author(record['Writer']) + self.add_copyright(record['Copyright']) + lyrics = record['Lyrics'] except UnicodeDecodeError as e: self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record {index}').format(index=index), translate('SongsPlugin.ZionWorxImport', 'Decoding error: {error}').format(error=e)) @@ -122,10 +119,3 @@ class ZionWorxImport(SongImport): if not self.finish(): self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index + (': "' + title + '"' if title else '')) - - def _decode(self, str): - """ - Strips all control characters (except new lines). - """ - # ZionWorx has no option for setting the encoding for its songs, so we assume encoding is always the same. - return str.translate(CONTROL_CHARS_MAP) From 8b3485223686f7c2c26456388eca21f69a4cecb6 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Sat, 28 Oct 2017 23:01:25 -0700 Subject: [PATCH 16/96] Rename some tests so that they can be picked up by nose2; Fix some patches that were wrong; Fix a bug in our code that the now running tests picked up --- openlp/plugins/bibles/lib/bibleimport.py | 2 + .../openlp_core/common/test_init.py | 6 +- .../openlp_core/ui/test_aboutform.py | 2 +- .../openlp_core/ui/test_slidecontroller.py | 4 +- .../openlp_plugins/bibles/test_bibleimport.py | 588 +++++++++--------- .../openlp_plugins/bibles/test_csvimport.py | 28 +- .../bibles/test_opensongimport.py | 36 +- .../openlp_plugins/bibles/test_osisimport.py | 32 +- .../openlp_plugins/songs/test_ewimport.py | 299 +++++---- .../openlp_plugins/songs/test_songselect.py | 2 +- 10 files changed, 500 insertions(+), 499 deletions(-) diff --git a/openlp/plugins/bibles/lib/bibleimport.py b/openlp/plugins/bibles/lib/bibleimport.py index 5083d7f6b..962fe1fd2 100644 --- a/openlp/plugins/bibles/lib/bibleimport.py +++ b/openlp/plugins/bibles/lib/bibleimport.py @@ -96,6 +96,8 @@ class BibleImport(BibleDB, LogMixin, RegistryProperties): if language_form.exec(bible_name): combo_box = language_form.language_combo_box language_id = combo_box.itemData(combo_box.currentIndex()) + else: + return False if not language_id: return None self.save_meta('language_id', language_id) diff --git a/tests/functional/openlp_core/common/test_init.py b/tests/functional/openlp_core/common/test_init.py index 532f11bac..5acfe59bb 100644 --- a/tests/functional/openlp_core/common/test_init.py +++ b/tests/functional/openlp_core/common/test_init.py @@ -347,7 +347,7 @@ class TestInit(TestCase, TestMixin): self.assertTrue(mocked_log.exception.called) self.assertFalse(result, 'delete_file should return False when an OSError is raised') - def test_get_file_encoding_done_test(self): + def test_get_file_encoding_done(self): """ Test get_file_encoding when the detector sets done to True """ @@ -368,7 +368,7 @@ class TestInit(TestCase, TestMixin): mocked_universal_detector_inst.close.assert_called_once_with() self.assertEqual(result, encoding_result) - def test_get_file_encoding_eof_test(self): + def test_get_file_encoding_eof(self): """ Test get_file_encoding when the end of the file is reached """ @@ -390,7 +390,7 @@ class TestInit(TestCase, TestMixin): mocked_universal_detector_inst.close.assert_called_once_with() self.assertEqual(result, encoding_result) - def test_get_file_encoding_oserror_test(self): + def test_get_file_encoding_oserror(self): """ Test get_file_encoding when the end of the file is reached """ diff --git a/tests/functional/openlp_core/ui/test_aboutform.py b/tests/functional/openlp_core/ui/test_aboutform.py index c30ef588e..0cba7b008 100644 --- a/tests/functional/openlp_core/ui/test_aboutform.py +++ b/tests/functional/openlp_core/ui/test_aboutform.py @@ -62,7 +62,7 @@ class TestFirstTimeForm(TestCase, TestMixin): self.assertTrue('OpenLP 3.1.5 build 3000' in about_form.about_text_edit.toPlainText(), "The build number should be set correctly") - def test_about_form_date_test(self): + def test_about_form_date(self): """ Test that the copyright date is included correctly """ diff --git a/tests/functional/openlp_core/ui/test_slidecontroller.py b/tests/functional/openlp_core/ui/test_slidecontroller.py index 145f779ac..b5f0d99ee 100644 --- a/tests/functional/openlp_core/ui/test_slidecontroller.py +++ b/tests/functional/openlp_core/ui/test_slidecontroller.py @@ -691,7 +691,7 @@ class TestSlideController(TestCase): ]) @patch('openlp.core.ui.slidecontroller.Settings') - def on_preview_double_click_unblank_display_test(self, MockedSettings): + 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() @@ -714,7 +714,7 @@ class TestSlideController(TestCase): self.assertEqual(0, slide_controller.on_preview_add_to_service.call_count, 'Should have not been called.') @patch('openlp.core.ui.slidecontroller.Settings') - def on_preview_double_click_add_to_service_test(self, MockedSettings): + 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() diff --git a/tests/functional/openlp_plugins/bibles/test_bibleimport.py b/tests/functional/openlp_plugins/bibles/test_bibleimport.py index a75741d76..0e8e43172 100644 --- a/tests/functional/openlp_plugins/bibles/test_bibleimport.py +++ b/tests/functional/openlp_plugins/bibles/test_bibleimport.py @@ -66,7 +66,7 @@ class TestBibleImport(TestCase): self.addCleanup(self.registry_patcher.stop) self.registry_patcher.start() - def init_kwargs_none_test(self): + def test_init_kwargs_none(self): """ Test the initialisation of the BibleImport Class when no key word arguments are supplied """ @@ -75,10 +75,10 @@ class TestBibleImport(TestCase): instance = BibleImport(MagicMock()) # THEN: The filename attribute should be None - self.assertIsNone(instance.filename) - self.assertIsInstance(instance, BibleDB) + assert instance.filename is None + assert isinstance(instance, BibleDB) - def init_kwargs_set_test(self): + def test_init_kwargs_set(self): """ Test the initialisation of the BibleImport Class when supplied with select keyword arguments """ @@ -88,268 +88,270 @@ class TestBibleImport(TestCase): instance = BibleImport(MagicMock(), **kwargs) # THEN: The filename keyword should be set to bible.xml - self.assertEqual(instance.filename, 'bible.xml') - self.assertIsInstance(instance, BibleDB) + assert instance.filename == 'bible.xml' + assert isinstance(instance, BibleDB) - def get_language_canceled_test(self): + @patch.object(BibleDB, '_setup') + @patch('openlp.plugins.bibles.forms.LanguageForm') + def test_get_language_canceled(self, MockedLanguageForm, mocked_setup): """ Test the BibleImport.get_language method when the user rejects the dialog box """ # GIVEN: A mocked LanguageForm with an exec method which returns QtDialog.Rejected and an instance of BibleDB - with patch.object(BibleDB, '_setup'), patch('openlp.plugins.bibles.forms.LanguageForm') as mocked_language_form: + # TODO: The integer value of QtDialog.Rejected is 0. Using the enumeration causes a seg fault for some reason + MockedLanguageForm.return_value.exec.return_value = 0 + instance = BibleImport(MagicMock()) + mocked_wizard = MagicMock() + instance.wizard = mocked_wizard - # The integer value of QtDialog.Rejected is 0. Using the enumeration causes a seg fault for some reason - mocked_language_form_instance = MagicMock(**{'exec.return_value': 0}) - mocked_language_form.return_value = mocked_language_form_instance - instance = BibleImport(MagicMock()) - mocked_wizard = MagicMock() - instance.wizard = mocked_wizard + # WHEN: Calling get_language() + result = instance.get_language('ESV') - # WHEN: Calling get_language() - result = instance.get_language() + # THEN: get_language() should return False + MockedLanguageForm.assert_called_once_with(mocked_wizard) + MockedLanguageForm.return_value.exec.assert_called_once_with('ESV') + assert result is False, 'get_language() should return False if the user rejects the dialog box' - # THEN: get_language() should return False - mocked_language_form.assert_called_once_with(mocked_wizard) - mocked_language_form_instance.exec.assert_called_once_with(None) - self.assertFalse(result, 'get_language() should return False if the user rejects the dialog box') - - def get_language_accepted_test(self): + @patch.object(BibleDB, 'save_meta') + @patch.object(BibleDB, '_setup') + @patch('openlp.plugins.bibles.forms.LanguageForm') + def test_get_language_accepted(self, MockedLanguageForm, mocked_setup, mocked_save_meta): """ Test the BibleImport.get_language method when the user accepts the dialog box """ # GIVEN: A mocked LanguageForm with an exec method which returns QtDialog.Accepted an instance of BibleDB and # a combobox with the selected item data as 10 - with patch.object(BibleDB, 'save_meta'), patch.object(BibleDB, '_setup'), \ - patch('openlp.plugins.bibles.forms.LanguageForm') as mocked_language_form: + # The integer value of QtDialog.Accepted is 1. Using the enumeration causes a seg fault for some reason + MockedLanguageForm.return_value.exec.return_value = 1 + MockedLanguageForm.return_value.language_combo_box.itemData.return_value = 10 + instance = BibleImport(MagicMock()) + mocked_wizard = MagicMock() + instance.wizard = mocked_wizard - # The integer value of QtDialog.Accepted is 1. Using the enumeration causes a seg fault for some reason - mocked_language_form_instance = MagicMock(**{'exec.return_value': 1, - 'language_combo_box.itemData.return_value': 10}) - mocked_language_form.return_value = mocked_language_form_instance - instance = BibleImport(MagicMock()) - mocked_wizard = MagicMock() - instance.wizard = mocked_wizard + # WHEN: Calling get_language() + result = instance.get_language('Bible Name') - # WHEN: Calling get_language() - result = instance.get_language('Bible Name') + # THEN: get_language() should return the id of the selected language in the combo box + MockedLanguageForm.assert_called_once_with(mocked_wizard) + MockedLanguageForm.return_value.exec.assert_called_once_with('Bible Name') + assert result == 10, 'get_language() should return the id of the language the user has chosen when ' \ + 'they accept the dialog box' - # THEN: get_language() should return the id of the selected language in the combo box - mocked_language_form.assert_called_once_with(mocked_wizard) - mocked_language_form_instance.exec.assert_called_once_with('Bible Name') - self.assertEqual(result, 10, 'get_language() should return the id of the language the user has chosen when ' - 'they accept the dialog box') - - def get_language_id_language_found_test(self): + @patch('openlp.plugins.bibles.lib.bibleimport.get_language') + @patch.object(BibleImport, 'get_language') + def test_get_language_id_language_found(self, mocked_db_get_language, mocked_get_language): """ Test get_language_id() when called with a name found in the languages list """ # GIVEN: A mocked languages.get_language which returns language and an instance of BibleImport - with patch('openlp.core.common.languages.get_language', return_value=Language(30, 'English', 'en')) \ - as mocked_languages_get_language, \ - patch.object(BibleImport, 'get_language') as mocked_db_get_language: - instance = BibleImport(MagicMock()) - instance.save_meta = MagicMock() + mocked_get_language.return_value = Language(30, 'English', 'en') + instance = BibleImport(MagicMock()) + instance.save_meta = MagicMock() - # WHEN: Calling get_language_id() with a language name and bible name - result = instance.get_language_id('English', 'KJV') + # WHEN: Calling get_language_id() with a language name and bible name + result = instance.get_language_id('English', 'KJV') - # THEN: The id of the language returned from languages.get_language should be returned - mocked_languages_get_language.assert_called_once_with('English') - self.assertFalse(mocked_db_get_language.called) - instance.save_meta.assert_called_once_with('language_id', 30) - self.assertEqual(result, 30) + # THEN: The id of the language returned from languages.get_language should be returned + mocked_get_language.assert_called_once_with('English') + assert mocked_db_get_language.called is False + instance.save_meta.assert_called_once_with('language_id', 30) + assert result == 30, 'Result should be 30, was {}'.format(result) - def get_language_id_language_not_found_test(self): + @patch('openlp.plugins.bibles.lib.bibleimport.get_language', return_value=None) + @patch.object(BibleImport, 'get_language', return_value=20) + def test_get_language_id_language_not_found(self, mocked_db_get_language, mocked_languages_get_language): """ Test get_language_id() when called with a name not found in the languages list """ # GIVEN: A mocked languages.get_language which returns language and an instance of BibleImport - with patch('openlp.core.common.languages.get_language', return_value=None) as mocked_languages_get_language, \ - patch.object(BibleImport, 'get_language', return_value=20) as mocked_db_get_language: - instance = BibleImport(MagicMock()) - instance.save_meta = MagicMock() + instance = BibleImport(MagicMock()) + instance.save_meta = MagicMock() - # WHEN: Calling get_language_id() with a language name and bible name - result = instance.get_language_id('RUS', 'KJV') + # WHEN: Calling get_language_id() with a language name and bible name + result = instance.get_language_id('RUS', 'KJV') - # THEN: The id of the language returned from languages.get_language should be returned - mocked_languages_get_language.assert_called_once_with('RUS') - mocked_db_get_language.assert_called_once_with('KJV') - instance.save_meta.assert_called_once_with('language_id', 20) - self.assertEqual(result, 20) + # THEN: The id of the language returned from languages.get_language should be returned + mocked_languages_get_language.assert_called_once_with('RUS') + mocked_db_get_language.assert_called_once_with('KJV') + instance.save_meta.assert_called_once_with('language_id', 20) + assert result == 20 - def get_language_id_user_choice_test(self): + @patch('openlp.plugins.bibles.lib.bibleimport.get_language', return_value=None) + @patch.object(BibleImport, 'get_language', return_value=40) + @patch.object(BibleImport, 'log_error') + def test_get_language_id_user_choice(self, mocked_log_error, mocked_db_get_language, mocked_languages_get_language): """ Test get_language_id() when the language is not found and the user is asked for the language """ # GIVEN: A mocked languages.get_language which returns None a mocked BibleDB.get_language which returns a # language id. - with patch('openlp.core.common.languages.get_language', return_value=None) as mocked_languages_get_language, \ - patch.object(BibleImport, 'get_language', return_value=40) as mocked_db_get_language, \ - patch.object(BibleImport, 'log_error') as mocked_log_error: - instance = BibleImport(MagicMock()) - instance.save_meta = MagicMock() + instance = BibleImport(MagicMock()) + instance.save_meta = MagicMock() - # WHEN: Calling get_language_id() with a language name and bible name - result = instance.get_language_id('English', 'KJV') + # WHEN: Calling get_language_id() with a language name and bible name + result = instance.get_language_id('English', 'KJV') - # THEN: The id of the language returned from BibleDB.get_language should be returned - mocked_languages_get_language.assert_called_once_with('English') - mocked_db_get_language.assert_called_once_with('KJV') - self.assertFalse(mocked_log_error.error.called) - instance.save_meta.assert_called_once_with('language_id', 40) - self.assertEqual(result, 40) + # THEN: The id of the language returned from BibleDB.get_language should be returned + mocked_languages_get_language.assert_called_once_with('English') + mocked_db_get_language.assert_called_once_with('KJV') + assert mocked_log_error.error.called is False + instance.save_meta.assert_called_once_with('language_id', 40) + assert result == 40 - def get_language_id_user_choice_rejected_test(self): + @patch('openlp.plugins.bibles.lib.bibleimport.get_language', return_value=None) + @patch.object(BibleImport, 'get_language', return_value=None) + @patch.object(BibleImport, 'log_error') + def test_get_language_id_user_choice_rejected(self, mocked_log_error, mocked_db_get_language, + mocked_languages_get_language): """ Test get_language_id() when the language is not found and the user rejects the dilaog box """ # GIVEN: A mocked languages.get_language which returns None a mocked BibleDB.get_language which returns a # language id. - with patch('openlp.core.common.languages.get_language', return_value=None) as mocked_languages_get_language, \ - patch.object(BibleImport, 'get_language', return_value=None) as mocked_db_get_language, \ - patch.object(BibleImport, 'log_error') as mocked_log_error: - instance = BibleImport(MagicMock()) - instance.save_meta = MagicMock() + instance = BibleImport(MagicMock()) + instance.save_meta = MagicMock() - # WHEN: Calling get_language_id() with a language name and bible name - result = instance.get_language_id('Qwerty', 'KJV') + # WHEN: Calling get_language_id() with a language name and bible name + result = instance.get_language_id('Qwerty', 'KJV') - # THEN: None should be returned and an error should be logged - mocked_languages_get_language.assert_called_once_with('Qwerty') - mocked_db_get_language.assert_called_once_with('KJV') - mocked_log_error.assert_called_once_with( - 'Language detection failed when importing from "KJV". User aborted language selection.') - self.assertFalse(instance.save_meta.called) - self.assertIsNone(result) + # THEN: None should be returned and an error should be logged + mocked_languages_get_language.assert_called_once_with('Qwerty') + mocked_db_get_language.assert_called_once_with('KJV') + mocked_log_error.assert_called_once_with( + 'Language detection failed when importing from "KJV". User aborted language selection.') + assert instance.save_meta.called is False + assert result is None - def get_book_ref_id_by_name_get_book_test(self): + @patch.object(BibleImport, 'log_debug') + @patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB', **{'get_book.return_value': {'id': 20}}) + def test_get_book_ref_id_by_name_get_book(self, MockBibleResourcesDB, mocked_log_debug): """ Test get_book_ref_id_by_name when the book is found as a book in BiblesResourcesDB """ # GIVEN: An instance of BibleImport and a mocked BiblesResourcesDB which returns a book id when get_book is # called - with patch.object(BibleImport, 'log_debug'), \ - patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB', - **{'get_book.return_value': {'id': 20}}): - instance = BibleImport(MagicMock()) + instance = BibleImport(MagicMock()) - # WHEN: Calling get_book_ref_id_by_name - result = instance.get_book_ref_id_by_name('Gen', 66, 4) + # WHEN: Calling get_book_ref_id_by_name + result = instance.get_book_ref_id_by_name('Gen', 66, 4) - # THEN: The bible id should be returned - self.assertEqual(result, 20) + # THEN: The bible id should be returned + assert result == 20 - def get_book_ref_id_by_name_get_alternative_book_name_test(self): + @patch.object(BibleImport, 'log_debug') + @patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB', + **{'get_book.return_value': None, 'get_alternative_book_name.return_value': 30}) + def test_get_book_ref_id_by_name_get_alternative_book_name(self, MockBibleResourcesDB, mocked_log_debug): """ Test get_book_ref_id_by_name when the book is found as an alternative book in BiblesResourcesDB """ # GIVEN: An instance of BibleImport and a mocked BiblesResourcesDB which returns a book id when # get_alternative_book_name is called - with patch.object(BibleImport, 'log_debug'), \ - patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB', - **{'get_book.return_value': None, 'get_alternative_book_name.return_value': 30}): - instance = BibleImport(MagicMock()) + instance = BibleImport(MagicMock()) - # WHEN: Calling get_book_ref_id_by_name - result = instance.get_book_ref_id_by_name('Gen', 66, 4) + # WHEN: Calling get_book_ref_id_by_name + result = instance.get_book_ref_id_by_name('Gen', 66, 4) - # THEN: The bible id should be returned - self.assertEqual(result, 30) + # THEN: The bible id should be returned + assert result == 30 - def get_book_ref_id_by_name_get_book_reference_id_test(self): + @patch.object(BibleImport, 'log_debug') + @patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB', + **{'get_book.return_value': None, 'get_alternative_book_name.return_value': None}) + @patch('openlp.plugins.bibles.lib.bibleimport.AlternativeBookNamesDB', **{'get_book_reference_id.return_value': 40}) + def test_get_book_ref_id_by_name_get_book_reference_id(self, MockAlterativeBookNamesDB, MockBibleResourcesDB, + mocked_log_debug): """ Test get_book_ref_id_by_name when the book is found as a book in AlternativeBookNamesDB """ # GIVEN: An instance of BibleImport and a mocked AlternativeBookNamesDB which returns a book id when # get_book_reference_id is called - with patch.object(BibleImport, 'log_debug'), \ - patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB', - **{'get_book.return_value': None, 'get_alternative_book_name.return_value': None}), \ - patch('openlp.plugins.bibles.lib.bibleimport.AlternativeBookNamesDB', - **{'get_book_reference_id.return_value': 40}): - instance = BibleImport(MagicMock()) + instance = BibleImport(MagicMock()) - # WHEN: Calling get_book_ref_id_by_name - result = instance.get_book_ref_id_by_name('Gen', 66, 4) + # WHEN: Calling get_book_ref_id_by_name + result = instance.get_book_ref_id_by_name('Gen', 66, 4) - # THEN: The bible id should be returned - self.assertEqual(result, 40) + # THEN: The bible id should be returned + assert result == 40 - def get_book_ref_id_by_name_book_name_form_rejected_test(self): + @patch.object(BibleImport, 'log_debug') + @patch.object(BibleImport, 'get_books') + @patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB', + **{'get_book.return_value': None, 'get_alternative_book_name.return_value': None}) + @patch('openlp.plugins.bibles.lib.bibleimport.AlternativeBookNamesDB', + **{'get_book_reference_id.return_value': None}) + @patch('openlp.plugins.bibles.forms.BookNameForm', + return_value=MagicMock(**{'exec.return_value': QDialog.Rejected})) + def test_get_book_ref_id_by_name_book_name_form_rejected(self, MockBookNameForm, MockAlterativeBookNamesDB, + MockBibleResourcesDB, mocked_get_books, mocked_log_debug): """ Test get_book_ref_id_by_name when the user rejects the BookNameForm """ # GIVEN: An instance of BibleImport and a mocked BookNameForm which simulates a user rejecting the dialog - with patch.object(BibleImport, 'log_debug'), patch.object(BibleImport, 'get_books'), \ - patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB', - **{'get_book.return_value': None, 'get_alternative_book_name.return_value': None}), \ - patch('openlp.plugins.bibles.lib.bibleimport.AlternativeBookNamesDB', - **{'get_book_reference_id.return_value': None}), \ - patch('openlp.plugins.bibles.forms.BookNameForm', - return_value=MagicMock(**{'exec.return_value': QDialog.Rejected})): - instance = BibleImport(MagicMock()) + instance = BibleImport(MagicMock()) - # WHEN: Calling get_book_ref_id_by_name - result = instance.get_book_ref_id_by_name('Gen', 66, 4) + # WHEN: Calling get_book_ref_id_by_name + result = instance.get_book_ref_id_by_name('Gen', 66, 4) - # THEN: None should be returned - self.assertIsNone(result) + # THEN: None should be returned + assert result is None - def get_book_ref_id_by_name_book_name_form_accepted_test(self): + @patch.object(BibleImport, 'log_debug') + @patch.object(BibleImport, 'get_books') + @patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB', + **{'get_book.return_value': None, 'get_alternative_book_name.return_value': None}) + @patch('openlp.plugins.bibles.lib.bibleimport.AlternativeBookNamesDB', + **{'get_book_reference_id.return_value': None}) + @patch('openlp.plugins.bibles.forms.BookNameForm', + return_value=MagicMock(**{'exec.return_value': QDialog.Accepted, 'book_id': 50})) + def test_get_book_ref_id_by_name_book_name_form_accepted(self, MockBookNameForm, MockAlterativeBookNamesDB, + MockBibleResourcesDB, mocked_get_books, mocked_log_debug): """ Test get_book_ref_id_by_name when the user accepts the BookNameForm """ # GIVEN: An instance of BibleImport and a mocked BookNameForm which simulates a user accepting the dialog - with patch.object(BibleImport, 'log_debug'), patch.object(BibleImport, 'get_books'), \ - patch('openlp.plugins.bibles.lib.bibleimport.BiblesResourcesDB', - **{'get_book.return_value': None, 'get_alternative_book_name.return_value': None}), \ - patch('openlp.plugins.bibles.lib.bibleimport.AlternativeBookNamesDB', - **{'get_book_reference_id.return_value': None}) as mocked_alternative_book_names_db, \ - patch('openlp.plugins.bibles.forms.BookNameForm', - return_value=MagicMock(**{'exec.return_value': QDialog.Accepted, 'book_id': 50})): - instance = BibleImport(MagicMock()) + instance = BibleImport(MagicMock()) - # WHEN: Calling get_book_ref_id_by_name - result = instance.get_book_ref_id_by_name('Gen', 66, 4) + # WHEN: Calling get_book_ref_id_by_name + result = instance.get_book_ref_id_by_name('Gen', 66, 4) - # THEN: An alternative book name should be created and a bible id should be returned - mocked_alternative_book_names_db.create_alternative_book_name.assert_called_once_with('Gen', 50, 4) - self.assertEqual(result, 50) + # THEN: An alternative book name should be created and a bible id should be returned + MockAlterativeBookNamesDB.create_alternative_book_name.assert_called_once_with('Gen', 50, 4) + assert result == 50 - def is_compressed_compressed_test(self): + @patch('openlp.plugins.bibles.lib.bibleimport.is_zipfile', return_value=True) + def test_is_compressed_compressed(self, mocked_is_zipfile): """ Test is_compressed when the 'file' being tested is compressed """ # GIVEN: An instance of BibleImport and a mocked is_zipfile which returns True - with patch('openlp.plugins.bibles.lib.bibleimport.is_zipfile', return_value=True): - instance = BibleImport(MagicMock()) + instance = BibleImport(MagicMock()) - # WHEN: Calling is_compressed - result = instance.is_compressed('file.ext') + # WHEN: Calling is_compressed + result = instance.is_compressed('file.ext') - # THEN: Then critical_error_message_box should be called informing the user that the file is compressed and - # True should be returned - self.mocked_critical_error_message_box.assert_called_once_with( - message='The file "file.ext" you supplied is compressed. You must decompress it before import.') - self.assertTrue(result) + # THEN: Then critical_error_message_box should be called informing the user that the file is compressed and + # True should be returned + self.mocked_critical_error_message_box.assert_called_once_with( + message='The file "file.ext" you supplied is compressed. You must decompress it before import.') + assert result is True - def is_compressed_not_compressed_test(self): + @patch('openlp.plugins.bibles.lib.bibleimport.is_zipfile', return_value=False) + def test_is_compressed_not_compressed(self, mocked_is_zipfile): """ Test is_compressed when the 'file' being tested is not compressed """ # GIVEN: An instance of BibleImport and a mocked is_zipfile which returns False - with patch('openlp.plugins.bibles.lib.bibleimport.is_zipfile', return_value=False): - instance = BibleImport(MagicMock()) + instance = BibleImport(MagicMock()) - # WHEN: Calling is_compressed - result = instance.is_compressed('file.ext') + # WHEN: Calling is_compressed + result = instance.is_compressed('file.ext') - # THEN: False should be returned and critical_error_message_box should not have been called - self.assertFalse(result) - self.assertFalse(self.mocked_critical_error_message_box.called) + # THEN: False should be returned and critical_error_message_box should not have been called + assert result is False + assert self.mocked_critical_error_message_box.called is False - def parse_xml_etree_test(self): + def test_parse_xml_etree(self): """ Test BibleImport.parse_xml() when called with the use_objectify default value """ @@ -362,12 +364,11 @@ class TestBibleImport(TestCase): result = instance.parse_xml('file.tst') # THEN: The result returned should contain the correct data, and should be an instance of eetree_Element - self.assertEqual(etree.tostring(result), - b'<root>\n <data><div>Test<p>data</p><a>to</a>keep</div></data>\n' - b' <data><unsupported>Test<x>data</x><y>to</y>discard</unsupported></data>\n</root>') - self.assertIsInstance(result, etree._Element) + assert etree.tostring(result) == b'<root>\n <data><div>Test<p>data</p><a>to</a>keep</div></data>\n' \ + b' <data><unsupported>Test<x>data</x><y>to</y>discard</unsupported></data>\n</root>' + assert isinstance(result, etree._Element) - def parse_xml_etree_use_objectify_test(self): + def test_parse_xml_etree_use_objectify(self): """ Test BibleImport.parse_xml() when called with use_objectify set to True """ @@ -380,12 +381,11 @@ class TestBibleImport(TestCase): result = instance.parse_xml('file.tst', use_objectify=True) # THEN: The result returned should contain the correct data, and should be an instance of ObjectifiedElement - self.assertEqual(etree.tostring(result), - b'<root><data><div>Test<p>data</p><a>to</a>keep</div></data>' - b'<data><unsupported>Test<x>data</x><y>to</y>discard</unsupported></data></root>') - self.assertIsInstance(result, objectify.ObjectifiedElement) + assert etree.tostring(result) == b'<root><data><div>Test<p>data</p><a>to</a>keep</div></data>' \ + b'<data><unsupported>Test<x>data</x><y>to</y>discard</unsupported></data></root>' + assert isinstance(result, objectify.ObjectifiedElement) - def parse_xml_elements_test(self): + def test_parse_xml_elements(self): """ Test BibleImport.parse_xml() when given a tuple of elements to remove """ @@ -399,10 +399,10 @@ class TestBibleImport(TestCase): result = instance.parse_xml('file.tst', elements=elements) # THEN: The result returned should contain the correct data - self.assertEqual(etree.tostring(result), - b'<root>\n <data><div>Test<p>data</p><a>to</a>keep</div></data>\n <data/>\n</root>') + assert etree.tostring(result) == \ + b'<root>\n <data><div>Test<p>data</p><a>to</a>keep</div></data>\n <data/>\n</root>' - def parse_xml_tags_test(self): + def test_parse_xml_tags(self): """ Test BibleImport.parse_xml() when given a tuple of tags to remove """ @@ -416,10 +416,10 @@ class TestBibleImport(TestCase): result = instance.parse_xml('file.tst', tags=tags) # THEN: The result returned should contain the correct data - self.assertEqual(etree.tostring(result), b'<root>\n <data>Testdatatokeep</data>\n <data><unsupported>Test' - b'<x>data</x><y>to</y>discard</unsupported></data>\n</root>') + assert etree.tostring(result) == b'<root>\n <data>Testdatatokeep</data>\n <data><unsupported>Test' \ + b'<x>data</x><y>to</y>discard</unsupported></data>\n</root>' - def parse_xml_elements_tags_test(self): + def test_parse_xml_elements_tags(self): """ Test BibleImport.parse_xml() when given a tuple of elements and of tags to remove """ @@ -434,53 +434,53 @@ class TestBibleImport(TestCase): result = instance.parse_xml('file.tst', elements=elements, tags=tags) # THEN: The result returned should contain the correct data - self.assertEqual(etree.tostring(result), b'<root>\n <data>Testdatatokeep</data>\n <data/>\n</root>') + assert etree.tostring(result) == b'<root>\n <data>Testdatatokeep</data>\n <data/>\n</root>' - def parse_xml_file_file_not_found_exception_test(self): + @patch.object(BibleImport, 'log_exception') + def test_parse_xml_file_file_not_found_exception(self, mocked_log_exception): """ Test that parse_xml handles a FileNotFoundError exception correctly """ - with patch.object(BibleImport, 'log_exception') as mocked_log_exception: - # GIVEN: A mocked open which raises a FileNotFoundError and an instance of BibleImporter - exception = FileNotFoundError() - exception.filename = 'file.tst' - exception.strerror = 'No such file or directory' - self.mocked_open.side_effect = exception - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + # GIVEN: A mocked open which raises a FileNotFoundError and an instance of BibleImporter + exception = FileNotFoundError() + exception.filename = 'file.tst' + exception.strerror = 'No such file or directory' + self.mocked_open.side_effect = exception + importer = BibleImport(MagicMock(), path='.', name='.', filename='') - # WHEN: Calling parse_xml - result = importer.parse_xml('file.tst') + # WHEN: Calling parse_xml + result = importer.parse_xml('file.tst') - # THEN: parse_xml should have caught the error, informed the user and returned None - mocked_log_exception.assert_called_once_with('Opening file.tst failed.') - self.mocked_critical_error_message_box.assert_called_once_with( - title='An Error Occured When Opening A File', - message='The following error occurred when trying to open\nfile.tst:\n\nNo such file or directory') - self.assertIsNone(result) + # THEN: parse_xml should have caught the error, informed the user and returned None + mocked_log_exception.assert_called_once_with('Opening file.tst failed.') + self.mocked_critical_error_message_box.assert_called_once_with( + title='An Error Occured When Opening A File', + message='The following error occurred when trying to open\nfile.tst:\n\nNo such file or directory') + assert result is None - def parse_xml_file_permission_error_exception_test(self): + @patch.object(BibleImport, 'log_exception') + def test_parse_xml_file_permission_error_exception(self, mocked_log_exception): """ Test that parse_xml handles a PermissionError exception correctly """ - with patch.object(BibleImport, 'log_exception') as mocked_log_exception: - # GIVEN: A mocked open which raises a PermissionError and an instance of BibleImporter - exception = PermissionError() - exception.filename = 'file.tst' - exception.strerror = 'Permission denied' - self.mocked_open.side_effect = exception - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + # GIVEN: A mocked open which raises a PermissionError and an instance of BibleImporter + exception = PermissionError() + exception.filename = 'file.tst' + exception.strerror = 'Permission denied' + self.mocked_open.side_effect = exception + importer = BibleImport(MagicMock(), path='.', name='.', filename='') - # WHEN: Calling parse_xml - result = importer.parse_xml('file.tst') + # WHEN: Calling parse_xml + result = importer.parse_xml('file.tst') - # THEN: parse_xml should have caught the error, informed the user and returned None - mocked_log_exception.assert_called_once_with('Opening file.tst failed.') - self.mocked_critical_error_message_box.assert_called_once_with( - title='An Error Occured When Opening A File', - message='The following error occurred when trying to open\nfile.tst:\n\nPermission denied') - self.assertIsNone(result) + # THEN: parse_xml should have caught the error, informed the user and returned None + mocked_log_exception.assert_called_once_with('Opening file.tst failed.') + self.mocked_critical_error_message_box.assert_called_once_with( + title='An Error Occured When Opening A File', + message='The following error occurred when trying to open\nfile.tst:\n\nPermission denied') + assert result is None - def set_current_chapter_test(self): + def test_set_current_chapter(self): """ Test set_current_chapter """ @@ -494,121 +494,121 @@ class TestBibleImport(TestCase): # THEN: Increment_progress_bar should have been called with a text string importer.wizard.increment_progress_bar.assert_called_once_with('Importing Book_Name Chapter...') - def validate_xml_file_compressed_file_test(self): + @patch.object(BibleImport, 'is_compressed', return_value=True) + def test_validate_xml_file_compressed_file(self, mocked_is_compressed): """ Test that validate_xml_file raises a ValidationError when is_compressed returns True """ # GIVEN: A mocked parse_xml which returns None - with patch.object(BibleImport, 'is_compressed', return_value=True): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', filename='') - # WHEN: Calling is_compressed - # THEN: ValidationError should be raised, with the message 'Compressed file' - with self.assertRaises(ValidationError) as context: - importer.validate_xml_file('file.name', 'xbible') - self.assertEqual(context.exception.msg, 'Compressed file') + # WHEN: Calling is_compressed + # THEN: ValidationError should be raised, with the message 'Compressed file' + with self.assertRaises(ValidationError) as context: + importer.validate_xml_file('file.name', 'xbible') + assert context.exception.msg == 'Compressed file' - def validate_xml_file_parse_xml_fails_test(self): + @patch.object(BibleImport, 'parse_xml', return_value=None) + @patch.object(BibleImport, 'is_compressed', return_value=False) + def test_validate_xml_file_parse_xml_fails(self, mocked_is_compressed, mocked_parse_xml): """ Test that validate_xml_file raises a ValidationError when parse_xml returns None """ # GIVEN: A mocked parse_xml which returns None - with patch.object(BibleImport, 'parse_xml', return_value=None), \ - patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', filename='') - # WHEN: Calling validate_xml_file - # THEN: ValidationError should be raised, with the message 'Error when opening file' - # the user that an OpenSong bible was found - with self.assertRaises(ValidationError) as context: - importer.validate_xml_file('file.name', 'xbible') - self.assertEqual(context.exception.msg, 'Error when opening file') + # WHEN: Calling validate_xml_file + # THEN: ValidationError should be raised, with the message 'Error when opening file' + # the user that an OpenSong bible was found + with self.assertRaises(ValidationError) as context: + importer.validate_xml_file('file.name', 'xbible') + assert context.exception.msg == 'Error when opening file' - def validate_xml_file_success_test(self): + @patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('<bible></bible>')) + @patch.object(BibleImport, 'is_compressed', return_value=False) + def test_validate_xml_file_success(self, mocked_is_compressed, mocked_parse_xml): """ Test that validate_xml_file returns True with valid XML """ # GIVEN: Some test data with an OpenSong Bible "bible" root tag - with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('<bible></bible>')), \ - patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', filename='') - # WHEN: Calling validate_xml_file - result = importer.validate_xml_file('file.name', 'bible') + # WHEN: Calling validate_xml_file + result = importer.validate_xml_file('file.name', 'bible') - # THEN: True should be returned - self.assertTrue(result) + # THEN: True should be returned + assert result is True - def validate_xml_file_opensong_root_test(self): + @patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('<bible></bible>')) + @patch.object(BibleImport, 'is_compressed', return_value=False) + def test_validate_xml_file_opensong_root(self, mocked_is_compressed, mocked_parse_xml): """ Test that validate_xml_file raises a ValidationError with an OpenSong root tag """ # GIVEN: Some test data with an Zefania root tag and an instance of BibleImport - with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('<bible></bible>')), \ - patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', filename='') - # WHEN: Calling validate_xml_file - # THEN: ValidationError should be raised, and the critical error message box should was called informing - # the user that an OpenSong bible was found - with self.assertRaises(ValidationError) as context: - importer.validate_xml_file('file.name', 'xbible') - self.assertEqual(context.exception.msg, 'Invalid xml.') - self.mocked_critical_error_message_box.assert_called_once_with( - message='Incorrect Bible file type supplied. This looks like an OpenSong XML bible.') + # WHEN: Calling validate_xml_file + # THEN: ValidationError should be raised, and the critical error message box should was called informing + # the user that an OpenSong bible was found + with self.assertRaises(ValidationError) as context: + importer.validate_xml_file('file.name', 'xbible') + assert context.exception.msg == 'Invalid xml.' + self.mocked_critical_error_message_box.assert_called_once_with( + message='Incorrect Bible file type supplied. This looks like an OpenSong XML bible.') - def validate_xml_file_osis_root_test(self): + @patch.object(BibleImport, 'parse_xml') + @patch.object(BibleImport, 'is_compressed', return_value=False) + def test_validate_xml_file_osis_root(self, mocked_is_compressed, mocked_parse_xml): """ Test that validate_xml_file raises a ValidationError with an OSIS root tag """ # GIVEN: Some test data with an Zefania root tag and an instance of BibleImport - with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring( - '<osis xmlns=\'http://www.bibletechnologies.net/2003/OSIS/namespace\'></osis>')), \ - patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + mocked_parse_xml.return_value = objectify.fromstring( + '<osis xmlns=\'http://www.bibletechnologies.net/2003/OSIS/namespace\'></osis>') + importer = BibleImport(MagicMock(), path='.', name='.', filename='') - # WHEN: Calling validate_xml_file - # THEN: ValidationError should be raised, and the critical error message box should was called informing - # the user that an OSIS bible was found - with self.assertRaises(ValidationError) as context: - importer.validate_xml_file('file.name', 'xbible') - self.assertEqual(context.exception.msg, 'Invalid xml.') - self.mocked_critical_error_message_box.assert_called_once_with( - message='Incorrect Bible file type supplied. This looks like an OSIS XML bible.') + # WHEN: Calling validate_xml_file + # THEN: ValidationError should be raised, and the critical error message box should was called informing + # the user that an OSIS bible was found + with self.assertRaises(ValidationError) as context: + importer.validate_xml_file('file.name', 'xbible') + assert context.exception.msg == 'Invalid xml.' + self.mocked_critical_error_message_box.assert_called_once_with( + message='Incorrect Bible file type supplied. This looks like an OSIS XML bible.') - def validate_xml_file_zefania_root_test(self): + @patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('<xmlbible></xmlbible>')) + @patch.object(BibleImport, 'is_compressed', return_value=False) + def test_validate_xml_file_zefania_root(self, mocked_is_compressed, mocked_parse_xml): """ Test that validate_xml_file raises a ValidationError with an Zefania root tag """ # GIVEN: Some test data with an Zefania root tag and an instance of BibleImport - with patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('<xmlbible></xmlbible>')), \ - patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', filename='') - # WHEN: Calling validate_xml_file - # THEN: ValidationError should be raised, and the critical error message box should was called informing - # the user that an Zefania bible was found - with self.assertRaises(ValidationError) as context: - importer.validate_xml_file('file.name', 'xbible') - self.assertEqual(context.exception.msg, 'Invalid xml.') - self.mocked_critical_error_message_box.assert_called_once_with( - message='Incorrect Bible file type supplied. This looks like an Zefania XML bible.') + # WHEN: Calling validate_xml_file + # THEN: ValidationError should be raised, and the critical error message box should was called informing + # the user that an Zefania bible was found + with self.assertRaises(ValidationError) as context: + importer.validate_xml_file('file.name', 'xbible') + assert context.exception.msg == 'Invalid xml.' + self.mocked_critical_error_message_box.assert_called_once_with( + message='Incorrect Bible file type supplied. This looks like an Zefania XML bible.') - def validate_xml_file_unknown_root_test(self): + @patch.object(BibleImport, 'parse_xml', return_value=objectify.fromstring('<unknownbible></unknownbible>')) + @patch.object(BibleImport, 'is_compressed', return_value=False) + def test_validate_xml_file_unknown_root(self, mocked_is_compressed, mocked_parse_xml): """ Test that validate_xml_file raises a ValidationError with an unknown root tag """ # GIVEN: Some test data with an unknown root tag and an instance of BibleImport - with patch.object( - BibleImport, 'parse_xml', return_value=objectify.fromstring('<unknownbible></unknownbible>')), \ - patch.object(BibleImport, 'is_compressed', return_value=False): - importer = BibleImport(MagicMock(), path='.', name='.', filename='') + importer = BibleImport(MagicMock(), path='.', name='.', filename='') - # WHEN: Calling validate_xml_file - # THEN: ValidationError should be raised, and the critical error message box should was called informing - # the user that a unknown xml bible was found - with self.assertRaises(ValidationError) as context: - importer.validate_xml_file('file.name', 'xbible') - self.assertEqual(context.exception.msg, 'Invalid xml.') - self.mocked_critical_error_message_box.assert_called_once_with( - message='Incorrect Bible file type supplied. This looks like an unknown type of XML bible.') + # WHEN: Calling validate_xml_file + # THEN: ValidationError should be raised, and the critical error message box should was called informing + # the user that a unknown xml bible was found + with self.assertRaises(ValidationError) as context: + importer.validate_xml_file('file.name', 'xbible') + assert context.exception.msg == 'Invalid xml.' + self.mocked_critical_error_message_box.assert_called_once_with( + message='Incorrect Bible file type supplied. This looks like an unknown type of XML bible.') diff --git a/tests/functional/openlp_plugins/bibles/test_csvimport.py b/tests/functional/openlp_plugins/bibles/test_csvimport.py index 63d3d5282..608e44bb9 100644 --- a/tests/functional/openlp_plugins/bibles/test_csvimport.py +++ b/tests/functional/openlp_plugins/bibles/test_csvimport.py @@ -66,7 +66,7 @@ class TestCSVImport(TestCase): self.assertEqual(importer.books_file, 'books.csv') self.assertEqual(importer.verses_file, 'verse.csv') - def book_namedtuple_test(self): + def test_book_namedtuple(self): """ Test that the Book namedtuple is created as expected """ @@ -80,7 +80,7 @@ class TestCSVImport(TestCase): self.assertEqual(result.name, 'name') self.assertEqual(result.abbreviation, 'abbreviation') - def verse_namedtuple_test(self): + def test_verse_namedtuple(self): """ Test that the Verse namedtuple is created as expected """ @@ -94,7 +94,7 @@ class TestCSVImport(TestCase): self.assertEqual(result.number, 'number') self.assertEqual(result.text, 'text') - def get_book_name_id_test(self): + def test_get_book_name_id(self): """ Test that get_book_name() returns the correct book when called with an id """ @@ -109,7 +109,7 @@ class TestCSVImport(TestCase): # THEN: get_book_name() should return the book name associated with that id from the books dictionary self.assertEqual(actual_result, expected_result) - def get_book_name_test(self): + def test_get_book_name(self): """ Test that get_book_name() returns the name when called with a non integer value """ @@ -124,7 +124,7 @@ class TestCSVImport(TestCase): # THEN: get_book_name() should return the input self.assertEqual(actual_result, expected_result) - def parse_csv_file_test(self): + def test_parse_csv_file(self): """ Test the parse_csv_file() with sample data """ @@ -147,7 +147,7 @@ class TestCSVImport(TestCase): mocked_open.assert_called_once_with('file.csv', 'r', encoding='utf-8', newline='') mocked_reader.assert_called_once_with(ANY, delimiter=',', quotechar='"') - def parse_csv_file_oserror_test(self): + def test_parse_csv_file_oserror(self): """ Test the parse_csv_file() handles an OSError correctly """ @@ -162,7 +162,7 @@ class TestCSVImport(TestCase): CSVBible.parse_csv_file('file.csv', None) self.assertEqual(context.exception.msg, 'Parsing "file.csv" failed') - def parse_csv_file_csverror_test(self): + def test_parse_csv_file_csverror(self): """ Test the parse_csv_file() handles an csv.Error correctly """ @@ -178,7 +178,7 @@ class TestCSVImport(TestCase): CSVBible.parse_csv_file('file.csv', None) self.assertEqual(context.exception.msg, 'Parsing "file.csv" failed') - def process_books_stopped_import_test(self): + def test_process_books_stopped_import(self): """ Test process books when the import is stopped """ @@ -197,7 +197,7 @@ class TestCSVImport(TestCase): self.assertFalse(importer.wizard.increment_progress_bar.called) self.assertEqual(result, {}) - def process_books_test(self): + def test_process_books(self): """ Test process books when it completes successfully """ @@ -222,7 +222,7 @@ class TestCSVImport(TestCase): [call('1. Mosebog', 2, 10), call('2. Mosebog', 2, 10)]) self.assertDictEqual(result, {1: '1. Mosebog', 2: '2. Mosebog'}) - def process_verses_stopped_import_test(self): + def test_process_verses_stopped_import(self): """ Test process_verses when the import is stopped """ @@ -242,7 +242,7 @@ class TestCSVImport(TestCase): self.assertFalse(importer.get_book_name.called) self.assertIsNone(result) - def process_verses_successful_test(self): + def test_process_verses_successful(self): """ Test process_verses when the import is successful """ @@ -274,7 +274,7 @@ class TestCSVImport(TestCase): call('1', 1, 2, 'Og Jorden var øde og tom, og der var Mørke over Verdensdybet. ' 'Men Guds Ånd svævede over Vandene.')]) - def do_import_invalid_language_id_test(self): + def test_do_import_invalid_language_id(self): """ Test do_import when the user cancels the language selection dialog box """ @@ -291,7 +291,7 @@ class TestCSVImport(TestCase): importer.get_language.assert_called_once_with('Bible Name') self.assertFalse(result) - def do_import_success_test(self): + def test_do_import_success(self): """ Test do_import when the import succeeds """ @@ -317,7 +317,7 @@ class TestCSVImport(TestCase): importer.process_verses.assert_called_once_with(['Verse 1'], ['Book 1']) self.assertTrue(result) - def file_import_test(self): + def test_file_import(self): """ Test the actual import of CSV Bible file """ diff --git a/tests/functional/openlp_plugins/bibles/test_opensongimport.py b/tests/functional/openlp_plugins/bibles/test_opensongimport.py index 5afd2d7d9..0e2ba8f70 100644 --- a/tests/functional/openlp_plugins/bibles/test_opensongimport.py +++ b/tests/functional/openlp_plugins/bibles/test_opensongimport.py @@ -69,7 +69,7 @@ class TestOpenSongImport(TestCase, TestMixin): # THEN: The importer should be an instance of BibleDB self.assertIsInstance(importer, BibleImport) - def get_text_no_text_test(self): + def test_get_text_no_text(self): """ Test that get_text handles elements containing text in a combination of text and tail attributes """ @@ -82,7 +82,7 @@ class TestOpenSongImport(TestCase, TestMixin): # THEN: A blank string should be returned self.assertEqual(result, '') - def get_text_text_test(self): + def test_get_text_text(self): """ Test that get_text handles elements containing text in a combination of text and tail attributes """ @@ -99,7 +99,7 @@ class TestOpenSongImport(TestCase, TestMixin): # THEN: The text returned should be as expected self.assertEqual(result, 'Element text sub_text_tail text sub_text_tail tail sub_text text sub_tail tail') - def parse_chapter_number_test(self): + def test_parse_chapter_number(self): """ Test parse_chapter_number when supplied with chapter number and an instance of OpenSongBible """ @@ -110,7 +110,7 @@ class TestOpenSongImport(TestCase, TestMixin): # THEN: The 10 should be returned as an Int self.assertEqual(result, 10) - def parse_chapter_number_empty_attribute_test(self): + def test_parse_chapter_number_empty_attribute(self): """ Testparse_chapter_number when the chapter number is an empty string. (Bug #1074727) """ @@ -121,7 +121,7 @@ class TestOpenSongImport(TestCase, TestMixin): # THEN: parse_chapter_number should increment the previous verse number self.assertEqual(result, 13) - def parse_verse_number_valid_verse_no_test(self): + def test_parse_verse_number_valid_verse_no(self): """ Test parse_verse_number when supplied with a valid verse number """ @@ -134,7 +134,7 @@ class TestOpenSongImport(TestCase, TestMixin): # THEN: parse_verse_number should return the verse number self.assertEqual(result, 15) - def parse_verse_number_verse_range_test(self): + def test_parse_verse_number_verse_range(self): """ Test parse_verse_number when supplied with a verse range """ @@ -147,7 +147,7 @@ class TestOpenSongImport(TestCase, TestMixin): # THEN: parse_verse_number should return the first verse number in the range self.assertEqual(result, 24) - def parse_verse_number_invalid_verse_no_test(self): + def test_parse_verse_number_invalid_verse_no(self): """ Test parse_verse_number when supplied with a invalid verse number """ @@ -160,7 +160,7 @@ class TestOpenSongImport(TestCase, TestMixin): # THEN: parse_verse_number should increment the previous verse number self.assertEqual(result, 42) - def parse_verse_number_empty_attribute_test(self): + def test_parse_verse_number_empty_attribute(self): """ Test parse_verse_number when the verse number is an empty string. (Bug #1074727) """ @@ -172,7 +172,7 @@ class TestOpenSongImport(TestCase, TestMixin): # THEN: parse_verse_number should increment the previous verse number self.assertEqual(result, 15) - def parse_verse_number_invalid_type_test(self): + def test_parse_verse_number_invalid_type(self): """ Test parse_verse_number when the verse number is an invalid type) """ @@ -188,7 +188,7 @@ class TestOpenSongImport(TestCase, TestMixin): mocked_log_warning.assert_called_once_with('Illegal verse number: (1, 2, 3)') self.assertEqual(result, 13) - def process_books_stop_import_test(self): + def test_process_books_stop_import(self): """ Test process_books when stop_import is set to True """ @@ -202,7 +202,7 @@ class TestOpenSongImport(TestCase, TestMixin): # THEN: find_and_create_book should not have been called self.assertFalse(self.mocked_find_and_create_book.called) - def process_books_completes_test(self): + def test_process_books_completes(self): """ Test process_books when it processes all books """ @@ -231,7 +231,7 @@ class TestOpenSongImport(TestCase, TestMixin): [call('db_book1', 'Chapter1'), call('db_book2', 'Chapter2')]) self.assertEqual(importer.session.commit.call_count, 2) - def process_chapters_stop_import_test(self): + def test_process_chapters_stop_import(self): """ Test process_chapters when stop_import is set to True """ @@ -247,7 +247,7 @@ class TestOpenSongImport(TestCase, TestMixin): self.assertFalse(importer.parse_chapter_number.called) @patch('openlp.plugins.bibles.lib.importers.opensong.parse_chapter_number', **{'side_effect': [1, 2]}) - def process_chapters_completes_test(self, mocked_parse_chapter_number): + def test_process_chapters_completes(self, mocked_parse_chapter_number): """ Test process_chapters when it completes """ @@ -279,7 +279,7 @@ class TestOpenSongImport(TestCase, TestMixin): self.assertEqual(importer.wizard.increment_progress_bar.call_args_list, [call('Importing Book 1...'), call('Importing Book 2...')]) - def process_verses_stop_import_test(self): + def test_process_verses_stop_import(self): """ Test process_verses when stop_import is set to True """ @@ -294,7 +294,7 @@ class TestOpenSongImport(TestCase, TestMixin): # THEN: importer.parse_verse_number not have been called self.assertFalse(importer.parse_verse_number.called) - def process_verses_completes_test(self): + def test_process_verses_completes(self): """ Test process_verses when it completes """ @@ -329,7 +329,7 @@ class TestOpenSongImport(TestCase, TestMixin): importer.create_verse.call_args_list, [call(1, 1, 1, 'Verse1 Text'), call(1, 1, 2, 'Verse2 Text')]) - def do_import_parse_xml_fails_test(self): + def test_do_import_parse_xml_fails(self): """ Test do_import when parse_xml fails (returns None) """ @@ -347,7 +347,7 @@ class TestOpenSongImport(TestCase, TestMixin): self.assertFalse(result) self.assertFalse(mocked_language_id.called) - def do_import_no_language_test(self): + def test_do_import_no_language(self): """ Test do_import when the user cancels the language selection dialog """ @@ -366,7 +366,7 @@ class TestOpenSongImport(TestCase, TestMixin): self.assertFalse(result) self.assertFalse(mocked_process_books.called) - def do_import_completes_test(self): + def test_do_import_completes(self): """ Test do_import when it completes successfully """ diff --git a/tests/functional/openlp_plugins/bibles/test_osisimport.py b/tests/functional/openlp_plugins/bibles/test_osisimport.py index 5be6c459c..a456d71f6 100644 --- a/tests/functional/openlp_plugins/bibles/test_osisimport.py +++ b/tests/functional/openlp_plugins/bibles/test_osisimport.py @@ -68,7 +68,7 @@ class TestOsisImport(TestCase): # THEN: The importer should be an instance of BibleDB self.assertIsInstance(importer, BibleDB) - def process_books_stop_import_test(self): + def test_process_books_stop_import(self): """ Test process_books when stop_import is set to True """ @@ -83,7 +83,7 @@ class TestOsisImport(TestCase): # THEN: find_and_create_book should not have been called self.assertFalse(self.mocked_find_and_create_book.called) - def process_books_completes_test(self): + def test_process_books_completes(self): """ Test process_books when it processes all books """ @@ -111,7 +111,7 @@ class TestOsisImport(TestCase): [call('db_book1', book1), call('db_book2', book2)]) self.assertEqual(importer.session.commit.call_count, 2) - def process_chapters_verse_in_chapter_verse_text_test(self): + def test_process_chapters_verse_in_chapter_verse_text(self): """ Test process_chapters when supplied with an etree element with a verse element nested in it """ @@ -137,7 +137,7 @@ class TestOsisImport(TestCase): mocked_set_current_chapter.assert_called_once_with(test_book.name, 2) mocked_process_verse.assert_called_once_with(test_book, 2, test_verse) - def process_chapters_verse_in_chapter_verse_milestone_test(self): + def test_process_chapters_verse_in_chapter_verse_milestone(self): """ Test process_chapters when supplied with an etree element with a verse element nested, when the verse system is based on milestones @@ -164,7 +164,7 @@ class TestOsisImport(TestCase): mocked_set_current_chapter.assert_called_once_with(test_book.name, 2) mocked_process_verse.assert_called_once_with(test_book, 2, test_verse, use_milestones=True) - def process_chapters_milestones_chapter_no_sid_test(self): + def test_process_chapters_milestones_chapter_no_sid(self): """ Test process_chapters when supplied with an etree element with a chapter and verse element in the milestone configuration, where the chapter is the "closing" milestone. (Missing the sID attribute) @@ -187,7 +187,7 @@ class TestOsisImport(TestCase): self.assertFalse(mocked_set_current_chapter.called) self.assertFalse(mocked_process_verse.called) - def process_chapters_milestones_chapter_sid_test(self): + def test_process_chapters_milestones_chapter_sid(self): """ Test process_chapters when supplied with an etree element with a chapter and verse element in the milestone configuration, where the chapter is the "opening" milestone. (Has the sID attribute) @@ -210,7 +210,7 @@ class TestOsisImport(TestCase): mocked_set_current_chapter.assert_called_once_with(test_book.name, 2) self.assertFalse(mocked_process_verse.called) - def process_chapters_milestones_verse_tag_test(self): + def test_process_chapters_milestones_verse_tag(self): """ Test process_chapters when supplied with an etree element with a chapter and verse element in the milestone configuration, where the verse is the "opening" milestone. (Has the sID attribute) @@ -235,7 +235,7 @@ class TestOsisImport(TestCase): self.assertFalse(mocked_set_current_chapter.called) mocked_process_verse.assert_called_once_with(test_book, 0, test_verse, use_milestones=True) - def process_verse_no_osis_id_test(self): + def test_process_verse_no_osis_id(self): """ Test process_verse when the element supplied does not have and osisID attribute """ @@ -253,7 +253,7 @@ class TestOsisImport(TestCase): # THEN: create_verse should not have been called self.assertFalse(self.mocked_create_verse.called) - def process_verse_use_milestones_no_s_id_test(self): + def test_process_verse_use_milestones_no_s_id(self): """ Test process_verse when called with use_milestones set to True, but the element supplied does not have and sID attribute @@ -272,7 +272,7 @@ class TestOsisImport(TestCase): # THEN: create_verse should not have been called self.assertFalse(self.mocked_create_verse.called) - def process_verse_use_milestones_no_tail_test(self): + def test_process_verse_use_milestones_no_tail(self): """ Test process_verse when called with use_milestones set to True, but the element supplied does not have a 'tail' """ @@ -290,7 +290,7 @@ class TestOsisImport(TestCase): # THEN: create_verse should not have been called self.assertFalse(self.mocked_create_verse.called) - def process_verse_use_milestones_success_test(self): + def test_process_verse_use_milestones_success(self): """ Test process_verse when called with use_milestones set to True, and the verse element successfully imports """ @@ -309,7 +309,7 @@ class TestOsisImport(TestCase): # THEN: create_verse should have been called with the test data self.mocked_create_verse.assert_called_once_with(1, 2, 4, 'Verse Text') - def process_verse_no_text_test(self): + def test_process_verse_no_text(self): """ Test process_verse when called with an empty verse element """ @@ -328,7 +328,7 @@ class TestOsisImport(TestCase): # THEN: create_verse should not have been called self.assertFalse(self.mocked_create_verse.called) - def process_verse_success_test(self): + def test_process_verse_success(self): """ Test process_verse when called with an element with text set """ @@ -347,7 +347,7 @@ class TestOsisImport(TestCase): # THEN: create_verse should have been called with the test data self.mocked_create_verse.assert_called_once_with(1, 2, 4, 'Verse Text') - def do_import_parse_xml_fails_test(self): + def test_do_import_parse_xml_fails(self): """ Test do_import when parse_xml fails (returns None) """ @@ -365,7 +365,7 @@ class TestOsisImport(TestCase): self.assertFalse(result) self.assertFalse(mocked_language_id.called) - def do_import_no_language_test(self): + def test_do_import_no_language(self): """ Test do_import when the user cancels the language selection dialog """ @@ -384,7 +384,7 @@ class TestOsisImport(TestCase): self.assertFalse(result) self.assertFalse(mocked_process_books.called) - def do_import_completes_test(self): + def test_do_import_completes(self): """ Test do_import when it completes successfully """ diff --git a/tests/functional/openlp_plugins/songs/test_ewimport.py b/tests/functional/openlp_plugins/songs/test_ewimport.py index ae9f873c5..e384319f0 100644 --- a/tests/functional/openlp_plugins/songs/test_ewimport.py +++ b/tests/functional/openlp_plugins/songs/test_ewimport.py @@ -239,160 +239,160 @@ class TestEasyWorshipSongImport(TestCase): self.assertIsNone(return_value, 'db_set_record_struct should return None') mocked_struct.Struct.assert_called_with('>50sHIB250s250s10sQ') - def test_get_field(self): + @patch('openlp.plugins.songs.lib.importers.easyworship.SongImport') + def test_get_field(self, MockSongImport): """ Test the :mod:`db_get_field` module """ # GIVEN: A mocked out SongImport class, a mocked out "manager", an encoding and some test data and known results - with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'): - mocked_manager = MagicMock() - importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) - importer.encoding = TEST_DATA_ENCODING - importer.fields = TEST_FIELDS - importer.field_descriptions = TEST_FIELD_DESCS - field_results = [(0, b'A Heart Like Thine'), (1, 100), (2, 102), (3, True), (6, None), (7, None)] + mocked_manager = MagicMock() + importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) + importer.encoding = TEST_DATA_ENCODING + importer.fields = TEST_FIELDS + importer.field_descriptions = TEST_FIELD_DESCS + field_results = [(0, b'A Heart Like Thine'), (1, 100), (2, 102), (3, True), (6, None), (7, None)] - # WHEN: Called with test data - for field_index, result in field_results: - return_value = importer.db_get_field(field_index) + # WHEN: Called with test data + for field_index, result in field_results: + return_value = importer.db_get_field(field_index) - # THEN: db_get_field should return the known results - self.assertEqual(return_value, result, - 'db_get_field should return "%s" when called with "%s"' % - (result, TEST_FIELDS[field_index])) + # THEN: db_get_field should return the known results + self.assertEqual(return_value, result, + 'db_get_field should return "%s" when called with "%s"' % + (result, TEST_FIELDS[field_index])) - def test_get_memo_field(self): + @patch('openlp.plugins.songs.lib.importers.easyworship.SongImport') + def test_get_memo_field(self, MockSongImport): """ Test the :mod:`db_get_field` module """ for test_results in GET_MEMO_FIELD_TEST_RESULTS: # GIVEN: A mocked out SongImport class, a mocked out "manager", a mocked out memo_file and an encoding - with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'): - mocked_manager = MagicMock() - mocked_memo_file = MagicMock() - importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) - importer.memo_file = mocked_memo_file - importer.encoding = TEST_DATA_ENCODING + mocked_manager = MagicMock() + mocked_memo_file = MagicMock() + importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) + importer.memo_file = mocked_memo_file + importer.encoding = TEST_DATA_ENCODING - # WHEN: Supplied with test fields and test field descriptions - importer.fields = TEST_FIELDS - importer.field_descriptions = TEST_FIELD_DESCS - field_index = test_results[0] - mocked_memo_file.read.return_value = test_results[1] - get_field_result = test_results[2]['return'] - get_field_read_calls = test_results[2]['read'] - get_field_seek_calls = test_results[2]['seek'] + # WHEN: Supplied with test fields and test field descriptions + importer.fields = TEST_FIELDS + importer.field_descriptions = TEST_FIELD_DESCS + field_index = test_results[0] + mocked_memo_file.read.return_value = test_results[1] + get_field_result = test_results[2]['return'] + get_field_read_calls = test_results[2]['read'] + get_field_seek_calls = test_results[2]['seek'] - # THEN: db_get_field should return the appropriate value with the appropriate mocked objects being - # called - self.assertEqual(importer.db_get_field(field_index), get_field_result) - for call in get_field_read_calls: - mocked_memo_file.read.assert_any_call(call) - for call in get_field_seek_calls: - if isinstance(call, int): - mocked_memo_file.seek.assert_any_call(call) - else: - mocked_memo_file.seek.assert_any_call(call[0], call[1]) + # THEN: db_get_field should return the appropriate value with the appropriate mocked objects being + # called + self.assertEqual(importer.db_get_field(field_index), get_field_result) + for call in get_field_read_calls: + mocked_memo_file.read.assert_any_call(call) + for call in get_field_seek_calls: + if isinstance(call, int): + mocked_memo_file.seek.assert_any_call(call) + else: + mocked_memo_file.seek.assert_any_call(call[0], call[1]) - def test_do_import_source_invalid(self): + @patch('openlp.plugins.songs.lib.importers.easyworship.SongImport') + @patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', side_effect=[True, False]) + def test_do_import_source_invalid(self, mocked_is_file, MockSongImport): """ Test the :mod:`do_import` module produces an error when Songs.MB not found. """ # GIVEN: A mocked out SongImport class, a mocked out "manager" - with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \ - patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', side_effect=[True, False]): - mocked_manager = MagicMock() - importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) - with patch.object(importer, 'log_error') as mocked_log_error: + mocked_manager = MagicMock() + importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) + with patch.object(importer, 'log_error') as mocked_log_error: - # WHEN: do_import is supplied with an import source (Songs.MB missing) - importer.import_source = 'Songs.DB' - importer.do_import() + # WHEN: do_import is supplied with an import source (Songs.MB missing) + importer.import_source = 'Songs.DB' + importer.do_import() - # THEN: do_import should have logged an error that the Songs.MB file could not be found. - mocked_log_error.assert_any_call(importer.import_source, - 'Could not find the "Songs.MB" file. It must be in the same folder as ' - 'the "Songs.DB" file.') + # THEN: do_import should have logged an error that the Songs.MB file could not be found. + mocked_log_error.assert_any_call(importer.import_source, + 'Could not find the "Songs.MB" file. It must be in the same folder as ' + 'the "Songs.DB" file.') - def test_do_import_database_validity(self): + @patch('openlp.plugins.songs.lib.importers.easyworship.SongImport') + @patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True) + @patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat') + def test_do_import_database_validity(self, mocked_stat, mocked_is_file, MockSongImport): """ Test the :mod:`do_import` module handles invalid database files correctly """ # GIVEN: A mocked out SongImport class, os.path and a mocked out "manager" - with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \ - patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \ - patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat') as mocked_stat: + mocked_manager = MagicMock() + importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) + importer.import_source = 'Songs.DB' - mocked_manager = MagicMock() - importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) - importer.import_source = 'Songs.DB' + # WHEN: DB file size is less than 0x800 + mocked_stat.return_value.st_size = 0x7FF - # WHEN: DB file size is less than 0x800 - mocked_stat.return_value.st_size = 0x7FF + # THEN: do_import should return None having called Path.stat() + self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800') + mocked_stat.assert_called_once_with() - # THEN: do_import should return None having called Path.stat() - self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800') - mocked_stat.assert_called_once_with() - - def test_do_import_memo_validty(self): + @patch('openlp.plugins.songs.lib.importers.easyworship.SongImport') + @patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True) + @patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat', **{'return_value.st_size': 0x800}) + @patch('openlp.plugins.songs.lib.importers.easyworship.Path.open') + @patch('openlp.plugins.songs.lib.importers.easyworship.struct') + def test_do_import_memo_validty(self, mocked_struct, mocked_open, mocked_stat, mocked_is_file, MockSongImport): """ Test the :mod:`do_import` module handles invalid memo files correctly """ # GIVEN: A mocked out SongImport class, a mocked out "manager" - with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \ - patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \ - patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat', **{'return_value.st_size': 0x800}), \ - patch('openlp.plugins.songs.lib.importers.easyworship.Path.open') as mocked_open, \ - patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct: - mocked_manager = MagicMock() - importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) - importer.import_source = 'Songs.DB' + mocked_manager = MagicMock() + importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) + importer.import_source = 'Songs.DB' - # WHEN: Unpacking first 35 bytes of Memo file - struct_unpack_return_values = [(0, 0x700, 2, 0, 0), (0, 0x800, 0, 0, 0), (0, 0x800, 5, 0, 0)] - mocked_struct.unpack.side_effect = struct_unpack_return_values + # WHEN: Unpacking first 35 bytes of Memo file + struct_unpack_return_values = [(0, 0x700, 2, 0, 0), (0, 0x800, 0, 0, 0), (0, 0x800, 5, 0, 0)] + mocked_struct.unpack.side_effect = struct_unpack_return_values - # THEN: do_import should return None having called closed the open files db and memo files. - for effect in struct_unpack_return_values: - self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800') - self.assertEqual(mocked_open().close.call_count, 2, - 'The open db and memo files should have been closed') - mocked_open().close.reset_mock() - self.assertIs(mocked_open().seek.called, False, 'db_file.seek should not have been called.') + # THEN: do_import should return None having called closed the open files db and memo files. + for effect in struct_unpack_return_values: + self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800') + self.assertEqual(mocked_open().close.call_count, 2, + 'The open db and memo files should have been closed') + mocked_open().close.reset_mock() + self.assertIs(mocked_open().seek.called, False, 'db_file.seek should not have been called.') - def test_code_page_to_encoding(self): + @patch('openlp.plugins.songs.lib.importers.easyworship.SongImport') + @patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True) + @patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat', **{'return_value.st_size': 0x800}) + @patch('openlp.plugins.songs.lib.importers.easyworship.Path.open') + @patch('builtins.open') + @patch('openlp.plugins.songs.lib.importers.easyworship.struct') + @patch('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding') + def test_code_page_to_encoding(self, mocked_retrieve_windows_encoding, mocked_struct, mocked_open, mocked_path_open, + mocked_path_stat, mocked_path_is_file, MockSongImport): """ Test the :mod:`do_import` converts the code page to the encoding correctly """ # GIVEN: A mocked out SongImport class, a mocked out "manager" - with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \ - patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \ - patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat', **{'return_value.st_size': 0x800}), \ - patch('openlp.plugins.songs.lib.importers.easyworship.Path.open'), \ - patch('builtins.open'), patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct, \ - patch('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding') as \ - mocked_retrieve_windows_encoding: - mocked_manager = MagicMock() - importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) - importer.import_source = 'Songs.DB' + mocked_manager = MagicMock() + importer = EasyWorshipSongImport(mocked_manager, file_paths=[]) + importer.import_source = 'Songs.DB' - # WHEN: Unpacking the code page - for code_page, encoding in CODE_PAGE_MAPPINGS: - struct_unpack_return_values = [(0, 0x800, 2, 0, 0), (code_page, )] - mocked_struct.unpack.side_effect = struct_unpack_return_values - mocked_retrieve_windows_encoding.return_value = False + # WHEN: Unpacking the code page + for code_page, encoding in CODE_PAGE_MAPPINGS: + struct_unpack_return_values = [(0, 0x800, 2, 0, 0), (code_page, )] + mocked_struct.unpack.side_effect = struct_unpack_return_values + mocked_retrieve_windows_encoding.return_value = False - # THEN: do_import should return None having called retrieve_windows_encoding with the correct encoding. - self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800') - mocked_retrieve_windows_encoding.assert_any_call(encoding) + # THEN: do_import should return None having called retrieve_windows_encoding with the correct encoding. + self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800') + mocked_retrieve_windows_encoding.assert_any_call(encoding) def test_db_file_import(self): - return self._test_db_file_import(os.path.join(TEST_PATH, 'Songs.DB')) + return self._run_db_file_import(os.path.join(TEST_PATH, 'Songs.DB')) def test_sqlite_db_file_import(self): - return self._test_db_file_import(os.path.join(TEST_PATH, 'ew6')) + return self._run_db_file_import(os.path.join(TEST_PATH, 'ew6')) - def _test_db_file_import(self, source_path): + def _run_db_file_import(self, source_path): """ Test the actual import of real song database files and check that the imported data is correct. """ @@ -448,62 +448,61 @@ class TestEasyWorshipSongImport(TestCase): 'verse_order_list for %s should be %s' % (title, verse_order_list)) mocked_finish.assert_called_with() - def test_ews_file_import(self): + @patch('openlp.plugins.songs.lib.importers.easyworship.SongImport') + @patch('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding') + def test_ews_file_import(self, mocked_retrieve_windows_encoding, MockSongImport): """ Test the actual import of song from ews file and check that the imported data is correct. """ # GIVEN: Test files with a mocked out SongImport class, a mocked out "manager", a mocked out "import_wizard", # and mocked out "author", "add_copyright", "add_verse", "finish" methods. - with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \ - patch('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding') \ - as mocked_retrieve_windows_encoding: - mocked_retrieve_windows_encoding.return_value = 'cp1252' - mocked_manager = MagicMock() - mocked_import_wizard = MagicMock() - mocked_add_author = MagicMock() - mocked_add_verse = MagicMock() - mocked_finish = MagicMock() - mocked_title = MagicMock() - mocked_finish.return_value = True - importer = EasyWorshipSongImportLogger(mocked_manager) - importer.import_wizard = mocked_import_wizard - importer.stop_import_flag = False - importer.add_author = mocked_add_author - importer.add_verse = mocked_add_verse - importer.title = mocked_title - importer.finish = mocked_finish - importer.topics = [] + mocked_retrieve_windows_encoding.return_value = 'cp1252' + mocked_manager = MagicMock() + mocked_import_wizard = MagicMock() + mocked_add_author = MagicMock() + mocked_add_verse = MagicMock() + mocked_finish = MagicMock() + mocked_title = MagicMock() + mocked_finish.return_value = True + importer = EasyWorshipSongImportLogger(mocked_manager) + importer.import_wizard = mocked_import_wizard + importer.stop_import_flag = False + importer.add_author = mocked_add_author + importer.add_verse = mocked_add_verse + importer.title = mocked_title + importer.finish = mocked_finish + importer.topics = [] - # WHEN: Importing ews file - importer.import_source = os.path.join(TEST_PATH, 'test1.ews') - import_result = importer.do_import() + # WHEN: Importing ews file + importer.import_source = os.path.join(TEST_PATH, 'test1.ews') + import_result = importer.do_import() - # THEN: do_import should return none, the song data should be as expected, and finish should have been - # called. - title = EWS_SONG_TEST_DATA['title'] - self.assertIsNone(import_result, 'do_import should return None when it has completed') - self.assertIn(title, importer._title_assignment_list, 'title for should be "%s"' % title) - mocked_add_author.assert_any_call(EWS_SONG_TEST_DATA['authors'][0]) - for verse_text, verse_tag in EWS_SONG_TEST_DATA['verses']: - mocked_add_verse.assert_any_call(verse_text, verse_tag) - mocked_finish.assert_called_with() + # THEN: do_import should return none, the song data should be as expected, and finish should have been + # called. + title = EWS_SONG_TEST_DATA['title'] + self.assertIsNone(import_result, 'do_import should return None when it has completed') + self.assertIn(title, importer._title_assignment_list, 'title for should be "%s"' % title) + mocked_add_author.assert_any_call(EWS_SONG_TEST_DATA['authors'][0]) + for verse_text, verse_tag in EWS_SONG_TEST_DATA['verses']: + mocked_add_verse.assert_any_call(verse_text, verse_tag) + mocked_finish.assert_called_with() - def test_import_rtf_unescaped_unicode(self): + @patch('openlp.plugins.songs.lib.importers.easyworship.SongImport') + def test_import_rtf_unescaped_unicode(self, MockSongImport): """ Test import of rtf without the expected escaping of unicode """ # GIVEN: A mocked out SongImport class, a mocked out "manager" and mocked out "author" method. - with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'): - mocked_manager = MagicMock() - mocked_add_author = MagicMock() - importer = EasyWorshipSongImportLogger(mocked_manager) - importer.add_author = mocked_add_author - importer.encoding = 'cp1252' + mocked_manager = MagicMock() + mocked_add_author = MagicMock() + importer = EasyWorshipSongImportLogger(mocked_manager) + importer.add_author = mocked_add_author + importer.encoding = 'cp1252' - # WHEN: running set_song_import_object on a verse string without the needed escaping - importer.set_song_import_object('Test Author', b'Det som var fr\x86n begynnelsen') + # WHEN: running set_song_import_object on a verse string without the needed escaping + importer.set_song_import_object('Test Author', b'Det som var fr\x86n begynnelsen') - # THEN: The import should fail - self.assertEquals(importer.entry_error_log, 'Unexpected data formatting.', 'Import should fail') + # THEN: The import should fail + self.assertEquals(importer.entry_error_log, 'Unexpected data formatting.', 'Import should fail') diff --git a/tests/functional/openlp_plugins/songs/test_songselect.py b/tests/functional/openlp_plugins/songs/test_songselect.py index 191bc4e5a..fa2b2931d 100644 --- a/tests/functional/openlp_plugins/songs/test_songselect.py +++ b/tests/functional/openlp_plugins/songs/test_songselect.py @@ -135,7 +135,7 @@ class TestSongSelectImport(TestCase, TestMixin): @patch('openlp.plugins.songs.lib.songselect.build_opener') @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') - def login_url_from_form_test(self, MockedBeautifulSoup, mocked_build_opener): + def test_login_url_from_form(self, MockedBeautifulSoup, mocked_build_opener): """ Test that the login URL is from the form """ From 7697febb2a1f42977bf48fd7baf302c3b4b491b9 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Thu, 2 Nov 2017 21:46:02 +0000 Subject: [PATCH 17/96] Fixes Bug #1661416 Fixes: https://launchpad.net/bugs/1661416 --- openlp/plugins/songusage/forms/songusagedetailform.py | 10 ++++++++-- openlp/plugins/songusage/songusageplugin.py | 9 +++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/openlp/plugins/songusage/forms/songusagedetailform.py b/openlp/plugins/songusage/forms/songusagedetailform.py index 147e26d10..dc3393c8d 100644 --- a/openlp/plugins/songusage/forms/songusagedetailform.py +++ b/openlp/plugins/songusage/forms/songusagedetailform.py @@ -54,8 +54,14 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP """ We need to set up the screen """ - self.from_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/from date')) - self.to_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/to date')) + to_date = Settings().value(self.plugin.settings_section + '/to date') + if not (isinstance(to_date, QtCore.QDate) and to_date.isValid()): + to_date = QtCore.QDate.currentDate() + from_date = Settings().value(self.plugin.settings_section + '/from date') + if not (isinstance(from_date, QtCore.QDate) and from_date.isValid()): + from_date = to_date.addYears(-1) + self.from_date_calendar.setSelectedDate(from_date) + self.to_date_calendar.setSelectedDate(to_date) self.report_path_edit.path = Settings().value(self.plugin.settings_section + '/last directory export') def on_report_path_edit_path_changed(self, file_path): diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py index a49661143..79f21a8cf 100644 --- a/openlp/plugins/songusage/songusageplugin.py +++ b/openlp/plugins/songusage/songusageplugin.py @@ -38,10 +38,7 @@ from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem log = logging.getLogger(__name__) -YEAR = QtCore.QDate().currentDate().year() -if QtCore.QDate().currentDate().month() < 9: - YEAR -= 1 - +TODAY = QtCore.QDate.currentDate() __default_settings__ = { 'songusage/db type': 'sqlite', @@ -50,8 +47,8 @@ __default_settings__ = { 'songusage/db hostname': '', 'songusage/db database': '', 'songusage/active': False, - 'songusage/to date': QtCore.QDate(YEAR, 8, 31), - 'songusage/from date': QtCore.QDate(YEAR - 1, 9, 1), + 'songusage/to date': TODAY, + 'songusage/from date': TODAY.addYears(-1), 'songusage/last directory export': None } From 94dd107abe38915412ac15fb26467cb9af03e6cd Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Fri, 3 Nov 2017 20:55:41 +0000 Subject: [PATCH 18/96] Changed merged errors Fixes: https://launchpad.net/bugs/1400415 --- openlp/core/common/__init__.py | 2 +- openlp/core/common/httputils.py | 8 ++++---- openlp/core/common/path.py | 2 +- openlp/core/lib/__init__.py | 2 +- openlp/core/ui/exceptionform.py | 2 +- openlp/core/ui/mainwindow.py | 2 +- openlp/core/ui/servicemanager.py | 8 ++++---- openlp/core/ui/thememanager.py | 6 +++--- openlp/core/version.py | 4 ++-- openlp/plugins/presentations/lib/pptviewcontroller.py | 2 +- tests/functional/openlp_core/common/test_httputils.py | 2 +- tests/functional/openlp_core/common/test_path.py | 8 ++++---- tests/functional/openlp_core/lib/test_lib.py | 2 +- tests/functional/openlp_core/ui/test_first_time.py | 2 +- .../presentations/test_presentationcontroller.py | 2 +- tests/utils/__init__.py | 2 +- 16 files changed, 28 insertions(+), 28 deletions(-) diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index b7e579803..d280cbde2 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -343,7 +343,7 @@ def delete_file(file_path): if file_path.exists(): file_path.unlink() return True - except (IOError, OSError): + except OSError: log.exception('Unable to delete file {file_path}'.format(file_path=file_path)) return False diff --git a/openlp/core/common/httputils.py b/openlp/core/common/httputils.py index 11ae7b563..21b778b80 100644 --- a/openlp/core/common/httputils.py +++ b/openlp/core/common/httputils.py @@ -97,8 +97,8 @@ def get_web_page(url, headers=None, update_openlp=False, proxies=None): response = requests.get(url, headers=headers, proxies=proxies, timeout=float(CONNECTION_TIMEOUT)) log.debug('Downloaded page {url}'.format(url=response.url)) break - except IOError: - # For now, catch IOError. All requests errors inherit from IOError + except OSError: + # For now, catch OSError. All requests errors inherit from OSError log.exception('Unable to connect to {url}'.format(url=url)) response = None if retries >= CONNECTION_RETRIES: @@ -127,7 +127,7 @@ def get_url_file_size(url): try: response = requests.head(url, timeout=float(CONNECTION_TIMEOUT), allow_redirects=True) return int(response.headers['Content-Length']) - except IOError: + except OSError: if retries > CONNECTION_RETRIES: raise ConnectionError('Unable to download {url}'.format(url=url)) else: @@ -173,7 +173,7 @@ def url_get_file(callback, url, file_path, sha256=None): file_path.unlink() return False break - except IOError: + except OSError: trace_error_handler(log) if retries > CONNECTION_RETRIES: if file_path.exists(): diff --git a/openlp/core/common/path.py b/openlp/core/common/path.py index 19e17470b..6b89acfb5 100644 --- a/openlp/core/common/path.py +++ b/openlp/core/common/path.py @@ -233,7 +233,7 @@ def create_paths(*paths, **kwargs): try: if not path.exists(): path.mkdir(parents=True) - except IOError: + except OSError: if not kwargs.get('do_not_log', False): log.exception('failed to check if directory exists or create directory') diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index f78065774..04d3b9e5e 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -103,7 +103,7 @@ def get_text_file_string(text_file_path): # no BOM was found file_handle.seek(0) content = file_handle.read() - except (IOError, UnicodeError): + except (OSError, UnicodeError): log.exception('Failed to open text file {text}'.format(text=text_file_path)) return content diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index 70fe2c416..45124dac8 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.py @@ -155,7 +155,7 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties): try: with file_path.open('w') as report_file: report_file.write(report_text) - except IOError: + except OSError: log.exception('Failed to write crash report') def on_send_report_button_clicked(self): diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 33cdc3301..c0e704afb 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -1367,7 +1367,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): '- Please wait for copy to finish').format(path=self.new_data_path)) dir_util.copy_tree(str(old_data_path), str(self.new_data_path)) log.info('Copy successful') - except (IOError, os.error, DistutilsFileError) as why: + except (OSError, DistutilsFileError) as why: self.application.set_normal_cursor() log.exception('Data copy failed {err}'.format(err=str(why))) err_text = translate('OpenLP.MainWindow', diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 5dda18044..71c6a1147 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -609,7 +609,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi if not os.path.exists(save_file): shutil.copy(audio_from, save_file) zip_file.write(audio_from, audio_to) - except IOError: + 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.')) @@ -670,7 +670,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi 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 IOError: + 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.')) @@ -802,11 +802,11 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi else: critical_error_message_box(message=translate('OpenLP.ServiceManager', 'File is not a valid service.')) self.log_error('File contains no service data') - except (IOError, NameError): + 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: + 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'), diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 5b4c5cbb9..1b39e5fec 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -604,7 +604,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R else: with full_name.open('wb') as out_file: out_file.write(theme_zip.read(zipped_file)) - except (IOError, zipfile.BadZipfile): + except (OSError, zipfile.BadZipFile): self.log_exception('Importing theme from zip failed {name}'.format(name=file_path)) raise ValidationError except ValidationError: @@ -667,7 +667,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R theme_path = theme_dir / '{file_name}.json'.format(file_name=name) try: theme_path.write_text(theme_pretty) - except IOError: + except OSError: self.log_exception('Saving theme to file failed') if image_source_path and image_destination_path: if self.old_background_image_path and image_destination_path != self.old_background_image_path: @@ -675,7 +675,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R if image_source_path != image_destination_path: try: copyfile(image_source_path, image_destination_path) - except IOError: + except OSError: self.log_exception('Failed to save theme image') self.generate_and_save_image(name, theme) diff --git a/openlp/core/version.py b/openlp/core/version.py index 6d038a3d9..314c4865f 100644 --- a/openlp/core/version.py +++ b/openlp/core/version.py @@ -96,7 +96,7 @@ class VersionWorker(QtCore.QObject): remote_version = response.text log.debug('New version found: %s', remote_version) break - except IOError: + except OSError: log.exception('Unable to connect to OpenLP server to download version file') retries += 1 else: @@ -182,7 +182,7 @@ def get_version(): try: version_file = open(file_path, 'r') full_version = str(version_file.read()).rstrip() - except IOError: + except OSError: log.exception('Error in version file.') full_version = '0.0.0-bzr000' finally: diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 0a403df37..ddabe07e1 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -70,7 +70,7 @@ class PptviewController(PresentationController): try: self.start_process() return self.process.CheckInstalled() - except WindowsError: + except OSError: return False def start_process(self): diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py index e620fa04e..5e7a396b2 100644 --- a/tests/functional/openlp_core/common/test_httputils.py +++ b/tests/functional/openlp_core/common/test_httputils.py @@ -233,7 +233,7 @@ class TestHttpUtils(TestCase, TestMixin): Test socket timeout gets caught """ # GIVEN: Mocked urlopen to fake a network disconnect in the middle of a download - mocked_requests.get.side_effect = IOError + mocked_requests.get.side_effect = OSError # WHEN: Attempt to retrieve a file url_get_file(MagicMock(), url='http://localhost/test', file_path=Path(self.tempfile)) diff --git a/tests/functional/openlp_core/common/test_path.py b/tests/functional/openlp_core/common/test_path.py index 4b30bd2cb..2ec89771b 100644 --- a/tests/functional/openlp_core/common/test_path.py +++ b/tests/functional/openlp_core/common/test_path.py @@ -371,13 +371,13 @@ class TestPath(TestCase): @patch('openlp.core.common.path.log') def test_create_paths_dir_io_error(self, mocked_logger): """ - Test the create_paths() when an IOError is raised + Test the create_paths() when an OSError is raised """ # GIVEN: A `Path` to check with patched out mkdir and exists methods mocked_path = MagicMock() - mocked_path.exists.side_effect = IOError('Cannot make directory') + mocked_path.exists.side_effect = OSError('Cannot make directory') - # WHEN: An IOError is raised when checking the if the path exists. + # WHEN: An OSError is raised when checking the if the path exists. create_paths(mocked_path) # THEN: The Error should have been logged @@ -385,7 +385,7 @@ class TestPath(TestCase): def test_create_paths_dir_value_error(self): """ - Test the create_paths() when an error other than IOError is raised + Test the create_paths() when an error other than OSError is raised """ # GIVEN: A `Path` to check with patched out mkdir and exists methods mocked_path = MagicMock() diff --git a/tests/functional/openlp_core/lib/test_lib.py b/tests/functional/openlp_core/lib/test_lib.py index f2bfaf79c..1352b5da5 100644 --- a/tests/functional/openlp_core/lib/test_lib.py +++ b/tests/functional/openlp_core/lib/test_lib.py @@ -168,7 +168,7 @@ class TestLib(TestCase): patch.object(Path, 'open'): file_path = Path('testfile.txt') file_path.is_file.return_value = True - file_path.open.side_effect = IOError() + file_path.open.side_effect = OSError() # WHEN: get_text_file_string is called result = get_text_file_string(file_path) diff --git a/tests/functional/openlp_core/ui/test_first_time.py b/tests/functional/openlp_core/ui/test_first_time.py index eb9464375..2be5e1ad6 100644 --- a/tests/functional/openlp_core/ui/test_first_time.py +++ b/tests/functional/openlp_core/ui/test_first_time.py @@ -40,7 +40,7 @@ class TestFirstTimeWizard(TestMixin, TestCase): Test get_web_page will attempt CONNECTION_RETRIES+1 connections - bug 1409031 """ # GIVEN: Initial settings and mocks - mocked_requests.get.side_effect = IOError('Unable to connect') + mocked_requests.get.side_effect = OSError('Unable to connect') # WHEN: A webpage is requested try: diff --git a/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py b/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py index 30ab11561..a921ef81e 100644 --- a/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py +++ b/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py @@ -144,7 +144,7 @@ class TestPresentationController(TestCase): # GIVEN: A mocked open, get_thumbnail_folder and exists with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text') as mocked_read_text, \ patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder: - mocked_read_text.side_effect = IOError() + mocked_read_text.side_effect = OSError() mocked_get_thumbnail_folder.return_value = Path('test') # WHEN: calling get_titles_and_notes diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index fd5aeccfd..dd4d78354 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -36,7 +36,7 @@ def convert_file_service_item(test_path, name, row=0): try: items = json.load(open_file) first_line = items[row] - except IOError: + except OSError: first_line = '' finally: open_file.close() From f307568cbbff54e390ed923dd277319634aa6738 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Fri, 3 Nov 2017 22:52:24 +0000 Subject: [PATCH 19/96] Misc test fixes --- openlp/core/common/actions.py | 1 + openlp/core/ui/servicemanager.py | 2 +- tests/functional/openlp_core/common/test_actions.py | 1 + tests/functional/openlp_core/common/test_i18n.py | 2 +- tests/interfaces/openlp_core/ui/test_projectormanager.py | 2 +- tests/interfaces/openlp_core/ui/test_projectorsourceform.py | 2 +- tests/interfaces/openlp_core/ui/test_thememanager.py | 2 +- 7 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openlp/core/common/actions.py b/openlp/core/common/actions.py index a5b417017..739aba4e3 100644 --- a/openlp/core/common/actions.py +++ b/openlp/core/common/actions.py @@ -366,6 +366,7 @@ class ActionList(object): continue if existing_action in affected_actions: return False + print(existing_action.shortcutContext()) if existing_action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]: return False elif action in self.get_all_child_objects(existing_action.parent()): diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index d13d7879d..f5a71934d 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -719,7 +719,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi 'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)')) else: file_path, filter_used = FileDialog.getSaveFileName( - self.main_window, UiStrings().SaveService, file_path, + self.main_window, UiStrings().SaveService, default_file_path, translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz);;')) if not file_path: return False diff --git a/tests/functional/openlp_core/common/test_actions.py b/tests/functional/openlp_core/common/test_actions.py index bd59d6577..57905654d 100644 --- a/tests/functional/openlp_core/common/test_actions.py +++ b/tests/functional/openlp_core/common/test_actions.py @@ -153,6 +153,7 @@ class TestActionList(TestCase, TestMixin): """ Prepare the tests """ + self.setup_application() self.action_list = ActionList.get_instance() self.build_settings() self.settings = Settings() diff --git a/tests/functional/openlp_core/common/test_i18n.py b/tests/functional/openlp_core/common/test_i18n.py index d6828fb6f..bffb819dc 100644 --- a/tests/functional/openlp_core/common/test_i18n.py +++ b/tests/functional/openlp_core/common/test_i18n.py @@ -155,7 +155,7 @@ def test_check_same_instance(): assert first_instance is second_instance, 'Two UiStrings objects should be the same instance' -def test_translate(self): +def test_translate(): """ Test the translate() function """ diff --git a/tests/interfaces/openlp_core/ui/test_projectormanager.py b/tests/interfaces/openlp_core/ui/test_projectormanager.py index ff95c4276..9184035b8 100644 --- a/tests/interfaces/openlp_core/ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core/ui/test_projectormanager.py @@ -42,8 +42,8 @@ class TestProjectorManager(TestCase, TestMixin): """ Create the UI and setup necessary options """ - self.build_settings() self.setup_application() + self.build_settings() Registry.create() with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url: if os.path.exists(TEST_DB): diff --git a/tests/interfaces/openlp_core/ui/test_projectorsourceform.py b/tests/interfaces/openlp_core/ui/test_projectorsourceform.py index 4b9e2f402..58094a17c 100644 --- a/tests/interfaces/openlp_core/ui/test_projectorsourceform.py +++ b/tests/interfaces/openlp_core/ui/test_projectorsourceform.py @@ -64,8 +64,8 @@ class ProjectorSourceFormTest(TestCase, TestMixin): Set up anything necessary for all tests """ mocked_init_url.return_value = 'sqlite:///{}'.format(TEST_DB) - self.build_settings() self.setup_application() + self.build_settings() Registry.create() # Do not try to recreate if we've already been created from a previous test if not hasattr(self, 'projectordb'): diff --git a/tests/interfaces/openlp_core/ui/test_thememanager.py b/tests/interfaces/openlp_core/ui/test_thememanager.py index 7f3927cf5..0808b12d0 100644 --- a/tests/interfaces/openlp_core/ui/test_thememanager.py +++ b/tests/interfaces/openlp_core/ui/test_thememanager.py @@ -41,8 +41,8 @@ class TestThemeManager(TestCase, TestMixin): """ Create the UI """ - self.build_settings() self.setup_application() + self.build_settings() Registry.create() self.theme_manager = ThemeManager() From 3140ea434d43a7ca8b4be11b26bafafc4ebdfdb8 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Wed, 8 Nov 2017 22:06:48 -0700 Subject: [PATCH 20/96] Add support for a multi to single setting migration --- openlp/core/common/settings.py | 60 ++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index a6bc549f1..6726624ef 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -62,6 +62,30 @@ def media_players_conv(string): return string +def upgrade_monitor(number, x_position, y_position, height, width, can_override, can_display_on_monitor): + """ + Upgrade them monitor setting from a few single entries to a composite JSON entry + + :param int number: The old monitor number + :param int x_position: The X position + :param int y_position: The Y position + :param bool can_override: Are the screen positions overridden + :param bool can_display_on_monitor: Can OpenLP display on the monitor + :returns dict: Dictionary with the new value + """ + return { + number: { + 'displayGeometry': { + 'x': x_position, + 'y': y_position, + 'height': height, + 'width': width + } + }, + 'canDisplayOnMonitor': can_display_on_monitor + } + + class Settings(QtCore.QSettings): """ Class to wrap QSettings. @@ -255,7 +279,9 @@ class Settings(QtCore.QSettings): ('core/logo file', 'core/logo file', [(str_to_path, None)]), ('presentations/last directory', 'presentations/last directory', [(str_to_path, None)]), ('images/last directory', 'images/last directory', [(str_to_path, None)]), - ('media/last directory', 'media/last directory', [(str_to_path, None)]) + ('media/last directory', 'media/last directory', [(str_to_path, None)]), + (['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override', + 'core/display on monitor'], 'core/monitors', [(upgrade_monitor, [1, 0, 0, None, None, False, False])]) ] @staticmethod @@ -464,31 +490,37 @@ class Settings(QtCore.QSettings): for version in range(current_version, __version__): version += 1 upgrade_list = getattr(self, '__setting_upgrade_{version}__'.format(version=version)) - for old_key, new_key, rules in upgrade_list: + for old_keys, new_key, rules in upgrade_list: # Once removed we don't have to do this again. - Can be removed once fully switched to the versioning # system. - if not self.contains(old_key): + if not isinstance(old_keys, (tuple, list)): + old_keys = [old_keys] + if not any([self.contains(old_key) for old_key in old_keys]): + log.warning('One of {} does not exist, skipping upgrade'.format(old_keys)) continue if new_key: # Get the value of the old_key. - old_value = super(Settings, self).value(old_key) + old_values = [super(Settings, self).value(old_key) for old_key in old_keys] # When we want to convert the value, we have to figure out the default value (because we cannot get # the default value from the central settings dict. if rules: - default_value = rules[0][1] - old_value = self._convert_value(old_value, default_value) + default_values = rules[0][1] + if not isinstance(default_values, (list, tuple)): + default_values = [default_values] + old_values = [self._convert_value(old_value, default_value) + for old_value, default_value in zip(old_values, default_values)] # Iterate over our rules and check what the old_value should be "converted" to. - for new, old in rules: + new_value = None + for new_rule, old_rule in rules: # If the value matches with the condition (rule), then use the provided value. This is used to # convert values. E. g. an old value 1 results in True, and 0 in False. - if callable(new): - old_value = new(old_value) - elif old == old_value: - old_value = new + if callable(new_rule): + new_value = new_rule(*old_values) + elif old_rule in old_values: + new_value = new_rule break - self.setValue(new_key, old_value) - if new_key != old_key: - self.remove(old_key) + self.setValue(new_key, new_value) + [self.remove(old_key) for old_key in old_keys if old_key != new_key] self.setValue('settings/version', version) def value(self, key): From 83eaad48ade4e3779dd199deeb856802bcad9768 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Thu, 9 Nov 2017 19:11:37 +0000 Subject: [PATCH 21/96] Fix action test failure by removing un used actions --- openlp/core/common/actions.py | 1 - openlp/core/ui/servicemanager.py | 16 ++-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/openlp/core/common/actions.py b/openlp/core/common/actions.py index 739aba4e3..a5b417017 100644 --- a/openlp/core/common/actions.py +++ b/openlp/core/common/actions.py @@ -366,7 +366,6 @@ class ActionList(object): continue if existing_action in affected_actions: return False - print(existing_action.shortcutContext()) if existing_action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]: return False elif action in self.get_all_child_objects(existing_action.parent()): diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index f5a71934d..f3b107a50 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -193,18 +193,6 @@ class Ui_ServiceManager(object): text=translate('OpenLP.ServiceManager', 'Move to &bottom'), icon=':/services/service_bottom.png', tooltip=translate('OpenLP.ServiceManager', 'Move item to the end of the service.'), can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_end) - self.down_action = self.order_toolbar.add_toolbar_action( - 'down', - text=translate('OpenLP.ServiceManager', 'Move &down'), can_shortcuts=True, - tooltip=translate('OpenLP.ServiceManager', 'Moves the selection down the window.'), visible=False, - triggers=self.on_move_selection_down) - action_list.add_action(self.down_action) - self.up_action = self.order_toolbar.add_toolbar_action( - 'up', - text=translate('OpenLP.ServiceManager', 'Move up'), can_shortcuts=True, - tooltip=translate('OpenLP.ServiceManager', 'Moves the selection up the window.'), visible=False, - triggers=self.on_move_selection_up) - action_list.add_action(self.up_action) self.order_toolbar.addSeparator() self.delete_action = self.order_toolbar.add_toolbar_action( 'delete', can_shortcuts=True, @@ -300,8 +288,8 @@ class Ui_ServiceManager(object): self.theme_menu = QtWidgets.QMenu(translate('OpenLP.ServiceManager', '&Change Item Theme')) self.menu.addMenu(self.theme_menu) self.service_manager_list.addActions([self.move_down_action, self.move_up_action, self.make_live_action, - self.move_top_action, self.move_bottom_action, self.up_action, - self.down_action, self.expand_action, self.collapse_action]) + self.move_top_action, self.move_bottom_action, self.expand_action, + self.collapse_action]) Registry().register_function('theme_update_list', self.update_theme_list) Registry().register_function('config_screen_changed', self.regenerate_service_items) Registry().register_function('theme_update_global', self.theme_change) From 6d66cadb0ae683ceafa3329d4b1682d3818e161d Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Thu, 9 Nov 2017 19:15:31 +0000 Subject: [PATCH 22/96] head --- openlp/core/widgets/edits.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/core/widgets/edits.py b/openlp/core/widgets/edits.py index e64125833..89310a713 100644 --- a/openlp/core/widgets/edits.py +++ b/openlp/core/widgets/edits.py @@ -242,7 +242,7 @@ class PathEdit(QtWidgets.QWidget): self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished) self.update_button_tool_tips() - @property + @QtCore.pyqtProperty('QVariant') def path(self): """ A property getter method to return the selected path. @@ -350,7 +350,7 @@ class PathEdit(QtWidgets.QWidget): :rtype: None """ if self._path != path: - self.path = path + self._path = path self.pathChanged.emit(path) From 1a0a828ad3bd7d68a61fc952f819a1cac3175a27 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 9 Nov 2017 19:25:26 +0000 Subject: [PATCH 23/96] fix errors --- openlp/core/api/endpoint/remote.py | 2 +- openlp/core/common/registry.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openlp/core/api/endpoint/remote.py b/openlp/core/api/endpoint/remote.py index 4741ada15..3a408f74e 100644 --- a/openlp/core/api/endpoint/remote.py +++ b/openlp/core/api/endpoint/remote.py @@ -27,7 +27,7 @@ from openlp.core.api.endpoint.core import TRANSLATED_STRINGS log = logging.getLogger(__name__) -remote_endpoint = Endpoint('remote', template_dir='remotes', static_dir='remotes') +remote_endpoint = Endpoint('remote', template_dir='remotes') @remote_endpoint.route('{view}') diff --git a/openlp/core/common/registry.py b/openlp/core/common/registry.py index 252274d4d..c3f9011ad 100644 --- a/openlp/core/common/registry.py +++ b/openlp/core/common/registry.py @@ -151,8 +151,9 @@ class Registry(object): trace_error_handler(log) log.exception('Exception for function {function}'.format(function=function)) else: - trace_error_handler(log) - log.exception('Event {event} called but not registered'.format(event=event)) + if self._logger.getEffectiveLevel() == logging.DEBUG: + trace_error_handler(log) + log.exception('Event {event} called but not registered'.format(event=event)) return results def get_flag(self, key): From 0d1bbe3417029c097ec71d057869cbec701b56c6 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 9 Nov 2017 19:42:30 +0000 Subject: [PATCH 24/96] add preview to live --- openlp/core/ui/slidecontroller.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 80eb155e0..c6db1f1de 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -236,6 +236,9 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): self.hide_menu.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) self.hide_menu.setMenu(QtWidgets.QMenu(translate('OpenLP.SlideController', 'Hide'), self.toolbar)) self.toolbar.add_toolbar_widget(self.hide_menu) + self.toolbar.add_toolbar_action('goPreview', icon=':/general/general_live.png', + tooltip=translate('OpenLP.SlideController', 'Move to preview.'), + triggers=self.on_go_preview) # The order of the blank to modes in Shortcuts list comes from here. self.desktop_screen_enable = create_action(self, 'desktopScreenEnable', text=translate('OpenLP.SlideController', 'Show Desktop'), @@ -1420,6 +1423,18 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): self.live_controller.add_service_manager_item(self.service_item, row) self.live_controller.preview_widget.setFocus() + def on_go_preview(self, field=None): + """ + If live copy slide item to preview controller from live Controller + """ + row = self.preview_widget.current_slide_number() + if -1 < row < self.preview_widget.slide_count(): + if self.service_item.from_service: + self.service_manager.preview_live(self.service_item.unique_identifier, row) + else: + self.preview_controller.add_service_manager_item(self.service_item, row) + self.preview_controller.preview_widget.setFocus() + def on_media_start(self, item): """ Respond to the arrival of a media service item From 7a345d577013e7661105acb24326bb5042df3d67 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 9 Nov 2017 19:55:01 +0000 Subject: [PATCH 25/96] fix code --- openlp/core/ui/slidecontroller.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index c6db1f1de..5b9872082 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -1429,10 +1429,7 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): """ row = self.preview_widget.current_slide_number() if -1 < row < self.preview_widget.slide_count(): - if self.service_item.from_service: - self.service_manager.preview_live(self.service_item.unique_identifier, row) - else: - self.preview_controller.add_service_manager_item(self.service_item, row) + self.preview_controller.add_service_manager_item(self.service_item, row) self.preview_controller.preview_widget.setFocus() def on_media_start(self, item): From 536a187fb9864dd9785eef8947d27959a1cc97c5 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Thu, 9 Nov 2017 20:39:17 +0000 Subject: [PATCH 26/96] PEP8 --- openlp/core/lib/__init__.py | 1 + tests/functional/openlp_core/widgets/test_views.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 04d3b9e5e..9bab13b71 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -38,6 +38,7 @@ log = logging.getLogger(__name__ + '.__init__') SLIMCHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\' + class ServiceItemContext(object): """ The context in which a Service Item is being generated diff --git a/tests/functional/openlp_core/widgets/test_views.py b/tests/functional/openlp_core/widgets/test_views.py index d931a5ef5..0fa028f11 100644 --- a/tests/functional/openlp_core/widgets/test_views.py +++ b/tests/functional/openlp_core/widgets/test_views.py @@ -627,4 +627,3 @@ class TestTreeWidgetWithDnD(TestCase): assert widget.allow_internal_dnd is False assert widget.indentation() == 0 assert widget.isAnimated() is True - From e0341ab3eb9590cf2ff294169ecfac94d1c2fff0 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Thu, 9 Nov 2017 21:24:38 +0000 Subject: [PATCH 27/96] fix tests --- openlp/core/common/registry.py | 2 +- .../openlp_core/ui/test_slidecontroller.py | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/openlp/core/common/registry.py b/openlp/core/common/registry.py index c3f9011ad..4c9e92108 100644 --- a/openlp/core/common/registry.py +++ b/openlp/core/common/registry.py @@ -151,7 +151,7 @@ class Registry(object): trace_error_handler(log) log.exception('Exception for function {function}'.format(function=function)) else: - if self._logger.getEffectiveLevel() == logging.DEBUG: + if log.getEffectiveLevel() == logging.DEBUG: trace_error_handler(log) log.exception('Event {event} called but not registered'.format(event=event)) return results diff --git a/tests/functional/openlp_core/ui/test_slidecontroller.py b/tests/functional/openlp_core/ui/test_slidecontroller.py index b5f0d99ee..bc13f6db8 100644 --- a/tests/functional/openlp_core/ui/test_slidecontroller.py +++ b/tests/functional/openlp_core/ui/test_slidecontroller.py @@ -208,6 +208,33 @@ class TestSlideController(TestCase): 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.display = 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 From a408e3d74e3a069db96d9595332ff0a005370af0 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Thu, 9 Nov 2017 21:11:03 -0700 Subject: [PATCH 28/96] Fix some linting issues, and fix the test for newer versions of PyLint --- openlp/core/common/mixins.py | 14 ++++++++++++++ tests/utils/test_pylint.py | 14 +++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/openlp/core/common/mixins.py b/openlp/core/common/mixins.py index 1bc6907a0..a07940e10 100644 --- a/openlp/core/common/mixins.py +++ b/openlp/core/common/mixins.py @@ -101,6 +101,20 @@ class RegistryProperties(object): """ This adds registry components to classes to use at run time. """ + _application = None + _plugin_manager = None + _image_manager = None + _media_controller = None + _service_manager = None + _preview_controller = None + _live_controller = None + _main_window = None + _renderer = None + _theme_manager = None + _settings_form = None + _alerts_manager = None + _projector_manager = None + @property def application(self): """ diff --git a/tests/utils/test_pylint.py b/tests/utils/test_pylint.py index 128c0741b..50ca64db6 100644 --- a/tests/utils/test_pylint.py +++ b/tests/utils/test_pylint.py @@ -58,17 +58,21 @@ class TestPylint(TestCase): # GIVEN: Some checks to disable and enable, and the pylint script disabled_checks = 'import-error,no-member' enabled_checks = 'missing-format-argument-key,unused-format-string-argument,bad-format-string' - if is_win() or 'arch' in platform.dist()[0].lower(): - pylint_script = 'pylint' - else: - pylint_script = 'pylint3' + pylint_kwargs = { + 'return_std': True + } + if version < '1.7.0': + if is_win() or 'arch' in platform.dist()[0].lower(): + pylint_kwargs.update({'script': 'pylint'}) + else: + pylint_kwargs.update({'script': 'pylint3'}) # WHEN: Running pylint (pylint_stdout, pylint_stderr) = \ lint.py_run('openlp --errors-only --disable={disabled} --enable={enabled} ' '--reports=no --output-format=parseable'.format(disabled=disabled_checks, enabled=enabled_checks), - return_std=True, script=pylint_script) + **pylint_kwargs) stdout = pylint_stdout.read() stderr = pylint_stderr.read() filtered_stdout = self._filter_tolerated_errors(stdout) From 3d4ed035e9cef0730c39787dedc9fc3cbc72ce18 Mon Sep 17 00:00:00 2001 From: Ken Roberts <alisonken1@gmail.com> Date: Fri, 10 Nov 2017 03:59:38 -0800 Subject: [PATCH 29/96] PJLink2-L updates --- openlp/core/lib/__init__.py | 3 - .../{lib/projector => projectors}/__init__.py | 12 ++++ .../projector => projectors}/constants.py | 0 .../core/{lib/projector => projectors}/db.py | 4 +- .../{ui/projector => projectors}/editform.py | 4 +- .../{ui/projector => projectors}/manager.py | 15 +++-- .../{lib/projector => projectors}/pjlink.py | 6 +- .../sourceselectform.py | 4 +- .../core/{ui/projector => projectors}/tab.py | 2 +- .../{lib/projector => projectors}/upgrade.py | 0 openlp/core/ui/__init__.py | 7 ++- openlp/core/ui/mainwindow.py | 2 +- openlp/core/ui/settingsform.py | 2 +- .../openlp_core/projectors}/__init__.py | 2 +- .../test_projector_constants.py | 4 +- .../{lib => projectors}/test_projector_db.py | 10 ++-- .../test_projector_pjlink_base.py | 7 +-- .../test_projector_pjlink_cmd_routing.py | 29 +++++----- .../test_projector_pjlink_commands.py | 57 +++++++++---------- .../openlp_core/ui/test_projectoreditform.py | 11 ++-- .../openlp_core/ui/test_projectormanager.py | 5 +- .../ui/test_projectorsourceform.py | 8 +-- 22 files changed, 97 insertions(+), 97 deletions(-) rename openlp/core/{lib/projector => projectors}/__init__.py (78%) rename openlp/core/{lib/projector => projectors}/constants.py (100%) rename openlp/core/{lib/projector => projectors}/db.py (99%) rename openlp/core/{ui/projector => projectors}/editform.py (99%) rename openlp/core/{ui/projector => projectors}/manager.py (98%) rename openlp/core/{lib/projector => projectors}/pjlink.py (99%) rename openlp/core/{ui/projector => projectors}/sourceselectform.py (99%) rename openlp/core/{ui/projector => projectors}/tab.py (99%) rename openlp/core/{lib/projector => projectors}/upgrade.py (100%) rename {openlp/core/ui/projector => tests/functional/openlp_core/projectors}/__init__.py (96%) rename tests/functional/openlp_core/{lib => projectors}/test_projector_constants.py (93%) rename tests/functional/openlp_core/{lib => projectors}/test_projector_db.py (98%) rename tests/functional/openlp_core/{lib => projectors}/test_projector_pjlink_base.py (97%) rename tests/functional/openlp_core/{lib => projectors}/test_projector_pjlink_cmd_routing.py (90%) rename tests/functional/openlp_core/{lib => projectors}/test_projector_pjlink_commands.py (96%) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 0f4078420..0b26fcaf4 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -620,6 +620,3 @@ from .serviceitem import ServiceItem, ServiceItemType, ItemCapabilities from .htmlbuilder import build_html, build_lyrics_format_css, build_lyrics_outline_css, build_chords_css from .imagemanager import ImageManager from .mediamanageritem import MediaManagerItem -from .projector.db import ProjectorDB, Projector -from .projector.pjlink import PJLink -from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING diff --git a/openlp/core/lib/projector/__init__.py b/openlp/core/projectors/__init__.py similarity index 78% rename from openlp/core/lib/projector/__init__.py rename to openlp/core/projectors/__init__.py index dc7d2c89d..a3f052d9b 100644 --- a/openlp/core/lib/projector/__init__.py +++ b/openlp/core/projectors/__init__.py @@ -25,10 +25,22 @@ Initialization for the openlp.core.ui.projector modules. """ +__all__ = ['PJLINK_PORT', 'ERROR_MSG', 'ERROR_STRING', 'DialogSourceStyle', 'PJLink', 'Projector', + 'ProjectorDB', 'ProjectorEditForm', 'ProjectorManager', 'ProjectorTab'] + +# Due to circular dependencies, put the imports after defines class DialogSourceStyle(object): """ An enumeration for projector dialog box type. """ Tabbed = 0 Single = 1 + + +from .constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING +from .db import Projector, ProjectorDB +from .editform import ProjectorEditForm +from .manager import ProjectorManager +from .pjlink import PJLink +from .tab import ProjectorTab diff --git a/openlp/core/lib/projector/constants.py b/openlp/core/projectors/constants.py similarity index 100% rename from openlp/core/lib/projector/constants.py rename to openlp/core/projectors/constants.py diff --git a/openlp/core/lib/projector/db.py b/openlp/core/projectors/db.py similarity index 99% rename from openlp/core/lib/projector/db.py rename to openlp/core/projectors/db.py index fa8934ae2..99fe9515b 100644 --- a/openlp/core/lib/projector/db.py +++ b/openlp/core/projectors/db.py @@ -43,8 +43,8 @@ from sqlalchemy.ext.declarative import declarative_base, declared_attr from sqlalchemy.orm import relationship from openlp.core.lib.db import Manager, init_db, init_url -from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES -from openlp.core.lib.projector import upgrade +from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES +from openlp.core.projectors import upgrade Base = declarative_base(MetaData()) diff --git a/openlp/core/ui/projector/editform.py b/openlp/core/projectors/editform.py similarity index 99% rename from openlp/core/ui/projector/editform.py rename to openlp/core/projectors/editform.py index bd3267665..4ae1f96d9 100644 --- a/openlp/core/ui/projector/editform.py +++ b/openlp/core/projectors/editform.py @@ -30,8 +30,8 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common import verify_ip_address from openlp.core.common.i18n import translate from openlp.core.lib import build_icon -from openlp.core.lib.projector.db import Projector -from openlp.core.lib.projector.constants import PJLINK_PORT +from openlp.core.projectors.db import Projector +from openlp.core.projectors.constants import PJLINK_PORT log = logging.getLogger(__name__) log.debug('editform loaded') diff --git a/openlp/core/ui/projector/manager.py b/openlp/core/projectors/manager.py similarity index 98% rename from openlp/core/ui/projector/manager.py rename to openlp/core/projectors/manager.py index 0770886e4..0e00d602d 100644 --- a/openlp/core/ui/projector/manager.py +++ b/openlp/core/projectors/manager.py @@ -34,14 +34,14 @@ from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.registry import RegistryBase from openlp.core.common.settings import Settings from openlp.core.lib.ui import create_widget_action -from openlp.core.lib.projector import DialogSourceStyle -from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHENTICATION, E_ERROR, \ +from openlp.core.projectors import DialogSourceStyle +from openlp.core.projectors.constants import ERROR_MSG, ERROR_STRING, E_AUTHENTICATION, E_ERROR, \ E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \ S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP -from openlp.core.lib.projector.db import ProjectorDB -from openlp.core.lib.projector.pjlink import PJLink, PJLinkUDP -from openlp.core.ui.projector.editform import ProjectorEditForm -from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle +from openlp.core.projectors.db import ProjectorDB +from openlp.core.projectors.pjlink import PJLink, PJLinkUDP +from openlp.core.projectors.editform import ProjectorEditForm +from openlp.core.projectors.sourceselectform import SourceSelectTabs, SourceSelectSingle from openlp.core.widgets.toolbar import OpenLPToolbar log = logging.getLogger(__name__) @@ -518,7 +518,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM projector.thread.quit() new_list = [] for item in self.projector_list: - if item.link.dbid == projector.link.dbid: + if item.link.db_item.id == projector.link.db_item.id: continue new_list.append(item) self.projector_list = new_list @@ -730,7 +730,6 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM thread.started.connect(item.link.thread_started) thread.finished.connect(item.link.thread_stopped) thread.finished.connect(thread.deleteLater) - item.link.projectorNetwork.connect(self.update_status) item.link.changeStatus.connect(self.update_status) item.link.projectorAuthentication.connect(self.authentication_error) item.link.projectorNoAuthentication.connect(self.no_authentication_error) diff --git a/openlp/core/lib/projector/pjlink.py b/openlp/core/projectors/pjlink.py similarity index 99% rename from openlp/core/lib/projector/pjlink.py rename to openlp/core/projectors/pjlink.py index 2272b971f..38013097f 100644 --- a/openlp/core/lib/projector/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -54,7 +54,7 @@ from PyQt5 import QtCore, QtNetwork from openlp.core.common import qmd5_hash from openlp.core.common.i18n import translate -from openlp.core.lib.projector.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \ +from openlp.core.projectors.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \ E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \ E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \ PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \ @@ -520,7 +520,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ # Signals sent by this module changeStatus = QtCore.pyqtSignal(str, int, str) - projectorNetwork = QtCore.pyqtSignal(int) # Projector network activity projectorStatus = QtCore.pyqtSignal(int) # Status update projectorAuthentication = QtCore.pyqtSignal(str) # Authentication error projectorNoAuthentication = QtCore.pyqtSignal(str) # PIN set and no authentication needed @@ -846,7 +845,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip)) return self.receive_data_signal() self.socket_timer.stop() - self.projectorNetwork.emit(S_NETWORK_RECEIVED) return self.get_data(buff=read, ip=self.ip) def get_data(self, buff, ip): @@ -925,7 +923,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): if cmd not in PJLINK_VALID_CMD: log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip)) return - self.projectorNetwork.emit(S_NETWORK_SENDING) log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.ip, command=cmd, data=opts, @@ -996,7 +993,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip())) log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue)) self.socket_timer.start() - self.projectorNetwork.emit(S_NETWORK_SENDING) sent = self.write(out.encode('{string_encoding}'.format(string_encoding='utf-8' if utf8 else 'ascii'))) self.waitForBytesWritten(2000) # 2 seconds should be enough if sent == -1: diff --git a/openlp/core/ui/projector/sourceselectform.py b/openlp/core/projectors/sourceselectform.py similarity index 99% rename from openlp/core/ui/projector/sourceselectform.py rename to openlp/core/projectors/sourceselectform.py index 0c150d25b..aaf8170ac 100644 --- a/openlp/core/ui/projector/sourceselectform.py +++ b/openlp/core/projectors/sourceselectform.py @@ -31,8 +31,8 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common import is_macosx from openlp.core.common.i18n import translate from openlp.core.lib import build_icon -from openlp.core.lib.projector.db import ProjectorSource -from openlp.core.lib.projector.constants import PJLINK_DEFAULT_SOURCES, PJLINK_DEFAULT_CODES +from openlp.core.projectors.db import ProjectorSource +from openlp.core.projectors.constants import PJLINK_DEFAULT_SOURCES, PJLINK_DEFAULT_CODES log = logging.getLogger(__name__) diff --git a/openlp/core/ui/projector/tab.py b/openlp/core/projectors/tab.py similarity index 99% rename from openlp/core/ui/projector/tab.py rename to openlp/core/projectors/tab.py index b7c2e5dda..29e6a9511 100644 --- a/openlp/core/ui/projector/tab.py +++ b/openlp/core/projectors/tab.py @@ -29,7 +29,7 @@ from PyQt5 import QtWidgets from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.settings import Settings from openlp.core.lib import SettingsTab -from openlp.core.lib.projector import DialogSourceStyle +from openlp.core.projectors import DialogSourceStyle log = logging.getLogger(__name__) log.debug('projectortab module loaded') diff --git a/openlp/core/lib/projector/upgrade.py b/openlp/core/projectors/upgrade.py similarity index 100% rename from openlp/core/lib/projector/upgrade.py rename to openlp/core/projectors/upgrade.py diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 2b8383755..2bf3e4e47 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -115,9 +115,10 @@ from .formattingtagcontroller import FormattingTagController from .shortcutlistform import ShortcutListForm from .servicemanager import ServiceManager from .thememanager import ThemeManager -from .projector.manager import ProjectorManager -from .projector.tab import ProjectorTab -from .projector.editform import ProjectorEditForm + +from openlp.core.projectors import ProjectorManager +from openlp.core.projectors import ProjectorTab +from openlp.core.projectors import ProjectorEditForm __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm', 'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'Display', 'AudioPlayer', diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 5fa9b2a2f..dd07357f9 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -55,7 +55,7 @@ from openlp.core.widgets.dialogs import FileDialog from openlp.core.widgets.docks import OpenLPDockWidget, MediaDockManager from openlp.core.ui.media import MediaController from openlp.core.ui.printserviceform import PrintServiceForm -from openlp.core.ui.projector.manager import ProjectorManager +from openlp.core.projectors import ProjectorManager from openlp.core.ui.style import PROGRESSBAR_STYLE, get_library_stylesheet from openlp.core.version import get_version diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index e7a86bac2..54bd01935 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -32,7 +32,7 @@ from openlp.core.common.registry import Registry from openlp.core.lib import build_icon from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab from openlp.core.ui.media import PlayerTab -from openlp.core.ui.projector.tab import ProjectorTab +from openlp.core.projectors import ProjectorTab from openlp.core.ui.settingsdialog import Ui_SettingsDialog log = logging.getLogger(__name__) diff --git a/openlp/core/ui/projector/__init__.py b/tests/functional/openlp_core/projectors/__init__.py similarity index 96% rename from openlp/core/ui/projector/__init__.py rename to tests/functional/openlp_core/projectors/__init__.py index eb20e9232..7efaa18af 100644 --- a/openlp/core/ui/projector/__init__.py +++ b/tests/functional/openlp_core/projectors/__init__.py @@ -20,5 +20,5 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The Projector driver module. +Module-level functions for the functional test suite """ diff --git a/tests/functional/openlp_core/lib/test_projector_constants.py b/tests/functional/openlp_core/projectors/test_projector_constants.py similarity index 93% rename from tests/functional/openlp_core/lib/test_projector_constants.py rename to tests/functional/openlp_core/projectors/test_projector_constants.py index 90fee1e13..ed1afa677 100644 --- a/tests/functional/openlp_core/lib/test_projector_constants.py +++ b/tests/functional/openlp_core/projectors/test_projector_constants.py @@ -20,7 +20,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.lib.projector.constants package. +Package to test the openlp.core.projectors.constants module. """ from unittest import TestCase @@ -37,7 +37,7 @@ class TestProjectorConstants(TestCase): from tests.resources.projector.data import TEST_VIDEO_CODES # WHEN: Import projector PJLINK_DEFAULT_CODES - from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES + from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES # THEN: Verify dictionary was build correctly self.assertEqual(PJLINK_DEFAULT_CODES, TEST_VIDEO_CODES, 'PJLink video strings should match') diff --git a/tests/functional/openlp_core/lib/test_projector_db.py b/tests/functional/openlp_core/projectors/test_projector_db.py similarity index 98% rename from tests/functional/openlp_core/lib/test_projector_db.py rename to tests/functional/openlp_core/projectors/test_projector_db.py index bcbc9c547..1dfe47a54 100644 --- a/tests/functional/openlp_core/lib/test_projector_db.py +++ b/tests/functional/openlp_core/projectors/test_projector_db.py @@ -20,7 +20,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.ui.projectordb find, edit, delete +Package to test the openlp.core.projectors.db module. record functions. PREREQUISITE: add_record() and get_all() functions validated. @@ -32,10 +32,10 @@ from tempfile import mkdtemp from unittest import TestCase from unittest.mock import patch -from openlp.core.lib.projector import upgrade from openlp.core.lib.db import upgrade_db -from openlp.core.lib.projector.constants import PJLINK_PORT -from openlp.core.lib.projector.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source +from openlp.core.projectors import upgrade +from openlp.core.projectors.constants import PJLINK_PORT +from openlp.core.projectors.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source from tests.resources.projector.data import TEST_DB_PJLINK1, TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA from tests.utils.constants import TEST_RESOURCES_PATH @@ -129,7 +129,7 @@ class TestProjectorDB(TestCase): """ Test case for ProjectorDB """ - @patch('openlp.core.lib.projector.db.init_url') + @patch('openlp.core.projectors.db.init_url') def setUp(self, mocked_init_url): """ Set up anything necessary for all tests diff --git a/tests/functional/openlp_core/lib/test_projector_pjlink_base.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py similarity index 97% rename from tests/functional/openlp_core/lib/test_projector_pjlink_base.py rename to tests/functional/openlp_core/projectors/test_projector_pjlink_base.py index 578f37ede..8c0c17955 100644 --- a/tests/functional/openlp_core/lib/test_projector_pjlink_base.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py @@ -20,14 +20,13 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.lib.projector.pjlink base package. +Package to test the openlp.core.projectors.pjlink base package. """ from unittest import TestCase from unittest.mock import call, patch, MagicMock -from openlp.core.lib.projector.db import Projector -from openlp.core.lib.projector.pjlink import PJLink -from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED +from openlp.core.projectors import PJLink, Projector +from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA diff --git a/tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py similarity index 90% rename from tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py rename to tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py index 006abfff6..92dd24f82 100644 --- a/tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py @@ -20,20 +20,19 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.lib.projector.pjlink class command routing. +Package to test the openlp.core.projectors.pjlink command routing. """ from unittest import TestCase from unittest.mock import patch, MagicMock -import openlp.core.lib.projector.pjlink -from openlp.core.lib.projector.db import Projector -from openlp.core.lib.projector.pjlink import PJLink -from openlp.core.lib.projector.constants import PJLINK_ERRORS, \ +import openlp.core.projectors.pjlink +from openlp.core.projectors import PJLink, Projector +from openlp.core.projectors.constants import PJLINK_ERRORS, \ E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED ''' -from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ +from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON ''' from tests.resources.projector.data import TEST_PIN, TEST1_DATA @@ -46,7 +45,7 @@ class TestPJLinkRouting(TestCase): """ Tests for the PJLink module command routing """ - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_call_clss(self, mock_log): """ Test process_command calls proper function @@ -66,7 +65,7 @@ class TestPJLinkRouting(TestCase): mock_process_clss.assert_called_with('1') @patch.object(pjlink_test, 'change_status') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_err1(self, mock_log, mock_change_status): """ Test ERR1 - Undefined projector function @@ -85,7 +84,7 @@ class TestPJLinkRouting(TestCase): mock_log.error.assert_called_with(log_text) @patch.object(pjlink_test, 'change_status') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_err2(self, mock_log, mock_change_status): """ Test ERR2 - Parameter Error @@ -104,7 +103,7 @@ class TestPJLinkRouting(TestCase): mock_log.error.assert_called_with(log_text) @patch.object(pjlink_test, 'change_status') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_err3(self, mock_log, mock_change_status): """ Test ERR3 - Unavailable error @@ -123,7 +122,7 @@ class TestPJLinkRouting(TestCase): mock_log.error.assert_called_with(log_text) @patch.object(pjlink_test, 'change_status') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_err4(self, mock_log, mock_change_status): """ Test ERR3 - Unavailable error @@ -144,7 +143,7 @@ class TestPJLinkRouting(TestCase): @patch.object(pjlink_test, 'projectorAuthentication') @patch.object(pjlink_test, 'change_status') @patch.object(pjlink_test, 'disconnect_from_host') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_erra(self, mock_log, mock_disconnect, mock_change_status, mock_err_authenticate): """ Test ERRA - Authentication Error @@ -163,7 +162,7 @@ class TestPJLinkRouting(TestCase): mock_change_status.assert_called_once_with(E_AUTHENTICATION) mock_log.error.assert_called_with(log_text) - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_future(self, mock_log): """ Test command valid but no method to process yet @@ -184,7 +183,7 @@ class TestPJLinkRouting(TestCase): mock_log.warning.assert_called_once_with(log_text) @patch.object(pjlink_test, 'pjlink_functions') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_invalid(self, mock_log, mock_functions): """ Test not a valid command @@ -203,7 +202,7 @@ class TestPJLinkRouting(TestCase): mock_log.error.assert_called_once_with(log_text) @patch.object(pjlink_test, 'pjlink_functions') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_ok(self, mock_log, mock_functions): """ Test command returned success diff --git a/tests/functional/openlp_core/lib/test_projector_pjlink_commands.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py similarity index 96% rename from tests/functional/openlp_core/lib/test_projector_pjlink_commands.py rename to tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py index 143206d0a..4907005ee 100644 --- a/tests/functional/openlp_core/lib/test_projector_pjlink_commands.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py @@ -20,15 +20,14 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.lib.projector.pjlink commands package. +Package to test the openlp.core.projectors.pjlink commands package. """ from unittest import TestCase from unittest.mock import patch -import openlp.core.lib.projector.pjlink -from openlp.core.lib.projector.db import Projector -from openlp.core.lib.projector.pjlink import PJLink -from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ +import openlp.core.projectors.pjlink +from openlp.core.projectors import PJLink, Projector +from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ PJLINK_POWR_STATUS, \ E_ERROR, E_NOT_CONNECTED, E_SOCKET_ADDRESS_NOT_AVAILABLE, E_UNKNOWN_SOCKET_ERROR, E_WARN, \ S_CONNECTED, S_OFF, S_ON, S_NOT_CONNECTED, S_CONNECTING, S_STANDBY @@ -50,7 +49,7 @@ class TestPJLinkCommands(TestCase): Tests for the PJLink module """ @patch.object(pjlink_test, 'changeStatus') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_change_status_connection_error(self, mock_log, mock_change_status): """ Test change_status with connection error @@ -74,7 +73,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times') @patch.object(pjlink_test, 'changeStatus') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_change_status_connection_status_connecting(self, mock_log, mock_change_status): """ Test change_status with connection status @@ -97,7 +96,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times') @patch.object(pjlink_test, 'changeStatus') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_change_status_connection_status_connected(self, mock_log, mock_change_status): """ Test change_status with connection status @@ -120,7 +119,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times') @patch.object(pjlink_test, 'changeStatus') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_change_status_connection_status_with_message(self, mock_log, mock_change_status): """ Test change_status with connection status @@ -144,7 +143,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times') @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_av_mute_status(self, mock_log, mock_send_command): """ Test sending command to retrieve shutter/audio state @@ -164,7 +163,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_available_inputs(self, mock_log, mock_send_command): """ Test sending command to retrieve avaliable inputs @@ -184,7 +183,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_error_status(self, mock_log, mock_send_command): """ Test sending command to retrieve projector error status @@ -204,7 +203,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_input_source(self, mock_log, mock_send_command): """ Test sending command to retrieve current input @@ -224,7 +223,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_lamp_status(self, mock_log, mock_send_command): """ Test sending command to retrieve lamp(s) status @@ -244,7 +243,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_manufacturer(self, mock_log, mock_send_command): """ Test sending command to retrieve manufacturer name @@ -264,7 +263,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_model(self, mock_log, mock_send_command): """ Test sending command to get model information @@ -284,7 +283,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_name(self, mock_log, mock_send_command): """ Test sending command to get user-assigned name @@ -304,7 +303,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_other_info(self, mock_log, mock_send_command): """ Test sending command to retrieve other information @@ -324,7 +323,7 @@ class TestPJLinkCommands(TestCase): mock_send_command.assert_called_once_with(cmd=test_data) @patch.object(pjlink_test, 'send_command') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_get_power_status(self, mock_log, mock_send_command): """ Test sending command to retrieve current power state @@ -599,7 +598,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(pjlink.pjlink_class, '2', 'Non-standard class reply should have set class=2') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_clss_invalid_nan(self, mock_log): """ Test CLSS reply has no class number @@ -616,7 +615,7 @@ class TestPJLinkCommands(TestCase): 'Non-standard class reply should have set class=1') mock_log.error.assert_called_once_with(log_text) - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_clss_invalid_no_version(self, mock_log): """ Test CLSS reply has no class number @@ -648,7 +647,7 @@ class TestPJLinkCommands(TestCase): # THEN: PJLink instance errors should be None self.assertIsNone(pjlink.projector_errors, 'projector_errors should have been set to None') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_erst_data_invalid_length(self, mock_log): """ Test test_projector_process_erst_data_invalid_length @@ -666,7 +665,7 @@ class TestPJLinkCommands(TestCase): self.assertTrue(mock_log.warning.called, 'Warning should have been logged') mock_log.warning.assert_called_once_with(log_text) - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_erst_data_invalid_nan(self, mock_log): """ Test test_projector_process_erst_data_invalid_nan @@ -764,7 +763,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(pjlink.source, '1', 'Input source should be set to "1"') @patch.object(pjlink_test, 'projectorUpdateIcons') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_inst(self, mock_log, mock_UpdateIcons): """ Test saving video source available information @@ -787,7 +786,7 @@ class TestPJLinkCommands(TestCase): mock_log.debug.assert_called_once_with(log_data) self.assertTrue(mock_UpdateIcons.emit.called, 'Update Icons should have been called') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_lamp_invalid(self, mock_log): """ Test status multiple lamp on/off and hours @@ -858,7 +857,7 @@ class TestPJLinkCommands(TestCase): self.assertEqual(pjlink.lamp[0]['Hours'], 22222, 'Lamp hours should have been set to 22222') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_name(self, mock_log): """ Test saving NAME data from projector @@ -1040,7 +1039,7 @@ class TestPJLinkCommands(TestCase): self.assertNotEquals(pjlink.serial_no, test_number, 'Projector serial number should NOT have been set') - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_sver(self, mock_log): """ Test invalid software version information - too long @@ -1061,7 +1060,7 @@ class TestPJLinkCommands(TestCase): self.assertIsNone(pjlink.sw_version_received, 'Received software version should not have changed') mock_log.debug.assert_called_once_with(test_log) - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_sver_changed(self, mock_log): """ Test invalid software version information - Received different than saved @@ -1086,7 +1085,7 @@ class TestPJLinkCommands(TestCase): # There was 4 calls, but only the last one is checked with this method mock_log.warning.assert_called_with(test_log) - @patch.object(openlp.core.lib.projector.pjlink, 'log') + @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_sver_invalid(self, mock_log): """ Test invalid software version information - too long diff --git a/tests/interfaces/openlp_core/ui/test_projectoreditform.py b/tests/interfaces/openlp_core/ui/test_projectoreditform.py index fc261d9f5..3f328cb04 100644 --- a/tests/interfaces/openlp_core/ui/test_projectoreditform.py +++ b/tests/interfaces/openlp_core/ui/test_projectoreditform.py @@ -20,7 +20,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Interface tests to test the openlp.core.ui.projector.editform.ProjectorEditForm() +Interface tests to test the openlp.core.projectors.editform.ProjectorEditForm() class and methods. """ import os @@ -28,8 +28,7 @@ from unittest import TestCase from unittest.mock import patch from openlp.core.common.registry import Registry -from openlp.core.lib.projector.db import Projector, ProjectorDB -from openlp.core.ui import ProjectorEditForm +from openlp.core.projectors import Projector, ProjectorDB, ProjectorEditForm, ProjectorManager from tests.helpers.testmixin import TestMixin from tests.resources.projector.data import TEST_DB, TEST1_DATA, TEST2_DATA @@ -48,7 +47,7 @@ class TestProjectorEditForm(TestCase, TestMixin): self.setup_application() self.build_settings() Registry.create() - with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url: + with patch('openlp.core.projectors.db.init_url') as mocked_init_url: if os.path.exists(TEST_DB): os.unlink(TEST_DB) mocked_init_url.return_value = 'sqlite:///' + TEST_DB @@ -66,7 +65,7 @@ class TestProjectorEditForm(TestCase, TestMixin): del self.projector_form self.destroy_settings() - @patch('openlp.core.ui.projector.editform.QtWidgets.QDialog.exec') + @patch('openlp.core.projectors.editform.QtWidgets.QDialog.exec') def test_edit_form_add_projector(self, mocked_exec): """ Test projector edit form with no parameters creates a new entry. @@ -84,7 +83,7 @@ class TestProjectorEditForm(TestCase, TestMixin): self.assertTrue((item.ip is None and item.name is None), 'Projector edit form should have a new Projector() instance to edit') - @patch('openlp.core.ui.projector.editform.QtWidgets.QDialog.exec') + @patch('openlp.core.projectors.editform.QtWidgets.QDialog.exec') def test_edit_form_edit_projector(self, mocked_exec): """ Test projector edit form with existing projector entry diff --git a/tests/interfaces/openlp_core/ui/test_projectormanager.py b/tests/interfaces/openlp_core/ui/test_projectormanager.py index ff95c4276..81c7ea863 100644 --- a/tests/interfaces/openlp_core/ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core/ui/test_projectormanager.py @@ -27,8 +27,7 @@ from unittest import TestCase from unittest.mock import patch, MagicMock from openlp.core.common.registry import Registry -from openlp.core.ui import ProjectorManager, ProjectorEditForm -from openlp.core.lib.projector.db import ProjectorDB +from openlp.core.projectors import ProjectorDB, ProjectorEditForm, ProjectorManager from tests.helpers.testmixin import TestMixin from tests.resources.projector.data import TEST_DB @@ -45,7 +44,7 @@ class TestProjectorManager(TestCase, TestMixin): self.build_settings() self.setup_application() Registry.create() - with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url: + with patch('openlp.core.projectors.db.init_url') as mocked_init_url: if os.path.exists(TEST_DB): os.unlink(TEST_DB) mocked_init_url.return_value = 'sqlite:///%s' % TEST_DB diff --git a/tests/interfaces/openlp_core/ui/test_projectorsourceform.py b/tests/interfaces/openlp_core/ui/test_projectorsourceform.py index 4b9e2f402..e8570ac7e 100644 --- a/tests/interfaces/openlp_core/ui/test_projectorsourceform.py +++ b/tests/interfaces/openlp_core/ui/test_projectorsourceform.py @@ -32,9 +32,9 @@ from unittest.mock import patch from PyQt5.QtWidgets import QDialog from openlp.core.common.registry import Registry -from openlp.core.lib.projector.db import ProjectorDB, Projector -from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES -from openlp.core.ui.projector.sourceselectform import source_group, SourceSelectSingle +from openlp.core.projectors.db import ProjectorDB, Projector +from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES +from openlp.core.projectors.sourceselectform import source_group, SourceSelectSingle from tests.helpers.testmixin import TestMixin from tests.resources.projector.data import TEST_DB, TEST1_DATA @@ -58,7 +58,7 @@ class ProjectorSourceFormTest(TestCase, TestMixin): """ Test class for the Projector Source Select form module """ - @patch('openlp.core.lib.projector.db.init_url') + @patch('openlp.core.projectors.db.init_url') def setUp(self, mocked_init_url): """ Set up anything necessary for all tests From ac61fb5a4fff38a30c3bdd4fadff084112ca6d5b Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Fri, 10 Nov 2017 08:57:35 -0700 Subject: [PATCH 30/96] Make the heading line as long as the others --- scripts/jenkins_script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 05c589ddd..706d808af 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -119,7 +119,7 @@ class JenkinsTrigger(object): Print the status information of the build triggered. """ print('Add this to your merge proposal:') - print('--------------------------------') + print('-' * 80) bzr = Popen(('bzr', 'revno'), stdout=PIPE, stderr=PIPE) raw_output, error = bzr.communicate() revno = raw_output.decode().strip() From a9692f83910b10ba75fbc4fdd0c329f0767580e7 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Fri, 10 Nov 2017 09:36:06 -0700 Subject: [PATCH 31/96] Print out the build info before the build starts with a WAITING status --- scripts/jenkins_script.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 706d808af..d0ba665b7 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -89,18 +89,18 @@ class JenkinsTrigger(object): """ Create the JenkinsTrigger instance. """ - self.build_number = {} + self.jobs = {} self.can_use_colour = can_use_colour and not os.name.startswith('nt') self.repo_name = get_repo_name() self.server = Jenkins(JENKINS_URL, username=username, password=password) - def fetch_build_numbers(self): + def fetch_jobs(self): """ - Get the next build number from all the jobs + Get the job info for all the jobs """ for job_name in OpenLPJobs.Jobs: job_info = self.server.get_job_info(job_name) - self.build_number[job_name] = job_info['nextBuildNumber'] + self.jobs[job_name] = job_info def trigger_build(self): """ @@ -111,7 +111,7 @@ class JenkinsTrigger(object): # We just want the name (not the email). name = ' '.join(raw_output.decode().split()[:-1]) cause = 'Build triggered by %s (%s)' % (name, self.repo_name) - self.fetch_build_numbers() + self.fetch_jobs() self.server.build_job(OpenLPJobs.Branch_Pull, {'BRANCH_NAME': self.repo_name, 'cause': cause}) def print_output(self): @@ -162,8 +162,10 @@ class JenkinsTrigger(object): :param job_name: The name of the job we want the information from. For example *Branch-01-Pull*. Use the class variables from the :class:`OpenLPJobs` class. """ - self.current_build = self._get_build_info(job_name, self.build_number[job_name]) - print('{:<70} [RUNNING]'.format(self.current_build['url']), end='', flush=True) + job = self.jobs[job_name] + print('{:<70} [WAITING]'.format(job['url'] + '/' + job['nextBuildNumber']), end='', flush=True) + self.current_build = self._get_build_info(job_name, job[job_name]['nextBuildNumber']) + print('\b\b\b\b\b\b\b\b\b[RUNNING]', end='', flush=True) is_success = False while self.current_build['building'] is True: time.sleep(0.5) From dd2d1002ce188a3e1cf094a79ddfa9bc3d9e1c4f Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Fri, 10 Nov 2017 10:51:42 -0700 Subject: [PATCH 32/96] Fix up a couple of issues, and move URL creation to where the job info is pulled --- scripts/jenkins_script.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index d0ba665b7..a669de71e 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -101,6 +101,7 @@ class JenkinsTrigger(object): for job_name in OpenLPJobs.Jobs: job_info = self.server.get_job_info(job_name) self.jobs[job_name] = job_info + self.jobs[job_name]['nextBuildUrl'] = '{url}{nextBuildNumber}/'.format(**job_info) def trigger_build(self): """ @@ -163,13 +164,13 @@ class JenkinsTrigger(object): variables from the :class:`OpenLPJobs` class. """ job = self.jobs[job_name] - print('{:<70} [WAITING]'.format(job['url'] + '/' + job['nextBuildNumber']), end='', flush=True) - self.current_build = self._get_build_info(job_name, job[job_name]['nextBuildNumber']) + print('{:<70} [WAITING]'.format(job['nextBuildUrl']), end='', flush=True) + self.current_build = self._get_build_info(job_name, job['nextBuildNumber']) print('\b\b\b\b\b\b\b\b\b[RUNNING]', end='', flush=True) is_success = False while self.current_build['building'] is True: time.sleep(0.5) - self.current_build = self.server.get_build_info(job_name, self.build_number[job_name]) + self.current_build = self.server.get_build_info(job_name, job['nextBuildNumber']) result_string = self.current_build['result'] is_success = result_string == 'SUCCESS' if self.can_use_colour: From 1c760e44e5fe5be57e9bb921c61cbe2dfe3f253e Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Fri, 10 Nov 2017 22:50:04 +0000 Subject: [PATCH 33/96] Small modification to upgrade settings as per superflys request --- openlp/core/common/settings.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index aa0e4a6f6..b98929138 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -255,10 +255,7 @@ class Settings(QtCore.QSettings): ('core/logo file', 'core/logo file', [(str_to_path, None)]), ('presentations/last directory', 'presentations/last directory', [(str_to_path, None)]), ('images/last directory', 'images/last directory', [(str_to_path, None)]), - ('media/last directory', 'media/last directory', [(str_to_path, None)]) - ] - - __setting_upgrade_3__ = [ + ('media/last directory', 'media/last directory', [(str_to_path, None)]), ('songuasge/db password', 'songusage/db password', []), ('songuasge/db hostname', 'songusage/db hostname', []), ('songuasge/db database', 'songusage/db database', []) From b1d88ac4f7e6c98b8177ea68832e3389debfce62 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Sat, 11 Nov 2017 16:36:50 +0000 Subject: [PATCH 34/96] test fix --- tests/functional/openlp_plugins/bibles/test_bibleimport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/openlp_plugins/bibles/test_bibleimport.py b/tests/functional/openlp_plugins/bibles/test_bibleimport.py index 1c13d8874..9b9a8fae8 100644 --- a/tests/functional/openlp_plugins/bibles/test_bibleimport.py +++ b/tests/functional/openlp_plugins/bibles/test_bibleimport.py @@ -362,7 +362,7 @@ class TestBibleImport(TestCase): instance.wizard = MagicMock() # WHEN: Calling parse_xml - result = instance.parse_xml('file.tst') + result = instance.parse_xml(Path('file.tst')) # THEN: The result returned should contain the correct data, and should be an instance of eetree_Element assert etree.tostring(result) == b'<root>\n <data><div>Test<p>data</p><a>to</a>keep</div></data>\n' \ From 9606b999760237e02f702e09be3ff783c89b1996 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Mon, 13 Nov 2017 20:07:20 +0000 Subject: [PATCH 35/96] Fix md5 thumbnail regression Fixes: https://launchpad.net/bugs/1692187 --- openlp/plugins/presentations/lib/mediaitem.py | 2 +- .../lib/presentationcontroller.py | 6 +++-- .../presentations/presentationplugin.py | 15 ++++++++++++ .../presentations/test_mediaitem.py | 24 +++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index b801597b1..04bec02a9 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -243,7 +243,7 @@ class PresentationMediaItem(MediaManagerItem): """ Clean up the files created such as thumbnails - :param openlp.core.common.path.Path file_path: File path of the presention to clean up after + :param openlp.core.common.path.Path file_path: File path of the presentation to clean up after :param bool clean_for_update: Only clean thumbnails if update is needed :rtype: None """ diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index dd099c130..bb424c9fa 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -139,7 +139,8 @@ class PresentationDocument(object): :return: The path to the thumbnail :rtype: openlp.core.common.path.Path """ - # TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed + # TODO: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in + # get_temp_folder and PresentationPluginapp_startup is removed if Settings().value('presentations/thumbnail_scheme') == 'md5': folder = md5_hash(bytes(self.file_path)) else: @@ -153,7 +154,8 @@ class PresentationDocument(object): :return: The path to the temporary file folder :rtype: openlp.core.common.path.Path """ - # TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed + # TODO: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in + # get_thumbnail_folder and PresentationPluginapp_startup is removed if Settings().value('presentations/thumbnail_scheme') == 'md5': folder = md5_hash(bytes(self.file_path)) else: diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 7f3333049..5e32af7b6 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -31,6 +31,7 @@ from PyQt5 import QtCore from openlp.core.api.http import register_endpoint from openlp.core.common import extension_loader from openlp.core.common.i18n import translate +from openlp.core.common.settings import Settings from openlp.core.lib import Plugin, StringContent, build_icon from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint from openlp.plugins.presentations.lib import PresentationController, PresentationMediaItem, PresentationTab @@ -136,6 +137,20 @@ class PresentationPlugin(Plugin): self.register_controllers(controller) return bool(self.controllers) + def app_startup(self): + """ + Perform tasks on application startup. + """ + # TODO: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in + # PresentationDocument.get_thumbnail_folder and PresentationDocument.get_temp_folder is removed + super().app_startup() + files_from_config = Settings().value('presentations/presentations files') + for file in files_from_config: + self.media_item.clean_up_thumbnails(file, clean_for_update=True) + self.media_item.list_view.clear() + Settings().setValue('presentations/thumbnail_scheme', 'md5') + self.media_item.validate_and_load(files_from_config) + @staticmethod def about(): """ diff --git a/tests/functional/openlp_plugins/presentations/test_mediaitem.py b/tests/functional/openlp_plugins/presentations/test_mediaitem.py index 1116ce4cb..fd28b9b03 100644 --- a/tests/functional/openlp_plugins/presentations/test_mediaitem.py +++ b/tests/functional/openlp_plugins/presentations/test_mediaitem.py @@ -133,3 +133,27 @@ class TestMediaItem(TestCase, TestMixin): # THEN: doc.presentation_deleted should have been called since the presentation file did not exists. mocked_doc.assert_has_calls([call.get_thumbnail_path(1, True), call.presentation_deleted()], True) + + @patch('openlp.plugins.presentations.lib.mediaitem.MediaManagerItem._setup') + @patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.setup_item') + @patch('openlp.plugins.presentations.lib.mediaitem.Settings') + def test_search(self, mocked_settings, *unreferenced_mocks): + """ + Test that the search method finds the correct results + """ + # GIVEN: A mocked Settings class which returns a list of Path objects, + # and an instance of the PresentationMediaItem + path_1 = Path('some_dir', 'Impress_file_1') + path_2 = Path('some_other_dir', 'impress_file_2') + path_3 = Path('another_dir', 'ppt_file') + mocked_returned_settings = MagicMock() + mocked_returned_settings.value.return_value = [path_1, path_2, path_3] + mocked_settings.return_value = mocked_returned_settings + media_item = PresentationMediaItem(None, MagicMock(), None) + media_item.settings_section = '' + + # WHEN: Calling search + results = media_item.search('IMPRE', False) + + # THEN: The first two results should have been returned + assert results == [[str(path_1), 'Impress_file_1'], [str(path_2), 'impress_file_2']] From 4f769d56229abb4b1e1d3a95693a4000f76fe7a9 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Mon, 13 Nov 2017 21:31:54 +0000 Subject: [PATCH 36/96] Fix the handler passing False Fixes: https://launchpad.net/bugs/1672777 --- openlp/core/ui/servicemanager.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 6d5c16fbd..3e1d0f544 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -216,7 +216,7 @@ class Ui_ServiceManager(object): text=translate('OpenLP.ServiceManager', 'Go Live'), icon=':/general/general_live.png', tooltip=translate('OpenLP.ServiceManager', 'Send the selected item to Live.'), category=UiStrings().Service, - triggers=self.make_live) + triggers=self.on_make_live_action_triggered) self.layout.addWidget(self.order_toolbar) # Connect up our signals and slots self.theme_combo_box.activated.connect(self.on_theme_combo_box_selected) @@ -1730,6 +1730,15 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.service_items[item]['service_item'].update_theme(theme) self.regenerate_service_items(True) + def on_make_live_action_triggered(self, checked): + """ + Handle `make_live_action` when the action is triggered. + + :param bool checked: Not Used. + :rtype: None + """ + self.make_live() + def get_drop_position(self): """ Getter for drop_position. Used in: MediaManagerItem From 4e9fcf2e505dbcef742d50c7fe5f19ab23b44620 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Mon, 13 Nov 2017 21:40:49 +0000 Subject: [PATCH 37/96] Sort media items by natural order Fixes: https://launchpad.net/bugs/1625087 --- openlp/plugins/custom/lib/db.py | 6 +++--- openlp/plugins/images/lib/mediaitem.py | 12 ++++++------ openlp/plugins/media/lib/mediaitem.py | 6 +++--- openlp/plugins/presentations/lib/mediaitem.py | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/openlp/plugins/custom/lib/db.py b/openlp/plugins/custom/lib/db.py index dc1f74567..6581ee5ae 100644 --- a/openlp/plugins/custom/lib/db.py +++ b/openlp/plugins/custom/lib/db.py @@ -26,7 +26,7 @@ the Custom plugin from sqlalchemy import Column, Table, types from sqlalchemy.orm import mapper -from openlp.core.common.i18n import get_locale_key +from openlp.core.common.i18n import get_natural_key from openlp.core.lib.db import BaseModel, init_db @@ -36,10 +36,10 @@ class CustomSlide(BaseModel): """ # By default sort the customs by its title considering language specific characters. def __lt__(self, other): - return get_locale_key(self.title) < get_locale_key(other.title) + return get_natural_key(self.title) < get_natural_key(other.title) def __eq__(self, other): - return get_locale_key(self.title) == get_locale_key(other.title) + return get_natural_key(self.title) == get_natural_key(other.title) def __hash__(self): """ diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index a4e76d51b..b678122a5 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -26,7 +26,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import delete_file, get_images_filter from openlp.core.common.applocation import AppLocation -from openlp.core.common.i18n import UiStrings, translate, get_locale_key +from openlp.core.common.i18n import UiStrings, translate, get_natural_key from openlp.core.common.path import Path, create_paths from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings @@ -271,7 +271,7 @@ class ImageMediaItem(MediaManagerItem): :param parent_group_id: The ID of the group that will be added recursively. """ image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id) - image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name)) + image_groups.sort(key=lambda group_object: get_natural_key(group_object.group_name)) folder_icon = build_icon(':/images/image_group.png') for image_group in image_groups: group = QtWidgets.QTreeWidgetItem() @@ -298,7 +298,7 @@ class ImageMediaItem(MediaManagerItem): combobox.clear() combobox.top_level_group_added = False image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id) - image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name)) + image_groups.sort(key=lambda group_object: get_natural_key(group_object.group_name)) for image_group in image_groups: combobox.addItem(prefix + image_group.group_name, image_group.id) self.fill_groups_combobox(combobox, image_group.id, prefix + ' ') @@ -355,7 +355,7 @@ class ImageMediaItem(MediaManagerItem): self.expand_group(open_group.id) # Sort the images by its filename considering language specific. # characters. - images.sort(key=lambda image_object: get_locale_key(image_object.file_path.name)) + images.sort(key=lambda image_object: get_natural_key(image_object.file_path.name)) for image in images: log.debug('Loading image: {name}'.format(name=image.file_path)) file_name = image.file_path.name @@ -533,9 +533,9 @@ class ImageMediaItem(MediaManagerItem): group_items.append(item) if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames): image_items.append(item) - group_items.sort(key=lambda item: get_locale_key(item.text(0))) + group_items.sort(key=lambda item: get_natural_key(item.text(0))) target_group.addChildren(group_items) - image_items.sort(key=lambda item: get_locale_key(item.text(0))) + image_items.sort(key=lambda item: get_natural_key(item.text(0))) target_group.addChildren(image_items) def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False, diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index ed787166b..f0ec1dfe2 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -26,7 +26,7 @@ import os from PyQt5 import QtCore, QtWidgets from openlp.core.common.applocation import AppLocation -from openlp.core.common.i18n import UiStrings, translate, get_locale_key +from openlp.core.common.i18n import UiStrings, translate, get_natural_key from openlp.core.common.path import Path, path_to_str, create_paths from openlp.core.common.mixins import RegistryProperties from openlp.core.common.registry import Registry @@ -362,7 +362,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): :param media: The media :param target_group: """ - media.sort(key=lambda file_name: get_locale_key(os.path.split(str(file_name))[1])) + media.sort(key=lambda file_name: get_natural_key(os.path.split(str(file_name))[1])) for track in media: track_info = QtCore.QFileInfo(track) item_name = None @@ -404,7 +404,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): :return: The media list """ media_file_paths = Settings().value(self.settings_section + '/media files') - media_file_paths.sort(key=lambda file_path: get_locale_key(file_path.name)) + media_file_paths.sort(key=lambda file_path: get_natural_key(file_path.name)) if media_type == MediaType.Audio: extension = self.media_controller.audio_extensions_list else: diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 04bec02a9..f58ba4861 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -23,7 +23,7 @@ import logging from PyQt5 import QtCore, QtGui, QtWidgets -from openlp.core.common.i18n import UiStrings, translate, get_locale_key +from openlp.core.common.i18n import UiStrings, translate, get_natural_key from openlp.core.common.path import Path, path_to_str, str_to_path from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings @@ -165,7 +165,7 @@ class PresentationMediaItem(MediaManagerItem): if not initial_load: self.main_window.display_progress_bar(len(file_paths)) # Sort the presentations by its filename considering language specific characters. - file_paths.sort(key=lambda file_path: get_locale_key(file_path.name)) + file_paths.sort(key=lambda file_path: get_natural_key(file_path.name)) for file_path in file_paths: if not initial_load: self.main_window.increment_progress_bar() From 1cb11832bdf4475690e45fa4f23648bff0541f93 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Tue, 14 Nov 2017 00:19:10 -0700 Subject: [PATCH 38/96] Added some tests for the settings upgrade and fixed a bug I had introduced. --- nose2.cfg | 5 + openlp/core/common/settings.py | 6 +- .../openlp_core/common/test_settings.py | 105 +++++++++++++++--- .../ui/media/vendor/test_mediainfoWrapper.py | 2 - 4 files changed, 96 insertions(+), 22 deletions(-) diff --git a/nose2.cfg b/nose2.cfg index 1f2e01126..ba02ed419 100644 --- a/nose2.cfg +++ b/nose2.cfg @@ -20,3 +20,8 @@ coverage-report = html [multiprocess] always-on = false processes = 4 + +[output-buffer] +always-on = True +stderr = True +stdout = False diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 6726624ef..3efac81d3 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -40,7 +40,7 @@ __version__ = 2 # Fix for bug #1014422. X11_BYPASS_DEFAULT = True -if is_linux(): +if is_linux(): # pragma: no cover # Default to False on Gnome. X11_BYPASS_DEFAULT = bool(not os.environ.get('GNOME_DESKTOP_SESSION_ID')) # Default to False on Xfce. @@ -495,7 +495,7 @@ class Settings(QtCore.QSettings): # system. if not isinstance(old_keys, (tuple, list)): old_keys = [old_keys] - if not any([self.contains(old_key) for old_key in old_keys]): + if any([not self.contains(old_key) for old_key in old_keys]): log.warning('One of {} does not exist, skipping upgrade'.format(old_keys)) continue if new_key: @@ -521,7 +521,7 @@ class Settings(QtCore.QSettings): break self.setValue(new_key, new_value) [self.remove(old_key) for old_key in old_keys if old_key != new_key] - self.setValue('settings/version', version) + self.setValue('settings/version', version) def value(self, key): """ diff --git a/tests/functional/openlp_core/common/test_settings.py b/tests/functional/openlp_core/common/test_settings.py index d54a6a1e1..b1f9b0a96 100644 --- a/tests/functional/openlp_core/common/test_settings.py +++ b/tests/functional/openlp_core/common/test_settings.py @@ -23,9 +23,10 @@ Package to test the openlp.core.lib.settings package. """ from unittest import TestCase -from unittest.mock import patch +from unittest.mock import call, patch -from openlp.core.common.settings import Settings +from openlp.core.common import settings +from openlp.core.common.settings import Settings, media_players_conv from tests.helpers.testmixin import TestMixin @@ -47,10 +48,19 @@ class TestSettings(TestCase, TestMixin): """ self.destroy_settings() + def test_media_players_conv(self): + """Test the media players conversion function""" + # GIVEN: A list of media players + media_players = 'phonon,webkit,vlc' + + # WHEN: The media converter function is called + result = media_players_conv(media_players) + + # THEN: The list should have been converted correctly + assert result == 'system,webkit,vlc' + def test_settings_basic(self): - """ - Test the Settings creation and its default usage - """ + """Test the Settings creation and its default usage""" # GIVEN: A new Settings setup # WHEN reading a setting for the first time @@ -65,10 +75,28 @@ class TestSettings(TestCase, TestMixin): # THEN the new value is returned when re-read self.assertTrue(Settings().value('core/has run wizard'), 'The saved value should have been returned') + def test_set_up_default_values(self): + """Test that the default values are updated""" + # GIVEN: A Settings object with defaults + # WHEN: set_up_default_values() is called + Settings.set_up_default_values() + + # THEN: The default values should have been added to the dictionary + assert 'advanced/default service name' in Settings.__default_settings__ + + def test_get_default_value(self): + """Test that the default value for a setting is returned""" + # GIVEN: A Settings class with a default value + Settings.__default_settings__['test/moo'] = 'baa' + + # WHEN: get_default_value() is called + result = Settings().get_default_value('test/moo') + + # THEN: The correct default value should be returned + assert result == 'baa' + def test_settings_override(self): - """ - Test the Settings creation and its override usage - """ + """Test the Settings creation and its override usage""" # GIVEN: an override for the settings screen_settings = { 'test/extend': 'very wide', @@ -88,9 +116,7 @@ class TestSettings(TestCase, TestMixin): self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned') def test_settings_override_with_group(self): - """ - Test the Settings creation and its override usage - with groups - """ + """Test the Settings creation and its override usage - with groups""" # GIVEN: an override for the settings screen_settings = { 'test/extend': 'very wide', @@ -112,9 +138,7 @@ class TestSettings(TestCase, TestMixin): self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned') def test_settings_nonexisting(self): - """ - Test the Settings on query for non-existing value - """ + """Test the Settings on query for non-existing value""" # GIVEN: A new Settings setup with self.assertRaises(KeyError) as cm: # WHEN reading a setting that doesn't exists @@ -124,9 +148,7 @@ class TestSettings(TestCase, TestMixin): self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception') def test_extend_default_settings(self): - """ - Test that the extend_default_settings method extends the default settings - """ + """Test that the extend_default_settings method extends the default settings""" # GIVEN: A patched __default_settings__ dictionary with patch.dict(Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 3}, True): @@ -138,3 +160,52 @@ class TestSettings(TestCase, TestMixin): self.assertEqual( Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4, 'test/extended 1': 1, 'test/extended 2': 2}) + + @patch('openlp.core.common.settings.QtCore.QSettings.contains') + @patch('openlp.core.common.settings.QtCore.QSettings.value') + @patch('openlp.core.common.settings.QtCore.QSettings.setValue') + @patch('openlp.core.common.settings.QtCore.QSettings.remove') + def test_upgrade_single_setting(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains): + """Test that the upgrade mechanism for settings works correctly for single value upgrades""" + # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones) + local_settings = Settings() + local_settings.__setting_upgrade_99__ = [ + ('single/value', 'single/new value', [(str, '')]) + ] + settings.__version__ = 99 + mocked_value.side_effect = [98, 10] + mocked_contains.return_value = True + + # WHEN: upgrade_settings() is called + local_settings.upgrade_settings() + + # THEN: The correct calls should have been made with the correct values + assert mocked_value.call_count == 2, 'Settings().value() should have been called twice' + assert mocked_value.call_args_list == [call('settings/version', 0), call('single/value')] + assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice' + assert mocked_setValue.call_args_list == [call('single/new value', '10'), call('settings/version', 99)] + mocked_contains.assert_called_once_with('single/value') + mocked_remove.assert_called_once_with('single/value') + + @patch('openlp.core.common.settings.QtCore.QSettings.contains') + @patch('openlp.core.common.settings.QtCore.QSettings.value') + @patch('openlp.core.common.settings.QtCore.QSettings.setValue') + @patch('openlp.core.common.settings.QtCore.QSettings.remove') + def test_upgrade_multiple_one_invalid(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains): + """Test that the upgrade mechanism for settings works correctly for multiple values where one is invalid""" + # GIVEN: A settings object with an upgrade step to take + local_settings = Settings() + local_settings.__setting_upgrade_99__ = [ + (['multiple/value 1', 'multiple/value 2'], 'single/new value', []) + ] + settings.__version__ = 99 + mocked_value.side_effect = [98, 10] + mocked_contains.side_effect = [True, False] + + # WHEN: upgrade_settings() is called + local_settings.upgrade_settings() + + # THEN: The correct calls should have been made with the correct values + mocked_value.assert_called_once_with('settings/version', 0) + mocked_setValue.assert_called_once_with('settings/version', 99) + assert mocked_contains.call_args_list == [call('multiple/value 1'), call('multiple/value 2')] diff --git a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py index f8fee253d..f2fb84c34 100644 --- a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py +++ b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py @@ -29,10 +29,8 @@ from unittest import TestCase from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', 'resources', 'media')) - TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]] - class TestMediainfoWrapper(TestCase): def test_media_length(self): From 66bae6716cccd2f4f712c528c5f2f90702299ee4 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Tue, 14 Nov 2017 17:35:37 +0000 Subject: [PATCH 39/96] chage media replace background context icon, add replace and reset actions to image context menu Fixes: https://launchpad.net/bugs/1650358 --- openlp/plugins/images/lib/mediaitem.py | 14 ++++++++++++++ openlp/plugins/media/lib/mediaitem.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index b678122a5..de347f8ce 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -81,8 +81,12 @@ class ImageMediaItem(MediaManagerItem): self.add_group_action.setToolTip(UiStrings().AddGroupDot) self.replace_action.setText(UiStrings().ReplaceBG) self.replace_action.setToolTip(UiStrings().ReplaceLiveBG) + self.replace_action_context.setText(UiStrings().ReplaceBG) + self.replace_action_context.setToolTip(UiStrings().ReplaceLiveBG) self.reset_action.setText(UiStrings().ResetBG) self.reset_action.setToolTip(UiStrings().ResetLiveBG) + self.reset_action_context.setText(UiStrings().ResetBG) + self.reset_action_context.setToolTip(UiStrings().ResetLiveBG) def required_icons(self): """ @@ -184,6 +188,13 @@ class ImageMediaItem(MediaManagerItem): self.list_view, text=translate('ImagePlugin', 'Add new image(s)'), icon=':/general/general_open.png', triggers=self.on_file_click) + create_widget_action(self.list_view, separator=True) + self.replace_action_context = create_widget_action( + self.list_view, text=UiStrings().ReplaceBG, icon=':/slides/slide_theme.png', + triggers=self.on_replace_click) + self.reset_action_context = create_widget_action( + self.list_view, text=UiStrings().ReplaceLiveBG, icon=':/system/system_close.png', + visible=False, triggers=self.on_reset_click) def add_start_header_bar(self): """ @@ -659,6 +670,7 @@ class ImageMediaItem(MediaManagerItem): Called to reset the Live background with the image selected. """ self.reset_action.setVisible(False) + self.reset_action_context.setVisible(False) self.live_controller.display.reset_image() def live_theme_changed(self): @@ -666,6 +678,7 @@ class ImageMediaItem(MediaManagerItem): Triggered by the change of theme in the slide controller. """ self.reset_action.setVisible(False) + self.reset_action_context.setVisible(False) def on_replace_click(self): """ @@ -683,6 +696,7 @@ class ImageMediaItem(MediaManagerItem): if file_path.exists(): if self.live_controller.display.direct_image(str(file_path), background): self.reset_action.setVisible(True) + self.reset_action_context.setVisible(True) else: critical_error_message_box( UiStrings().LiveBGError, diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index f0ec1dfe2..e47ed70e0 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -176,7 +176,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): def add_custom_context_actions(self): create_widget_action(self.list_view, separator=True) self.replace_action_context = create_widget_action( - self.list_view, text=UiStrings().ReplaceBG, icon=':/slides/slide_blank.png', + self.list_view, text=UiStrings().ReplaceBG, icon=':/slides/slide_theme.png', triggers=self.on_replace_click) self.reset_action_context = create_widget_action( self.list_view, text=UiStrings().ReplaceLiveBG, icon=':/system/system_close.png', From 2ccd6a01f00da6562e80c15b511a5aa6dfc6df4d Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Tue, 14 Nov 2017 22:13:38 +0000 Subject: [PATCH 40/96] Set the default filter in accordance to the current service file type Fixes: https://launchpad.net/bugs/1673251 --- openlp/core/ui/servicemanager.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 3e1d0f544..81edd4930 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -704,15 +704,19 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi directory_path = Settings().value(self.main_window.service_manager_settings_section + '/last directory') if directory_path: default_file_path = directory_path / default_file_path - # SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in - # the long term. lite_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files - lite (*.oszl)') packaged_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)') - + if self._file_name.endswith('oszl'): + default_filter = lite_filter + else: + default_filter = packaged_filter + # SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in + # the long term. if self._file_name.endswith('oszl') or self.service_has_all_original_files: file_path, filter_used = FileDialog.getSaveFileName( self.main_window, UiStrings().SaveService, default_file_path, - '{packaged};; {lite}'.format(packaged=packaged_filter, lite=lite_filter)) + '{packaged};; {lite}'.format(packaged=packaged_filter, lite=lite_filter), + default_filter) else: file_path, filter_used = FileDialog.getSaveFileName( self.main_window, UiStrings().SaveService, default_file_path, From 4fbed73e958f8b62fc60d07013b01cf01572f647 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Tue, 14 Nov 2017 22:30:46 +0000 Subject: [PATCH 41/96] Typo fix --- openlp/plugins/presentations/lib/messagelistener.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 2bfd1b3ed..33f5e0693 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -191,7 +191,7 @@ class Controller(object): """ Based on the handler passed at startup triggers the previous slide event. """ - log.debug('Live = {live}, previous'.formta(live=self.is_live)) + log.debug('Live = {live}, previous'.format(live=self.is_live)) if not self.doc: return if not self.is_live: From d05e39fc70dfd37fc801e79ea4a07b6602355c1f Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Tue, 14 Nov 2017 23:55:57 -0700 Subject: [PATCH 42/96] Fix up test after merges --- tests/functional/openlp_core/common/test_settings.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/functional/openlp_core/common/test_settings.py b/tests/functional/openlp_core/common/test_settings.py index 825057326..5d7e88fbe 100644 --- a/tests/functional/openlp_core/common/test_settings.py +++ b/tests/functional/openlp_core/common/test_settings.py @@ -115,13 +115,14 @@ class TestSettings(TestCase, TestMixin): def test_save_existing_setting(self): """Test that saving an existing setting returns the new value""" # GIVEN: An existing setting + Settings().extend_default_settings({'test/existing value': None}) Settings().setValue('test/existing value', 'old value') # WHEN a new value is saved into config - Settings().setValue('test/extend', 'new value') + Settings().setValue('test/existing value', 'new value') # THEN the new value is returned when re-read - assert Settings().value('test/extend') == 'new value', 'The saved value should be returned' + assert Settings().value('test/existing value') == 'new value', 'The saved value should be returned' def test_settings_override_with_group(self): """Test the Settings creation and its override usage - with groups""" From 8c3fd99ed52ecff8a80d4fe01d414dffca2b95d5 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Wed, 15 Nov 2017 00:03:35 -0700 Subject: [PATCH 43/96] Fix missing line --- .../openlp_core/ui/media/vendor/test_mediainfoWrapper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py index f2fb84c34..6ec2431b9 100644 --- a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py +++ b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py @@ -31,6 +31,7 @@ from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', 'resources', 'media')) TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]] + class TestMediainfoWrapper(TestCase): def test_media_length(self): From 35e13022a640d57fc3fa318ee763646f89271e79 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Wed, 15 Nov 2017 18:33:21 +0000 Subject: [PATCH 44/96] image media test fixes --- tests/functional/openlp_plugins/images/test_lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/functional/openlp_plugins/images/test_lib.py b/tests/functional/openlp_plugins/images/test_lib.py index 4dfef57da..877ad722d 100644 --- a/tests/functional/openlp_plugins/images/test_lib.py +++ b/tests/functional/openlp_plugins/images/test_lib.py @@ -173,12 +173,14 @@ class TestImageMediaItem(TestCase): """ # GIVEN: A mocked version of reset_action self.media_item.reset_action = MagicMock() + self.media_item.reset_action_context = MagicMock() # WHEN: on_reset_click is called self.media_item.on_reset_click() # THEN: the reset_action should be set visible, and the image should be reset self.media_item.reset_action.setVisible.assert_called_with(False) + self.media_item.reset_action_context.setVisible.assert_called_with(False) self.media_item.live_controller.display.reset_image.assert_called_with() @patch('openlp.plugins.images.lib.mediaitem.delete_file') From 3560b27bfb8c6629cbf7668c2e202a6f0929db6e Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Wed, 15 Nov 2017 21:58:19 +0000 Subject: [PATCH 45/96] Use the OLDXML output type for the new version of mediainfo Fixes: https://launchpad.net/bugs/1732348 --- .../core/ui/media/vendor/mediainfoWrapper.py | 20 +++++++++++++++---- .../presentations/lib/pdfcontroller.py | 2 -- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 6f270d46e..1b55fb4bb 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -25,10 +25,10 @@ information related to the rwquested media. """ import json import os -from subprocess import Popen +import re +from subprocess import Popen, check_output from tempfile import mkstemp -import six from bs4 import BeautifulSoup, NavigableString ENV_DICT = os.environ @@ -80,7 +80,7 @@ class Track(object): def to_data(self): data = {} - for k, v in six.iteritems(self.__dict__): + for k, v in self.__dict__.items(): if k != 'xml_dom_fragment': data[k] = v return data @@ -100,7 +100,11 @@ class MediaInfoWrapper(object): @staticmethod def parse(filename, environment=ENV_DICT): - command = ["mediainfo", "-f", "--Output=XML", filename] + if MediaInfoWrapper._version(): + format = 'OLDXML' + else: + format = 'XML' + command = ["mediainfo", "-f", "--Output={format}".format(format=format), filename] fileno_out, fname_out = mkstemp(suffix=".xml", prefix="media-") fileno_err, fname_err = mkstemp(suffix=".err", prefix="media-") fp_out = os.fdopen(fileno_out, 'r+b') @@ -116,6 +120,14 @@ class MediaInfoWrapper(object): os.unlink(fname_err) return MediaInfoWrapper(xml_dom) + @staticmethod + def _version(): + # For now we're only intrested in knowing the new style version (yy.mm) + version_str = check_output(['mediainfo', '--version']) + return re.search(b'\d\d\.\d\d', version_str) + + + def _populate_tracks(self): if self.xml_dom is None: return diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 26eb87d85..715e4e3e7 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -19,8 +19,6 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### - -import os import logging import re from subprocess import check_output, CalledProcessError From 923dc1436e8955be767ba758d4c41f69df284434 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Wed, 15 Nov 2017 17:19:26 -0700 Subject: [PATCH 46/96] Remove monitor migration --- openlp/core/common/settings.py | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index afc75dd25..225feb4e1 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -62,30 +62,6 @@ def media_players_conv(string): return string -def upgrade_monitor(number, x_position, y_position, height, width, can_override, can_display_on_monitor): - """ - Upgrade them monitor setting from a few single entries to a composite JSON entry - - :param int number: The old monitor number - :param int x_position: The X position - :param int y_position: The Y position - :param bool can_override: Are the screen positions overridden - :param bool can_display_on_monitor: Can OpenLP display on the monitor - :returns dict: Dictionary with the new value - """ - return { - number: { - 'displayGeometry': { - 'x': x_position, - 'y': y_position, - 'height': height, - 'width': width - } - }, - 'canDisplayOnMonitor': can_display_on_monitor - } - - class Settings(QtCore.QSettings): """ Class to wrap QSettings. @@ -283,9 +259,7 @@ class Settings(QtCore.QSettings): ('media/last directory', 'media/last directory', [(str_to_path, None)]), ('songuasge/db password', 'songusage/db password', []), ('songuasge/db hostname', 'songusage/db hostname', []), - ('songuasge/db database', 'songusage/db database', []), - (['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override', - 'core/display on monitor'], 'core/monitors', [(upgrade_monitor, [1, 0, 0, None, None, False, False])]) + ('songuasge/db database', 'songusage/db database', []) ] @staticmethod From fad67f733932f10c1ca0de91fb439f05a5554135 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Wed, 15 Nov 2017 22:03:19 -0700 Subject: [PATCH 47/96] Add some more tests --- nose2.cfg | 10 +-- .../openlp_core/common/test_settings.py | 74 +++++++++++++++++++ 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/nose2.cfg b/nose2.cfg index ba02ed419..ae73407d7 100644 --- a/nose2.cfg +++ b/nose2.cfg @@ -1,5 +1,5 @@ [unittest] -verbose = true +verbose = True plugins = nose2.plugins.mp [log-capture] @@ -9,16 +9,16 @@ filter = -nose log-level = ERROR [test-result] -always-on = true -descriptions = true +always-on = True +descriptions = True [coverage] -always-on = true +always-on = True coverage = openlp coverage-report = html [multiprocess] -always-on = false +always-on = False processes = 4 [output-buffer] diff --git a/tests/functional/openlp_core/common/test_settings.py b/tests/functional/openlp_core/common/test_settings.py index 5d7e88fbe..ff6ee8765 100644 --- a/tests/functional/openlp_core/common/test_settings.py +++ b/tests/functional/openlp_core/common/test_settings.py @@ -22,6 +22,7 @@ """ Package to test the openlp.core.lib.settings package. """ +from pathlib import Path from unittest import TestCase from unittest.mock import call, patch @@ -196,6 +197,32 @@ class TestSettings(TestCase, TestMixin): mocked_contains.assert_called_once_with('single/value') mocked_remove.assert_called_once_with('single/value') + @patch('openlp.core.common.settings.QtCore.QSettings.contains') + @patch('openlp.core.common.settings.QtCore.QSettings.value') + @patch('openlp.core.common.settings.QtCore.QSettings.setValue') + @patch('openlp.core.common.settings.QtCore.QSettings.remove') + def test_upgrade_setting_value(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains): + """Test that the upgrade mechanism for settings correctly uses the new value when it's not a function""" + # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones) + local_settings = Settings() + local_settings.__setting_upgrade_99__ = [ + ('values/old value', 'values/new value', [(True, 1)]) + ] + settings.__version__ = 99 + mocked_value.side_effect = [98, 1] + mocked_contains.return_value = True + + # WHEN: upgrade_settings() is called + local_settings.upgrade_settings() + + # THEN: The correct calls should have been made with the correct values + assert mocked_value.call_count == 2, 'Settings().value() should have been called twice' + assert mocked_value.call_args_list == [call('settings/version', 0), call('values/old value')] + assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice' + assert mocked_setValue.call_args_list == [call('values/new value', True), call('settings/version', 99)] + mocked_contains.assert_called_once_with('values/old value') + mocked_remove.assert_called_once_with('values/old value') + @patch('openlp.core.common.settings.QtCore.QSettings.contains') @patch('openlp.core.common.settings.QtCore.QSettings.value') @patch('openlp.core.common.settings.QtCore.QSettings.setValue') @@ -218,3 +245,50 @@ class TestSettings(TestCase, TestMixin): mocked_value.assert_called_once_with('settings/version', 0) mocked_setValue.assert_called_once_with('settings/version', 99) assert mocked_contains.call_args_list == [call('multiple/value 1'), call('multiple/value 2')] + + def test_can_upgrade(self): + """Test the Settings.can_upgrade() method""" + # GIVEN: A Settings object + local_settings = Settings() + + # WHEN: can_upgrade() is run + result = local_settings.can_upgrade() + + # THEN: The result should be True + assert result is True, 'The settings should be upgradeable' + + def test_convert_value_setting_none_str(self): + """Test the Settings._convert_value() method when a setting is None and the default value is a string""" + # GIVEN: A settings object + # WHEN: _convert_value() is run + result = Settings()._convert_value(None, 'string') + + # THEN: The result should be an empty string + assert result == '', 'The result should be an empty string' + + def test_convert_value_setting_none_list(self): + """Test the Settings._convert_value() method when a setting is None and the default value is a list""" + # GIVEN: A settings object + # WHEN: _convert_value() is run + result = Settings()._convert_value(None, [None]) + + # THEN: The result should be an empty list + assert result == [], 'The result should be an empty list' + + def test_convert_value_setting_json_Path(self): + """Test the Settings._convert_value() method when a setting is JSON and represents a Path object""" + # GIVEN: A settings object + # WHEN: _convert_value() is run + result = Settings()._convert_value('{"__Path__": ["openlp", "core"]}', None) + + # THEN: The result should be a Path object + assert isinstance(result, Path), 'The result should be a Path object' + + def test_convert_value_setting_bool_str(self): + """Test the Settings._convert_value() method when a setting is supposed to be a boolean""" + # GIVEN: A settings object + # WHEN: _convert_value() is run + result = Settings()._convert_value('false', True) + + # THEN: The result should be False + assert result is False, 'The result should be False' From ab810734d86489eb8721d5b95ad78db5a32cd378 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Thu, 16 Nov 2017 17:17:47 +0000 Subject: [PATCH 48/96] Rework mediainfo fix Fixes: https://launchpad.net/bugs/1732348 --- .../core/ui/media/vendor/mediainfoWrapper.py | 30 +++---------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 1b55fb4bb..3045bf070 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -100,34 +100,12 @@ class MediaInfoWrapper(object): @staticmethod def parse(filename, environment=ENV_DICT): - if MediaInfoWrapper._version(): - format = 'OLDXML' - else: - format = 'XML' - command = ["mediainfo", "-f", "--Output={format}".format(format=format), filename] - fileno_out, fname_out = mkstemp(suffix=".xml", prefix="media-") - fileno_err, fname_err = mkstemp(suffix=".err", prefix="media-") - fp_out = os.fdopen(fileno_out, 'r+b') - fp_err = os.fdopen(fileno_err, 'r+b') - p = Popen(command, stdout=fp_out, stderr=fp_err, env=environment) - p.wait() - fp_out.seek(0) - - xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(fp_out.read()) - fp_out.close() - fp_err.close() - os.unlink(fname_out) - os.unlink(fname_err) + xml = check_output(['mediainfo', '-f', '--Output=XML', '--Inform=OLDXML', filename]) + if not xml.startswith(b'<?xml'): + xml = check_output(['mediainfo', '-f', '--Output=XML', filename]) + xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(xml) return MediaInfoWrapper(xml_dom) - @staticmethod - def _version(): - # For now we're only intrested in knowing the new style version (yy.mm) - version_str = check_output(['mediainfo', '--version']) - return re.search(b'\d\d\.\d\d', version_str) - - - def _populate_tracks(self): if self.xml_dom is None: return From d216766586b8eeca1f7d5bd7d27eebca9ac302af Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Thu, 16 Nov 2017 17:43:17 +0000 Subject: [PATCH 49/96] Remove some unused imports --- openlp/core/ui/media/vendor/mediainfoWrapper.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 3045bf070..97d936d6b 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -25,9 +25,7 @@ information related to the rwquested media. """ import json import os -import re -from subprocess import Popen, check_output -from tempfile import mkstemp +from subprocess import check_output from bs4 import BeautifulSoup, NavigableString From c46cd951ef083696ea502bb7f5db9a254441e924 Mon Sep 17 00:00:00 2001 From: Ken Roberts <alisonken1@gmail.com> Date: Thu, 16 Nov 2017 15:53:53 -0800 Subject: [PATCH 50/96] Projector imports to fully-qualified, renamed projector dock --- openlp/core/projectors/__init__.py | 16 +++------------- openlp/core/ui/__init__.py | 6 +++--- openlp/core/ui/firsttimewizard.py | 4 ++-- openlp/core/ui/mainwindow.py | 6 +++--- openlp/core/ui/settingsform.py | 2 +- .../projectors/test_projector_pjlink_base.py | 3 ++- .../test_projector_pjlink_cmd_routing.py | 3 ++- .../projectors/test_projector_pjlink_commands.py | 3 ++- .../openlp_core/ui/test_projectoreditform.py | 4 +++- .../openlp_core/ui/test_projectormanager.py | 4 +++- 10 files changed, 24 insertions(+), 27 deletions(-) diff --git a/openlp/core/projectors/__init__.py b/openlp/core/projectors/__init__.py index a3f052d9b..396422902 100644 --- a/openlp/core/projectors/__init__.py +++ b/openlp/core/projectors/__init__.py @@ -20,27 +20,17 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ - :mod:`openlp.core.ui.projector` + :mod:`openlp.core.projectors` - Initialization for the openlp.core.ui.projector modules. + Initialization for the openlp.core.projectors modules. """ -__all__ = ['PJLINK_PORT', 'ERROR_MSG', 'ERROR_STRING', 'DialogSourceStyle', 'PJLink', 'Projector', - 'ProjectorDB', 'ProjectorEditForm', 'ProjectorManager', 'ProjectorTab'] +from openlp.core.projectors.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING -# Due to circular dependencies, put the imports after defines class DialogSourceStyle(object): """ An enumeration for projector dialog box type. """ Tabbed = 0 Single = 1 - - -from .constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING -from .db import Projector, ProjectorDB -from .editform import ProjectorEditForm -from .manager import ProjectorManager -from .pjlink import PJLink -from .tab import ProjectorTab diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 2bf3e4e47..5c2463e5d 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -116,9 +116,9 @@ from .shortcutlistform import ShortcutListForm from .servicemanager import ServiceManager from .thememanager import ThemeManager -from openlp.core.projectors import ProjectorManager -from openlp.core.projectors import ProjectorTab -from openlp.core.projectors import ProjectorEditForm +from openlp.core.projectors.editform import ProjectorEditForm +from openlp.core.projectors.manager import ProjectorManager +from openlp.core.projectors.tab import ProjectorTab __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm', 'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'Display', 'AudioPlayer', diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index be893cd5e..5212479ef 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -261,8 +261,8 @@ class UiFirstTimeWizard(object): self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Alerts – Display informative messages while showing other slides')) self.projectors_check_box.setText(translate('OpenLP.FirstTimeWizard', - 'Projectors – Control PJLink compatible projects on your network' - ' from OpenLP')) + 'Projector Controller – Control PJLink compatible projects on your' + ' network from OpenLP')) self.no_internet_page.setTitle(translate('OpenLP.FirstTimeWizard', 'No Internet Connection')) self.no_internet_page.setSubTitle( translate('OpenLP.FirstTimeWizard', 'Unable to detect an Internet connection.')) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 1e1a56daa..c1d452193 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -48,6 +48,7 @@ from openlp.core.display.screens import ScreenList from openlp.core.display.renderer import Renderer from openlp.core.lib import PluginManager, ImageManager, PluginStatus, build_icon from openlp.core.lib.ui import create_action +from openlp.core.projectors.manager import ProjectorManager from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \ ShortcutListForm, FormattingTagForm, PreviewController from openlp.core.ui.firsttimeform import FirstTimeForm @@ -55,7 +56,6 @@ from openlp.core.widgets.dialogs import FileDialog from openlp.core.widgets.docks import OpenLPDockWidget, MediaDockManager from openlp.core.ui.media import MediaController from openlp.core.ui.printserviceform import PrintServiceForm -from openlp.core.projectors import ProjectorManager from openlp.core.ui.style import PROGRESSBAR_STYLE, get_library_stylesheet from openlp.core.version import get_version @@ -375,7 +375,7 @@ class Ui_MainWindow(object): self.media_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Library')) self.service_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Service')) self.theme_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Themes')) - self.projector_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Projectors')) + self.projector_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Projector Controller')) self.file_new_item.setText(translate('OpenLP.MainWindow', '&New Service')) self.file_new_item.setToolTip(UiStrings().NewService) self.file_new_item.setStatusTip(UiStrings().CreateService) @@ -407,7 +407,7 @@ class Ui_MainWindow(object): translate('OpenLP.MainWindow', 'Import settings from a *.config file previously exported from ' 'this or another machine.')) self.settings_import_item.setText(translate('OpenLP.MainWindow', 'Settings')) - self.view_projector_manager_item.setText(translate('OpenLP.MainWindow', '&Projectors')) + self.view_projector_manager_item.setText(translate('OpenLP.MainWindow', '&Projector Controller')) self.view_projector_manager_item.setToolTip(translate('OpenLP.MainWindow', 'Hide or show Projectors.')) self.view_projector_manager_item.setStatusTip(translate('OpenLP.MainWindow', 'Toggle visibility of the Projectors.')) diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index 54bd01935..4d7c4ff6c 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -30,9 +30,9 @@ from openlp.core.api import ApiTab from openlp.core.common.mixins import RegistryProperties from openlp.core.common.registry import Registry from openlp.core.lib import build_icon +from openlp.core.projectors.tab import ProjectorTab from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab from openlp.core.ui.media import PlayerTab -from openlp.core.projectors import ProjectorTab from openlp.core.ui.settingsdialog import Ui_SettingsDialog log = logging.getLogger(__name__) diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py index 8c0c17955..75b32d8c1 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py @@ -25,8 +25,9 @@ Package to test the openlp.core.projectors.pjlink base package. from unittest import TestCase from unittest.mock import call, patch, MagicMock -from openlp.core.projectors import PJLink, Projector from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED +from openlp.core.projectors.db import Projector +from openlp.core.projectors.pjlink import PJLink from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py index 92dd24f82..431da0606 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py @@ -27,9 +27,10 @@ from unittest import TestCase from unittest.mock import patch, MagicMock import openlp.core.projectors.pjlink -from openlp.core.projectors import PJLink, Projector from openlp.core.projectors.constants import PJLINK_ERRORS, \ E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED +from openlp.core.projectors.db import Projector +from openlp.core.projectors.pjlink import PJLink ''' from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py index 4907005ee..584b63cf9 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py @@ -26,11 +26,12 @@ from unittest import TestCase from unittest.mock import patch import openlp.core.projectors.pjlink -from openlp.core.projectors import PJLink, Projector from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ PJLINK_POWR_STATUS, \ E_ERROR, E_NOT_CONNECTED, E_SOCKET_ADDRESS_NOT_AVAILABLE, E_UNKNOWN_SOCKET_ERROR, E_WARN, \ S_CONNECTED, S_OFF, S_ON, S_NOT_CONNECTED, S_CONNECTING, S_STANDBY +from openlp.core.projectors.db import Projector +from openlp.core.projectors.pjlink import PJLink from tests.resources.projector.data import TEST_PIN, TEST1_DATA diff --git a/tests/interfaces/openlp_core/ui/test_projectoreditform.py b/tests/interfaces/openlp_core/ui/test_projectoreditform.py index 3f328cb04..ec2539a29 100644 --- a/tests/interfaces/openlp_core/ui/test_projectoreditform.py +++ b/tests/interfaces/openlp_core/ui/test_projectoreditform.py @@ -28,7 +28,9 @@ from unittest import TestCase from unittest.mock import patch from openlp.core.common.registry import Registry -from openlp.core.projectors import Projector, ProjectorDB, ProjectorEditForm, ProjectorManager +from openlp.core.projectors.db import Projector, ProjectorDB +from openlp.core.projectors.editform import ProjectorEditForm +from openlp.core.projectors.manager import ProjectorManager from tests.helpers.testmixin import TestMixin from tests.resources.projector.data import TEST_DB, TEST1_DATA, TEST2_DATA diff --git a/tests/interfaces/openlp_core/ui/test_projectormanager.py b/tests/interfaces/openlp_core/ui/test_projectormanager.py index e26a5cfe2..484d4d68a 100644 --- a/tests/interfaces/openlp_core/ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core/ui/test_projectormanager.py @@ -27,7 +27,9 @@ from unittest import TestCase from unittest.mock import patch, MagicMock from openlp.core.common.registry import Registry -from openlp.core.projectors import ProjectorDB, ProjectorEditForm, ProjectorManager +from openlp.core.projectors.db import ProjectorDB +from openlp.core.projectors.editform import ProjectorEditForm +from openlp.core.projectors.manager import ProjectorManager from tests.helpers.testmixin import TestMixin from tests.resources.projector.data import TEST_DB From 9196db5af0d4c83370b8fcd4f27e39bdf843fa82 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Sat, 18 Nov 2017 11:23:15 +0000 Subject: [PATCH 51/96] Pathlib refactors and test fixes --- openlp/core/api/deploy.py | 15 ++-- openlp/core/api/endpoint/controller.py | 11 +-- openlp/core/api/endpoint/pluginhelpers.py | 15 ++-- openlp/core/api/http/endpoint.py | 12 +-- openlp/core/api/http/wsgiapp.py | 8 +- openlp/core/common/path.py | 3 + openlp/core/display/renderer.py | 4 +- openlp/core/lib/__init__.py | 9 +- openlp/core/lib/plugin.py | 17 +++- openlp/core/ui/maindisplay.py | 2 + openlp/core/ui/mainwindow.py | 7 +- openlp/core/ui/thememanager.py | 13 +-- openlp/core/version.py | 11 +-- openlp/plugins/images/lib/mediaitem.py | 2 +- openlp/plugins/presentations/lib/mediaitem.py | 4 +- .../lib/presentationcontroller.py | 2 +- .../functional/openlp_core/api/test_deploy.py | 26 +++--- tests/functional/openlp_core/lib/test_lib.py | 86 ++++++++++--------- .../openlp_core/ui/test_thememanager.py | 8 +- .../presentations/test_pdfcontroller.py | 20 ++--- .../openlp_core/lib/test_pluginmanager.py | 7 +- 21 files changed, 143 insertions(+), 139 deletions(-) diff --git a/openlp/core/api/deploy.py b/openlp/core/api/deploy.py index 0419b45db..b64cc40d5 100644 --- a/openlp/core/api/deploy.py +++ b/openlp/core/api/deploy.py @@ -22,7 +22,6 @@ """ Download and "install" the remote web client """ -import os from zipfile import ZipFile from openlp.core.common.applocation import AppLocation @@ -30,18 +29,18 @@ from openlp.core.common.registry import Registry from openlp.core.common.httputils import url_get_file, get_web_page, get_url_file_size -def deploy_zipfile(app_root, zip_name): +def deploy_zipfile(app_root_path, zip_name): """ Process the downloaded zip file and add to the correct directory - :param zip_name: the zip file to be processed - :param app_root: the directory where the zip get expanded to + :param str zip_name: the zip file name to be processed + :param openlp.core.common.path.Path app_root_path: The directory to expand the zip to :return: None """ - zip_file = os.path.join(app_root, zip_name) - web_zip = ZipFile(zip_file) - web_zip.extractall(app_root) + zip_path = app_root_path / zip_name + web_zip = ZipFile(str(zip_path)) + web_zip.extractall(str(app_root_path)) def download_sha256(): @@ -67,4 +66,4 @@ def download_and_check(callback=None): if url_get_file(callback, 'https://get.openlp.org/webclient/site.zip', AppLocation.get_section_data_path('remotes') / 'site.zip', sha256=sha256): - deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip') + deploy_zipfile(AppLocation.get_section_data_path('remotes'), 'site.zip') diff --git a/openlp/core/api/endpoint/controller.py b/openlp/core/api/endpoint/controller.py index 8ecfdb732..13e8d1681 100644 --- a/openlp/core/api/endpoint/controller.py +++ b/openlp/core/api/endpoint/controller.py @@ -28,6 +28,7 @@ import json from openlp.core.api.http.endpoint import Endpoint from openlp.core.api.http import requires_auth from openlp.core.common.applocation import AppLocation +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, create_thumb @@ -66,12 +67,12 @@ def controller_text(request): elif current_item.is_image() and not frame.get('image', '') and Settings().value('api/thumbnails'): item['tag'] = str(index + 1) thumbnail_path = os.path.join('images', 'thumbnails', frame['title']) - full_thumbnail_path = str(AppLocation.get_data_path() / thumbnail_path) + full_thumbnail_path = AppLocation.get_data_path() / thumbnail_path # Create thumbnail if it doesn't exists - if not os.path.exists(full_thumbnail_path): - create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False) - Registry().get('image_manager').add_image(full_thumbnail_path, frame['title'], None, 88, 88) - item['img'] = urllib.request.pathname2url(os.path.sep + thumbnail_path) + if not full_thumbnail_path.exists(): + create_thumb(Path(current_item.get_frame_path(index)), full_thumbnail_path, False) + Registry().get('image_manager').add_image(str(full_thumbnail_path), frame['title'], None, 88, 88) + item['img'] = urllib.request.pathname2url(os.path.sep + str(thumbnail_path)) item['text'] = str(frame['title']) item['html'] = str(frame['title']) else: diff --git a/openlp/core/api/endpoint/pluginhelpers.py b/openlp/core/api/endpoint/pluginhelpers.py index 9377bde6a..b8f606fbc 100644 --- a/openlp/core/api/endpoint/pluginhelpers.py +++ b/openlp/core/api/endpoint/pluginhelpers.py @@ -19,7 +19,6 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -import os import json import re import urllib @@ -103,7 +102,7 @@ def display_thumbnails(request, controller_name, log, dimensions, file_name, sli :param controller_name: which controller is requesting the image :param log: the logger object :param dimensions: the image size eg 88x88 - :param file_name: the file name of the image + :param str file_name: the file name of the image :param slide: the individual image name :return: """ @@ -124,12 +123,10 @@ def display_thumbnails(request, controller_name, log, dimensions, file_name, sli if controller_name and file_name: file_name = urllib.parse.unquote(file_name) if '..' not in file_name: # no hacking please + full_path = AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name if slide: - full_path = str(AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name / slide) - else: - full_path = str(AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name) - if os.path.exists(full_path): - path, just_file_name = os.path.split(full_path) - Registry().get('image_manager').add_image(full_path, just_file_name, None, width, height) - image = Registry().get('image_manager').get_image(full_path, just_file_name, width, height) + full_path = full_path / slide + if full_path.exists(): + Registry().get('image_manager').add_image(full_path, full_path.name, None, width, height) + image = Registry().get('image_manager').get_image(full_path, full_path.name, width, height) return Response(body=image_to_byte(image, False), status=200, content_type='image/png', charset='utf8') diff --git a/openlp/core/api/http/endpoint.py b/openlp/core/api/http/endpoint.py index fe2b11d9a..011b7e73a 100644 --- a/openlp/core/api/http/endpoint.py +++ b/openlp/core/api/http/endpoint.py @@ -22,8 +22,6 @@ """ The Endpoint class, which provides plugins with a way to serve their own portion of the API """ -import os - from mako.template import Template from openlp.core.common.applocation import AppLocation @@ -67,13 +65,17 @@ class Endpoint(object): def render_template(self, filename, **kwargs): """ Render a mako template + + :param str filename: The file name of the template to render. + :return: The rendered template object. + :rtype: mako.template.Template """ - root = str(AppLocation.get_section_data_path('remotes')) + root_path = AppLocation.get_section_data_path('remotes') if not self.template_dir: raise Exception('No template directory specified') - path = os.path.join(root, self.template_dir, filename) + path = root_path / self.template_dir / filename if self.static_dir: kwargs['static_url'] = '/{prefix}/static'.format(prefix=self.url_prefix) kwargs['static_url'] = kwargs['static_url'].replace('//', '/') kwargs['assets_url'] = '/assets' - return Template(filename=path, input_encoding='utf-8').render(**kwargs) + return Template(filename=str(path), input_encoding='utf-8').render(**kwargs) diff --git a/openlp/core/api/http/wsgiapp.py b/openlp/core/api/http/wsgiapp.py index f948d4096..4c863b7cb 100644 --- a/openlp/core/api/http/wsgiapp.py +++ b/openlp/core/api/http/wsgiapp.py @@ -25,7 +25,6 @@ App stuff """ import json import logging -import os import re from webob import Request, Response @@ -138,12 +137,11 @@ class WSGIApplication(object): Add a static directory as a route """ if route not in self.static_routes: - root = str(AppLocation.get_section_data_path('remotes')) - static_path = os.path.abspath(os.path.join(root, static_dir)) - if not os.path.exists(static_path): + static_path = AppLocation.get_section_data_path('remotes') / static_dir + if not static_path.exists(): log.error('Static path "%s" does not exist. Skipping creating static route/', static_path) return - self.static_routes[route] = DirectoryApp(static_path) + self.static_routes[route] = DirectoryApp(str(static_path.resolve())) def dispatch(self, request): """ diff --git a/openlp/core/common/path.py b/openlp/core/common/path.py index 6b89acfb5..05d2ea4eb 100644 --- a/openlp/core/common/path.py +++ b/openlp/core/common/path.py @@ -69,6 +69,9 @@ class Path(PathVariant): path = path.relative_to(base_path) return {'__Path__': path.parts} + def rmtree(self, *args, **kwargs): + shutil.rmtree(str(self), *args, **kwargs) + def replace_params(args, kwargs, params): """ diff --git a/openlp/core/display/renderer.py b/openlp/core/display/renderer.py index 9dd7f3d1f..836e5edea 100644 --- a/openlp/core/display/renderer.py +++ b/openlp/core/display/renderer.py @@ -198,6 +198,7 @@ class Renderer(RegistryBase, LogMixin, RegistryProperties): :param theme_data: The theme to generated a preview for. :param force_page: Flag to tell message lines per page need to be generated. + :rtype: QtGui.QPixmap """ # save value for use in format_slide self.force_page = force_page @@ -222,8 +223,7 @@ class Renderer(RegistryBase, LogMixin, RegistryProperties): self.display.build_html(service_item) raw_html = service_item.get_rendered_frame(0) self.display.text(raw_html, False) - preview = self.display.preview() - return preview + return self.display.preview() self.force_page = False def format_slide(self, text, item): diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 9bab13b71..49bae26eb 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -179,8 +179,9 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None): height of 88 is used. :return: The final icon. """ - ext = os.path.splitext(thumb_path)[1].lower() - reader = QtGui.QImageReader(image_path) + # TODO: To path object + thumb_path = Path(thumb_path) + reader = QtGui.QImageReader(str(image_path)) if size is None: # No size given; use default height of 88 if reader.size().isEmpty(): @@ -207,10 +208,10 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None): # Invalid; use default height of 88 reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88)) thumb = reader.read() - thumb.save(thumb_path, ext[1:]) + thumb.save(str(thumb_path), thumb_path.suffix[1:].lower()) if not return_icon: return - if os.path.exists(thumb_path): + if thumb_path.exists(): return build_icon(thumb_path) # Fallback for files with animation support. return build_icon(image_path) diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index f155b3ce7..7e3b2e416 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -139,10 +139,6 @@ class Plugin(QtCore.QObject, RegistryProperties): self.text_strings = {} self.set_plugin_text_strings() self.name_strings = self.text_strings[StringContent.Name] - if version: - self.version = version - else: - self.version = get_version()['version'] self.settings_section = self.name self.icon = None self.media_item_class = media_item_class @@ -162,6 +158,19 @@ class Plugin(QtCore.QObject, RegistryProperties): Settings.extend_default_settings(default_settings) Registry().register_function('{name}_add_service_item'.format(name=self.name), self.process_add_service_event) Registry().register_function('{name}_config_updated'.format(name=self.name), self.config_update) + self._setup(version) + + def _setup(self, version): + """ + Run some initial setup. This method is separate from __init__ in order to mock it out in tests. + + :param version: Defaults to *None*, which means that the same version number is used as OpenLP's version number. + :rtype: None + """ + if version: + self.version = version + else: + self.version = get_version()['version'] def check_pre_conditions(self): """ diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index ca634910e..daf6a165c 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -398,6 +398,8 @@ class MainDisplay(Display, LogMixin, RegistryProperties): def preview(self): """ Generates a preview of the image displayed. + + :rtype: QtGui.QPixmap """ was_visible = self.isVisible() self.application.process_events() diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index c0e704afb..60fabdf58 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -296,10 +296,9 @@ class Ui_MainWindow(object): # Give QT Extra Hint that this is an About Menu Item self.about_item.setMenuRole(QtWidgets.QAction.AboutRole) if is_win(): - self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'OpenLP.chm') + self.local_help_file = AppLocation.get_directory(AppLocation.AppDir) / 'OpenLP.chm' elif is_macosx(): - self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), - '..', 'Resources', 'OpenLP.help') + self.local_help_file = AppLocation.get_directory(AppLocation.AppDir) / '..' / 'Resources' / 'OpenLP.help' self.user_manual_item = create_action(main_window, 'userManualItem', icon=':/system/system_help_contents.png', can_shortcuts=True, category=UiStrings().Help, triggers=self.on_help_clicked) @@ -760,7 +759,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): Use the Online manual in other cases. (Linux) """ if is_macosx() or is_win(): - QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(self.local_help_file)) + QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(str(self.local_help_file))) else: import webbrowser webbrowser.open_new('http://manual.openlp.org/') diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 1b39e5fec..fa8a9e4d9 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -497,12 +497,12 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R name = translate('OpenLP.ThemeManager', '{name} (default)').format(name=text_name) else: name = text_name - thumb = self.thumb_path / '{name}.png'.format(name=text_name) + thumb_path = self.thumb_path / '{name}.png'.format(name=text_name) item_name = QtWidgets.QListWidgetItem(name) - if validate_thumb(theme_path, thumb): - icon = build_icon(thumb) + if validate_thumb(theme_path, thumb_path): + icon = build_icon(thumb_path) else: - icon = create_thumb(str(theme_path), str(thumb)) + icon = create_thumb(theme_path, thumb_path) item_name.setIcon(icon) item_name.setData(QtCore.Qt.UserRole, text_name) self.theme_list_widget.addItem(item_name) @@ -692,7 +692,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R sample_path_name.unlink() frame.save(str(sample_path_name), 'png') thumb_path = self.thumb_path / '{name}.png'.format(name=theme_name) - create_thumb(str(sample_path_name), str(thumb_path), False) + create_thumb(sample_path_name, thumb_path, False) def update_preview_images(self): """ @@ -710,7 +710,8 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R Call the renderer to build a Sample Image :param theme_data: The theme to generated a preview for. - :param force_page: Flag to tell message lines per page need to be generated. + :param force_page: Flag to tell message lines per page need to be generated.7 + :rtype: QtGui.QPixmap """ return self.renderer.generate_preview(theme_data, force_page) diff --git a/openlp/core/version.py b/openlp/core/version.py index 314c4865f..6ba1b9ecc 100644 --- a/openlp/core/version.py +++ b/openlp/core/version.py @@ -23,7 +23,6 @@ The :mod:`openlp.core.version` module downloads the version details for OpenLP. """ import logging -import os import platform import sys import time @@ -176,18 +175,12 @@ def get_version(): full_version = '{tag}-bzr{tree}'.format(tag=tag_version.strip(), tree=tree_revision.strip()) else: # We're not running the development version, let's use the file. - file_path = str(AppLocation.get_directory(AppLocation.VersionDir)) - file_path = os.path.join(file_path, '.version') - version_file = None + file_path = AppLocation.get_directory(AppLocation.VersionDir) / '.version' try: - version_file = open(file_path, 'r') - full_version = str(version_file.read()).rstrip() + full_version = file_path.read_text().rstrip() except OSError: log.exception('Error in version file.') full_version = '0.0.0-bzr000' - finally: - if version_file: - version_file.close() bits = full_version.split('-') APPLICATION_VERSION = { 'full': full_version, diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index de347f8ce..552a63d44 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -377,7 +377,7 @@ class ImageMediaItem(MediaManagerItem): if validate_thumb(image.file_path, thumbnail_path): icon = build_icon(thumbnail_path) else: - icon = create_thumb(str(image.file_path), str(thumbnail_path)) + icon = create_thumb(image.file_path,thumbnail_path) item_name = QtWidgets.QTreeWidgetItem([file_name]) item_name.setText(0, file_name) item_name.setIcon(0, icon) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index f58ba4861..923953040 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -198,10 +198,10 @@ class PresentationMediaItem(MediaManagerItem): if not (preview_path and preview_path.exists()): icon = build_icon(':/general/general_delete.png') else: - if validate_thumb(Path(preview_path), Path(thumbnail_path)): + if validate_thumb(preview_path, thumbnail_path): icon = build_icon(thumbnail_path) else: - icon = create_thumb(str(preview_path), str(thumbnail_path)) + icon = create_thumb(preview_path, thumbnail_path) else: if initial_load: icon = build_icon(':/general/general_delete.png') diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index bb424c9fa..db5786626 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -267,7 +267,7 @@ class PresentationDocument(object): return if image_path.is_file(): thumb_path = self.get_thumbnail_path(index, False) - create_thumb(str(image_path), str(thumb_path), False, QtCore.QSize(-1, 360)) + create_thumb(image_path, thumb_path, False, QtCore.QSize(-1, 360)) def get_thumbnail_path(self, slide_no, check_exists=False): """ diff --git a/tests/functional/openlp_core/api/test_deploy.py b/tests/functional/openlp_core/api/test_deploy.py index be36fb9c7..702e2a35d 100644 --- a/tests/functional/openlp_core/api/test_deploy.py +++ b/tests/functional/openlp_core/api/test_deploy.py @@ -19,15 +19,13 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### - -import os -import shutil from tempfile import mkdtemp from unittest import TestCase from openlp.core.api.deploy import deploy_zipfile +from openlp.core.common.path import Path, copyfile -TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources')) +TEST_PATH = (Path(__file__).parent / '..' / '..' / '..' / 'resources').resolve() class TestRemoteDeploy(TestCase): @@ -39,25 +37,25 @@ class TestRemoteDeploy(TestCase): """ Setup for tests """ - self.app_root = mkdtemp() + self.app_root_path = Path(mkdtemp()) def tearDown(self): """ Clean up after tests """ - shutil.rmtree(self.app_root) + self.app_root_path.rmtree() def test_deploy_zipfile(self): """ Remote Deploy tests - test the dummy zip file is processed correctly """ # GIVEN: A new downloaded zip file - aa = TEST_PATH - zip_file = os.path.join(TEST_PATH, 'remotes', 'site.zip') - app_root = os.path.join(self.app_root, 'site.zip') - shutil.copyfile(zip_file, app_root) - # WHEN: I process the zipfile - deploy_zipfile(self.app_root, 'site.zip') + zip_path = TEST_PATH / 'remotes' / 'site.zip' + app_root_path = self.app_root_path / 'site.zip' + copyfile(zip_path, app_root_path) - # THEN test if www directory has been created - self.assertTrue(os.path.isdir(os.path.join(self.app_root, 'www')), 'We should have a www directory') + # WHEN: I process the zipfile + deploy_zipfile(self.app_root_path, 'site.zip') + + # THEN: test if www directory has been created + self.assertTrue((self.app_root_path / 'www').is_dir(), 'We should have a www directory') diff --git a/tests/functional/openlp_core/lib/test_lib.py b/tests/functional/openlp_core/lib/test_lib.py index 1352b5da5..67649fa49 100644 --- a/tests/functional/openlp_core/lib/test_lib.py +++ b/tests/functional/openlp_core/lib/test_lib.py @@ -274,32 +274,32 @@ class TestLib(TestCase): Test the create_thumb() function with a given size. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') thumb_size = QtCore.QSize(10, 20) # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created and scaled to the given size. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(thumb_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(thumb_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -308,14 +308,14 @@ class TestLib(TestCase): Test the create_thumb() function with no size specified. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') expected_size = QtCore.QSize(63, 88) # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -326,14 +326,15 @@ class TestLib(TestCase): icon = create_thumb(image_path, thumb_path) # THEN: Check if the thumb was created, retaining its aspect ratio. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), + 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -342,33 +343,34 @@ class TestLib(TestCase): Test the create_thumb() function with invalid size specified. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') thumb_size = QtCore.QSize(-1, -1) expected_size = QtCore.QSize(63, 88) # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created, retaining its aspect ratio. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), + 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -377,33 +379,33 @@ class TestLib(TestCase): Test the create_thumb() function with a size of only width specified. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') thumb_size = QtCore.QSize(100, -1) expected_size = QtCore.QSize(100, 137) # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created, retaining its aspect ratio. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -412,33 +414,33 @@ class TestLib(TestCase): Test the create_thumb() function with a size of only height specified. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') thumb_size = QtCore.QSize(-1, 100) expected_size = QtCore.QSize(72, 100) # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created, retaining its aspect ratio. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass @@ -447,8 +449,8 @@ class TestLib(TestCase): Test the create_thumb() function with a size of only height specified. """ # GIVEN: An image to create a thumb of. - image_path = os.path.join(TEST_PATH, 'church.jpg') - thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg') + image_path = Path(TEST_PATH, 'church.jpg') + thumb_path = Path(TEST_PATH, 'church_thumb.jpg') thumb_size = QtCore.QSize(-1, 100) expected_size_1 = QtCore.QSize(88, 88) expected_size_2 = QtCore.QSize(100, 100) @@ -456,12 +458,12 @@ class TestLib(TestCase): # Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the # last test. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size: @@ -469,10 +471,10 @@ class TestLib(TestCase): icon = create_thumb(image_path, thumb_path, size=None) # THEN: Check if the thumb was created with aspect ratio of 1. - self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size_1, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size_1, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # WHEN: Create the thumb. with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size: @@ -482,11 +484,11 @@ class TestLib(TestCase): # THEN: Check if the thumb was created with aspect ratio of 1. self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size_2, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') + self.assertEqual(expected_size_2, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: - os.remove(thumb_path) + thumb_path.unlink() except: pass diff --git a/tests/functional/openlp_core/ui/test_thememanager.py b/tests/functional/openlp_core/ui/test_thememanager.py index 62bf980b4..f44f558b0 100644 --- a/tests/functional/openlp_core/ui/test_thememanager.py +++ b/tests/functional/openlp_core/ui/test_thememanager.py @@ -199,16 +199,16 @@ class TestThemeManager(TestCase): theme_manager._create_theme_from_xml = MagicMock() theme_manager.generate_and_save_image = MagicMock() theme_manager.theme_path = None - folder = Path(mkdtemp()) + folder_path = Path(mkdtemp()) theme_file = Path(TEST_RESOURCES_PATH, 'themes', 'Moss_on_tree.otz') # WHEN: We try to unzip it - theme_manager.unzip_theme(theme_file, folder) + theme_manager.unzip_theme(theme_file, folder_path) # THEN: Files should be unpacked - self.assertTrue((folder / 'Moss on tree' / 'Moss on tree.xml').exists()) + self.assertTrue((folder_path / 'Moss on tree' / 'Moss on tree.xml').exists()) self.assertEqual(mocked_critical_error_message_box.call_count, 0, 'No errors should have happened') - shutil.rmtree(str(folder)) + folder_path.rmtree() def test_unzip_theme_invalid_version(self): """ diff --git a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py index a7281e062..9bd492983 100644 --- a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py +++ b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py @@ -67,10 +67,10 @@ class TestPdfController(TestCase, TestMixin): self.desktop.screenGeometry.return_value = SCREEN['size'] self.screens = ScreenList.create(self.desktop) Settings().extend_default_settings(__default_settings__) - self.temp_folder = Path(mkdtemp()) - self.thumbnail_folder = Path(mkdtemp()) + self.temp_folder_path = Path(mkdtemp()) + self.thumbnail_folder_path = Path(mkdtemp()) self.mock_plugin = MagicMock() - self.mock_plugin.settings_section = self.temp_folder + self.mock_plugin.settings_section = self.temp_folder_path def tearDown(self): """ @@ -78,8 +78,8 @@ class TestPdfController(TestCase, TestMixin): """ del self.screens self.destroy_settings() - shutil.rmtree(str(self.thumbnail_folder)) - shutil.rmtree(str(self.temp_folder)) + self.thumbnail_folder_path.rmtree() + self.temp_folder_path.rmtree() def test_constructor(self): """ @@ -105,8 +105,8 @@ class TestPdfController(TestCase, TestMixin): controller = PdfController(plugin=self.mock_plugin) if not controller.check_available(): raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test') - controller.temp_folder = self.temp_folder - controller.thumbnail_folder = self.thumbnail_folder + controller.temp_folder = self.temp_folder_path + controller.thumbnail_folder = self.thumbnail_folder_path document = PdfDocument(controller, test_file) loaded = document.load_presentation() @@ -125,14 +125,14 @@ class TestPdfController(TestCase, TestMixin): controller = PdfController(plugin=self.mock_plugin) if not controller.check_available(): raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test') - controller.temp_folder = self.temp_folder - controller.thumbnail_folder = self.thumbnail_folder + controller.temp_folder = self.temp_folder_path + controller.thumbnail_folder = self.thumbnail_folder_path document = PdfDocument(controller, test_file) loaded = document.load_presentation() # THEN: The load should succeed and pictures should be created and have been scales to fit the screen self.assertTrue(loaded, 'The loading of the PDF should succeed.') - image = QtGui.QImage(os.path.join(str(self.temp_folder), 'pdf_test1.pdf', 'mainslide001.png')) + image = QtGui.QImage(os.path.join(str(self.temp_folder_path), 'pdf_test1.pdf', 'mainslide001.png')) # Based on the converter used the resolution will differ a bit if controller.gsbin: self.assertEqual(760, image.height(), 'The height should be 760') diff --git a/tests/interfaces/openlp_core/lib/test_pluginmanager.py b/tests/interfaces/openlp_core/lib/test_pluginmanager.py index 2e7d979a6..588c15520 100644 --- a/tests/interfaces/openlp_core/lib/test_pluginmanager.py +++ b/tests/interfaces/openlp_core/lib/test_pluginmanager.py @@ -23,7 +23,6 @@ Package to test the openlp.core.lib.pluginmanager package. """ import sys -import shutil import gc from tempfile import mkdtemp from unittest import TestCase @@ -50,8 +49,8 @@ class TestPluginManager(TestCase, TestMixin): """ self.setup_application() self.build_settings() - self.temp_dir = Path(mkdtemp('openlp')) - Settings().setValue('advanced/data path', self.temp_dir) + self.temp_dir_path = Path(mkdtemp('openlp')) + Settings().setValue('advanced/data path', self.temp_dir_path) Registry.create() Registry().register('service_list', MagicMock()) self.main_window = QtWidgets.QMainWindow() @@ -64,7 +63,7 @@ class TestPluginManager(TestCase, TestMixin): # On windows we need to manually garbage collect to close sqlalchemy files # to avoid errors when temporary files are deleted. gc.collect() - shutil.rmtree(str(self.temp_dir)) + self.temp_dir_path.rmtree() @patch('openlp.plugins.songusage.lib.db.init_schema') @patch('openlp.plugins.songs.lib.db.init_schema') From f07d6e736c855fb3ae51644420af523da57fc960 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Sat, 18 Nov 2017 22:37:24 +0000 Subject: [PATCH 52/96] Moar pathlib refactors --- openlp/core/api/endpoint/core.py | 12 ---- openlp/core/lib/mediamanageritem.py | 42 ++++++------- openlp/core/lib/pluginmanager.py | 3 +- openlp/core/ui/maindisplay.py | 4 +- openlp/core/ui/mainwindow.py | 59 ++++++++----------- .../presentations/presentationplugin.py | 9 +-- 6 files changed, 53 insertions(+), 76 deletions(-) diff --git a/openlp/core/api/endpoint/core.py b/openlp/core/api/endpoint/core.py index 2988e03aa..8b9bcc4c0 100644 --- a/openlp/core/api/endpoint/core.py +++ b/openlp/core/api/endpoint/core.py @@ -172,15 +172,3 @@ def main_image(request): 'slide_image': 'data:image/png;base64,' + str(image_to_byte(live_controller.slide_image)) } return {'results': result} - - -def get_content_type(file_name): - """ - Examines the extension of the file and determines what the content_type should be, defaults to text/plain - Returns the extension and the content_type - - :param file_name: name of file - """ - ext = os.path.splitext(file_name)[1] - content_type = FILE_TYPES.get(ext, 'text/plain') - return ext, content_type diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index cc884279c..8091b1a2e 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -318,10 +318,10 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): self, self.on_new_prompt, Settings().value(self.settings_section + '/last directory'), self.on_new_file_masks) - log.info('New files(s) {file_paths}'.format(file_paths=file_paths)) + log.info('New file(s) {file_paths}'.format(file_paths=file_paths)) if file_paths: self.application.set_busy_cursor() - self.validate_and_load([path_to_str(path) for path in file_paths]) + self.validate_and_load(file_paths) self.application.set_normal_cursor() def load_file(self, data): @@ -330,23 +330,24 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): :param data: A dictionary containing the list of files to be loaded and the target """ - new_files = [] + new_file_paths = [] error_shown = False for file_name in data['files']: - file_type = file_name.split('.')[-1] - if file_type.lower() not in self.on_new_file_masks: + file_path = str_to_path(file_name) # TODO: + if file_path.suffix[1:].lower() not in self.on_new_file_masks: if not error_shown: - critical_error_message_box(translate('OpenLP.MediaManagerItem', 'Invalid File Type'), - translate('OpenLP.MediaManagerItem', - 'Invalid File {name}.\n' - 'Suffix not supported').format(name=file_name)) + critical_error_message_box( + translate('OpenLP.MediaManagerItem', 'Invalid File Type'), + translate('OpenLP.MediaManagerItem', + 'Invalid File {file_path}.\nFile extension not supported').format( + file_path=file_path)) error_shown = True else: - new_files.append(file_name) - if new_files: + new_file_paths.append(file_path) + if new_file_paths: if 'target' in data: - self.validate_and_load(new_files, data['target']) - self.validate_and_load(new_files) + self.validate_and_load(new_file_paths, data['target']) + self.validate_and_load(new_file_paths) def dnd_move_internal(self, target): """ @@ -356,31 +357,30 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): """ pass - def validate_and_load(self, files, target_group=None): + def validate_and_load(self, file_paths, target_group=None): """ Process a list for files either from the File Dialog or from Drag and Drop - :param files: The files to be loaded. + :param list[openlp.core.common.path.Path] file_paths: The files to be loaded. :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files """ full_list = [] for count in range(self.list_view.count()): - full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole)) + full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole)) # TODO: Path objects duplicates_found = False files_added = False - for file_path in files: - if file_path in full_list: + for file_path in file_paths: + if path_to_str(file_path) in full_list: duplicates_found = True else: files_added = True - full_list.append(file_path) + full_list.append(path_to_str(file_path)) if full_list and files_added: if target_group is None: self.list_view.clear() self.load_list(full_list, target_group) - last_dir = os.path.split(files[0])[0] - Settings().setValue(self.settings_section + '/last directory', Path(last_dir)) + Settings().setValue(self.settings_section + '/last directory', file_paths[0].parent) Settings().setValue('{section}/{section} files'.format(section=self.settings_section), self.get_file_list()) if duplicates_found: critical_error_message_box(UiStrings().Duplicate, diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index 061788b25..d4a02f8c0 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -43,8 +43,7 @@ class PluginManager(RegistryBase, LogMixin, RegistryProperties): """ super(PluginManager, self).__init__(parent) self.log_info('Plugin manager Initialising') - self.base_path = os.path.abspath(str(AppLocation.get_directory(AppLocation.PluginsDir))) - self.log_debug('Base path {path}'.format(path=self.base_path)) + self.log_debug('Base path {path}'.format(path=AppLocation.get_directory(AppLocation.PluginsDir))) self.plugins = [] self.log_info('Plugin manager Initialised') diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index daf6a165c..80cccdb4c 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -29,7 +29,6 @@ Some of the code for this form is based on the examples at: """ import html import logging -import os from PyQt5 import QtCore, QtWidgets, QtWebKit, QtWebKitWidgets, QtGui, QtMultimedia @@ -490,8 +489,7 @@ class MainDisplay(Display, LogMixin, RegistryProperties): service_item = ServiceItem() service_item.title = 'webkit' service_item.processor = 'webkit' - path = os.path.join(str(AppLocation.get_section_data_path('themes')), - self.service_item.theme_data.theme_name) + path = str(AppLocation.get_section_data_path('themes') / self.service_item.theme_data.theme_name) service_item.add_from_command(path, path_to_str(self.service_item.theme_data.background_filename), ':/media/slidecontroller_multimedia.png') diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 60fabdf58..5cdee786e 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -649,8 +649,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): self.application.process_events() plugin.first_time() self.application.process_events() - temp_dir = os.path.join(str(gettempdir()), 'openlp') - shutil.rmtree(temp_dir, True) + temp_path = Path(gettempdir(), 'openlp') + temp_path.rmtree(True) def on_first_time_wizard_clicked(self): """ @@ -1219,7 +1219,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): settings.remove('custom slide') settings.remove('service') settings.beginGroup(self.general_settings_section) - self.recent_files = [path_to_str(file_path) for file_path in settings.value('recent files')] + self.recent_files = settings.value('recent files') settings.endGroup() settings.beginGroup(self.ui_settings_section) self.move(settings.value('main window position')) @@ -1243,7 +1243,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): log.debug('Saving QSettings') settings = Settings() settings.beginGroup(self.general_settings_section) - settings.setValue('recent files', [str_to_path(file) for file in self.recent_files]) + settings.setValue('recent files', self.recent_files) settings.endGroup() settings.beginGroup(self.ui_settings_section) settings.setValue('main window position', self.pos()) @@ -1259,26 +1259,24 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): Updates the recent file menu with the latest list of service files accessed. """ recent_file_count = Settings().value('advanced/recent file count') - existing_recent_files = [recentFile for recentFile in self.recent_files if os.path.isfile(str(recentFile))] - recent_files_to_display = existing_recent_files[0:recent_file_count] self.recent_files_menu.clear() - for file_id, filename in enumerate(recent_files_to_display): - log.debug('Recent file name: {name}'.format(name=filename)) + count = 0 + for recent_path in self.recent_files: + if not recent_path.is_file(): + continue + count += 1 + log.debug('Recent file name: {name}'.format(name=recent_path)) action = create_action(self, '', - text='&{n} {name}'.format(n=file_id + 1, - name=os.path.splitext(os.path.basename(str(filename)))[0]), - data=filename, - triggers=self.service_manager_contents.on_recent_service_clicked) + text='&{n} {name}'.format(n=count, name=recent_path.name), + data=recent_path, triggers=self.service_manager_contents.on_recent_service_clicked) self.recent_files_menu.addAction(action) - clear_recent_files_action = create_action(self, '', - text=translate('OpenLP.MainWindow', 'Clear List', 'Clear List of ' - 'recent files'), - statustip=translate('OpenLP.MainWindow', 'Clear the list of recent ' - 'files.'), - enabled=bool(self.recent_files), - triggers=self.clear_recent_file_menu) + if count == recent_file_count: + break + clear_recent_files_action = \ + create_action(self, '', text=translate('OpenLP.MainWindow', 'Clear List', 'Clear List of recent files'), + statustip=translate('OpenLP.MainWindow', 'Clear the list of recent files.'), + enabled=bool(self.recent_files), triggers=self.clear_recent_file_menu) add_actions(self.recent_files_menu, (None, clear_recent_files_action)) - clear_recent_files_action.setEnabled(bool(self.recent_files)) def add_recent_file(self, filename): """ @@ -1290,20 +1288,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): # actually stored in the settings therefore the default value of 20 will # always be used. max_recent_files = Settings().value('advanced/max recent files') - if filename: - # Add some cleanup to reduce duplication in the recent file list - filename = os.path.abspath(filename) - # abspath() only capitalises the drive letter if it wasn't provided - # in the given filename which then causes duplication. - if filename[1:3] == ':\\': - filename = filename[0].upper() + filename[1:] - if filename in self.recent_files: - self.recent_files.remove(filename) - if not isinstance(self.recent_files, list): - self.recent_files = [self.recent_files] - self.recent_files.insert(0, filename) - while len(self.recent_files) > max_recent_files: - self.recent_files.pop() + file_path = Path(filename) + # Some cleanup to reduce duplication in the recent file list + file_path = file_path.resolve() + if file_path in self.recent_files: + self.recent_files.remove(file_path) + self.recent_files.insert(0, file_path) + self.recent_files = self.recent_files[:max_recent_files] def clear_recent_file_menu(self): """ diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 5e32af7b6..4c4ba5253 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -31,6 +31,7 @@ from PyQt5 import QtCore from openlp.core.api.http import register_endpoint from openlp.core.common import extension_loader from openlp.core.common.i18n import translate +from openlp.core.common.path import path_to_str from openlp.core.common.settings import Settings from openlp.core.lib import Plugin, StringContent, build_icon from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint @@ -144,12 +145,12 @@ class PresentationPlugin(Plugin): # TODO: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in # PresentationDocument.get_thumbnail_folder and PresentationDocument.get_temp_folder is removed super().app_startup() - files_from_config = Settings().value('presentations/presentations files') - for file in files_from_config: - self.media_item.clean_up_thumbnails(file, clean_for_update=True) + presentation_paths = Settings().value('presentations/presentations files') + for path in presentation_paths: + self.media_item.clean_up_thumbnails(path, clean_for_update=True) self.media_item.list_view.clear() Settings().setValue('presentations/thumbnail_scheme', 'md5') - self.media_item.validate_and_load(files_from_config) + self.media_item.validate_and_load(presentation_paths) @staticmethod def about(): From 1b168dd7bfef74f739c144ccfdcf1cddc58aae7b Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Sat, 18 Nov 2017 23:14:28 +0000 Subject: [PATCH 53/96] More pathlib refactors --- openlp/core/common/__init__.py | 11 ----- openlp/core/ui/servicemanager.py | 16 ++++--- .../openlp_core/common/test_init.py | 43 +------------------ .../openlp_core/ui/test_servicemanager.py | 4 +- 4 files changed, 12 insertions(+), 62 deletions(-) diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index d280cbde2..99e222041 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -318,17 +318,6 @@ def get_filesystem_encoding(): return encoding -def split_filename(path): - """ - Return a list of the parts in a given path. - """ - path = os.path.abspath(path) - if not os.path.isfile(path): - return path, '' - else: - return os.path.split(path) - - def delete_file(file_path): """ Deletes a file from the system. diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 81edd4930..9c943afaf 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -32,7 +32,7 @@ from tempfile import mkstemp from PyQt5 import QtCore, QtGui, QtWidgets -from openlp.core.common import ThemeLevel, split_filename, delete_file +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 @@ -319,7 +319,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.service_id = 0 # is a new service and has not been saved self._modified = False - self._file_name = '' + self._service_path = None self.service_has_all_original_files = True self.list_double_clicked = False @@ -366,7 +366,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi :param openlp.core.common.path.Path file_path: The service file name :rtype: None """ - self._file_name = path_to_str(file_path) + self._service_path = file_path self.main_window.set_service_modified(self.is_modified(), self.short_file_name()) Settings().setValue('servicemanager/last file', file_path) if file_path and file_path.suffix == '.oszl': @@ -377,14 +377,16 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi def file_name(self): """ Return the current file name including path. + + :rtype: openlp.core.common.path.Path """ - return self._file_name + return self._service_path def short_file_name(self): """ Return the current file name, excluding the path. """ - return split_filename(self._file_name)[1] + return self._service_path.name def reset_supported_suffixes(self): """ @@ -706,13 +708,13 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi default_file_path = directory_path / default_file_path lite_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files - lite (*.oszl)') packaged_filter = translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)') - if self._file_name.endswith('oszl'): + if self._service_path and self._service_path.suffix == '.oszl': default_filter = lite_filter else: default_filter = packaged_filter # SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in # the long term. - if self._file_name.endswith('oszl') or self.service_has_all_original_files: + if self._service_path and self._service_path.suffix == '.oszl' or self.service_has_all_original_files: file_path, filter_used = FileDialog.getSaveFileName( self.main_window, UiStrings().SaveService, default_file_path, '{packaged};; {lite}'.format(packaged=packaged_filter, lite=lite_filter), diff --git a/tests/functional/openlp_core/common/test_init.py b/tests/functional/openlp_core/common/test_init.py index 5acfe59bb..9b86c99aa 100644 --- a/tests/functional/openlp_core/common/test_init.py +++ b/tests/functional/openlp_core/common/test_init.py @@ -28,7 +28,7 @@ from unittest import TestCase from unittest.mock import MagicMock, PropertyMock, call, patch from openlp.core.common import add_actions, clean_filename, delete_file, get_file_encoding, get_filesystem_encoding, \ - get_uno_command, get_uno_instance, split_filename + get_uno_command, get_uno_instance from openlp.core.common.path import Path from tests.helpers.testmixin import TestMixin @@ -236,47 +236,6 @@ class TestInit(TestCase, TestMixin): mocked_getdefaultencoding.assert_called_with() self.assertEqual('utf-8', result, 'The result should be "utf-8"') - def test_split_filename_with_file_path(self): - """ - Test the split_filename() function with a path to a file - """ - # GIVEN: A path to a file. - if os.name == 'nt': - file_path = 'C:\\home\\user\\myfile.txt' - wanted_result = ('C:\\home\\user', 'myfile.txt') - else: - file_path = '/home/user/myfile.txt' - wanted_result = ('/home/user', 'myfile.txt') - with patch('openlp.core.common.os.path.isfile') as mocked_is_file: - mocked_is_file.return_value = True - - # WHEN: Split the file name. - result = split_filename(file_path) - - # THEN: A tuple should be returned. - self.assertEqual(wanted_result, result, 'A tuple with the dir and file name should have been returned') - - def test_split_filename_with_dir_path(self): - """ - Test the split_filename() function with a path to a directory - """ - # GIVEN: A path to a dir. - if os.name == 'nt': - file_path = 'C:\\home\\user\\mydir' - wanted_result = ('C:\\home\\user\\mydir', '') - else: - file_path = '/home/user/mydir' - wanted_result = ('/home/user/mydir', '') - with patch('openlp.core.common.os.path.isfile') as mocked_is_file: - mocked_is_file.return_value = False - - # WHEN: Split the file name. - result = split_filename(file_path) - - # THEN: A tuple should be returned. - self.assertEqual(wanted_result, result, - 'A two-entry tuple with the directory and file name (empty) should have been returned.') - def test_clean_filename(self): """ Test the clean_filename() function diff --git a/tests/functional/openlp_core/ui/test_servicemanager.py b/tests/functional/openlp_core/ui/test_servicemanager.py index adf241d35..3c0958506 100644 --- a/tests/functional/openlp_core/ui/test_servicemanager.py +++ b/tests/functional/openlp_core/ui/test_servicemanager.py @@ -637,7 +637,7 @@ class TestServiceManager(TestCase): Registry().register('main_window', mocked_main_window) Registry().register('application', MagicMock()) service_manager = ServiceManager(None) - service_manager._file_name = os.path.join('temp', 'filename.osz') + service_manager._service_path = os.path.join('temp', 'filename.osz') service_manager._save_lite = False service_manager.service_items = [] service_manager.service_theme = 'Default' @@ -666,7 +666,7 @@ class TestServiceManager(TestCase): Registry().register('main_window', mocked_main_window) Registry().register('application', MagicMock()) service_manager = ServiceManager(None) - service_manager._file_name = os.path.join('temp', 'filename.osz') + service_manager._service_path = os.path.join('temp', 'filename.osz') service_manager._save_lite = False service_manager.service_items = [] service_manager.service_theme = 'Default' From a864dbbbc99822cd79fbadef3a8e19b701bf5a11 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Sun, 19 Nov 2017 21:57:38 +0000 Subject: [PATCH 54/96] Minor tidyups --- openlp/core/ui/mainwindow.py | 2 +- openlp/core/ui/servicemanager.py | 2 +- openlp/plugins/media/mediaplugin.py | 9 +++++---- openlp/plugins/presentations/presentationplugin.py | 1 - 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 7c3881203..312e9d445 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -40,7 +40,7 @@ from openlp.core.common import is_win, is_macosx, add_actions from openlp.core.common.actions import ActionList, CategoryOrder from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import LanguageManager, UiStrings, translate -from openlp.core.common.path import Path, copyfile, create_paths, path_to_str, str_to_path +from openlp.core.common.path import Path, copyfile, create_paths from openlp.core.common.mixins import RegistryProperties from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 9c943afaf..e6fb34e2f 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -37,7 +37,7 @@ 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.mixins import LogMixin, RegistryProperties -from openlp.core.common.path import Path, create_paths, path_to_str, str_to_path +from openlp.core.common.path import Path, create_paths, 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 diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index 5efe8c910..461e78746 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -78,10 +78,10 @@ class MediaPlugin(Plugin): """ log.debug('check_installed Mediainfo') # Try to find mediainfo in the path - exists = process_check_binary('mediainfo') + exists = process_check_binary(Path('mediainfo')) # If mediainfo is not in the path, try to find it in the application folder if not exists: - exists = process_check_binary(os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'mediainfo')) + exists = process_check_binary(AppLocation.get_directory(AppLocation.AppDir) / 'mediainfo') return exists def app_startup(self): @@ -165,10 +165,11 @@ def process_check_binary(program_path): """ Function that checks whether a binary MediaInfo is present - :param program_path:The full path to the binary to check. + :param openlp.core.common.path.Path program_path:The full path to the binary to check. :return: If exists or not + :rtype: bool """ - runlog = check_binary_exists(Path(program_path)) + runlog = check_binary_exists(program_path) # Analyse the output to see it the program is mediainfo for line in runlog.splitlines(): decoded_line = line.decode() diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 4c4ba5253..a68a22176 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -31,7 +31,6 @@ from PyQt5 import QtCore from openlp.core.api.http import register_endpoint from openlp.core.common import extension_loader from openlp.core.common.i18n import translate -from openlp.core.common.path import path_to_str from openlp.core.common.settings import Settings from openlp.core.lib import Plugin, StringContent, build_icon from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint From 7d0b84126962632f46f54ae08f1859db3fbce1f2 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Mon, 20 Nov 2017 21:57:34 +0000 Subject: [PATCH 55/96] tidyups --- openlp/core/common/path.py | 28 ++++++------------- openlp/core/lib/mediamanageritem.py | 4 +-- openlp/core/ui/thememanager.py | 8 +++--- .../lib/presentationcontroller.py | 6 ++-- .../openlp_core/common/test_path.py | 20 +++++++------ .../songs/test_openlyricsexport.py | 5 ++-- 6 files changed, 32 insertions(+), 39 deletions(-) diff --git a/openlp/core/common/path.py b/openlp/core/common/path.py index 05d2ea4eb..0e4b45c2a 100644 --- a/openlp/core/common/path.py +++ b/openlp/core/common/path.py @@ -69,8 +69,15 @@ class Path(PathVariant): path = path.relative_to(base_path) return {'__Path__': path.parts} - def rmtree(self, *args, **kwargs): - shutil.rmtree(str(self), *args, **kwargs) + def rmtree(self, ignore_errors=False, onerror=None): + """ + Provide an interface to :func:`shutil.rmtree` + + :param bool ignore_errors: Ignore errors + :param onerror: Handler function to handle any errors + :rtype: None + """ + shutil.rmtree(str(self), ignore_errors, onerror) def replace_params(args, kwargs, params): @@ -156,23 +163,6 @@ def copytree(*args, **kwargs): return str_to_path(shutil.copytree(*args, **kwargs)) -def rmtree(*args, **kwargs): - """ - Wraps :func:shutil.rmtree` so that we can accept Path objects. - - :param openlp.core.common.path.Path path: Takes a Path object which is then converted to a str object - :return: Passes the return from :func:`shutil.rmtree` back - :rtype: None - - See the following link for more information on the other parameters: - https://docs.python.org/3/library/shutil.html#shutil.rmtree - """ - - args, kwargs = replace_params(args, kwargs, ((0, 'path', path_to_str),)) - - return shutil.rmtree(*args, **kwargs) - - def which(*args, **kwargs): """ Wraps :func:shutil.which` so that it return a Path objects. diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 8091b1a2e..55306267c 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -333,7 +333,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): new_file_paths = [] error_shown = False for file_name in data['files']: - file_path = str_to_path(file_name) # TODO: + file_path = str_to_path(file_name) if file_path.suffix[1:].lower() not in self.on_new_file_masks: if not error_shown: critical_error_message_box( @@ -367,7 +367,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): """ full_list = [] for count in range(self.list_view.count()): - full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole)) # TODO: Path objects + full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole)) duplicates_found = False files_added = False for file_path in file_paths: diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index fa8a9e4d9..fae75f81d 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -32,7 +32,7 @@ from openlp.core.common import delete_file from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import UiStrings, translate, get_locale_key from openlp.core.common.mixins import LogMixin, RegistryProperties -from openlp.core.common.path import Path, copyfile, create_paths, path_to_str, rmtree +from openlp.core.common.path import Path, copyfile, create_paths, path_to_str from openlp.core.common.registry import Registry, RegistryBase from openlp.core.common.settings import Settings from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \ @@ -376,7 +376,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R delete_file(self.theme_path / thumb) delete_file(self.thumb_path / thumb) try: - rmtree(self.theme_path / theme) + (self.theme_path / theme).rmtree() except OSError: self.log_exception('Error deleting theme {name}'.format(name=theme)) @@ -431,7 +431,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R 'The theme_name export failed because this error occurred: {err}') .format(err=ose.strerror)) if theme_path.exists(): - rmtree(theme_path, True) + theme_path.rmtree(ignore_errors=True) return False def on_import_theme(self, checked=None): @@ -710,7 +710,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R Call the renderer to build a Sample Image :param theme_data: The theme to generated a preview for. - :param force_page: Flag to tell message lines per page need to be generated.7 + :param force_page: Flag to tell message lines per page need to be generated. :rtype: QtGui.QPixmap """ return self.renderer.generate_preview(theme_data, force_page) diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index db5786626..b54d8afa9 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -25,7 +25,7 @@ from PyQt5 import QtCore from openlp.core.common import md5_hash from openlp.core.common.applocation import AppLocation -from openlp.core.common.path import Path, create_paths, rmtree +from openlp.core.common.path import Path, create_paths from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib import create_thumb, validate_thumb @@ -126,9 +126,9 @@ class PresentationDocument(object): thumbnail_folder_path = self.get_thumbnail_folder() temp_folder_path = self.get_temp_folder() if thumbnail_folder_path.exists(): - rmtree(thumbnail_folder_path) + thumbnail_folder_path.rmtree() if temp_folder_path.exists(): - rmtree(temp_folder_path) + temp_folder_path.rmtree() except OSError: log.exception('Failed to delete presentation controller files') diff --git a/tests/functional/openlp_core/common/test_path.py b/tests/functional/openlp_core/common/test_path.py index 2ec89771b..498b7aaa0 100644 --- a/tests/functional/openlp_core/common/test_path.py +++ b/tests/functional/openlp_core/common/test_path.py @@ -26,7 +26,7 @@ import os from unittest import TestCase from unittest.mock import ANY, MagicMock, patch -from openlp.core.common.path import Path, copy, copyfile, copytree, create_paths, path_to_str, replace_params, rmtree, \ +from openlp.core.common.path import Path, copy, copyfile, copytree, create_paths, path_to_str, replace_params, \ str_to_path, which @@ -172,31 +172,35 @@ class TestShutil(TestCase): """ Test :func:`rmtree` """ - # GIVEN: A mocked :func:`shutil.rmtree` + # GIVEN: A mocked :func:`shutil.rmtree` and a test Path object with patch('openlp.core.common.path.shutil.rmtree', return_value=None) as mocked_shutil_rmtree: + path = Path('test', 'path') # WHEN: Calling :func:`openlp.core.common.path.rmtree` with the path parameter as Path object type - result = rmtree(Path('test', 'path')) + result = path.rmtree() # THEN: :func:`shutil.rmtree` should have been called with the str equivalents of the Path object. - mocked_shutil_rmtree.assert_called_once_with(os.path.join('test', 'path')) + mocked_shutil_rmtree.assert_called_once_with( + os.path.join('test', 'path'), False, None) self.assertIsNone(result) def test_rmtree_optional_params(self): """ Test :func:`openlp.core.common.path.rmtree` when optional parameters are passed """ - # GIVEN: A mocked :func:`shutil.rmtree` - with patch('openlp.core.common.path.shutil.rmtree', return_value='') as mocked_shutil_rmtree: + # GIVEN: A mocked :func:`shutil.rmtree` and a test Path object. + with patch('openlp.core.common.path.shutil.rmtree', return_value=None) as mocked_shutil_rmtree: + path = Path('test', 'path') mocked_on_error = MagicMock() # WHEN: Calling :func:`openlp.core.common.path.rmtree` with :param:`ignore_errors` set to True and # :param:`onerror` set to a mocked object - rmtree(Path('test', 'path'), ignore_errors=True, onerror=mocked_on_error) + path.rmtree(ignore_errors=True, onerror=mocked_on_error) # THEN: :func:`shutil.rmtree` should have been called with the optional parameters, with out any of the # values being modified - mocked_shutil_rmtree.assert_called_once_with(ANY, ignore_errors=True, onerror=mocked_on_error) + mocked_shutil_rmtree.assert_called_once_with( + os.path.join('test', 'path'), True, mocked_on_error) def test_which_no_command(self): """ diff --git a/tests/functional/openlp_plugins/songs/test_openlyricsexport.py b/tests/functional/openlp_plugins/songs/test_openlyricsexport.py index 0fd4767db..85bf0818c 100644 --- a/tests/functional/openlp_plugins/songs/test_openlyricsexport.py +++ b/tests/functional/openlp_plugins/songs/test_openlyricsexport.py @@ -22,13 +22,12 @@ """ This module contains tests for the OpenLyrics song importer. """ -import shutil from tempfile import mkdtemp from unittest import TestCase from unittest.mock import MagicMock, patch from openlp.core.common.registry import Registry -from openlp.core.common.path import Path, rmtree +from openlp.core.common.path import Path from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport from tests.helpers.testmixin import TestMixin @@ -49,7 +48,7 @@ class TestOpenLyricsExport(TestCase, TestMixin): """ Cleanup """ - rmtree(self.temp_folder) + self.temp_folder.rmtree() def test_export_same_filename(self): """ From 97e6e759bd457828c12ba8344d5d38e004f948ee Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Tue, 21 Nov 2017 07:15:05 +0000 Subject: [PATCH 56/96] test fix --- tests/functional/openlp_core/lib/test_lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/openlp_core/lib/test_lib.py b/tests/functional/openlp_core/lib/test_lib.py index 67649fa49..15126c14e 100644 --- a/tests/functional/openlp_core/lib/test_lib.py +++ b/tests/functional/openlp_core/lib/test_lib.py @@ -320,7 +320,7 @@ class TestLib(TestCase): pass # Only continue when the thumb does not exist. - self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') + self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path) From bd2efc8ec028fda8879a6a9e6c0be006ff3ea4aa Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Tue, 21 Nov 2017 07:23:02 +0000 Subject: [PATCH 57/96] PEP8 --- openlp/plugins/images/lib/mediaitem.py | 2 +- tests/functional/openlp_core/lib/test_lib.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 552a63d44..25a8d2353 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -377,7 +377,7 @@ class ImageMediaItem(MediaManagerItem): if validate_thumb(image.file_path, thumbnail_path): icon = build_icon(thumbnail_path) else: - icon = create_thumb(image.file_path,thumbnail_path) + icon = create_thumb(image.file_path, thumbnail_path) item_name = QtWidgets.QTreeWidgetItem([file_name]) item_name.setText(0, file_name) item_name.setIcon(0, icon) diff --git a/tests/functional/openlp_core/lib/test_lib.py b/tests/functional/openlp_core/lib/test_lib.py index 15126c14e..e9788060e 100644 --- a/tests/functional/openlp_core/lib/test_lib.py +++ b/tests/functional/openlp_core/lib/test_lib.py @@ -401,7 +401,8 @@ class TestLib(TestCase): self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + self.assertEqual( + expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: @@ -436,7 +437,8 @@ class TestLib(TestCase): self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + self.assertEqual( + expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: @@ -474,7 +476,8 @@ class TestLib(TestCase): self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size_1, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + self.assertEqual( + expected_size_1, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # WHEN: Create the thumb. with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size: @@ -484,7 +487,8 @@ class TestLib(TestCase): # THEN: Check if the thumb was created with aspect ratio of 1. self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size_2, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + self.assertEqual( + expected_size_2, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: From 7badbca8cf96132c2e187654b1774ad32f82bcae Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Wed, 22 Nov 2017 20:21:57 +0000 Subject: [PATCH 58/96] Pathfixes --- openlp/plugins/media/forms/mediaclipselectorform.py | 3 ++- openlp/plugins/media/lib/mediaitem.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/media/forms/mediaclipselectorform.py b/openlp/plugins/media/forms/mediaclipselectorform.py index af08870df..2bbb5fcdf 100644 --- a/openlp/plugins/media/forms/mediaclipselectorform.py +++ b/openlp/plugins/media/forms/mediaclipselectorform.py @@ -28,6 +28,7 @@ from datetime import datetime from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import is_win, is_linux, is_macosx +from openlp.core.common.path import Path from openlp.core.common.i18n import translate from openlp.core.common.mixins import RegistryProperties from openlp.plugins.media.forms.mediaclipselectordialog import Ui_MediaClipSelector @@ -615,7 +616,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro break # Append the new name to the optical string and the path optical += new_optical_name + ':' + path - self.media_item.add_optical_clip(optical) + self.media_item.add_optical_clip(Path(optical)) def media_state_wait(self, media_state): """ diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index e47ed70e0..99454ff13 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -455,5 +455,5 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): return # Append the optical string to the media list file_paths.append(optical) - self.load_list([optical]) + self.load_list([str(optical)]) Settings().setValue(self.settings_section + '/media files', file_paths) From 29f7d8967f58c17adacc2c145e73066957b6900f Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Wed, 22 Nov 2017 21:39:40 +0000 Subject: [PATCH 59/96] service fixes --- openlp/core/ui/media/mediacontroller.py | 2 -- openlp/core/ui/servicemanager.py | 7 +++++-- openlp/plugins/media/lib/mediaitem.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index c72d6669d..9c8973ca8 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -498,8 +498,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): :param controller: The media controller. :return: True if setup succeeded else False. """ - if controller is None: - controller = self.display_controllers[DisplayControllerType.Plugin] # stop running videos self.media_reset(controller) # Setup media info diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index e6fb34e2f..76d696ba0 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -350,7 +350,10 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi if modified: self.service_id += 1 self._modified = modified - service_file = self.short_file_name() or translate('OpenLP.ServiceManager', 'Untitled Service') + if self._service_path: + service_file = self._service_path.name + else: + service_file = translate('OpenLP.ServiceManager', 'Untitled Service') self.main_window.set_service_modified(modified, service_file) def is_modified(self): @@ -367,7 +370,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi :rtype: None """ self._service_path = file_path - self.main_window.set_service_modified(self.is_modified(), self.short_file_name()) + self.main_window.set_service_modified(self.is_modified(), file_path.name) Settings().setValue('servicemanager/last file', file_path) if file_path and file_path.suffix == '.oszl': self._save_lite = True diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 99454ff13..8ca595040 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -269,7 +269,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): service_item.add_from_command(filename, name, CLAPPERBOARD) service_item.title = clip_name # Set the length - self.media_controller.media_setup_optical(name, title, audio_track, subtitle_track, start, end, None, None) + #self.media_controller.media_setup_optical(name, title, audio_track, subtitle_track, start, end, None, None) service_item.set_media_length((end - start) / 1000) service_item.start_time = start / 1000 service_item.end_time = end / 1000 From 572e1efb9c207498e6ce5a2831a401c12c64b47c Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Wed, 22 Nov 2017 21:56:56 +0000 Subject: [PATCH 60/96] Fix dvd clip selection. Pretty much coppied from lp:~mikey74/openlp/dvdplayerfix/ Fixes: https://launchpad.net/bugs/1514545 --- openlp/core/ui/media/mediacontroller.py | 6 +++--- openlp/core/ui/media/vlcplayer.py | 11 ++++++----- openlp/plugins/media/forms/mediaclipselectorform.py | 10 +++++----- openlp/plugins/media/lib/mediaitem.py | 7 +++---- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index 9c8973ca8..10c384b0c 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -507,9 +507,9 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): controller.media_info.media_type = MediaType.CD else: controller.media_info.media_type = MediaType.DVD - controller.media_info.start_time = start // 1000 - controller.media_info.end_time = end // 1000 - controller.media_info.length = (end - start) // 1000 + controller.media_info.start_time = start + controller.media_info.end_time = end + controller.media_info.length = (end - start) controller.media_info.title_track = title controller.media_info.audio_track = audio_track controller.media_info.subtitle_track = subtitle_track diff --git a/openlp/core/ui/media/vlcplayer.py b/openlp/core/ui/media/vlcplayer.py index 605f6a8a1..4ad9d990f 100644 --- a/openlp/core/ui/media/vlcplayer.py +++ b/openlp/core/ui/media/vlcplayer.py @@ -280,7 +280,8 @@ class VlcPlayer(MediaPlayer): start_time = controller.media_info.start_time log.debug('mediatype: ' + str(controller.media_info.media_type)) # Set tracks for the optical device - if controller.media_info.media_type == MediaType.DVD: + if controller.media_info.media_type == MediaType.DVD and \ + self.get_live_state() != MediaState.Paused and self.get_preview_state() != MediaState.Paused: log.debug('vlc play, playing started') if controller.media_info.title_track > 0: log.debug('vlc play, title_track set: ' + str(controller.media_info.title_track)) @@ -350,7 +351,7 @@ class VlcPlayer(MediaPlayer): """ if display.controller.media_info.media_type == MediaType.CD \ or display.controller.media_info.media_type == MediaType.DVD: - seek_value += int(display.controller.media_info.start_time * 1000) + seek_value += int(display.controller.media_info.start_time) if display.vlc_media_player.is_seekable(): display.vlc_media_player.set_time(seek_value) @@ -386,15 +387,15 @@ class VlcPlayer(MediaPlayer): self.stop(display) controller = display.controller if controller.media_info.end_time > 0: - if display.vlc_media_player.get_time() > controller.media_info.end_time * 1000: + if display.vlc_media_player.get_time() > controller.media_info.end_time: self.stop(display) self.set_visible(display, False) if not controller.seek_slider.isSliderDown(): controller.seek_slider.blockSignals(True) if display.controller.media_info.media_type == MediaType.CD \ or display.controller.media_info.media_type == MediaType.DVD: - controller.seek_slider.setSliderPosition(display.vlc_media_player.get_time() - - int(display.controller.media_info.start_time * 1000)) + controller.seek_slider.setSliderPosition( + display.vlc_media_player.get_time() - int(display.controller.media_info.start_time)) else: controller.seek_slider.setSliderPosition(display.vlc_media_player.get_time()) controller.seek_slider.blockSignals(False) diff --git a/openlp/plugins/media/forms/mediaclipselectorform.py b/openlp/plugins/media/forms/mediaclipselectorform.py index 2bbb5fcdf..be9e69fbd 100644 --- a/openlp/plugins/media/forms/mediaclipselectorform.py +++ b/openlp/plugins/media/forms/mediaclipselectorform.py @@ -110,7 +110,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro self.subtitle_tracks_combobox.clear() self.audio_tracks_combobox.clear() self.titles_combo_box.clear() - time = QtCore.QTime() + time = QtCore.QTime(0, 0, 0) self.start_position_edit.setTime(time) self.end_timeedit.setTime(time) self.position_timeedit.setTime(time) @@ -295,7 +295,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro :param clicked: Given from signal, not used. """ vlc_ms_pos = self.vlc_media_player.get_time() - time = QtCore.QTime() + time = QtCore.QTime(0, 0, 0) new_pos_time = time.addMSecs(vlc_ms_pos) self.start_position_edit.setTime(new_pos_time) # If start time is after end time, update end time. @@ -311,7 +311,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro :param clicked: Given from signal, not used. """ vlc_ms_pos = self.vlc_media_player.get_time() - time = QtCore.QTime() + time = QtCore.QTime(0, 0, 0) new_pos_time = time.addMSecs(vlc_ms_pos) self.end_timeedit.setTime(new_pos_time) # If start time is after end time, update start time. @@ -448,7 +448,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro self.position_slider.setMaximum(self.playback_length) # setup start and end time rounded_vlc_ms_length = int(round(self.playback_length / 100.0) * 100.0) - time = QtCore.QTime() + time = QtCore.QTime(0, 0, 0) playback_length_time = time.addMSecs(rounded_vlc_ms_length) self.start_position_edit.setMaximumTime(playback_length_time) self.end_timeedit.setMaximumTime(playback_length_time) @@ -506,7 +506,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro if self.vlc_media_player: vlc_ms_pos = self.vlc_media_player.get_time() rounded_vlc_ms_pos = int(round(vlc_ms_pos / 100.0) * 100.0) - time = QtCore.QTime() + time = QtCore.QTime(0, 0, 0) new_pos_time = time.addMSecs(rounded_vlc_ms_pos) self.position_timeedit.setTime(new_pos_time) self.position_slider.setSliderPosition(vlc_ms_pos) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 8ca595040..1c7556aa9 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -269,10 +269,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): service_item.add_from_command(filename, name, CLAPPERBOARD) service_item.title = clip_name # Set the length - #self.media_controller.media_setup_optical(name, title, audio_track, subtitle_track, start, end, None, None) - service_item.set_media_length((end - start) / 1000) - service_item.start_time = start / 1000 - service_item.end_time = end / 1000 + service_item.set_media_length(end - start) + service_item.start_time = start + service_item.end_time = end service_item.add_capability(ItemCapabilities.IsOptical) else: if not os.path.exists(filename): From 2b9d2a994b7d0f226adec8932422c81d2e7b8e43 Mon Sep 17 00:00:00 2001 From: Ken Roberts <alisonken1@gmail.com> Date: Fri, 24 Nov 2017 00:30:37 -0800 Subject: [PATCH 61/96] Bugfix 1734275 Nonstandard LAMP reply --- openlp/core/projectors/manager.py | 12 +- openlp/core/projectors/pjlink.py | 23 +-- .../projectors/test_projector_bugfixes_01.py | 134 ++++++++++++++++++ .../projectors/test_projector_pjlink_base.py | 54 +------ .../test_projector_pjlink_commands.py | 29 ---- 5 files changed, 155 insertions(+), 97 deletions(-) create mode 100644 tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py diff --git a/openlp/core/projectors/manager.py b/openlp/core/projectors/manager.py index 0e00d602d..f9e3b191e 100644 --- a/openlp/core/projectors/manager.py +++ b/openlp/core/projectors/manager.py @@ -672,14 +672,16 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM data=projector.model_filter) count = 1 for item in projector.link.lamp: + if item['On'] is None: + onoff = translate('OpenLP.ProjectorManager', 'Unavailable') + elif item['On']: + onoff = translate('OpenLP.ProjectorManager', 'ON') + else: + onoff = translate('OpenLP.ProjectorManager', 'OFF') message += '<b>{title} {count}</b> {status} '.format(title=translate('OpenLP.ProjectorManager', 'Lamp'), count=count, - status=translate('OpenLP.ProjectorManager', - 'ON') - if item['On'] - else translate('OpenLP.ProjectorManager', - 'OFF')) + status=onoff) message += '<b>{title}</b>: {hours}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Hours'), hours=item['Hours']) diff --git a/openlp/core/projectors/pjlink.py b/openlp/core/projectors/pjlink.py index 38013097f..6ebc462f2 100644 --- a/openlp/core/projectors/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -403,16 +403,19 @@ class PJLinkCommands(object): """ lamps = [] data_dict = data.split() - while data_dict: - try: - fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True} - except ValueError: - # In case of invalid entry - log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data)) - return - lamps.append(fill) - data_dict.pop(0) # Remove lamp hours - data_dict.pop(0) # Remove lamp on/off + if len(data_dict) < 2: + lamps.append({'Hours': int(data_dict[0]), 'On': None}) + else: + while data_dict: + try: + fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True} + except ValueError: + # In case of invalid entry + log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data)) + return + lamps.append(fill) + data_dict.pop(0) # Remove lamp hours + data_dict.pop(0) # Remove lamp on/off self.lamp = lamps return diff --git a/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py new file mode 100644 index 000000000..6b5b4c54e --- /dev/null +++ b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2015 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +Package to test the openlp.core.projectors.pjlink base package. +""" +from unittest import TestCase +from unittest.mock import patch + +from openlp.core.projectors.db import Projector +from openlp.core.projectors.pjlink import PJLink + +from tests.resources.projector.data import TEST_PIN, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA + +pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) + + +class TestPJLinkBugs(TestCase): + """ + Tests for the PJLink module bugfixes + """ + def test_bug_1550891_process_clss_nonstandard_reply_1(self): + """ + Bugfix 1550891: CLSS request returns non-standard reply with Optoma/Viewsonic projector + """ + # GIVEN: Test object + pjlink = pjlink_test + + # WHEN: Process non-standard reply + pjlink.process_clss('Class 1') + + # THEN: Projector class should be set with proper value + self.assertEqual(pjlink.pjlink_class, '1', + 'Non-standard class reply should have set class=1') + + def test_bug_1550891_process_clss_nonstandard_reply_2(self): + """ + Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector + """ + # GIVEN: Test object + pjlink = pjlink_test + + # WHEN: Process non-standard reply + pjlink.process_clss('Version2') + + # THEN: Projector class should be set with proper value + # NOTE: At this time BenQ is Class 1, but we're trying a different value to verify + self.assertEqual(pjlink.pjlink_class, '2', + 'Non-standard class reply should have set class=2') + + @patch.object(pjlink_test, 'send_command') + @patch.object(pjlink_test, 'waitForReadyRead') + @patch.object(pjlink_test, 'projectorAuthentication') + @patch.object(pjlink_test, 'timer') + @patch.object(pjlink_test, 'socket_timer') + def test_bug_1593882_no_pin_authenticated_connection(self, + mock_socket_timer, + mock_timer, + mock_authentication, + mock_ready_read, + mock_send_command): + """ + Test bug 1593882 no pin and authenticated request exception + """ + # GIVEN: Test object and mocks + pjlink = pjlink_test + pjlink.pin = None + mock_ready_read.return_value = True + + # WHEN: call with authentication request and pin not set + pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) + + # THEN: 'No Authentication' signal should have been sent + mock_authentication.emit.assert_called_with(pjlink.ip) + + @patch.object(pjlink_test, 'waitForReadyRead') + @patch.object(pjlink_test, 'state') + @patch.object(pjlink_test, '_send_command') + @patch.object(pjlink_test, 'timer') + @patch.object(pjlink_test, 'socket_timer') + def test_bug_1593883_pjlink_authentication(self, + mock_socket_timer, + mock_timer, + mock_send_command, + mock_state, + mock_waitForReadyRead): + """ + Test bugfix 1593883 pjlink authentication + """ + # GIVEN: Test object and data + pjlink = pjlink_test + pjlink.pin = TEST_PIN + mock_state.return_value = pjlink.ConnectedState + mock_waitForReadyRead.return_value = True + + # WHEN: Athenticated connection is called + pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) + + # THEN: send_command should have the proper authentication + self.assertEqual("{test}".format(test=mock_send_command.call_args), + "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) + + def test_bug_1734275_pjlink_nonstandard_lamp(self): + """ + Test bugfix 17342785 non-standard LAMP response + """ + # GIVEN: Test object + pjlink = pjlink_test + + # WHEN: Process lamp command called with only hours and no lamp power state + pjlink.process_lamp("45") + + # THEN: Lamp should show hours as 45 and lamp power as Unavailable + self.assertEqual(len(pjlink.lamp), 1, 'There should only be 1 lamp available') + self.assertEqual(pjlink.lamp[0]['Hours'], 45, 'Lamp hours should have equalled 45') + self.assertIsNone(pjlink.lamp[0]['On'], 'Lamp power should be "Unavailable"') diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py index 75b32d8c1..7253df032 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py @@ -29,7 +29,7 @@ from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_ from openlp.core.projectors.db import Projector from openlp.core.projectors.pjlink import PJLink -from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA +from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST1_DATA pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) @@ -79,58 +79,6 @@ class TestPJLinkBase(TestCase): 'change_status should have been called with "{}"'.format( ERROR_STRING[E_PARAMETER])) - @patch.object(pjlink_test, 'send_command') - @patch.object(pjlink_test, 'waitForReadyRead') - @patch.object(pjlink_test, 'projectorAuthentication') - @patch.object(pjlink_test, 'timer') - @patch.object(pjlink_test, 'socket_timer') - def test_bug_1593882_no_pin_authenticated_connection(self, - mock_socket_timer, - mock_timer, - mock_authentication, - mock_ready_read, - mock_send_command): - """ - Test bug 1593882 no pin and authenticated request exception - """ - # GIVEN: Test object and mocks - pjlink = pjlink_test - pjlink.pin = None - mock_ready_read.return_value = True - - # WHEN: call with authentication request and pin not set - pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) - - # THEN: 'No Authentication' signal should have been sent - mock_authentication.emit.assert_called_with(pjlink.ip) - - @patch.object(pjlink_test, 'waitForReadyRead') - @patch.object(pjlink_test, 'state') - @patch.object(pjlink_test, '_send_command') - @patch.object(pjlink_test, 'timer') - @patch.object(pjlink_test, 'socket_timer') - def test_bug_1593883_pjlink_authentication(self, - mock_socket_timer, - mock_timer, - mock_send_command, - mock_state, - mock_waitForReadyRead): - """ - Test bugfix 1593883 pjlink authentication - """ - # GIVEN: Test object and data - pjlink = pjlink_test - pjlink.pin = TEST_PIN - mock_state.return_value = pjlink.ConnectedState - mock_waitForReadyRead.return_value = True - - # WHEN: Athenticated connection is called - pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) - - # THEN: send_command should have the proper authentication - self.assertEqual("{test}".format(test=mock_send_command.call_args), - "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) - @patch.object(pjlink_test, 'disconnect_from_host') def test_socket_abort(self, mock_disconnect): """ diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py index 584b63cf9..32544dd09 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py @@ -570,35 +570,6 @@ class TestPJLinkCommands(TestCase): self.assertEqual(pjlink.pjlink_class, '2', 'Projector should have set class=2') - def test_projector_process_clss_nonstandard_reply_optoma(self): - """ - Bugfix 1550891: CLSS request returns non-standard reply with Optoma projector - """ - # GIVEN: Test object - pjlink = pjlink_test - - # WHEN: Process non-standard reply - pjlink.process_clss('Class 1') - - # THEN: Projector class should be set with proper value - self.assertEqual(pjlink.pjlink_class, '1', - 'Non-standard class reply should have set class=1') - - def test_projector_process_clss_nonstandard_reply_benq(self): - """ - Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector - """ - # GIVEN: Test object - pjlink = pjlink_test - - # WHEN: Process non-standard reply - pjlink.process_clss('Version2') - - # THEN: Projector class should be set with proper value - # NOTE: At this time BenQ is Class 1, but we're trying a different value to verify - self.assertEqual(pjlink.pjlink_class, '2', - 'Non-standard class reply should have set class=2') - @patch.object(openlp.core.projectors.pjlink, 'log') def test_projector_process_clss_invalid_nan(self, mock_log): """ From b650ef5730f231821ec58ee9d565365b8cf6464f Mon Sep 17 00:00:00 2001 From: Ken Roberts <alisonken1@gmail.com> Date: Fri, 24 Nov 2017 11:08:23 -0800 Subject: [PATCH 62/96] Refactor mocks --- openlp/core/projectors/manager.py | 8 +-- openlp/core/projectors/pjlink.py | 14 ++-- .../projectors/test_projector_bugfixes_01.py | 64 +++++++++---------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/openlp/core/projectors/manager.py b/openlp/core/projectors/manager.py index f9e3b191e..b352858b1 100644 --- a/openlp/core/projectors/manager.py +++ b/openlp/core/projectors/manager.py @@ -673,15 +673,15 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM count = 1 for item in projector.link.lamp: if item['On'] is None: - onoff = translate('OpenLP.ProjectorManager', 'Unavailable') + status = translate('OpenLP.ProjectorManager', 'Unavailable') elif item['On']: - onoff = translate('OpenLP.ProjectorManager', 'ON') + status = translate('OpenLP.ProjectorManager', 'ON') else: - onoff = translate('OpenLP.ProjectorManager', 'OFF') + status = translate('OpenLP.ProjectorManager', 'OFF') message += '<b>{title} {count}</b> {status} '.format(title=translate('OpenLP.ProjectorManager', 'Lamp'), count=count, - status=onoff) + status=status) message += '<b>{title}</b>: {hours}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Hours'), hours=item['Hours']) diff --git a/openlp/core/projectors/pjlink.py b/openlp/core/projectors/pjlink.py index 6ebc462f2..16a65bd11 100644 --- a/openlp/core/projectors/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -402,20 +402,20 @@ class PJLinkCommands(object): :param data: Lamp(s) status. """ lamps = [] - data_dict = data.split() - if len(data_dict) < 2: - lamps.append({'Hours': int(data_dict[0]), 'On': None}) + lamp_list = data.split() + if len(lamp_list) < 2: + lamps.append({'Hours': int(lamp_list[0]), 'On': None}) else: - while data_dict: + while lamp_list: try: - fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True} + fill = {'Hours': int(lamp_list[0]), 'On': False if lamp_list[1] == '0' else True} except ValueError: # In case of invalid entry log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data)) return lamps.append(fill) - data_dict.pop(0) # Remove lamp hours - data_dict.pop(0) # Remove lamp on/off + lamp_list.pop(0) # Remove lamp hours + lamp_list.pop(0) # Remove lamp on/off self.lamp = lamps return diff --git a/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py index 6b5b4c54e..c33220d4a 100644 --- a/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py +++ b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py @@ -30,19 +30,29 @@ from openlp.core.projectors.pjlink import PJLink from tests.resources.projector.data import TEST_PIN, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA -pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) - class TestPJLinkBugs(TestCase): """ Tests for the PJLink module bugfixes """ + def setUp(self): + ''' + Initialization + ''' + self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) + + def tearDown(self): + ''' + Cleanups + ''' + self.pjlink_test = None + def test_bug_1550891_process_clss_nonstandard_reply_1(self): """ Bugfix 1550891: CLSS request returns non-standard reply with Optoma/Viewsonic projector """ # GIVEN: Test object - pjlink = pjlink_test + pjlink = self.pjlink_test # WHEN: Process non-standard reply pjlink.process_clss('Class 1') @@ -56,7 +66,7 @@ class TestPJLinkBugs(TestCase): Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector """ # GIVEN: Test object - pjlink = pjlink_test + pjlink = self.pjlink_test # WHEN: Process non-standard reply pjlink.process_clss('Version2') @@ -66,22 +76,17 @@ class TestPJLinkBugs(TestCase): self.assertEqual(pjlink.pjlink_class, '2', 'Non-standard class reply should have set class=2') - @patch.object(pjlink_test, 'send_command') - @patch.object(pjlink_test, 'waitForReadyRead') - @patch.object(pjlink_test, 'projectorAuthentication') - @patch.object(pjlink_test, 'timer') - @patch.object(pjlink_test, 'socket_timer') - def test_bug_1593882_no_pin_authenticated_connection(self, - mock_socket_timer, - mock_timer, - mock_authentication, - mock_ready_read, - mock_send_command): + def test_bug_1593882_no_pin_authenticated_connection(self): """ Test bug 1593882 no pin and authenticated request exception """ # GIVEN: Test object and mocks - pjlink = pjlink_test + mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() + mock_timer = patch.object(self.pjlink_test, 'timer').start() + mock_authentication = patch.object(self.pjlink_test, 'projectorAuthentication').start() + mock_ready_read = patch.object(self.pjlink_test, 'waitForReadyRead').start() + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() + pjlink = self.pjlink_test pjlink.pin = None mock_ready_read.return_value = True @@ -91,22 +96,17 @@ class TestPJLinkBugs(TestCase): # THEN: 'No Authentication' signal should have been sent mock_authentication.emit.assert_called_with(pjlink.ip) - @patch.object(pjlink_test, 'waitForReadyRead') - @patch.object(pjlink_test, 'state') - @patch.object(pjlink_test, '_send_command') - @patch.object(pjlink_test, 'timer') - @patch.object(pjlink_test, 'socket_timer') - def test_bug_1593883_pjlink_authentication(self, - mock_socket_timer, - mock_timer, - mock_send_command, - mock_state, - mock_waitForReadyRead): + def test_bug_1593883_pjlink_authentication(self): """ Test bugfix 1593883 pjlink authentication """ # GIVEN: Test object and data - pjlink = pjlink_test + mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() + mock_timer = patch.object(self.pjlink_test, 'timer').start() + mock_send_command = patch.object(self.pjlink_test, 'write').start() + mock_state = patch.object(self.pjlink_test, 'state').start() + mock_waitForReadyRead = patch.object(self.pjlink_test, 'waitForReadyRead').start() + pjlink = self.pjlink_test pjlink.pin = TEST_PIN mock_state.return_value = pjlink.ConnectedState mock_waitForReadyRead.return_value = True @@ -116,14 +116,14 @@ class TestPJLinkBugs(TestCase): # THEN: send_command should have the proper authentication self.assertEqual("{test}".format(test=mock_send_command.call_args), - "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) + "call(b'{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) - def test_bug_1734275_pjlink_nonstandard_lamp(self): + def test_bug_1734275_process_lamp_nonstandard_reply(self): """ Test bugfix 17342785 non-standard LAMP response """ # GIVEN: Test object - pjlink = pjlink_test + pjlink = self.pjlink_test # WHEN: Process lamp command called with only hours and no lamp power state pjlink.process_lamp("45") @@ -131,4 +131,4 @@ class TestPJLinkBugs(TestCase): # THEN: Lamp should show hours as 45 and lamp power as Unavailable self.assertEqual(len(pjlink.lamp), 1, 'There should only be 1 lamp available') self.assertEqual(pjlink.lamp[0]['Hours'], 45, 'Lamp hours should have equalled 45') - self.assertIsNone(pjlink.lamp[0]['On'], 'Lamp power should be "Unavailable"') + self.assertIsNone(pjlink.lamp[0]['On'], 'Lamp power should be "None"') From 8fa9bdcf580f5842b87199a2bdafc01831eb0162 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Thu, 30 Nov 2017 15:15:11 -0700 Subject: [PATCH 63/96] Skip the test if not on Linux --- .../functional/openlp_plugins/songs/test_openoffice.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/functional/openlp_plugins/songs/test_openoffice.py b/tests/functional/openlp_plugins/songs/test_openoffice.py index 4172a553c..82cce6e10 100644 --- a/tests/functional/openlp_plugins/songs/test_openoffice.py +++ b/tests/functional/openlp_plugins/songs/test_openoffice.py @@ -25,12 +25,14 @@ This module contains tests for the OpenOffice/LibreOffice importer. from unittest import TestCase, SkipTest from unittest.mock import MagicMock, patch -from openlp.core.common.registry import Registry -try: - from openlp.plugins.songs.lib.importers.openoffice import OpenOfficeImport -except ImportError: +from openlp.core.common import is_linux + +if not is_linux: raise SkipTest('Could not import OpenOfficeImport probably due to unavailability of uno') +from openlp.core.common.registry import Registry +from openlp.plugins.songs.lib.importers.openoffice import OpenOfficeImport + from tests.helpers.testmixin import TestMixin From d9e2994deb6ec562e0c91cb4128eb968b31645de Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Thu, 30 Nov 2017 15:29:41 -0700 Subject: [PATCH 64/96] Actually run the function, ID10T --- tests/functional/openlp_plugins/songs/test_openoffice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/openlp_plugins/songs/test_openoffice.py b/tests/functional/openlp_plugins/songs/test_openoffice.py index 82cce6e10..e122da806 100644 --- a/tests/functional/openlp_plugins/songs/test_openoffice.py +++ b/tests/functional/openlp_plugins/songs/test_openoffice.py @@ -27,7 +27,7 @@ from unittest.mock import MagicMock, patch from openlp.core.common import is_linux -if not is_linux: +if not is_linux(): raise SkipTest('Could not import OpenOfficeImport probably due to unavailability of uno') from openlp.core.common.registry import Registry From b3669c4f5dd49c7b5ee2fc3033aedd7908854156 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Thu, 30 Nov 2017 15:46:03 -0700 Subject: [PATCH 65/96] Change things around a bit --- .../openlp_plugins/songs/test_openoffice.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/functional/openlp_plugins/songs/test_openoffice.py b/tests/functional/openlp_plugins/songs/test_openoffice.py index e122da806..45ef2acfd 100644 --- a/tests/functional/openlp_plugins/songs/test_openoffice.py +++ b/tests/functional/openlp_plugins/songs/test_openoffice.py @@ -22,20 +22,20 @@ """ This module contains tests for the OpenOffice/LibreOffice importer. """ -from unittest import TestCase, SkipTest +from unittest import TestCase, skipIf from unittest.mock import MagicMock, patch -from openlp.core.common import is_linux - -if not is_linux(): - raise SkipTest('Could not import OpenOfficeImport probably due to unavailability of uno') - from openlp.core.common.registry import Registry -from openlp.plugins.songs.lib.importers.openoffice import OpenOfficeImport from tests.helpers.testmixin import TestMixin +try: + from openlp.plugins.songs.lib.importers.openoffice import OpenOfficeImport +except ImportError: + OpenOfficeImport = None + +@skipIf(OpenOfficeImport is None, 'Could not import OpenOfficeImport probably due to unavailability of uno') class TestOpenOfficeImport(TestCase, TestMixin): """ Test the :class:`~openlp.plugins.songs.lib.importer.openoffice.OpenOfficeImport` class From ba392da66590265395a2632c02122df8e74f65c6 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Thu, 30 Nov 2017 17:31:48 -0700 Subject: [PATCH 66/96] Skip locale test on macOS until we can figure it out --- tests/functional/openlp_core/common/test_i18n.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/functional/openlp_core/common/test_i18n.py b/tests/functional/openlp_core/common/test_i18n.py index bffb819dc..4f4ca2aec 100644 --- a/tests/functional/openlp_core/common/test_i18n.py +++ b/tests/functional/openlp_core/common/test_i18n.py @@ -22,8 +22,10 @@ """ Package to test the openlp.core.lib.languages package. """ +from unittest import skipIf from unittest.mock import MagicMock, patch +from openlp.core.common import is_macosx from openlp.core.common.i18n import LANGUAGES, Language, UiStrings, get_language, get_locale_key, get_natural_key, \ translate @@ -110,6 +112,7 @@ def test_get_language_invalid_with_none(): assert language is None +@skipIf(is_macosx(), 'This test doesn\'t work on macOS currently') def test_get_locale_key(): """ Test the get_locale_key(string) function From e5d82de5dbd9dec461244de30bde170b14a2742d Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Thu, 30 Nov 2017 17:44:22 -0700 Subject: [PATCH 67/96] Add new macOS build to CI script --- scripts/jenkins_script.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index a669de71e..f6d07236f 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -63,9 +63,10 @@ class OpenLPJobs(object): Branch_Coverage = 'Branch-04b-Test_Coverage' Branch_Pylint = 'Branch-04c-Code_Analysis2' Branch_AppVeyor = 'Branch-05-AppVeyor-Tests' + Branch_macOS = 'Branch-07-macOS-Tests' Jobs = [Branch_Pull, Branch_Functional, Branch_Interface, Branch_PEP, Branch_Coverage, Branch_Pylint, - Branch_AppVeyor] + Branch_AppVeyor, Branch_macOS] class Colour(object): From 287debf9cf6580859f7ec606a98e1b35e207e4e3 Mon Sep 17 00:00:00 2001 From: Raoul Snyman <raoul@snyman.info> Date: Fri, 1 Dec 2017 00:26:51 -0700 Subject: [PATCH 68/96] Add a way to continue watching all jobs, even if one fails --- scripts/jenkins_script.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index f6d07236f..c7ca693be 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -116,7 +116,7 @@ class JenkinsTrigger(object): self.fetch_jobs() self.server.build_job(OpenLPJobs.Branch_Pull, {'BRANCH_NAME': self.repo_name, 'cause': cause}) - def print_output(self): + def print_output(self, can_continue=False): """ Print the status information of the build triggered. """ @@ -127,13 +127,21 @@ class JenkinsTrigger(object): revno = raw_output.decode().strip() print('%s (revision %s)' % (get_repo_name(), revno)) + failed_builds = [] for job in OpenLPJobs.Jobs: if not self.__print_build_info(job): if self.current_build: - print('Stopping after failure, see {}console for more details'.format(self.current_build['url'])) - else: + failed_builds.append((self.current_build['fullDisplayName'], self.current_build['url'])) + if not can_continue: print('Stopping after failure') - break + break + print('') + if failed_builds: + print('Failed builds:') + for build_name, url in failed_builds: + print(' - {}: {}console'.format(build_name, url)) + else: + print('All builds passed') def open_browser(self): """ @@ -228,6 +236,7 @@ def main(): help='Disable coloured output (always disabled on Windows)') parser.add_argument('-u', '--username', required=True, help='Your Jenkins username') parser.add_argument('-p', '--password', required=True, help='Your Jenkins password or personal token') + parser.add_argument('-c', '--always-continue', action='store_true', default=False, help='Continue despite failure') args = parser.parse_args() if not get_repo_name(): @@ -239,7 +248,7 @@ def main(): if args.open_browser: jenkins_trigger.open_browser() if not args.disable_output: - jenkins_trigger.print_output() + jenkins_trigger.print_output(can_continue=args.always_continue) if __name__ == '__main__': From 90118af85c9f3aaf389de141d07b379cff266330 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 2 Dec 2017 09:11:22 +0000 Subject: [PATCH 69/96] fix abend --- openlp/core/api/http/server.py | 15 ++++++++------- openlp/core/api/websockets.py | 13 +++++++------ openlp/core/ui/mainwindow.py | 5 ++--- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/openlp/core/api/http/server.py b/openlp/core/api/http/server.py index 782940f2d..b17888ddb 100644 --- a/openlp/core/api/http/server.py +++ b/openlp/core/api/http/server.py @@ -82,13 +82,14 @@ class HttpServer(RegistryBase, RegistryProperties, LogMixin): Initialise the http server, and start the http server """ super(HttpServer, self).__init__(parent) - self.worker = HttpWorker() - self.thread = QtCore.QThread() - self.worker.moveToThread(self.thread) - self.thread.started.connect(self.worker.run) - self.thread.start() - Registry().register_function('download_website', self.first_time) - Registry().register_function('get_website_version', self.website_version) + if Registry().get_flag('no_web_server'): + self.worker = HttpWorker() + self.thread = QtCore.QThread() + self.worker.moveToThread(self.thread) + self.thread.started.connect(self.worker.run) + self.thread.start() + Registry().register_function('download_website', self.first_time) + Registry().register_function('get_website_version', self.website_version) Registry().set_flag('website_version', '0.0') def bootstrap_post_set_up(self): diff --git a/openlp/core/api/websockets.py b/openlp/core/api/websockets.py index 90dca8208..3417eb74d 100644 --- a/openlp/core/api/websockets.py +++ b/openlp/core/api/websockets.py @@ -70,12 +70,13 @@ class WebSocketServer(RegistryProperties, LogMixin): Initialise and start the WebSockets server """ super(WebSocketServer, self).__init__() - self.settings_section = 'api' - self.worker = WebSocketWorker(self) - self.thread = QtCore.QThread() - self.worker.moveToThread(self.thread) - self.thread.started.connect(self.worker.run) - self.thread.start() + if Registry().get_flag('no_web_server'): + self.settings_section = 'api' + self.worker = WebSocketWorker(self) + self.thread = QtCore.QThread() + self.worker.moveToThread(self.thread) + self.thread.started.connect(self.worker.run) + self.thread.start() def start_server(self): """ diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 312e9d445..ee07cbd69 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -504,9 +504,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): Settings().set_up_default_values() self.about_form = AboutForm(self) MediaController() - if Registry().get_flag('no_web_server'): - websockets.WebSocketServer() - server.HttpServer() + websockets.WebSocketServer() + server.HttpServer() SettingsForm(self) self.formatting_tag_form = FormattingTagForm(self) self.shortcut_form = ShortcutListForm(self) From 4f882c460b7d631e2d1683c199233bd98caa70ca Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 2 Dec 2017 09:31:13 +0000 Subject: [PATCH 70/96] fix tests --- .../openlp_core/api/http/test_http.py | 18 +++++++++++++++++- .../openlp_core/api/test_websockets.py | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/tests/functional/openlp_core/api/http/test_http.py b/tests/functional/openlp_core/api/http/test_http.py index d9002b2ec..b3a3f6625 100644 --- a/tests/functional/openlp_core/api/http/test_http.py +++ b/tests/functional/openlp_core/api/http/test_http.py @@ -45,12 +45,28 @@ class TestHttpServer(TestCase): @patch('openlp.core.api.http.server.QtCore.QThread') def test_server_start(self, mock_qthread, mock_thread): """ - Test the starting of the Waitress Server + Test the starting of the Waitress Server with the disable flag set off """ # GIVEN: A new httpserver # WHEN: I start the server + Registry().set_flag('no_web_server', True) HttpServer() # THEN: the api environment should have been created self.assertEquals(1, mock_qthread.call_count, 'The qthread should have been called once') self.assertEquals(1, mock_thread.call_count, 'The http thread should have been called once') + + @patch('openlp.core.api.http.server.HttpWorker') + @patch('openlp.core.api.http.server.QtCore.QThread') + def test_server_start(self, mock_qthread, mock_thread): + """ + Test the starting of the Waitress Server with the disable flag set off + """ + # GIVEN: A new httpserver + # WHEN: I start the server + Registry().set_flag('no_web_server', False) + HttpServer() + + # THEN: the api environment should have been created + self.assertEquals(0, mock_qthread.call_count, 'The qthread should not have have been called') + self.assertEquals(0, mock_thread.call_count, 'The http thread should not have been called') diff --git a/tests/functional/openlp_core/api/test_websockets.py b/tests/functional/openlp_core/api/test_websockets.py index 99abcdbc0..f400c50a5 100644 --- a/tests/functional/openlp_core/api/test_websockets.py +++ b/tests/functional/openlp_core/api/test_websockets.py @@ -66,16 +66,32 @@ class TestWSServer(TestCase, TestMixin): @patch('openlp.core.api.websockets.QtCore.QThread') def test_serverstart(self, mock_qthread, mock_worker): """ - Test the starting of the WebSockets Server + Test the starting of the WebSockets Server with the disabled flag set on """ # GIVEN: A new httpserver # WHEN: I start the server + Registry().set_flag('no_web_server', True) WebSocketServer() # THEN: the api environment should have been created self.assertEquals(1, mock_qthread.call_count, 'The qthread should have been called once') self.assertEquals(1, mock_worker.call_count, 'The http thread should have been called once') + @patch('openlp.core.api.websockets.WebSocketWorker') + @patch('openlp.core.api.websockets.QtCore.QThread') + def test_serverstart_not_required(self, mock_qthread, mock_worker): + """ + Test the starting of the WebSockets Server with the disabled flag set off + """ + # GIVEN: A new httpserver and the server is not required + # WHEN: I start the server + Registry().set_flag('no_web_server', False) + WebSocketServer() + + # THEN: the api environment should have been created + self.assertEquals(0, mock_qthread.call_count, 'The qthread should not have been called') + self.assertEquals(0, mock_worker.call_count, 'The http thread should not have been called') + def test_main_poll(self): """ Test the main_poll function returns the correct JSON From 0a695ea7e650901eaec2637fafc8aa2f2e4610be Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 2 Dec 2017 09:37:55 +0000 Subject: [PATCH 71/96] fix tests --- tests/functional/openlp_core/ui/test_mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/openlp_core/ui/test_mainwindow.py b/tests/functional/openlp_core/ui/test_mainwindow.py index 5e1a69cbc..4a0404eab 100644 --- a/tests/functional/openlp_core/ui/test_mainwindow.py +++ b/tests/functional/openlp_core/ui/test_mainwindow.py @@ -155,7 +155,7 @@ class TestMainWindow(TestCase, TestMixin): # WHEN: you check the started functions # THEN: the following registry functions should have been registered - assert len(self.registry.service_list) == 12, \ + assert len(self.registry.service_list) == 13, \ 'The registry should have 12 services, got {}'.format(self.registry.service_list.keys()) assert len(self.registry.functions_list) == 19, \ 'The registry should have 19 functions, got {}'.format(self.registry.functions_list.keys()) From 70d2d73171842a1227687954b1372c0043a09e62 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 2 Dec 2017 10:52:13 +0000 Subject: [PATCH 72/96] missed test name --- tests/functional/openlp_core/api/http/test_http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/openlp_core/api/http/test_http.py b/tests/functional/openlp_core/api/http/test_http.py index b3a3f6625..ed584c1b9 100644 --- a/tests/functional/openlp_core/api/http/test_http.py +++ b/tests/functional/openlp_core/api/http/test_http.py @@ -58,7 +58,7 @@ class TestHttpServer(TestCase): @patch('openlp.core.api.http.server.HttpWorker') @patch('openlp.core.api.http.server.QtCore.QThread') - def test_server_start(self, mock_qthread, mock_thread): + def test_server_start_not_required(self, mock_qthread, mock_thread): """ Test the starting of the Waitress Server with the disable flag set off """ From 582e2e267a291a18609a108a6b5fbd7095ff55fa Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Sat, 2 Dec 2017 21:47:11 +0000 Subject: [PATCH 73/96] Minor fixes Fixes: https://launchpad.net/bugs/1735765 --- openlp/core/api/deploy.py | 2 ++ openlp/core/api/http/server.py | 5 ++++- openlp/core/app.py | 4 ++-- openlp/core/common/settings.py | 2 +- openlp/core/ui/servicemanager.py | 3 ++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/openlp/core/api/deploy.py b/openlp/core/api/deploy.py index b64cc40d5..a42f83f0c 100644 --- a/openlp/core/api/deploy.py +++ b/openlp/core/api/deploy.py @@ -52,6 +52,8 @@ def download_sha256(): web_config = get_web_page('https://get.openlp.org/webclient/download.cfg', headers={'User-Agent': user_agent}) except ConnectionError: return False + if not web_config: + return None file_bits = web_config.split() return file_bits[0], file_bits[2] diff --git a/openlp/core/api/http/server.py b/openlp/core/api/http/server.py index b17888ddb..c80275801 100644 --- a/openlp/core/api/http/server.py +++ b/openlp/core/api/http/server.py @@ -67,7 +67,10 @@ class HttpWorker(QtCore.QObject): address = Settings().value('api/ip address') port = Settings().value('api/port') Registry().execute('get_website_version') - serve(application, host=address, port=port) + try: + serve(application, host=address, port=port) + except OSError: + log.exception('An error occurred when serving the application.') def stop(self): pass diff --git a/openlp/core/app.py b/openlp/core/app.py index 19943e3f0..114a62807 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -403,8 +403,8 @@ def main(args=None): .format(back_up_path=back_up_path)) QtWidgets.QMessageBox.information( None, translate('OpenLP', 'Settings Upgrade'), - translate('OpenLP', 'Your settings are about to upgraded. A backup will be created at {back_up_path}') - .format(back_up_path=back_up_path)) + translate('OpenLP', 'Your settings are about to be upgraded. A backup will be created at ' + '{back_up_path}').format(back_up_path=back_up_path)) settings.export(back_up_path) settings.upgrade_settings() # First time checks in settings diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 225feb4e1..54f1d9b2a 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -236,7 +236,7 @@ class Settings(QtCore.QSettings): ('bibles/last search type', '', []), ('custom/last search type', 'custom/last used search type', []), # The following changes are being made for the conversion to using Path objects made in 2.6 development - ('advanced/data path', 'advanced/data path', [(str_to_path, None)]), + ('advanced/data path', 'advanced/data path', [(lambda p: Path(p) if p is not None else None, None)]), ('crashreport/last directory', 'crashreport/last directory', [(str_to_path, None)]), ('servicemanager/last directory', 'servicemanager/last directory', [(str_to_path, None)]), ('servicemanager/last file', 'servicemanager/last file', [(str_to_path, None)]), diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 76d696ba0..29718e09a 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -389,7 +389,8 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi """ Return the current file name, excluding the path. """ - return self._service_path.name + if self._service_path: + return self._service_path.name def reset_supported_suffixes(self): """ From 7043a20530ba9ccf08ae60c41bac6a6942ef27ae Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Sat, 2 Dec 2017 22:10:22 +0000 Subject: [PATCH 74/96] Few test fixes --- .../openlp_core/lib/test_exceptions.py | 45 +++++++++++++++++++ .../openlp_core/lib/test_mediamanageritem.py | 18 ++++---- 2 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 tests/functional/openlp_core/lib/test_exceptions.py diff --git a/tests/functional/openlp_core/lib/test_exceptions.py b/tests/functional/openlp_core/lib/test_exceptions.py new file mode 100644 index 000000000..bf5efa949 --- /dev/null +++ b/tests/functional/openlp_core/lib/test_exceptions.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +Package to test the openlp.core.lib.exceptions package. +""" +from unittest import TestCase + +from openlp.core.lib.exceptions import ValidationError + + +class TestValidationError(TestCase): + """ + Test the ValidationError Class + """ + def test_validation_error(self): + """ + Test the creation of a ValidationError + """ + # GIVEN: The ValidationError class + + # WHEN: Creating an instance of ValidationError + error = ValidationError('Test ValidationError') + + # THEN: Then calling str on the error should return the correct text and it should be an instance of `Exception` + assert str(error) == 'Test ValidationError' + assert isinstance(error, Exception) diff --git a/tests/functional/openlp_core/lib/test_mediamanageritem.py b/tests/functional/openlp_core/lib/test_mediamanageritem.py index fba8ce36b..a6152cbdf 100644 --- a/tests/functional/openlp_core/lib/test_mediamanageritem.py +++ b/tests/functional/openlp_core/lib/test_mediamanageritem.py @@ -42,8 +42,8 @@ class TestMediaManagerItem(TestCase, TestMixin): self.mocked_setup = self.setup_patcher.start() self.addCleanup(self.setup_patcher.stop) - @patch(u'openlp.core.lib.mediamanageritem.Settings') - @patch(u'openlp.core.lib.mediamanageritem.MediaManagerItem.on_preview_click') + @patch('openlp.core.lib.mediamanageritem.Settings') + @patch('openlp.core.lib.mediamanageritem.MediaManagerItem.on_preview_click') def test_on_double_clicked(self, mocked_on_preview_click, MockedSettings): """ Test that when an item is double-clicked then the item is previewed @@ -75,8 +75,8 @@ class TestMediaManagerItem(TestCase, TestMixin): self.assertTrue(mmi.has_delete_icon, 'By default a delete icon should be present') self.assertFalse(mmi.add_to_service_item, 'There should be no add_to_service icon by default') - @patch(u'openlp.core.lib.mediamanageritem.Settings') - @patch(u'openlp.core.lib.mediamanageritem.MediaManagerItem.on_live_click') + @patch('openlp.core.lib.mediamanageritem.Settings') + @patch('openlp.core.lib.mediamanageritem.MediaManagerItem.on_live_click') def test_on_double_clicked_go_live(self, mocked_on_live_click, MockedSettings): """ Test that when "Double-click to go live" is enabled that the item goes live @@ -93,9 +93,9 @@ class TestMediaManagerItem(TestCase, TestMixin): # THEN: on_live_click() should have been called mocked_on_live_click.assert_called_with() - @patch(u'openlp.core.lib.mediamanageritem.Settings') - @patch(u'openlp.core.lib.mediamanageritem.MediaManagerItem.on_live_click') - @patch(u'openlp.core.lib.mediamanageritem.MediaManagerItem.on_preview_click') + @patch('openlp.core.lib.mediamanageritem.Settings') + @patch('openlp.core.lib.mediamanageritem.MediaManagerItem.on_live_click') + @patch('openlp.core.lib.mediamanageritem.MediaManagerItem.on_preview_click') def test_on_double_clicked_single_click_preview(self, mocked_on_preview_click, mocked_on_live_click, MockedSettings): """ @@ -111,5 +111,5 @@ class TestMediaManagerItem(TestCase, TestMixin): mmi.on_double_clicked() # THEN: on_live_click() should have been called - self.assertEqual(0, mocked_on_live_click.call_count, u'on_live_click() should not have been called') - self.assertEqual(0, mocked_on_preview_click.call_count, u'on_preview_click() should not have been called') + self.assertEqual(0, mocked_on_live_click.call_count, 'on_live_click() should not have been called') + self.assertEqual(0, mocked_on_preview_click.call_count, 'on_preview_click() should not have been called') From 159056f06fbb49fc4aa2ecf8ecdc5884d2cb72a0 Mon Sep 17 00:00:00 2001 From: Ken Roberts <alisonken1@gmail.com> Date: Sun, 3 Dec 2017 16:24:47 -0800 Subject: [PATCH 75/96] PJLink2-M updates --- openlp/core/projectors/__init__.py | 2 - openlp/core/projectors/constants.py | 18 ++ openlp/core/projectors/db.py | 2 +- openlp/core/projectors/pjlink.py | 241 ++++++++++++------ .../projectors/test_projector_bugfixes_01.py | 43 +--- .../projectors/test_projector_pjlink_base.py | 80 +++--- .../test_projector_pjlink_cmd_routing.py | 56 ++-- ...y => test_projector_pjlink_commands_01.py} | 2 +- .../test_projector_pjlink_commands_02.py | 198 ++++++++++++++ 9 files changed, 449 insertions(+), 193 deletions(-) rename tests/functional/openlp_core/projectors/{test_projector_pjlink_commands.py => test_projector_pjlink_commands_01.py} (99%) create mode 100644 tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py diff --git a/openlp/core/projectors/__init__.py b/openlp/core/projectors/__init__.py index 396422902..66cfd6080 100644 --- a/openlp/core/projectors/__init__.py +++ b/openlp/core/projectors/__init__.py @@ -25,8 +25,6 @@ Initialization for the openlp.core.projectors modules. """ -from openlp.core.projectors.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING - class DialogSourceStyle(object): """ diff --git a/openlp/core/projectors/constants.py b/openlp/core/projectors/constants.py index 715896133..c700141b8 100644 --- a/openlp/core/projectors/constants.py +++ b/openlp/core/projectors/constants.py @@ -144,6 +144,24 @@ PJLINK_VALID_CMD = { } } +# QAbstractSocketState enums converted to string +S_QSOCKET_STATE = { + 0: 'QSocketState - UnconnectedState', + 1: 'QSocketState - HostLookupState', + 2: 'QSocketState - ConnectingState', + 3: 'QSocketState - ConnectedState', + 4: 'QSocketState - BoundState', + 5: 'QSocketState - ListeningState (internal use only)', + 6: 'QSocketState - ClosingState', + 'UnconnectedState': 0, + 'HostLookupState': 1, + 'ConnectingState': 2, + 'ConnectedState': 3, + 'BoundState': 4, + 'ListeningState': 5, + 'ClosingState': 6, +} + # Error and status codes S_OK = E_OK = 0 # E_OK included since I sometimes forget # Error codes. Start at 200 so we don't duplicate system error codes. diff --git a/openlp/core/projectors/db.py b/openlp/core/projectors/db.py index 99fe9515b..fe8785861 100644 --- a/openlp/core/projectors/db.py +++ b/openlp/core/projectors/db.py @@ -415,7 +415,7 @@ class ProjectorDB(Manager): for key in projector.source_available: item = self.get_object_filtered(ProjectorSource, and_(ProjectorSource.code == key, - ProjectorSource.projector_id == projector.dbid)) + ProjectorSource.projector_id == projector.id)) if item is None: source_dict[key] = PJLINK_DEFAULT_CODES[key] else: diff --git a/openlp/core/projectors/pjlink.py b/openlp/core/projectors/pjlink.py index 16a65bd11..99fb6956c 100644 --- a/openlp/core/projectors/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -58,8 +58,7 @@ from openlp.core.projectors.constants import CONNECTION_ERRORS, CR, ERROR_MSG, E E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \ E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \ PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \ - STATUS_STRING, S_CONNECTED, S_CONNECTING, S_INFO, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \ - S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS + STATUS_STRING, S_CONNECTED, S_CONNECTING, S_INFO, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_QSOCKET_STATE, S_STATUS log = logging.getLogger(__name__) log.debug('pjlink loaded') @@ -123,7 +122,8 @@ class PJLinkCommands(object): 'INST': self.process_inst, 'LAMP': self.process_lamp, 'NAME': self.process_name, - 'PJLINK': self.check_login, + 'PJLINK': self.process_pjlink, + # 'PJLINK': self.check_login, 'POWR': self.process_powr, 'SNUM': self.process_snum, 'SVER': self.process_sver, @@ -135,7 +135,8 @@ class PJLinkCommands(object): """ Initialize instance variables. Also used to reset projector-specific information to default. """ - log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state())) + log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, + state=S_QSOCKET_STATE[self.state()])) self.fan = None # ERST self.filter_time = None # FILT self.lamp = None # LAMP @@ -165,6 +166,7 @@ class PJLinkCommands(object): self.socket_timer.stop() self.send_busy = False self.send_queue = [] + self.priority_queue = [] def process_command(self, cmd, data): """ @@ -176,18 +178,19 @@ class PJLinkCommands(object): log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip, cmd=cmd, data=data)) - # Check if we have a future command not available yet - _cmd = cmd.upper() + # cmd should already be in uppercase, but data may be in mixed-case. + # Due to some replies should stay as mixed-case, validate using separate uppercase check _data = data.upper() - if _cmd not in PJLINK_VALID_CMD: + # Check if we have a future command not available yet + if cmd not in PJLINK_VALID_CMD: log.error("({ip}) Ignoring command='{cmd}' (Invalid/Unknown)".format(ip=self.ip, cmd=cmd)) return elif _data == 'OK': - log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd)) - # A command returned successfully, no further processing needed - return - elif _cmd not in self.pjlink_functions: - log.warning("({ip}) Unable to process command='{cmd}' (Future option)".format(ip=self.ip, cmd=cmd)) + log.debug("({ip}) Command '{cmd}' returned OK".format(ip=self.ip, cmd=cmd)) + # A command returned successfully, so do a query on command to verify status + return self.send_command(cmd=cmd) + elif cmd not in self.pjlink_functions: + log.warning("({ip}) Unable to process command='{cmd}' (Future option?)".format(ip=self.ip, cmd=cmd)) return elif _data in PJLINK_ERRORS: # Oops - projector error @@ -211,12 +214,10 @@ class PJLinkCommands(object): elif _data == PJLINK_ERRORS[E_PROJECTOR]: # Projector/display error self.change_status(E_PROJECTOR) - self.receive_data_signal() return # Command checks already passed log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd)) - self.receive_data_signal() - self.pjlink_functions[_cmd](data) + self.pjlink_functions[cmd](data) def process_avmt(self, data): """ @@ -430,6 +431,51 @@ class PJLinkCommands(object): log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name)) return + def process_pjlink(self, data): + """ + Process initial socket connection to terminal. + + :param data: Initial packet with authentication scheme + """ + log.debug("({ip}) Processing PJLINK command".format(ip=self.ip)) + chk = data.split(" ") + if len(chk[0]) != 1: + # Invalid - after splitting, first field should be 1 character, either '0' or '1' only + log.error("({ip}) Invalid initial authentication scheme - aborting".format(ip=self.ip)) + return self.disconnect_from_host() + elif chk[0] == '0': + # Normal connection no authentication + if len(chk) > 1: + # Invalid data - there should be nothing after a normal authentication scheme + log.error("({ip}) Normal connection with extra information - aborting".format(ip=self.ip)) + return self.disconnect_from_host() + elif self.pin: + log.error("({ip}) Normal connection but PIN set - aborting".format(ip=self.ip)) + return self.disconnect_from_host() + else: + data_hash = None + elif chk[0] == '1': + if len(chk) < 2: + # Not enough information for authenticated connection + log.error("({ip}) Authenticated connection but not enough info - aborting".format(ip=self.ip)) + return self.disconnect_from_host() + elif not self.pin: + log.error("({ip}) Authenticate connection but no PIN - aborting".format(ip=self.ip)) + return self.disconnect_from_host() + else: + data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=self.pin.encode('utf-8')), + encoding='ascii') + # Passed basic checks, so start connection + self.readyRead.connect(self.get_socket) + if not self.no_poll: + log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.ip)) + self.timer.setInterval(2000) # Set 2 seconds for initial information + self.timer.start() + self.change_status(S_CONNECTED) + log.debug("({ip}) process_pjlink(): Sending 'CLSS' initial command'".format(ip=self.ip)) + # Since this is an initial connection, make it a priority just in case + return self.send_command(cmd="CLSS", salt=data_hash, priority=True) + def process_powr(self, data): """ Power status. See PJLink specification for format. @@ -573,6 +619,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): self.widget = None # QListBox entry self.timer = None # Timer that calls the poll_loop self.send_queue = [] + self.priority_queue = [] self.send_busy = False # Socket timer for some possible brain-dead projectors or network cable pulled self.socket_timer = None @@ -586,6 +633,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): self.connected.connect(self.check_login) self.disconnected.connect(self.disconnect_from_host) self.error.connect(self.get_error) + self.projectorReceivedData.connect(self._send_command) def thread_stopped(self): """ @@ -608,6 +656,11 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): self.projectorReceivedData.disconnect(self._send_command) except TypeError: pass + try: + self.readyRead.connect(self.get_socket) # Set in process_pjlink + except TypeError: + pass + self.disconnect_from_host() self.deleteLater() self.i_am_running = False @@ -625,10 +678,10 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): Retrieve information from projector that changes. Normally called by timer(). """ - if self.state() != self.ConnectedState: + if self.state() != S_QSOCKET_STATE['ConnectedState']: log.warning("({ip}) poll_loop(): Not connected - returning".format(ip=self.ip)) return - log.debug('({ip}) Updating projector status'.format(ip=self.ip)) + log.debug('({ip}) poll_loop(): Updating projector status'.format(ip=self.ip)) # Reset timer in case we were called from a set command if self.timer.interval() < self.poll_time: # Reset timer to 5 seconds @@ -640,28 +693,28 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): if self.pjlink_class == '2': check_list.extend(['FILT', 'FREZ']) for command in check_list: - self.send_command(command, queue=True) + self.send_command(command) # The following commands do not change, so only check them once if self.power == S_ON and self.source_available is None: - self.send_command('INST', queue=True) + self.send_command('INST') if self.other_info is None: - self.send_command('INFO', queue=True) + self.send_command('INFO') if self.manufacturer is None: - self.send_command('INF1', queue=True) + self.send_command('INF1') if self.model is None: - self.send_command('INF2', queue=True) + self.send_command('INF2') if self.pjlink_name is None: - self.send_command('NAME', queue=True) + self.send_command('NAME') if self.pjlink_class == '2': # Class 2 specific checks if self.serial_no is None: - self.send_command('SNUM', queue=True) + self.send_command('SNUM') if self.sw_version is None: - self.send_command('SVER', queue=True) + self.send_command('SVER') if self.model_filter is None: - self.send_command('RFIL', queue=True) + self.send_command('RFIL') if self.model_lamp is None: - self.send_command('RLMP', queue=True) + self.send_command('RLMP') def _get_status(self, status): """ @@ -713,14 +766,12 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): code=status_code, message=status_message if msg is None else msg)) self.changeStatus.emit(self.ip, status, message) + self.projectorUpdateIcons.emit() @QtCore.pyqtSlot() def check_login(self, data=None): """ - Processes the initial connection and authentication (if needed). - Starts poll timer if connection is established. - - NOTE: Qt md5 hash function doesn't work with projector authentication. Use the python md5 hash function. + Processes the initial connection and convert to a PJLink packet if valid initial connection :param data: Optional data if called from another routine """ @@ -733,12 +784,12 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): self.change_status(E_SOCKET_TIMEOUT) return read = self.readLine(self.max_size) - self.readLine(self.max_size) # Clean out the trailing \r\n + self.readLine(self.max_size) # Clean out any trailing whitespace if read is None: log.warning('({ip}) read is None - socket error?'.format(ip=self.ip)) return elif len(read) < 8: - log.warning('({ip}) Not enough data read)'.format(ip=self.ip)) + log.warning('({ip}) Not enough data read - skipping'.format(ip=self.ip)) return data = decode(read, 'utf-8') # Possibility of extraneous data on input when reading. @@ -750,9 +801,16 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): # PJLink initial login will be: # 'PJLink 0' - Unauthenticated login - no extra steps required. # 'PJLink 1 XXXXXX' Authenticated login - extra processing required. - if not data.upper().startswith('PJLINK'): - # Invalid response + if not data.startswith('PJLINK'): + # Invalid initial packet - close socket + log.error("({ip}) Invalid initial packet received - closing socket".format(ip=self.ip)) return self.disconnect_from_host() + log.debug("({ip}) check_login(): Formatting initial connection prompt to PJLink packet".format(ip=self.ip)) + return self.get_data("{start}{clss}{data}".format(start=PJLINK_PREFIX, + clss="1", + data=data.replace(" ", "=", 1)).encode('utf-8')) + # TODO: The below is replaced by process_pjlink() - remove when working properly + """ if '=' in data: # Processing a login reply data_check = data.strip().split('=') @@ -801,6 +859,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): log.debug('({ip}) Starting timer'.format(ip=self.ip)) self.timer.setInterval(2000) # Set 2 seconds for initial information self.timer.start() + """ def _trash_buffer(self, msg=None): """ @@ -848,32 +907,43 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip)) return self.receive_data_signal() self.socket_timer.stop() - return self.get_data(buff=read, ip=self.ip) + self.get_data(buff=read, ip=self.ip) + return self.receive_data_signal() - def get_data(self, buff, ip): + def get_data(self, buff, ip=None): """ Process received data :param buff: Data to process. :param ip: (optional) Destination IP. """ + ip = self.ip if ip is None else ip log.debug("({ip}) get_data(ip='{ip_in}' buffer='{buff}'".format(ip=self.ip, ip_in=ip, buff=buff)) # NOTE: Class2 has changed to some values being UTF-8 data_in = decode(buff, 'utf-8') data = data_in.strip() - if (len(data) < 7) or (not data.startswith(PJLINK_PREFIX)): - return self._trash_buffer(msg='get_data(): Invalid packet - length or prefix') + # Initial packet checks + if (len(data) < 7): + return self._trash_buffer(msg="get_data(): Invalid packet - length") elif len(data) > self.max_size: - return self._trash_buffer(msg='get_data(): Invalid packet - too long') + return self._trash_buffer(msg="get_data(): Invalid packet - too long") + elif not data.startswith(PJLINK_PREFIX): + return self._trash_buffer(msg="get_data(): Invalid packet - PJLink prefix missing") elif '=' not in data: - return self._trash_buffer(msg='get_data(): Invalid packet does not have equal') - log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data)) + return self._trash_buffer(msg="get_data(): Invalid packet - Does not have '='") + log.debug("({ip}) get_data(): Checking new data '{data}'".format(ip=self.ip, data=data)) header, data = data.split('=') + # At this point, the header should contain: + # "PVCCCC" + # Where: + # P = PJLINK_PREFIX + # V = PJLink class or version + # C = PJLink command try: - version, cmd = header[1], header[2:] + version, cmd = header[1], header[2:].upper() except ValueError as e: self.change_status(E_INVALID_DATA) - log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip())) + log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in)) return self._trash_buffer('get_data(): Expected header + command + data') if cmd not in PJLINK_VALID_CMD: log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd)) @@ -881,6 +951,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): if int(self.pjlink_class) < int(version): log.warning('({ip}) get_data(): Projector returned class reply higher ' 'than projector stated class'.format(ip=self.ip)) + self.send_busy = False return self.process_command(cmd, data) @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError) @@ -910,19 +981,18 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): self.reset_information() return - def send_command(self, cmd, opts='?', salt=None, queue=False): + def send_command(self, cmd, opts='?', salt=None, priority=False): """ Add command to output queue if not already in queue. :param cmd: Command to send :param opts: Command option (if any) - defaults to '?' (get information) :param salt: Optional salt for md5 hash initial authentication - :param queue: Option to force add to queue rather than sending directly + :param priority: Option to send packet now rather than queue it up """ if self.state() != self.ConnectedState: log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip)) - self.send_queue = [] - return + return self.reset_information() if cmd not in PJLINK_VALID_CMD: log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip)) return @@ -939,28 +1009,26 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): header = PJLINK_HEADER.format(linkclass=cmd_ver[0]) else: # NOTE: Once we get to version 3 then think about looping - log.error('({ip}): send_command(): PJLink class check issue? aborting'.format(ip=self.ip)) + log.error('({ip}): send_command(): PJLink class check issue? Aborting'.format(ip=self.ip)) return out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt, header=header, command=cmd, options=opts, suffix=CR) - if out in self.send_queue: - # Already there, so don't add - log.debug('({ip}) send_command(out="{data}") Already in queue - skipping'.format(ip=self.ip, - data=out.strip())) - elif not queue and len(self.send_queue) == 0: - # Nothing waiting to send, so just send it - log.debug('({ip}) send_command(out="{data}") Sending data'.format(ip=self.ip, data=out.strip())) - return self._send_command(data=out) + if out in self.priority_queue: + log.debug("({ip}) send_command(): Already in priority queue - skipping".format(ip=self.ip)) + elif out in self.send_queue: + log.debug("({ip}) send_command(): Already in normal queue - skipping".format(ip=self.ip)) else: - log.debug('({ip}) send_command(out="{data}") adding to queue'.format(ip=self.ip, data=out.strip())) - self.send_queue.append(out) - self.projectorReceivedData.emit() - log.debug('({ip}) send_command(): send_busy is {data}'.format(ip=self.ip, data=self.send_busy)) - if not self.send_busy: - log.debug('({ip}) send_command() calling _send_string()'.format(ip=self.ip)) + if priority: + log.debug("({ip}) send_command(): Adding to priority queue".format(ip=self.ip)) + self.priority_queue.append(out) + else: + log.debug("({ip}) send_command(): Adding to normal queue".format(ip=self.ip)) + self.send_queue.append(out) + if self.priority_queue or self.send_queue: + # May be some initial connection setup so make sure we send data self._send_command() @QtCore.pyqtSlot() @@ -971,43 +1039,53 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): :param data: Immediate data to send :param utf8: Send as UTF-8 string otherwise send as ASCII string """ - log.debug('({ip}) _send_string()'.format(ip=self.ip)) - log.debug('({ip}) _send_string(): Connection status: {data}'.format(ip=self.ip, data=self.state())) + # Funny looking data check, but it's a quick check for data=None + log.debug("({ip}) _send_command(data='{data}')".format(ip=self.ip, data=data.strip() if data else data)) + log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.ip, + data=S_QSOCKET_STATE[self.state()])) if self.state() != self.ConnectedState: - log.debug('({ip}) _send_string() Not connected - abort'.format(ip=self.ip)) - self.send_queue = [] + log.debug('({ip}) _send_command() Not connected - abort'.format(ip=self.ip)) self.send_busy = False - return + return self.disconnect_from_host() + if data and data not in self.priority_queue: + log.debug("({ip}) _send_command(): Priority packet - adding to priority queue".format(ip=self.ip)) + self.priority_queue.append(data) + if self.send_busy: # Still waiting for response from last command sent + log.debug("({ip}) _send_command(): Still busy, returning".format(ip=self.ip)) + log.debug('({ip}) _send_command(): Priority queue = {data}'.format(ip=self.ip, data=self.priority_queue)) + log.debug('({ip}) _send_command(): Normal queue = {data}'.format(ip=self.ip, data=self.send_queue)) return - if data is not None: - out = data - log.debug('({ip}) _send_string(data="{data}")'.format(ip=self.ip, data=out.strip())) + + if len(self.priority_queue) != 0: + out = self.priority_queue.pop(0) + log.debug("({ip}) _send_command(): Getting priority queued packet".format(ip=self.ip)) elif len(self.send_queue) != 0: out = self.send_queue.pop(0) - log.debug('({ip}) _send_string(queued data="{data}"%s)'.format(ip=self.ip, data=out.strip())) + log.debug('({ip}) _send_command(): Getting normal queued packet'.format(ip=self.ip)) else: # No data to send - log.debug('({ip}) _send_string(): No data to send'.format(ip=self.ip)) + log.debug('({ip}) _send_command(): No data to send'.format(ip=self.ip)) self.send_busy = False return self.send_busy = True - log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip())) - log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue)) + log.debug('({ip}) _send_command(): Sending "{data}"'.format(ip=self.ip, data=out.strip())) self.socket_timer.start() sent = self.write(out.encode('{string_encoding}'.format(string_encoding='utf-8' if utf8 else 'ascii'))) self.waitForBytesWritten(2000) # 2 seconds should be enough if sent == -1: # Network error? - log.warning("({ip}) _send_command(): -1 received".format(ip=self.ip)) + log.warning("({ip}) _send_command(): -1 received - disconnecting from host".format(ip=self.ip)) self.change_status(E_NETWORK, translate('OpenLP.PJLink', 'Error while sending data to projector')) + self.disconnect_from_host() def connect_to_host(self): """ Initiate connection to projector. """ + log.debug("{ip}) connect_to_host(): Starting connection".format(ip=self.ip)) if self.state() == self.ConnectedState: log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip)) return @@ -1023,22 +1101,19 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): if abort: log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip)) else: - log.warning('({ip}) disconnect_from_host(): Not connected - returning'.format(ip=self.ip)) - self.reset_information() + log.warning('({ip}) disconnect_from_host(): Not connected'.format(ip=self.ip)) self.disconnectFromHost() try: self.readyRead.disconnect(self.get_socket) except TypeError: pass + log.debug('({ip}) disconnect_from_host() ' + 'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0])) if abort: self.change_status(E_NOT_CONNECTED) else: - log.debug('({ip}) disconnect_from_host() ' - 'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0])) - if self.status_connect != E_NOT_CONNECTED: - self.change_status(S_NOT_CONNECTED) + self.change_status(S_NOT_CONNECTED) self.reset_information() - self.projectorUpdateIcons.emit() def get_av_mute_status(self): """ diff --git a/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py index c33220d4a..da0aae47f 100644 --- a/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py +++ b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py @@ -23,12 +23,11 @@ Package to test the openlp.core.projectors.pjlink base package. """ from unittest import TestCase -from unittest.mock import patch from openlp.core.projectors.db import Projector from openlp.core.projectors.pjlink import PJLink -from tests.resources.projector.data import TEST_PIN, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA +from tests.resources.projector.data import TEST1_DATA class TestPJLinkBugs(TestCase): @@ -80,43 +79,17 @@ class TestPJLinkBugs(TestCase): """ Test bug 1593882 no pin and authenticated request exception """ - # GIVEN: Test object and mocks - mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() - mock_timer = patch.object(self.pjlink_test, 'timer').start() - mock_authentication = patch.object(self.pjlink_test, 'projectorAuthentication').start() - mock_ready_read = patch.object(self.pjlink_test, 'waitForReadyRead').start() - mock_send_command = patch.object(self.pjlink_test, 'send_command').start() - pjlink = self.pjlink_test - pjlink.pin = None - mock_ready_read.return_value = True - - # WHEN: call with authentication request and pin not set - pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) - - # THEN: 'No Authentication' signal should have been sent - mock_authentication.emit.assert_called_with(pjlink.ip) + # Test now part of test_projector_pjlink_commands_02 + # Keeping here for bug reference + pass def test_bug_1593883_pjlink_authentication(self): """ - Test bugfix 1593883 pjlink authentication + Test bugfix 1593883 pjlink authentication and ticket 92187 """ - # GIVEN: Test object and data - mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() - mock_timer = patch.object(self.pjlink_test, 'timer').start() - mock_send_command = patch.object(self.pjlink_test, 'write').start() - mock_state = patch.object(self.pjlink_test, 'state').start() - mock_waitForReadyRead = patch.object(self.pjlink_test, 'waitForReadyRead').start() - pjlink = self.pjlink_test - pjlink.pin = TEST_PIN - mock_state.return_value = pjlink.ConnectedState - mock_waitForReadyRead.return_value = True - - # WHEN: Athenticated connection is called - pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) - - # THEN: send_command should have the proper authentication - self.assertEqual("{test}".format(test=mock_send_command.call_args), - "call(b'{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) + # Test now part of test_projector_pjlink_commands_02 + # Keeping here for bug reference + pass def test_bug_1734275_process_lamp_nonstandard_reply(self): """ diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py index 7253df032..c86904bad 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py @@ -25,11 +25,11 @@ Package to test the openlp.core.projectors.pjlink base package. from unittest import TestCase from unittest.mock import call, patch, MagicMock -from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED +from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED, S_QSOCKET_STATE from openlp.core.projectors.db import Projector from openlp.core.projectors.pjlink import PJLink -from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST1_DATA +from tests.resources.projector.data import TEST1_DATA pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) @@ -38,29 +38,17 @@ class TestPJLinkBase(TestCase): """ Tests for the PJLink module """ - @patch.object(pjlink_test, 'readyRead') - @patch.object(pjlink_test, 'send_command') - @patch.object(pjlink_test, 'waitForReadyRead') - @patch('openlp.core.common.qmd5_hash') - def test_authenticated_connection_call(self, - mock_qmd5_hash, - mock_waitForReadyRead, - mock_send_command, - mock_readyRead): - """ - Ticket 92187: Fix for projector connect with PJLink authentication exception. - """ - # GIVEN: Test object - pjlink = pjlink_test + def setUp(self): + ''' + TestPJLinkCommands part 2 initialization + ''' + self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) - # WHEN: Calling check_login with authentication request: - pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) - - # THEN: Should have called qmd5_hash - self.assertTrue(mock_qmd5_hash.called_with(TEST_SALT, - "Connection request should have been called with TEST_SALT")) - self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN, - "Connection request should have been called with TEST_PIN")) + def tearDown(self): + ''' + TestPJLinkCommands part 2 cleanups + ''' + self.pjlink_test = None @patch.object(pjlink_test, 'change_status') def test_status_change(self, mock_change_status): @@ -110,18 +98,18 @@ class TestPJLinkBase(TestCase): # THEN: poll_loop should exit without calling any other method self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method') - @patch.object(pjlink_test, 'send_command') - def test_poll_loop_start(self, mock_send_command): + def test_poll_loop_start(self): """ Test PJLink.poll_loop makes correct calls """ - # GIVEN: test object and test data - pjlink = pjlink_test - pjlink.state = MagicMock() - pjlink.timer = MagicMock() - pjlink.timer.interval = MagicMock() - pjlink.timer.setInterval = MagicMock() - pjlink.timer.start = MagicMock() + # GIVEN: Mocks and test data + mock_state = patch.object(self.pjlink_test, 'state').start() + mock_state.return_value = S_QSOCKET_STATE['ConnectedState'] + mock_timer = patch.object(self.pjlink_test, 'timer').start() + mock_timer.interval.return_value = 10 + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() + + pjlink = self.pjlink_test pjlink.poll_time = 20 pjlink.power = S_ON pjlink.source_available = None @@ -130,19 +118,17 @@ class TestPJLinkBase(TestCase): pjlink.model = None pjlink.pjlink_name = None pjlink.ConnectedState = S_CONNECTED - pjlink.timer.interval.return_value = 10 - pjlink.state.return_value = S_CONNECTED call_list = [ - call('POWR', queue=True), - call('ERST', queue=True), - call('LAMP', queue=True), - call('AVMT', queue=True), - call('INPT', queue=True), - call('INST', queue=True), - call('INFO', queue=True), - call('INF1', queue=True), - call('INF2', queue=True), - call('NAME', queue=True), + call('POWR'), + call('ERST'), + call('LAMP'), + call('AVMT'), + call('INPT'), + call('INST'), + call('INFO'), + call('INF1'), + call('INF2'), + call('NAME'), ] # WHEN: PJLink.poll_loop is called @@ -150,8 +136,8 @@ class TestPJLinkBase(TestCase): # THEN: proper calls were made to retrieve projector data # First, call to update the timer with the next interval - self.assertTrue(pjlink.timer.setInterval.called, 'Should have updated the timer') + self.assertTrue(mock_timer.setInterval.called) # Next, should have called the timer to start - self.assertTrue(pjlink.timer.start.called, 'Should have started the timer') + self.assertTrue(mock_timer.start.called, 'Should have started the timer') # Finally, should have called send_command with a list of projetctor status checks mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries') diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py index 431da0606..a5a6eca40 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py @@ -46,6 +46,18 @@ class TestPJLinkRouting(TestCase): """ Tests for the PJLink module command routing """ + def setUp(self): + ''' + TestPJLinkCommands part 2 initialization + ''' + self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) + + def tearDown(self): + ''' + TestPJLinkCommands part 2 cleanups + ''' + self.pjlink_test = None + @patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_call_clss(self, mock_log): """ @@ -163,21 +175,20 @@ class TestPJLinkRouting(TestCase): mock_change_status.assert_called_once_with(E_AUTHENTICATION) mock_log.error.assert_called_with(log_text) - @patch.object(openlp.core.projectors.pjlink, 'log') - def test_process_command_future(self, mock_log): + def test_process_command_future(self): """ Test command valid but no method to process yet """ - # GIVEN: Test object - pjlink = pjlink_test - log_text = "(127.0.0.1) Unable to process command='CLSS' (Future option)" - mock_log.reset_mock() - # Remove a valid command so we can test valid command but not available yet - pjlink.pjlink_functions.pop('CLSS') + # GIVEN: Initial mocks and data + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() + mock_functions = patch.object(self.pjlink_test, 'pjlink_functions').start() + mock_functions.return_value = [] + + pjlink = self.pjlink_test + log_text = "(111.111.111.111) Unable to process command='CLSS' (Future option?)" # WHEN: process_command called with an unknown command - with patch.object(pjlink, 'pjlink_functions') as mock_functions: - pjlink.process_command(cmd='CLSS', data='DONT CARE') + pjlink.process_command(cmd='CLSS', data='DONT CARE') # THEN: Error should be logged and no command called self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') @@ -202,23 +213,20 @@ class TestPJLinkRouting(TestCase): self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') mock_log.error.assert_called_once_with(log_text) - @patch.object(pjlink_test, 'pjlink_functions') - @patch.object(openlp.core.projectors.pjlink, 'log') - def test_process_command_ok(self, mock_log, mock_functions): + def test_process_command_ok(self): """ Test command returned success """ - # GIVEN: Test object - pjlink = pjlink_test - mock_functions.reset_mock() - mock_log.reset_mock() + # GIVEN: Initial mocks and data + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() - # WHEN: process_command called with an unknown command - pjlink.process_command(cmd='CLSS', data='OK') - log_text = '(127.0.0.1) Command "CLSS" returned OK' + pjlink = self.pjlink_test + log_text = "(111.111.111.111) Command 'POWR' returned OK" - # THEN: Error should be logged and no command called - self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') - self.assertEqual(mock_log.debug.call_count, 2, 'log.debug() should have been called twice') - # Although we called it twice, only the last log entry is saved + # WHEN: process_command called with a command that returns OK + pjlink.process_command(cmd='POWR', data='OK') + + # THEN: Appropriate calls should have been made mock_log.debug.assert_called_with(log_text) + mock_send_command.assert_called_once_with(cmd='POWR') diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py similarity index 99% rename from tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py rename to tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py index 32544dd09..0917a9357 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py @@ -47,7 +47,7 @@ for pos in range(0, len(PJLINK_ERST_DATA)): class TestPJLinkCommands(TestCase): """ - Tests for the PJLink module + Tests for the PJLinkCommands class part 1 """ @patch.object(pjlink_test, 'changeStatus') @patch.object(openlp.core.projectors.pjlink, 'log') diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py new file mode 100644 index 000000000..08682ccb7 --- /dev/null +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2015 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +Package to test the openlp.core.projectors.pjlink commands package. +""" +from unittest import TestCase +from unittest.mock import patch, call + +import openlp.core.projectors.pjlink +from openlp.core.projectors.constants import S_CONNECTED +from openlp.core.projectors.db import Projector +from openlp.core.projectors.pjlink import PJLink + +from tests.resources.projector.data import TEST_HASH, TEST_PIN, TEST_SALT, TEST1_DATA + + +class TestPJLinkCommands(TestCase): + """ + Tests for the PJLinkCommands class part 2 + """ + def setUp(self): + ''' + TestPJLinkCommands part 2 initialization + ''' + self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) + + def tearDown(self): + ''' + TestPJLinkCommands part 2 cleanups + ''' + self.pjlink_test = None + + def test_process_pjlink_normal(self): + """ + Test initial connection prompt with no authentication + """ + # GIVEN: Initial mocks and data + mock_log = patch.object(openlp.core.projectors.pjlink, "log").start() + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() + mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start() + mock_change_status = patch.object(self.pjlink_test, 'change_status').start() + pjlink = self.pjlink_test + pjlink.pin = None + log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ] + + # WHEN: process_pjlink called with no authentication required + pjlink.process_pjlink(data="0") + + # THEN: proper processing should have occured + mock_log.debug.has_calls(log_check) + mock_disconnect_from_host.assert_not_called() + mock_readyRead.connect.assert_called_once() + mock_change_status.assert_called_once_with(S_CONNECTED) + mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=None) + + def test_process_pjlink_authenticate(self): + """ + Test initial connection prompt with authentication + """ + # GIVEN: Initial mocks and data + mock_log = patch.object(openlp.core.projectors.pjlink, "log").start() + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() + mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start() + mock_change_status = patch.object(self.pjlink_test, 'change_status').start() + pjlink = self.pjlink_test + pjlink.pin = TEST_PIN + log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ] + + # WHEN: process_pjlink called with no authentication required + pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT)) + + # THEN: proper processing should have occured + mock_log.debug.has_calls(log_check) + mock_disconnect_from_host.assert_not_called() + mock_readyRead.connect.assert_called_once() + mock_change_status.assert_called_once_with(S_CONNECTED) + mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=TEST_HASH) + + def test_process_pjlink_normal_pin_set_error(self): + """ + Test process_pjlinnk called with no authentication but pin is set + """ + # GIVEN: Initial mocks and data + # GIVEN: Initial mocks and data + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() + pjlink = self.pjlink_test + pjlink.pin = TEST_PIN + log_check = [call('(111.111.111.111) Normal connection but PIN set - aborting'), ] + + # WHEN: process_pjlink called with invalid authentication scheme + pjlink.process_pjlink(data='0') + + # THEN: Proper calls should be made + mock_log.error.assert_has_calls(log_check) + mock_disconnect_from_host.assert_called_once() + mock_send_command.assert_not_called() + + def test_process_pjlink_normal_with_salt_error(self): + """ + Test process_pjlinnk called with no authentication but pin is set + """ + # GIVEN: Initial mocks and data + # GIVEN: Initial mocks and data + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() + pjlink = self.pjlink_test + pjlink.pin = TEST_PIN + log_check = [call('(111.111.111.111) Normal connection with extra information - aborting'), ] + + # WHEN: process_pjlink called with invalid authentication scheme + pjlink.process_pjlink(data='0 {salt}'.format(salt=TEST_SALT)) + + # THEN: Proper calls should be made + mock_log.error.assert_has_calls(log_check) + mock_disconnect_from_host.assert_called_once() + mock_send_command.assert_not_called() + + def test_process_pjlink_invalid_authentication_scheme_length_error(self): + """ + Test initial connection prompt with authentication scheme longer than 1 character + """ + # GIVEN: Initial mocks and data + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() + pjlink = self.pjlink_test + log_check = [call('(111.111.111.111) Invalid initial authentication scheme - aborting'), ] + + # WHEN: process_pjlink called with invalid authentication scheme + pjlink.process_pjlink(data='01') + + # THEN: socket should be closed and invalid data logged + mock_log.error.assert_has_calls(log_check) + mock_disconnect_from_host.assert_called_once() + mock_send_command.assert_not_called() + + def test_process_pjlink_invalid_authentication_data_length_error(self): + """ + Test initial connection prompt with authentication no salt + """ + # GIVEN: Initial mocks and data + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() + log_check = [call('(111.111.111.111) Authenticated connection but not enough info - aborting'), ] + pjlink = self.pjlink_test + + # WHEN: process_pjlink called with no salt + pjlink.process_pjlink(data='1') + + # THEN: socket should be closed and invalid data logged + mock_log.error.assert_has_calls(log_check) + mock_disconnect_from_host.assert_called_once() + mock_send_command.assert_not_called() + + def test_process_pjlink_authenticate_pin_not_set_error(self): + """ + Test process_pjlink authentication but pin not set + """ + # GIVEN: Initial mocks and data + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() + log_check = [call('(111.111.111.111) Authenticate connection but no PIN - aborting'), ] + pjlink = self.pjlink_test + pjlink.pin = None + + # WHEN: process_pjlink called with no salt + pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT)) + + # THEN: socket should be closed and invalid data logged + mock_log.error.assert_has_calls(log_check) + mock_disconnect_from_host.assert_called_once() + mock_send_command.assert_not_called() From 4374200ab04c3f39e04b01d38978162adab94238 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Mon, 4 Dec 2017 20:32:02 +0000 Subject: [PATCH 76/96] Test fixes --- .../openlp_core/ui/media/test_vlcplayer.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/functional/openlp_core/ui/media/test_vlcplayer.py b/tests/functional/openlp_core/ui/media/test_vlcplayer.py index 6e2fe73c6..96ab629c6 100644 --- a/tests/functional/openlp_core/ui/media/test_vlcplayer.py +++ b/tests/functional/openlp_core/ui/media/test_vlcplayer.py @@ -693,9 +693,9 @@ class TestVLCPlayer(TestCase, TestMixin): vlc_player.set_state(MediaState.Paused, mocked_display) # WHEN: play() is called - with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait, \ - patch.object(vlc_player, 'volume') as mocked_volume: - mocked_media_state_wait.return_value = True + with patch.object(vlc_player, 'media_state_wait', return_value=True) as mocked_media_state_wait, \ + patch.object(vlc_player, 'volume') as mocked_volume, \ + patch.object(vlc_player, 'get_live_state', return_value=MediaState.Loaded): result = vlc_player.play(mocked_display) # THEN: A bunch of things should happen to play the media @@ -872,7 +872,7 @@ class TestVLCPlayer(TestCase, TestMixin): mocked_display = MagicMock() mocked_display.controller.media_info.media_type = MediaType.DVD mocked_display.vlc_media_player.is_seekable.return_value = True - mocked_display.controller.media_info.start_time = 3 + mocked_display.controller.media_info.start_time = 3000 vlc_player = VlcPlayer(None) # WHEN: seek() is called @@ -976,7 +976,7 @@ class TestVLCPlayer(TestCase, TestMixin): mocked_display = MagicMock() mocked_display.controller = mocked_controller mocked_display.vlc_media.get_state.return_value = 1 - mocked_display.vlc_media_player.get_time.return_value = 400000 + mocked_display.vlc_media_player.get_time.return_value = 400 mocked_display.controller.media_info.media_type = MediaType.DVD vlc_player = VlcPlayer(None) @@ -990,7 +990,7 @@ class TestVLCPlayer(TestCase, TestMixin): self.assertEqual(2, mocked_stop.call_count) mocked_display.vlc_media_player.get_time.assert_called_with() mocked_set_visible.assert_called_with(mocked_display, False) - mocked_controller.seek_slider.setSliderPosition.assert_called_with(300000) + mocked_controller.seek_slider.setSliderPosition.assert_called_with(300) expected_calls = [call(True), call(False)] self.assertEqual(expected_calls, mocked_controller.seek_slider.blockSignals.call_args_list) From baed1934446668b0cd8b99151f3936d1a0266563 Mon Sep 17 00:00:00 2001 From: Phill Ridout <phill.ridout@gmail.com> Date: Mon, 4 Dec 2017 20:49:59 +0000 Subject: [PATCH 77/96] PEP8 --- openlp/core/ui/media/vlcplayer.py | 2 +- tests/functional/openlp_core/lib/test_exceptions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/core/ui/media/vlcplayer.py b/openlp/core/ui/media/vlcplayer.py index 4ad9d990f..840471cfe 100644 --- a/openlp/core/ui/media/vlcplayer.py +++ b/openlp/core/ui/media/vlcplayer.py @@ -281,7 +281,7 @@ class VlcPlayer(MediaPlayer): log.debug('mediatype: ' + str(controller.media_info.media_type)) # Set tracks for the optical device if controller.media_info.media_type == MediaType.DVD and \ - self.get_live_state() != MediaState.Paused and self.get_preview_state() != MediaState.Paused: + self.get_live_state() != MediaState.Paused and self.get_preview_state() != MediaState.Paused: log.debug('vlc play, playing started') if controller.media_info.title_track > 0: log.debug('vlc play, title_track set: ' + str(controller.media_info.title_track)) diff --git a/tests/functional/openlp_core/lib/test_exceptions.py b/tests/functional/openlp_core/lib/test_exceptions.py index bf5efa949..c0de323b7 100644 --- a/tests/functional/openlp_core/lib/test_exceptions.py +++ b/tests/functional/openlp_core/lib/test_exceptions.py @@ -38,7 +38,7 @@ class TestValidationError(TestCase): # GIVEN: The ValidationError class # WHEN: Creating an instance of ValidationError - error = ValidationError('Test ValidationError') + error = ValidationError('Test ValidationError') # THEN: Then calling str on the error should return the correct text and it should be an instance of `Exception` assert str(error) == 'Test ValidationError' From 44b82d8ca292c7eab4f440e1860ffec99917ed5e Mon Sep 17 00:00:00 2001 From: Ken Roberts <alisonken1@gmail.com> Date: Mon, 4 Dec 2017 16:54:15 -0800 Subject: [PATCH 78/96] Fix mocks to use correct python version tests --- openlp/core/projectors/pjlink.py | 2 +- .../test_projector_pjlink_commands_02.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openlp/core/projectors/pjlink.py b/openlp/core/projectors/pjlink.py index 99fb6956c..0a18aef33 100644 --- a/openlp/core/projectors/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -657,7 +657,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): except TypeError: pass try: - self.readyRead.connect(self.get_socket) # Set in process_pjlink + self.readyRead.disconnect(self.get_socket) # Set in process_pjlink except TypeError: pass diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py index 08682ccb7..62ba4ec76 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py @@ -69,7 +69,7 @@ class TestPJLinkCommands(TestCase): # THEN: proper processing should have occured mock_log.debug.has_calls(log_check) mock_disconnect_from_host.assert_not_called() - mock_readyRead.connect.assert_called_once() + self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once') mock_change_status.assert_called_once_with(S_CONNECTED) mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=None) @@ -93,7 +93,7 @@ class TestPJLinkCommands(TestCase): # THEN: proper processing should have occured mock_log.debug.has_calls(log_check) mock_disconnect_from_host.assert_not_called() - mock_readyRead.connect.assert_called_once() + self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once') mock_change_status.assert_called_once_with(S_CONNECTED) mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=TEST_HASH) @@ -115,7 +115,7 @@ class TestPJLinkCommands(TestCase): # THEN: Proper calls should be made mock_log.error.assert_has_calls(log_check) - mock_disconnect_from_host.assert_called_once() + self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') mock_send_command.assert_not_called() def test_process_pjlink_normal_with_salt_error(self): @@ -136,7 +136,7 @@ class TestPJLinkCommands(TestCase): # THEN: Proper calls should be made mock_log.error.assert_has_calls(log_check) - mock_disconnect_from_host.assert_called_once() + self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') mock_send_command.assert_not_called() def test_process_pjlink_invalid_authentication_scheme_length_error(self): @@ -155,7 +155,7 @@ class TestPJLinkCommands(TestCase): # THEN: socket should be closed and invalid data logged mock_log.error.assert_has_calls(log_check) - mock_disconnect_from_host.assert_called_once() + self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') mock_send_command.assert_not_called() def test_process_pjlink_invalid_authentication_data_length_error(self): @@ -174,7 +174,7 @@ class TestPJLinkCommands(TestCase): # THEN: socket should be closed and invalid data logged mock_log.error.assert_has_calls(log_check) - mock_disconnect_from_host.assert_called_once() + self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') mock_send_command.assert_not_called() def test_process_pjlink_authenticate_pin_not_set_error(self): @@ -194,5 +194,5 @@ class TestPJLinkCommands(TestCase): # THEN: socket should be closed and invalid data logged mock_log.error.assert_has_calls(log_check) - mock_disconnect_from_host.assert_called_once() + self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') mock_send_command.assert_not_called() From eec0e325dfc1d61157706019d88493a8a5c2e253 Mon Sep 17 00:00:00 2001 From: Ken Roberts <alisonken1@gmail.com> Date: Sat, 9 Dec 2017 03:17:05 -0800 Subject: [PATCH 79/96] OLP Style cleanups --- openlp/core/projectors/constants.py | 2 +- openlp/core/projectors/pjlink.py | 141 +++++++++--------- .../test_projector_pjlink_cmd_routing.py | 6 +- .../test_projector_pjlink_commands_01.py | 24 +-- 4 files changed, 88 insertions(+), 85 deletions(-) diff --git a/openlp/core/projectors/constants.py b/openlp/core/projectors/constants.py index c700141b8..a9410d109 100644 --- a/openlp/core/projectors/constants.py +++ b/openlp/core/projectors/constants.py @@ -159,7 +159,7 @@ S_QSOCKET_STATE = { 'ConnectedState': 3, 'BoundState': 4, 'ListeningState': 5, - 'ClosingState': 6, + 'ClosingState': 6 } # Error and status codes diff --git a/openlp/core/projectors/pjlink.py b/openlp/core/projectors/pjlink.py index 0a18aef33..12ae7e1d0 100644 --- a/openlp/core/projectors/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -110,7 +110,7 @@ class PJLinkCommands(object): """ log.debug('PJlinkCommands(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs)) super().__init__() - # Map command to function + # Map PJLink command to method self.pjlink_functions = { 'AVMT': self.process_avmt, 'CLSS': self.process_clss, @@ -123,6 +123,7 @@ class PJLinkCommands(object): 'LAMP': self.process_lamp, 'NAME': self.process_name, 'PJLINK': self.process_pjlink, + # TODO: Part of check_login refactor - remove when done # 'PJLINK': self.check_login, 'POWR': self.process_powr, 'SNUM': self.process_snum, @@ -183,14 +184,14 @@ class PJLinkCommands(object): _data = data.upper() # Check if we have a future command not available yet if cmd not in PJLINK_VALID_CMD: - log.error("({ip}) Ignoring command='{cmd}' (Invalid/Unknown)".format(ip=self.ip, cmd=cmd)) + log.error('({ip}) Ignoring command="{cmd}" (Invalid/Unknown)'.format(ip=self.ip, cmd=cmd)) return elif _data == 'OK': - log.debug("({ip}) Command '{cmd}' returned OK".format(ip=self.ip, cmd=cmd)) + log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd)) # A command returned successfully, so do a query on command to verify status return self.send_command(cmd=cmd) elif cmd not in self.pjlink_functions: - log.warning("({ip}) Unable to process command='{cmd}' (Future option?)".format(ip=self.ip, cmd=cmd)) + log.warning('({ip}) Unable to process command="{cmd}" (Future option?)'.format(ip=self.ip, cmd=cmd)) return elif _data in PJLINK_ERRORS: # Oops - projector error @@ -260,19 +261,19 @@ class PJLinkCommands(object): # : Received: '%1CLSS=Class 1' (Optoma) # : Received: '%1CLSS=Version1' (BenQ) if len(data) > 1: - log.warning("({ip}) Non-standard CLSS reply: '{data}'".format(ip=self.ip, data=data)) + log.warning('({ip}) Non-standard CLSS reply: "{data}"'.format(ip=self.ip, data=data)) # Due to stupid projectors not following standards (Optoma, BenQ comes to mind), # AND the different responses that can be received, the semi-permanent way to # fix the class reply is to just remove all non-digit characters. try: clss = re.findall('\d', data)[0] # Should only be the first match except IndexError: - log.error("({ip}) No numbers found in class version reply '{data}' - " - "defaulting to class '1'".format(ip=self.ip, data=data)) + log.error('({ip}) No numbers found in class version reply "{data}" - ' + 'defaulting to class "1"'.format(ip=self.ip, data=data)) clss = '1' elif not data.isdigit(): - log.error("({ip}) NAN clss version reply '{data}' - " - "defaulting to class '1'".format(ip=self.ip, data=data)) + log.error('({ip}) NAN CLSS version reply "{data}" - ' + 'defaulting to class "1"'.format(ip=self.ip, data=data)) clss = '1' else: clss = data @@ -290,7 +291,7 @@ class PJLinkCommands(object): """ if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']: count = PJLINK_ERST_DATA['DATA_LENGTH'] - log.warning("{ip}) Invalid error status response '{data}': length != {count}".format(ip=self.ip, + log.warning('{ip}) Invalid error status response "{data}": length != {count}'.format(ip=self.ip, data=data, count=count)) return @@ -298,7 +299,7 @@ class PJLinkCommands(object): datacheck = int(data) except ValueError: # Bad data - ignore - log.warning("({ip}) Invalid error status response '{data}'".format(ip=self.ip, data=data)) + log.warning('({ip}) Invalid error status response "{data}"'.format(ip=self.ip, data=data)) return if datacheck == 0: self.projector_errors = None @@ -437,30 +438,30 @@ class PJLinkCommands(object): :param data: Initial packet with authentication scheme """ - log.debug("({ip}) Processing PJLINK command".format(ip=self.ip)) - chk = data.split(" ") + log.debug('({ip}) Processing PJLINK command'.format(ip=self.ip)) + chk = data.split(' ') if len(chk[0]) != 1: # Invalid - after splitting, first field should be 1 character, either '0' or '1' only - log.error("({ip}) Invalid initial authentication scheme - aborting".format(ip=self.ip)) + log.error('({ip}) Invalid initial authentication scheme - aborting'.format(ip=self.ip)) return self.disconnect_from_host() elif chk[0] == '0': # Normal connection no authentication if len(chk) > 1: # Invalid data - there should be nothing after a normal authentication scheme - log.error("({ip}) Normal connection with extra information - aborting".format(ip=self.ip)) + log.error('({ip}) Normal connection with extra information - aborting'.format(ip=self.ip)) return self.disconnect_from_host() elif self.pin: - log.error("({ip}) Normal connection but PIN set - aborting".format(ip=self.ip)) + log.error('({ip}) Normal connection but PIN set - aborting'.format(ip=self.ip)) return self.disconnect_from_host() else: data_hash = None elif chk[0] == '1': if len(chk) < 2: # Not enough information for authenticated connection - log.error("({ip}) Authenticated connection but not enough info - aborting".format(ip=self.ip)) + log.error('({ip}) Authenticated connection but not enough info - aborting'.format(ip=self.ip)) return self.disconnect_from_host() elif not self.pin: - log.error("({ip}) Authenticate connection but no PIN - aborting".format(ip=self.ip)) + log.error('({ip}) Authenticate connection but no PIN - aborting'.format(ip=self.ip)) return self.disconnect_from_host() else: data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=self.pin.encode('utf-8')), @@ -472,7 +473,7 @@ class PJLinkCommands(object): self.timer.setInterval(2000) # Set 2 seconds for initial information self.timer.start() self.change_status(S_CONNECTED) - log.debug("({ip}) process_pjlink(): Sending 'CLSS' initial command'".format(ip=self.ip)) + log.debug('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=self.ip)) # Since this is an initial connection, make it a priority just in case return self.send_command(cmd="CLSS", salt=data_hash, priority=True) @@ -496,7 +497,7 @@ class PJLinkCommands(object): self.send_command('INST') else: # Log unknown status response - log.warning('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data)) + log.warning('({ip}) Unknown power response: "{data}"'.format(ip=self.ip, data=data)) return def process_rfil(self, data): @@ -506,9 +507,9 @@ class PJLinkCommands(object): if self.model_filter is None: self.model_filter = data else: - log.warning("({ip}) Filter model already set".format(ip=self.ip)) - log.warning("({ip}) Saved model: '{old}'".format(ip=self.ip, old=self.model_filter)) - log.warning("({ip}) New model: '{new}'".format(ip=self.ip, new=data)) + log.warning('({ip}) Filter model already set'.format(ip=self.ip)) + log.warning('({ip}) Saved model: "{old}"'.format(ip=self.ip, old=self.model_filter)) + log.warning('({ip}) New model: "{new}"'.format(ip=self.ip, new=data)) def process_rlmp(self, data): """ @@ -517,9 +518,9 @@ class PJLinkCommands(object): if self.model_lamp is None: self.model_lamp = data else: - log.warning("({ip}) Lamp model already set".format(ip=self.ip)) - log.warning("({ip}) Saved lamp: '{old}'".format(ip=self.ip, old=self.model_lamp)) - log.warning("({ip}) New lamp: '{new}'".format(ip=self.ip, new=data)) + log.warning('({ip}) Lamp model already set'.format(ip=self.ip)) + log.warning('({ip}) Saved lamp: "{old}"'.format(ip=self.ip, old=self.model_lamp)) + log.warning('({ip}) New lamp: "{new}"'.format(ip=self.ip, new=data)) def process_snum(self, data): """ @@ -528,16 +529,16 @@ class PJLinkCommands(object): :param data: Serial number from projector. """ if self.serial_no is None: - log.debug("({ip}) Setting projector serial number to '{data}'".format(ip=self.ip, data=data)) + log.debug('({ip}) Setting projector serial number to "{data}"'.format(ip=self.ip, data=data)) self.serial_no = data self.db_update = False else: # Compare serial numbers and see if we got the same projector if self.serial_no != data: - log.warning("({ip}) Projector serial number does not match saved serial number".format(ip=self.ip)) - log.warning("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.serial_no)) - log.warning("({ip}) Received: '{new}'".format(ip=self.ip, new=data)) - log.warning("({ip}) NOT saving serial number".format(ip=self.ip)) + log.warning('({ip}) Projector serial number does not match saved serial number'.format(ip=self.ip)) + log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.serial_no)) + log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data)) + log.warning('({ip}) NOT saving serial number'.format(ip=self.ip)) self.serial_no_received = data def process_sver(self, data): @@ -546,20 +547,20 @@ class PJLinkCommands(object): """ if len(data) > 32: # Defined in specs max version is 32 characters - log.warning("Invalid software version - too long") + log.warning('Invalid software version - too long') return elif self.sw_version is None: - log.debug("({ip}) Setting projector software version to '{data}'".format(ip=self.ip, data=data)) + log.debug('({ip}) Setting projector software version to "{data}"'.format(ip=self.ip, data=data)) self.sw_version = data self.db_update = True else: # Compare software version and see if we got the same projector if self.serial_no != data: - log.warning("({ip}) Projector software version does not match saved " - "software version".format(ip=self.ip)) - log.warning("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.sw_version)) - log.warning("({ip}) Received: '{new}'".format(ip=self.ip, new=data)) - log.warning("({ip}) Saving new serial number as sw_version_received".format(ip=self.ip)) + log.warning('({ip}) Projector software version does not match saved ' + 'software version'.format(ip=self.ip)) + log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.sw_version)) + log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data)) + log.warning('({ip}) Saving new serial number as sw_version_received'.format(ip=self.ip)) self.sw_version_received = data @@ -586,9 +587,9 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): :param poll_time: Time (in seconds) to poll connected projector :param socket_timeout: Time (in seconds) to abort the connection if no response """ - log.debug('PJlink(projector={projector}, args={args} kwargs={kwargs})'.format(projector=projector, - args=args, - kwargs=kwargs)) + log.debug('PJlink(projector="{projector}", args="{args}" kwargs="{kwargs}")'.format(projector=projector, + args=args, + kwargs=kwargs)) super().__init__() self.entry = projector self.ip = self.entry.ip @@ -660,7 +661,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): self.readyRead.disconnect(self.get_socket) # Set in process_pjlink except TypeError: pass - self.disconnect_from_host() self.deleteLater() self.i_am_running = False @@ -679,7 +679,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): Normally called by timer(). """ if self.state() != S_QSOCKET_STATE['ConnectedState']: - log.warning("({ip}) poll_loop(): Not connected - returning".format(ip=self.ip)) + log.warning('({ip}) poll_loop(): Not connected - returning'.format(ip=self.ip)) return log.debug('({ip}) poll_loop(): Updating projector status'.format(ip=self.ip)) # Reset timer in case we were called from a set command @@ -803,12 +803,12 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): # 'PJLink 1 XXXXXX' Authenticated login - extra processing required. if not data.startswith('PJLINK'): # Invalid initial packet - close socket - log.error("({ip}) Invalid initial packet received - closing socket".format(ip=self.ip)) + log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.ip)) return self.disconnect_from_host() - log.debug("({ip}) check_login(): Formatting initial connection prompt to PJLink packet".format(ip=self.ip)) - return self.get_data("{start}{clss}{data}".format(start=PJLINK_PREFIX, - clss="1", - data=data.replace(" ", "=", 1)).encode('utf-8')) + log.debug('({ip}) check_login(): Formatting initial connection prompt to PJLink packet'.format(ip=self.ip)) + return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX, + clss='1', + data=data.replace(' ', '=', 1)).encode('utf-8')) # TODO: The below is replaced by process_pjlink() - remove when working properly """ if '=' in data: @@ -865,13 +865,13 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ Clean out extraneous stuff in the buffer. """ - log.warning("({ip}) {message}".format(ip=self.ip, message='Invalid packet' if msg is None else msg)) + log.warning('({ip}) {message}'.format(ip=self.ip, message='Invalid packet' if msg is None else msg)) self.send_busy = False trash_count = 0 while self.bytesAvailable() > 0: trash = self.read(self.max_size) trash_count += len(trash) - log.debug("({ip}) Finished cleaning buffer - {count} bytes dropped".format(ip=self.ip, + log.debug('({ip}) Finished cleaning buffer - {count} bytes dropped'.format(ip=self.ip, count=trash_count)) return @@ -883,7 +883,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): :param data: Data to process. buffer must be formatted as a proper PJLink packet. :param ip: Destination IP for buffer. """ - log.debug("({ip}) get_buffer(data='{buff}' ip='{ip_in}'".format(ip=self.ip, buff=data, ip_in=ip)) + log.debug('({ip}) get_buffer(data="{buff}" ip="{ip_in}"'.format(ip=self.ip, buff=data, ip_in=ip)) if ip is None: log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.ip)) return @@ -901,7 +901,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): return # Although we have a packet length limit, go ahead and use a larger buffer read = self.readLine(1024) - log.debug("({ip}) get_socket(): '{buff}'".format(ip=self.ip, buff=read)) + log.debug('({ip}) get_socket(): "{buff}"'.format(ip=self.ip, buff=read)) if read == -1: # No data available log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip)) @@ -917,21 +917,24 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): :param buff: Data to process. :param ip: (optional) Destination IP. """ - ip = self.ip if ip is None else ip - log.debug("({ip}) get_data(ip='{ip_in}' buffer='{buff}'".format(ip=self.ip, ip_in=ip, buff=buff)) + # Since "self" is not available to options and the "ip" keyword is a "maybe I'll use in the future", + # set to default here + if ip is None: + ip = self.ip + log.debug('({ip}) get_data(ip="{ip_in}" buffer="{buff}"'.format(ip=self.ip, ip_in=ip, buff=buff)) # NOTE: Class2 has changed to some values being UTF-8 data_in = decode(buff, 'utf-8') data = data_in.strip() # Initial packet checks if (len(data) < 7): - return self._trash_buffer(msg="get_data(): Invalid packet - length") + return self._trash_buffer(msg='get_data(): Invalid packet - length') elif len(data) > self.max_size: - return self._trash_buffer(msg="get_data(): Invalid packet - too long") + return self._trash_buffer(msg='get_data(): Invalid packet - too long') elif not data.startswith(PJLINK_PREFIX): - return self._trash_buffer(msg="get_data(): Invalid packet - PJLink prefix missing") + return self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing') elif '=' not in data: - return self._trash_buffer(msg="get_data(): Invalid packet - Does not have '='") - log.debug("({ip}) get_data(): Checking new data '{data}'".format(ip=self.ip, data=data)) + return self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="') + log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data)) header, data = data.split('=') # At this point, the header should contain: # "PVCCCC" @@ -1017,15 +1020,15 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): options=opts, suffix=CR) if out in self.priority_queue: - log.debug("({ip}) send_command(): Already in priority queue - skipping".format(ip=self.ip)) + log.debug('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.ip)) elif out in self.send_queue: - log.debug("({ip}) send_command(): Already in normal queue - skipping".format(ip=self.ip)) + log.debug('({ip}) send_command(): Already in normal queue - skipping'.format(ip=self.ip)) else: if priority: - log.debug("({ip}) send_command(): Adding to priority queue".format(ip=self.ip)) + log.debug('({ip}) send_command(): Adding to priority queue'.format(ip=self.ip)) self.priority_queue.append(out) else: - log.debug("({ip}) send_command(): Adding to normal queue".format(ip=self.ip)) + log.debug('({ip}) send_command(): Adding to normal queue'.format(ip=self.ip)) self.send_queue.append(out) if self.priority_queue or self.send_queue: # May be some initial connection setup so make sure we send data @@ -1040,7 +1043,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): :param utf8: Send as UTF-8 string otherwise send as ASCII string """ # Funny looking data check, but it's a quick check for data=None - log.debug("({ip}) _send_command(data='{data}')".format(ip=self.ip, data=data.strip() if data else data)) + log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.ip, data=data.strip() if data else data)) log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.ip, data=S_QSOCKET_STATE[self.state()])) if self.state() != self.ConnectedState: @@ -1048,19 +1051,19 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): self.send_busy = False return self.disconnect_from_host() if data and data not in self.priority_queue: - log.debug("({ip}) _send_command(): Priority packet - adding to priority queue".format(ip=self.ip)) + log.debug('({ip}) _send_command(): Priority packet - adding to priority queue'.format(ip=self.ip)) self.priority_queue.append(data) if self.send_busy: # Still waiting for response from last command sent - log.debug("({ip}) _send_command(): Still busy, returning".format(ip=self.ip)) + log.debug('({ip}) _send_command(): Still busy, returning'.format(ip=self.ip)) log.debug('({ip}) _send_command(): Priority queue = {data}'.format(ip=self.ip, data=self.priority_queue)) log.debug('({ip}) _send_command(): Normal queue = {data}'.format(ip=self.ip, data=self.send_queue)) return if len(self.priority_queue) != 0: out = self.priority_queue.pop(0) - log.debug("({ip}) _send_command(): Getting priority queued packet".format(ip=self.ip)) + log.debug('({ip}) _send_command(): Getting priority queued packet'.format(ip=self.ip)) elif len(self.send_queue) != 0: out = self.send_queue.pop(0) log.debug('({ip}) _send_command(): Getting normal queued packet'.format(ip=self.ip)) @@ -1076,7 +1079,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): self.waitForBytesWritten(2000) # 2 seconds should be enough if sent == -1: # Network error? - log.warning("({ip}) _send_command(): -1 received - disconnecting from host".format(ip=self.ip)) + log.warning('({ip}) _send_command(): -1 received - disconnecting from host'.format(ip=self.ip)) self.change_status(E_NETWORK, translate('OpenLP.PJLink', 'Error while sending data to projector')) self.disconnect_from_host() @@ -1085,7 +1088,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ Initiate connection to projector. """ - log.debug("{ip}) connect_to_host(): Starting connection".format(ip=self.ip)) + log.debug('{ip}) connect_to_host(): Starting connection'.format(ip=self.ip)) if self.state() == self.ConnectedState: log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip)) return diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py index a5a6eca40..4ff133061 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py @@ -185,7 +185,7 @@ class TestPJLinkRouting(TestCase): mock_functions.return_value = [] pjlink = self.pjlink_test - log_text = "(111.111.111.111) Unable to process command='CLSS' (Future option?)" + log_text = '(111.111.111.111) Unable to process command="CLSS" (Future option?)' # WHEN: process_command called with an unknown command pjlink.process_command(cmd='CLSS', data='DONT CARE') @@ -207,7 +207,7 @@ class TestPJLinkRouting(TestCase): # WHEN: process_command called with an unknown command pjlink.process_command(cmd='Unknown', data='Dont Care') - log_text = "(127.0.0.1) Ignoring command='Unknown' (Invalid/Unknown)" + log_text = '(127.0.0.1) Ignoring command="Unknown" (Invalid/Unknown)' # THEN: Error should be logged and no command called self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') @@ -222,7 +222,7 @@ class TestPJLinkRouting(TestCase): mock_send_command = patch.object(self.pjlink_test, 'send_command').start() pjlink = self.pjlink_test - log_text = "(111.111.111.111) Command 'POWR' returned OK" + log_text = '(111.111.111.111) Command "POWR" returned OK' # WHEN: process_command called with a command that returns OK pjlink.process_command(cmd='POWR', data='OK') diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py index 0917a9357..8340a0fd0 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py @@ -580,7 +580,7 @@ class TestPJLinkCommands(TestCase): # WHEN: Process invalid reply pjlink.process_clss('Z') - log_text = "(127.0.0.1) NAN clss version reply 'Z' - defaulting to class '1'" + log_text = '(127.0.0.1) NAN CLSS version reply "Z" - defaulting to class "1"' # THEN: Projector class should be set with default value self.assertEqual(pjlink.pjlink_class, '1', @@ -597,7 +597,7 @@ class TestPJLinkCommands(TestCase): # WHEN: Process invalid reply pjlink.process_clss('Invalid') - log_text = "(127.0.0.1) No numbers found in class version reply 'Invalid' - defaulting to class '1'" + log_text = '(127.0.0.1) No numbers found in class version reply "Invalid" - defaulting to class "1"' # THEN: Projector class should be set with default value self.assertEqual(pjlink.pjlink_class, '1', @@ -627,7 +627,7 @@ class TestPJLinkCommands(TestCase): # GIVEN: Test object pjlink = pjlink_test pjlink.projector_errors = None - log_text = "127.0.0.1) Invalid error status response '11111111': length != 6" + log_text = '127.0.0.1) Invalid error status response "11111111": length != 6' # WHEN: process_erst called with invalid data (too many values pjlink.process_erst('11111111') @@ -645,7 +645,7 @@ class TestPJLinkCommands(TestCase): # GIVEN: Test object pjlink = pjlink_test pjlink.projector_errors = None - log_text = "(127.0.0.1) Invalid error status response '1111Z1'" + log_text = '(127.0.0.1) Invalid error status response "1111Z1"' # WHEN: process_erst called with invalid data (too many values pjlink.process_erst('1111Z1') @@ -671,8 +671,8 @@ class TestPJLinkCommands(TestCase): # THEN: PJLink instance errors should match chk_value for chk in pjlink.projector_errors: self.assertEqual(pjlink.projector_errors[chk], chk_string, - "projector_errors['{chk}'] should have been set to {err}".format(chk=chk, - err=chk_string)) + 'projector_errors["{chk}"] should have been set to "{err}"'.format(chk=chk, + err=chk_string)) def test_projector_process_erst_all_error(self): """ @@ -690,8 +690,8 @@ class TestPJLinkCommands(TestCase): # THEN: PJLink instance errors should match chk_value for chk in pjlink.projector_errors: self.assertEqual(pjlink.projector_errors[chk], chk_string, - "projector_errors['{chk}'] should have been set to {err}".format(chk=chk, - err=chk_string)) + 'projector_errors["{chk}"] should have been set to "{err}"'.format(chk=chk, + err=chk_string)) def test_projector_process_erst_warn_cover_only(self): """ @@ -744,9 +744,9 @@ class TestPJLinkCommands(TestCase): pjlink = pjlink_test pjlink.source_available = [] test_data = '21 10 30 31 11 20' - test_saved = ['10', '11', '20', '21', '30', '31'] - log_data = '(127.0.0.1) Setting projector sources_available to ' \ - '"[\'10\', \'11\', \'20\', \'21\', \'30\', \'31\']"' + test_saved = ["10", "11", "20", "21", "30", "31"] + log_data = "(127.0.0.1) Setting projector sources_available to " \ + "\"['10', '11', '20', '21', '30', '31']\"" mock_UpdateIcons.reset_mock() mock_log.reset_mock() @@ -1021,7 +1021,7 @@ class TestPJLinkCommands(TestCase): pjlink.sw_version = None pjlink.sw_version_received = None test_data = 'Test 1 Subtest 1' - test_log = "(127.0.0.1) Setting projector software version to 'Test 1 Subtest 1'" + test_log = '(127.0.0.1) Setting projector software version to "Test 1 Subtest 1"' mock_log.reset_mock() # WHEN: process_sver called with invalid data From d2ba2ad599a1e47b6021ed9f1e1ec3b8828a7e8a Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 9 Dec 2017 15:00:39 +0000 Subject: [PATCH 80/96] First attempt --- tests/functional/openlp_core/api/http/test_error.py | 8 ++++---- tests/functional/openlp_core/api/http/test_http.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/functional/openlp_core/api/http/test_error.py b/tests/functional/openlp_core/api/http/test_error.py index 1a694705c..599ecfc03 100644 --- a/tests/functional/openlp_core/api/http/test_error.py +++ b/tests/functional/openlp_core/api/http/test_error.py @@ -42,8 +42,8 @@ class TestApiError(TestCase): raise NotFound() # THEN: we get an error and a status - self.assertEquals('Not Found', context.exception.message, 'A Not Found exception should be thrown') - self.assertEquals(404, context.exception.status, 'A 404 status should be thrown') + assert 'Not Found' == context.exception.message, 'A Not Found exception should be thrown' + assert 404 == context.exception.status, 'A 404 status should be thrown' def test_server_error(self): """ @@ -55,5 +55,5 @@ class TestApiError(TestCase): raise ServerError() # THEN: we get an error and a status - self.assertEquals('Server Error', context.exception.message, 'A Not Found exception should be thrown') - self.assertEquals(500, context.exception.status, 'A 500 status should be thrown') + assert'Server Error' == context.exception.message, 'A Not Found exception should be thrown' + assert 500 == context.exception.status, 'A 500 status should be thrown' diff --git a/tests/functional/openlp_core/api/http/test_http.py b/tests/functional/openlp_core/api/http/test_http.py index ed584c1b9..c3cd1d1f4 100644 --- a/tests/functional/openlp_core/api/http/test_http.py +++ b/tests/functional/openlp_core/api/http/test_http.py @@ -53,8 +53,8 @@ class TestHttpServer(TestCase): HttpServer() # THEN: the api environment should have been created - self.assertEquals(1, mock_qthread.call_count, 'The qthread should have been called once') - self.assertEquals(1, mock_thread.call_count, 'The http thread should have been called once') + assert mock_qthread.call_count == 1, 'The qthread should have been called once' + assert mock_thread.call_count == 0, 'The http thread should have been called once' @patch('openlp.core.api.http.server.HttpWorker') @patch('openlp.core.api.http.server.QtCore.QThread') @@ -68,5 +68,5 @@ class TestHttpServer(TestCase): HttpServer() # THEN: the api environment should have been created - self.assertEquals(0, mock_qthread.call_count, 'The qthread should not have have been called') - self.assertEquals(0, mock_thread.call_count, 'The http thread should not have been called') + assert mock_qthread.call_count == 0, 'The qthread should not have have been called' + assert mock_thread.call_count == 1, 'The http thread should not have been called' From 02df3149c722fe00dab50572b094e58c758b6215 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 9 Dec 2017 15:21:59 +0000 Subject: [PATCH 81/96] First attempt 2 --- .../openlp_core/api/http/test_http.py | 4 +-- .../openlp_core/api/http/test_wsgiapp.py | 6 ++-- .../functional/openlp_core/api/test_deploy.py | 2 +- tests/functional/openlp_core/api/test_tab.py | 23 +++++++------- .../openlp_core/api/test_websockets.py | 31 +++++++++---------- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/tests/functional/openlp_core/api/http/test_http.py b/tests/functional/openlp_core/api/http/test_http.py index c3cd1d1f4..7dd8418ae 100644 --- a/tests/functional/openlp_core/api/http/test_http.py +++ b/tests/functional/openlp_core/api/http/test_http.py @@ -54,7 +54,7 @@ class TestHttpServer(TestCase): # THEN: the api environment should have been created assert mock_qthread.call_count == 1, 'The qthread should have been called once' - assert mock_thread.call_count == 0, 'The http thread should have been called once' + assert mock_thread.call_count == 1, 'The http thread should have been called once' @patch('openlp.core.api.http.server.HttpWorker') @patch('openlp.core.api.http.server.QtCore.QThread') @@ -69,4 +69,4 @@ class TestHttpServer(TestCase): # THEN: the api environment should have been created assert mock_qthread.call_count == 0, 'The qthread should not have have been called' - assert mock_thread.call_count == 1, 'The http thread should not have been called' + assert mock_thread.call_count == 0, 'The http thread should not have been called' diff --git a/tests/functional/openlp_core/api/http/test_wsgiapp.py b/tests/functional/openlp_core/api/http/test_wsgiapp.py index aff27404a..17b3ae8c5 100644 --- a/tests/functional/openlp_core/api/http/test_wsgiapp.py +++ b/tests/functional/openlp_core/api/http/test_wsgiapp.py @@ -61,7 +61,7 @@ class TestRouting(TestCase): application.dispatch(rqst) # THEN: the not found returned - self.assertEqual(context.exception.args[0], 'Not Found', 'URL not found in dispatcher') + assert context.exception.args[0] == 'Not Found', 'URL not found in dispatcher' # WHEN: when the URL is correct and dispatch called rqst = MagicMock() @@ -69,8 +69,8 @@ class TestRouting(TestCase): rqst.method = 'GET' application.dispatch(rqst) # THEN: the not found id called - self.assertEqual(1, application.route_map['^\\/test\\/image$']['GET'].call_count, - 'main_index function should have been called') + assert 1 == application.route_map['^\\/test\\/image$']['GET'].call_count, \ + 'main_index function should have been called' @test_endpoint.route('image') diff --git a/tests/functional/openlp_core/api/test_deploy.py b/tests/functional/openlp_core/api/test_deploy.py index 702e2a35d..0aaf1308b 100644 --- a/tests/functional/openlp_core/api/test_deploy.py +++ b/tests/functional/openlp_core/api/test_deploy.py @@ -58,4 +58,4 @@ class TestRemoteDeploy(TestCase): deploy_zipfile(self.app_root_path, 'site.zip') # THEN: test if www directory has been created - self.assertTrue((self.app_root_path / 'www').is_dir(), 'We should have a www directory') + assert (self.app_root_path / 'www').is_dir(), 'We should have a www directory' diff --git a/tests/functional/openlp_core/api/test_tab.py b/tests/functional/openlp_core/api/test_tab.py index f3c4f31e6..1601c223b 100644 --- a/tests/functional/openlp_core/api/test_tab.py +++ b/tests/functional/openlp_core/api/test_tab.py @@ -81,8 +81,8 @@ class TestApiTab(TestCase, TestMixin): # WHEN: the default ip address is given ip_address = self.form.get_ip_address(ZERO_URL) # THEN: the default ip address will be returned - self.assertTrue(re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), - 'The return value should be a valid ip address') + assert re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}') == ip_address, \ + 'The return value should be a valid ip address' def test_get_ip_address_with_ip(self): """ @@ -93,7 +93,7 @@ class TestApiTab(TestCase, TestMixin): # WHEN: the default ip address is given ip_address = self.form.get_ip_address(given_ip) # THEN: the default ip address will be returned - self.assertEqual(ip_address, given_ip, 'The return value should be %s' % given_ip) + assert ip_address == given_ip, 'The return value should be %s' % given_ip def test_set_urls(self): """ @@ -104,12 +104,11 @@ class TestApiTab(TestCase, TestMixin): # WHEN: the urls are generated self.form.set_urls() # THEN: the following links are returned - self.assertEqual(self.form.remote_url.text(), - "<a href=\"http://192.168.1.1:4316/\">http://192.168.1.1:4316/</a>", - 'The return value should be a fully formed link') - self.assertEqual(self.form.stage_url.text(), - "<a href=\"http://192.168.1.1:4316/stage\">http://192.168.1.1:4316/stage</a>", - 'The return value should be a fully formed stage link') - self.assertEqual(self.form.live_url.text(), - "<a href=\"http://192.168.1.1:4316/main\">http://192.168.1.1:4316/main</a>", - 'The return value should be a fully formed main link') + assert self.form.remote_url.text() == "<a href=\"http://192.168.1.1:4316/\">http://192.168.1.1:4316/</a>", \ + 'The return value should be a fully formed link' + assert self.form.stage_url.text() == \ + "<a href=\"http://192.168.1.1:4316/stage\">http://192.168.1.1:4316/stage</a>", \ + 'The return value should be a fully formed stage link' + assert self.form.live_url.text() == \ + "<a href=\"http://192.168.1.1:4316/main\">http://192.168.1.1:4316/main</a>", \ + 'The return value should be a fully formed main link' diff --git a/tests/functional/openlp_core/api/test_websockets.py b/tests/functional/openlp_core/api/test_websockets.py index f400c50a5..7cc3f376c 100644 --- a/tests/functional/openlp_core/api/test_websockets.py +++ b/tests/functional/openlp_core/api/test_websockets.py @@ -74,8 +74,8 @@ class TestWSServer(TestCase, TestMixin): WebSocketServer() # THEN: the api environment should have been created - self.assertEquals(1, mock_qthread.call_count, 'The qthread should have been called once') - self.assertEquals(1, mock_worker.call_count, 'The http thread should have been called once') + assert mock_qthread.call_count == 1, 'The qthread should have been called once' + assert mock_worker.call_count == 1, 'The http thread should have been called once' @patch('openlp.core.api.websockets.WebSocketWorker') @patch('openlp.core.api.websockets.QtCore.QThread') @@ -89,8 +89,8 @@ class TestWSServer(TestCase, TestMixin): WebSocketServer() # THEN: the api environment should have been created - self.assertEquals(0, mock_qthread.call_count, 'The qthread should not have been called') - self.assertEquals(0, mock_worker.call_count, 'The http thread should not have been called') + assert mock_qthread.call_count == 0, 'The qthread should not have been called' + assert mock_worker.call_count == 0, 'The http thread should not have been called' def test_main_poll(self): """ @@ -102,8 +102,7 @@ class TestWSServer(TestCase, TestMixin): Registry().register('live_controller', mocked_live_controller) # THEN: the live json should be generated main_json = self.poll.main_poll() - self.assertEquals(b'{"results": {"slide_count": 5}}', main_json, - 'The return value should match the defined json') + assert b'{"results": {"slide_count": 5}}' == main_json, 'The return value should match the defined json' def test_poll(self): """ @@ -130,13 +129,13 @@ class TestWSServer(TestCase, TestMixin): mocked_is_chords_active.return_value = True poll_json = self.poll.poll() # THEN: the live json should be generated and match expected results - self.assertTrue(poll_json['results']['blank'], 'The blank return value should be True') - self.assertFalse(poll_json['results']['theme'], 'The theme return value should be False') - self.assertFalse(poll_json['results']['display'], 'The display return value should be False') - self.assertFalse(poll_json['results']['isSecure'], 'The isSecure return value should be False') - self.assertFalse(poll_json['results']['isAuthorised'], 'The isAuthorised return value should be False') - self.assertTrue(poll_json['results']['twelve'], 'The twelve return value should be False') - self.assertEquals(poll_json['results']['version'], 3, 'The version return value should be 3') - self.assertEquals(poll_json['results']['slide'], 5, 'The slide return value should be 5') - self.assertEquals(poll_json['results']['service'], 21, 'The version return value should be 21') - self.assertEquals(poll_json['results']['item'], '23-34-45', 'The item return value should match 23-34-45') + assert poll_json['results']['blank'], 'The blank return value should be True' + assert poll_json['results']['theme'], 'The theme return value should be False' + assert poll_json['results']['display'], 'The display return value should be False' + assert poll_json['results']['isSecure'], 'The isSecure return value should be False' + assert poll_json['results']['isAuthorised'], 'The isAuthorised return value should be False' + assert poll_json['results']['twelve'], 'The twelve return value should be False' + assert poll_json['results']['version'] == 3, 'The version return value should be 3' + assert poll_json['results']['slide'] == 5, 'The slide return value should be 5' + assert poll_json['results']['service'] == 21, 'The version return value should be 21' + assert poll_json['results']['item'] == '23-34-45', 'The item return value should match 23-34-45' From 3a9d2f6a61c444cdbb3e669e2ca6ad6af0a7527e Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 9 Dec 2017 15:28:09 +0000 Subject: [PATCH 82/96] Fix test --- tests/functional/openlp_core/api/test_tab.py | 2 +- tests/functional/openlp_core/api/test_websockets.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/functional/openlp_core/api/test_tab.py b/tests/functional/openlp_core/api/test_tab.py index 1601c223b..fe0f3a81b 100644 --- a/tests/functional/openlp_core/api/test_tab.py +++ b/tests/functional/openlp_core/api/test_tab.py @@ -81,7 +81,7 @@ class TestApiTab(TestCase, TestMixin): # WHEN: the default ip address is given ip_address = self.form.get_ip_address(ZERO_URL) # THEN: the default ip address will be returned - assert re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}') == ip_address, \ + assert (re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'), ip_address) is True, \ 'The return value should be a valid ip address' def test_get_ip_address_with_ip(self): diff --git a/tests/functional/openlp_core/api/test_websockets.py b/tests/functional/openlp_core/api/test_websockets.py index 7cc3f376c..d09aac143 100644 --- a/tests/functional/openlp_core/api/test_websockets.py +++ b/tests/functional/openlp_core/api/test_websockets.py @@ -129,12 +129,12 @@ class TestWSServer(TestCase, TestMixin): mocked_is_chords_active.return_value = True poll_json = self.poll.poll() # THEN: the live json should be generated and match expected results - assert poll_json['results']['blank'], 'The blank return value should be True' - assert poll_json['results']['theme'], 'The theme return value should be False' - assert poll_json['results']['display'], 'The display return value should be False' - assert poll_json['results']['isSecure'], 'The isSecure return value should be False' - assert poll_json['results']['isAuthorised'], 'The isAuthorised return value should be False' - assert poll_json['results']['twelve'], 'The twelve return value should be False' + assert poll_json['results']['blank'] is False, 'The blank return value should be True' + assert poll_json['results']['theme'] is False, 'The theme return value should be False' + assert poll_json['results']['display'] is False, 'The display return value should be False' + assert poll_json['results']['isSecure'] is False, 'The isSecure return value should be False' + assert poll_json['results']['isAuthorised'] is False, 'The isAuthorised return value should be False' + assert poll_json['results']['twelve'] is True, 'The twelve return value should be False' assert poll_json['results']['version'] == 3, 'The version return value should be 3' assert poll_json['results']['slide'] == 5, 'The slide return value should be 5' assert poll_json['results']['service'] == 21, 'The version return value should be 21' From ed2b87aed3893d1ce40dc4e4e3405b5f467ab96f Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 9 Dec 2017 15:32:05 +0000 Subject: [PATCH 83/96] Fix tests --- tests/functional/openlp_core/api/test_tab.py | 2 +- tests/functional/openlp_core/api/test_websockets.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/functional/openlp_core/api/test_tab.py b/tests/functional/openlp_core/api/test_tab.py index fe0f3a81b..2c47776f8 100644 --- a/tests/functional/openlp_core/api/test_tab.py +++ b/tests/functional/openlp_core/api/test_tab.py @@ -81,7 +81,7 @@ class TestApiTab(TestCase, TestMixin): # WHEN: the default ip address is given ip_address = self.form.get_ip_address(ZERO_URL) # THEN: the default ip address will be returned - assert (re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'), ip_address) is True, \ + assert re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address) is True, \ 'The return value should be a valid ip address' def test_get_ip_address_with_ip(self): diff --git a/tests/functional/openlp_core/api/test_websockets.py b/tests/functional/openlp_core/api/test_websockets.py index d09aac143..8fa1411b8 100644 --- a/tests/functional/openlp_core/api/test_websockets.py +++ b/tests/functional/openlp_core/api/test_websockets.py @@ -129,12 +129,12 @@ class TestWSServer(TestCase, TestMixin): mocked_is_chords_active.return_value = True poll_json = self.poll.poll() # THEN: the live json should be generated and match expected results - assert poll_json['results']['blank'] is False, 'The blank return value should be True' + assert poll_json['results']['blank'] is True, 'The blank return value should be True' assert poll_json['results']['theme'] is False, 'The theme return value should be False' assert poll_json['results']['display'] is False, 'The display return value should be False' assert poll_json['results']['isSecure'] is False, 'The isSecure return value should be False' assert poll_json['results']['isAuthorised'] is False, 'The isAuthorised return value should be False' - assert poll_json['results']['twelve'] is True, 'The twelve return value should be False' + assert poll_json['results']['twelve'] is False, 'The twelve return value should be False' assert poll_json['results']['version'] == 3, 'The version return value should be 3' assert poll_json['results']['slide'] == 5, 'The slide return value should be 5' assert poll_json['results']['service'] == 21, 'The version return value should be 21' From 0c8217f33c42fc55f61fb57fa1c18111a84f88e5 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 9 Dec 2017 15:34:16 +0000 Subject: [PATCH 84/96] Fix tests --- tests/functional/openlp_core/api/test_websockets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/openlp_core/api/test_websockets.py b/tests/functional/openlp_core/api/test_websockets.py index 8fa1411b8..7da90123d 100644 --- a/tests/functional/openlp_core/api/test_websockets.py +++ b/tests/functional/openlp_core/api/test_websockets.py @@ -134,7 +134,7 @@ class TestWSServer(TestCase, TestMixin): assert poll_json['results']['display'] is False, 'The display return value should be False' assert poll_json['results']['isSecure'] is False, 'The isSecure return value should be False' assert poll_json['results']['isAuthorised'] is False, 'The isAuthorised return value should be False' - assert poll_json['results']['twelve'] is False, 'The twelve return value should be False' + assert poll_json['results']['twelve'] is True, 'The twelve return value should be True' assert poll_json['results']['version'] == 3, 'The version return value should be 3' assert poll_json['results']['slide'] == 5, 'The slide return value should be 5' assert poll_json['results']['service'] == 21, 'The version return value should be 21' From 595ae30cd4e5ec55db2f54a41bcdfad6010cc044 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 9 Dec 2017 15:39:31 +0000 Subject: [PATCH 85/96] Fix tests --- tests/functional/openlp_core/api/test_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/openlp_core/api/test_tab.py b/tests/functional/openlp_core/api/test_tab.py index 2c47776f8..bd701784d 100644 --- a/tests/functional/openlp_core/api/test_tab.py +++ b/tests/functional/openlp_core/api/test_tab.py @@ -81,7 +81,7 @@ class TestApiTab(TestCase, TestMixin): # WHEN: the default ip address is given ip_address = self.form.get_ip_address(ZERO_URL) # THEN: the default ip address will be returned - assert re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address) is True, \ + assert re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \ 'The return value should be a valid ip address' def test_get_ip_address_with_ip(self): From 17b70f0c0af44bddc3200c4008d43da318a39cf1 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 9 Dec 2017 16:11:45 +0000 Subject: [PATCH 86/96] Fix tests --- .../openlp_core/common/test_actions.py | 34 ++++++------- .../openlp_core/common/test_common.py | 35 ++++++------- .../openlp_core/common/test_httputils.py | 10 ++-- .../openlp_core/common/test_init.py | 51 +++++++++---------- 4 files changed, 65 insertions(+), 65 deletions(-) diff --git a/tests/functional/openlp_core/common/test_actions.py b/tests/functional/openlp_core/common/test_actions.py index 57905654d..d5d1abcf0 100644 --- a/tests/functional/openlp_core/common/test_actions.py +++ b/tests/functional/openlp_core/common/test_actions.py @@ -59,8 +59,8 @@ class TestCategoryActionList(TestCase): self.list.append(self.action1) # THEN: The actions should (not) be in the list. - self.assertTrue(self.action1 in self.list) - self.assertFalse(self.action2 in self.list) + assert self.action1 in self.list + assert self.action2 not in self.list def test_len(self): """ @@ -69,14 +69,14 @@ class TestCategoryActionList(TestCase): # GIVEN: The list. # WHEN: Do nothing. # THEN: Check the length. - self.assertEqual(len(self.list), 0, "The length should be 0.") + assert len(self.list) == 0, "The length should be 0." # GIVEN: The list. # WHEN: Append an action. self.list.append(self.action1) # THEN: Check the length. - self.assertEqual(len(self.list), 1, "The length should be 1.") + assert len(self.list) == 1, "The length should be 1." def test_append(self): """ @@ -88,10 +88,10 @@ class TestCategoryActionList(TestCase): self.list.append(self.action2) # THEN: Check if the actions are in the list and check if they have the correct weights. - self.assertTrue(self.action1 in self.list) - self.assertTrue(self.action2 in self.list) - self.assertEqual(self.list.actions[0], (0, self.action1)) - self.assertEqual(self.list.actions[1], (1, self.action2)) + assert self.action1 in self.list + assert self.action2 in self.list + assert self.list.actions[0] == (0, self.action1) + assert self.list.actions[1] == (1, self.action2) def test_add(self): """ @@ -106,11 +106,11 @@ class TestCategoryActionList(TestCase): self.list.add(self.action2, action2_weight) # THEN: Check if they were added and have the specified weights. - self.assertTrue(self.action1 in self.list) - self.assertTrue(self.action2 in self.list) + assert self.action1 in self.list + assert self.action2 in self.list # Now check if action1 is second and action2 is first (due to their weights). - self.assertEqual(self.list.actions[0], (41, self.action2)) - self.assertEqual(self.list.actions[1], (42, self.action1)) + assert self.list.actions[0] == (41, self.action2) + assert self.list.actions[1] == (42, self.action1) def test_iterator(self): """ @@ -121,11 +121,11 @@ class TestCategoryActionList(TestCase): self.list.add(self.action2) # WHEN: Iterating over the list - list = [a for a in self.list] + local_list = [a for a in self.list] # THEN: Make sure they are returned in correct order - self.assertEquals(len(self.list), 2) - self.assertIs(list[0], self.action1) - self.assertIs(list[1], self.action2) + assert len(self.list) == 2 + assert local_list[0] == self.action1 + assert local_list[1] == self.action2 def test_remove(self): """ @@ -138,7 +138,7 @@ class TestCategoryActionList(TestCase): self.list.remove(self.action1) # THEN: Now the element should not be in the list anymore. - self.assertFalse(self.action1 in self.list) + assert self.action1 not in self.list # THEN: Check if an exception is raised when trying to remove a not present action. self.assertRaises(ValueError, self.list.remove, self.action2) diff --git a/tests/functional/openlp_core/common/test_common.py b/tests/functional/openlp_core/common/test_common.py index 1d817ee90..334ceb21b 100644 --- a/tests/functional/openlp_core/common/test_common.py +++ b/tests/functional/openlp_core/common/test_common.py @@ -48,7 +48,7 @@ class TestCommonFunctions(TestCase): extension_loader('glob', ['file2.py', 'file3.py']) # THEN: `extension_loader` should not try to import any files - self.assertFalse(mocked_import_module.called) + assert mocked_import_module.called is False def test_extension_loader_files_found(self): """ @@ -69,7 +69,8 @@ class TestCommonFunctions(TestCase): # THEN: `extension_loader` should only try to import the files that are matched by the blob, excluding the # files listed in the `excluded_files` argument - mocked_import_module.assert_has_calls([call('openlp.import_dir.file1'), call('openlp.import_dir.file4')]) + mocked_import_module.assert_has_calls([call('openlp.import_dir.file1'), + call('openlp.import_dir.file4')]) def test_extension_loader_import_error(self): """ @@ -87,7 +88,7 @@ class TestCommonFunctions(TestCase): extension_loader('glob') # THEN: The `ImportError` should be caught and logged - self.assertTrue(mocked_logger.warning.called) + assert mocked_logger.warning.called def test_extension_loader_os_error(self): """ @@ -105,7 +106,7 @@ class TestCommonFunctions(TestCase): extension_loader('glob') # THEN: The `OSError` should be caught and logged - self.assertTrue(mocked_logger.warning.called) + assert mocked_logger.warning.called def test_de_hump_conversion(self): """ @@ -118,7 +119,7 @@ class TestCommonFunctions(TestCase): new_string = de_hump(string) # THEN: the new string should be converted to python format - self.assertEqual(new_string, "my_class", 'The class name should have been converted') + assert new_string == "my_class", 'The class name should have been converted' def test_de_hump_static(self): """ @@ -131,7 +132,7 @@ class TestCommonFunctions(TestCase): new_string = de_hump(string) # THEN: the new string should be converted to python format - self.assertEqual(new_string, "my_class", 'The class name should have been preserved') + assert new_string == "my_class", 'The class name should have been preserved' def test_path_to_module(self): """ @@ -144,7 +145,7 @@ class TestCommonFunctions(TestCase): result = path_to_module(path) # THEN: path_to_module should return the module name - self.assertEqual(result, 'openlp.core.ui.media.webkitplayer') + assert result == 'openlp.core.ui.media.webkitplayer' def test_trace_error_handler(self): """ @@ -174,9 +175,9 @@ class TestCommonFunctions(TestCase): mocked_sys.platform = 'win32' # THEN: The three platform functions should perform properly - self.assertTrue(is_win(), 'is_win() should return True') - self.assertFalse(is_macosx(), 'is_macosx() should return False') - self.assertFalse(is_linux(), 'is_linux() should return False') + assert is_win(), 'is_win() should return True' + assert is_macosx() is False, 'is_macosx() should return False' + assert is_linux() is False, 'is_linux() should return False' def test_is_macosx(self): """ @@ -190,9 +191,9 @@ class TestCommonFunctions(TestCase): mocked_sys.platform = 'darwin' # THEN: The three platform functions should perform properly - self.assertTrue(is_macosx(), 'is_macosx() should return True') - self.assertFalse(is_win(), 'is_win() should return False') - self.assertFalse(is_linux(), 'is_linux() should return False') + assert is_macosx(), 'is_macosx() should return True' + assert is_win() is False, 'is_win() should return False' + assert is_linux() is False, 'is_linux() should return False' def test_is_linux(self): """ @@ -206,9 +207,9 @@ class TestCommonFunctions(TestCase): mocked_sys.platform = 'linux3' # THEN: The three platform functions should perform properly - self.assertTrue(is_linux(), 'is_linux() should return True') - self.assertFalse(is_win(), 'is_win() should return False') - self.assertFalse(is_macosx(), 'is_macosx() should return False') + assert is_linux(), 'is_linux() 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_clean_button_text(self): """ @@ -222,4 +223,4 @@ class TestCommonFunctions(TestCase): actual_text = clean_button_text(input_text) # THEN: The text should have been cleaned - self.assertEqual(expected_text, actual_text, 'The text should be clean') + assert expected_text == actual_text, 'The text should be clean' diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py index 5e7a396b2..ad1ab6e46 100644 --- a/tests/functional/openlp_core/common/test_httputils.py +++ b/tests/functional/openlp_core/common/test_httputils.py @@ -59,7 +59,7 @@ class TestHttpUtils(TestCase, TestMixin): # THEN: The user agent is a Linux (or ChromeOS) user agent result = 'Linux' in user_agent or 'CrOS' in user_agent - self.assertTrue(result, 'The user agent should be a valid Linux user agent') + assert result, 'The user agent should be a valid Linux user agent' def test_get_user_agent_windows(self): """ @@ -74,7 +74,7 @@ class TestHttpUtils(TestCase, TestMixin): user_agent = get_user_agent() # THEN: The user agent is a Linux (or ChromeOS) user agent - self.assertIn('Windows', user_agent, 'The user agent should be a valid Windows user agent') + assert 'Windows' in user_agent, 'The user agent should be a valid Windows user agent' def test_get_user_agent_macos(self): """ @@ -89,7 +89,7 @@ class TestHttpUtils(TestCase, TestMixin): user_agent = get_user_agent() # THEN: The user agent is a Linux (or ChromeOS) user agent - self.assertIn('Mac OS X', user_agent, 'The user agent should be a valid OS X user agent') + assert 'Mac OS X' in user_agent, 'The user agent should be a valid OS X user agent' def test_get_user_agent_default(self): """ @@ -104,7 +104,7 @@ class TestHttpUtils(TestCase, TestMixin): user_agent = get_user_agent() # THEN: The user agent is a Linux (or ChromeOS) user agent - self.assertIn('NetBSD', user_agent, 'The user agent should be the default user agent') + assert 'NetBSD'in user_agent, 'The user agent should be the default user agent' def test_get_web_page_no_url(self): """ @@ -117,7 +117,7 @@ class TestHttpUtils(TestCase, TestMixin): result = get_web_page(test_url) # THEN: None should be returned - self.assertIsNone(result, 'The return value of get_web_page should be None') + assert result is None, 'The return value of get_web_page should be None' @patch('openlp.core.common.httputils.requests') @patch('openlp.core.common.httputils.get_user_agent') diff --git a/tests/functional/openlp_core/common/test_init.py b/tests/functional/openlp_core/common/test_init.py index 9b86c99aa..fbeaec957 100644 --- a/tests/functional/openlp_core/common/test_init.py +++ b/tests/functional/openlp_core/common/test_init.py @@ -63,8 +63,8 @@ class TestInit(TestCase, TestMixin): add_actions(mocked_target, empty_list) # THEN: The add method on the mocked target is never called - self.assertEqual(0, mocked_target.addSeparator.call_count, 'addSeparator method should not have been called') - self.assertEqual(0, mocked_target.addAction.call_count, 'addAction method should not have been called') + assert mocked_target.addSeparator.call_count == 0, 'addSeparator method should not have been called' + assert mocked_target.addAction.call_count == 0, 'addAction method should not have been called' def test_add_actions_none_action(self): """ @@ -79,7 +79,7 @@ class TestInit(TestCase, TestMixin): # THEN: The addSeparator method is called, but the addAction method is never called mocked_target.addSeparator.assert_called_with() - self.assertEqual(0, mocked_target.addAction.call_count, 'addAction method should not have been called') + assert mocked_target.addAction.call_count == 0, 'addAction method should not have been called' def test_add_actions_add_action(self): """ @@ -93,7 +93,7 @@ class TestInit(TestCase, TestMixin): add_actions(mocked_target, action_list) # THEN: The addSeparator method is not called, and the addAction method is called - self.assertEqual(0, mocked_target.addSeparator.call_count, 'addSeparator method should not have been called') + assert mocked_target.addSeparator.call_count == 0, 'addSeparator method should not have been called' mocked_target.addAction.assert_called_with('action') def test_add_actions_action_and_none(self): @@ -150,9 +150,8 @@ class TestInit(TestCase, TestMixin): result = get_uno_command() # THEN: The command 'libreoffice' should be called with the appropriate parameters - self.assertEquals(result, - 'libreoffice --nologo --norestore --minimized --nodefault --nofirststartwizard' - ' "--accept=pipe,name=openlp_pipe;urp;"') + assert result == 'libreoffice --nologo --norestore --minimized --nodefault --nofirststartwizard' \ + ' "--accept=pipe,name=openlp_pipe;urp;"' def test_get_uno_command_only_soffice_command_exists(self): """ @@ -169,8 +168,8 @@ class TestInit(TestCase, TestMixin): result = get_uno_command() # THEN: The command 'soffice' should be called with the appropriate parameters - self.assertEquals(result, 'soffice --nologo --norestore --minimized --nodefault --nofirststartwizard' - ' "--accept=pipe,name=openlp_pipe;urp;"') + assert result == 'soffice --nologo --norestore --minimized --nodefault --nofirststartwizard' \ + ' "--accept=pipe,name=openlp_pipe;urp;"' def test_get_uno_command_when_no_command_exists(self): """ @@ -198,8 +197,8 @@ class TestInit(TestCase, TestMixin): result = get_uno_command('socket') # THEN: The connection parameters should be set for socket - self.assertEqual(result, 'libreoffice --nologo --norestore --minimized --nodefault --nofirststartwizard' - ' "--accept=socket,host=localhost,port=2002;urp;"') + assert result == 'libreoffice --nologo --norestore --minimized --nodefault --nofirststartwizard' \ + ' "--accept=socket,host=localhost,port=2002;urp;"' def test_get_filesystem_encoding_sys_function_not_called(self): """ @@ -215,8 +214,8 @@ class TestInit(TestCase, TestMixin): # THEN: getdefaultencoding should have been called mocked_getfilesystemencoding.assert_called_with() - self.assertEqual(0, mocked_getdefaultencoding.called, 'getdefaultencoding should not have been called') - self.assertEqual('cp1252', result, 'The result should be "cp1252"') + assert mocked_getdefaultencoding.called == 0, 'getdefaultencoding should not have been called' + assert 'cp1252' == result, 'The result should be "cp1252"' def test_get_filesystem_encoding_sys_function_is_called(self): """ @@ -234,7 +233,7 @@ class TestInit(TestCase, TestMixin): # THEN: getdefaultencoding should have been called mocked_getfilesystemencoding.assert_called_with() mocked_getdefaultencoding.assert_called_with() - self.assertEqual('utf-8', result, 'The result should be "utf-8"') + assert 'utf-8' == result, 'The result should be "utf-8"' def test_clean_filename(self): """ @@ -248,7 +247,7 @@ class TestInit(TestCase, TestMixin): result = clean_filename(invalid_name) # THEN: The file name should be cleaned. - self.assertEqual(wanted_name, result, 'The file name should not contain any special characters.') + assert wanted_name == result, 'The file name should not contain any special characters.' def test_delete_file_no_path(self): """ @@ -259,7 +258,7 @@ class TestInit(TestCase, TestMixin): result = delete_file(None) # THEN: delete_file should return False - self.assertFalse(result, "delete_file should return False when called with None") + assert not result, "delete_file should return False when called with None" def test_delete_file_path_success(self): """ @@ -272,7 +271,7 @@ class TestInit(TestCase, TestMixin): result = delete_file(Path('path', 'file.ext')) # THEN: delete_file should return True - self.assertTrue(result, 'delete_file should return True when it successfully deletes a file') + assert result, 'delete_file should return True when it successfully deletes a file' def test_delete_file_path_no_file_exists(self): """ @@ -286,8 +285,8 @@ class TestInit(TestCase, TestMixin): result = delete_file(Path('path', 'file.ext')) # THEN: The function should not attempt to delete the file and it should return True - self.assertFalse(mocked_unlink.called) - self.assertTrue(result, 'delete_file should return True when the file doesnt exist') + assert mocked_unlink.called is False + assert result, 'delete_file should return True when the file doesnt exist' def test_delete_file_path_exception(self): """ @@ -303,8 +302,8 @@ class TestInit(TestCase, TestMixin): result = delete_file(Path('path', 'file.ext')) # THEN: The exception should be logged and `delete_file` should return False - self.assertTrue(mocked_log.exception.called) - self.assertFalse(result, 'delete_file should return False when an OSError is raised') + assert mocked_log.exception.called + assert result is False, 'delete_file should return False when an OSError is raised' def test_get_file_encoding_done(self): """ @@ -323,9 +322,9 @@ class TestInit(TestCase, TestMixin): # THEN: The feed method of UniversalDetector should only br called once before returning a result mocked_open.assert_called_once_with('rb') - self.assertEqual(mocked_universal_detector_inst.feed.mock_calls, [call(b"data" * 256)]) + assert mocked_universal_detector_inst.feed.mock_calls == [call(b"data" * 256)] mocked_universal_detector_inst.close.assert_called_once_with() - self.assertEqual(result, encoding_result) + assert result == encoding_result def test_get_file_encoding_eof(self): """ @@ -345,9 +344,9 @@ class TestInit(TestCase, TestMixin): # THEN: The feed method of UniversalDetector should have been called twice before returning a result mocked_open.assert_called_once_with('rb') - self.assertEqual(mocked_universal_detector_inst.feed.mock_calls, [call(b"data" * 256), call(b"data" * 4)]) + assert mocked_universal_detector_inst.feed.mock_calls == [call(b"data" * 256), call(b"data" * 4)] mocked_universal_detector_inst.close.assert_called_once_with() - self.assertEqual(result, encoding_result) + assert result, encoding_result def test_get_file_encoding_oserror(self): """ @@ -364,4 +363,4 @@ class TestInit(TestCase, TestMixin): # THEN: log.exception should be called and get_file_encoding should return None mocked_log.exception.assert_called_once_with('Error detecting file encoding') - self.assertIsNone(result) + assert not result From ac95f4e3cad395ee79d5b597df5143087698e4c6 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 9 Dec 2017 16:29:58 +0000 Subject: [PATCH 87/96] Finish common --- .../openlp_core/common/test_json.py | 12 +++---- .../openlp_core/common/test_mixins.py | 8 ++--- .../openlp_core/common/test_path.py | 34 +++++++++---------- .../common/test_projector_utilities.py | 26 +++++++------- .../openlp_core/common/test_registry.py | 30 ++++++++-------- .../openlp_core/common/test_settings.py | 11 +++--- 6 files changed, 60 insertions(+), 61 deletions(-) diff --git a/tests/functional/openlp_core/common/test_json.py b/tests/functional/openlp_core/common/test_json.py index 3b0631dc4..392b30c9b 100644 --- a/tests/functional/openlp_core/common/test_json.py +++ b/tests/functional/openlp_core/common/test_json.py @@ -45,7 +45,7 @@ class TestOpenLPJsonDecoder(TestCase): result = instance.object_hook({'__Path__': ['test', 'path']}) # THEN: A Path object should be returned - self.assertEqual(result, Path('test', 'path')) + assert result == Path('test', 'path') def test_object_hook_non_path_object(self): """ @@ -59,8 +59,8 @@ class TestOpenLPJsonDecoder(TestCase): result = instance.object_hook({'key': 'value'}) # THEN: The object should be returned unchanged and a Path object should not have been initiated - self.assertEqual(result, {'key': 'value'}) - self.assertFalse(mocked_path.called) + assert result == {'key': 'value'} + assert not mocked_path.called def test_json_decode(self): """ @@ -73,7 +73,7 @@ class TestOpenLPJsonDecoder(TestCase): obj = json.loads(json_string, cls=OpenLPJsonDecoder) # THEN: The object returned should be a python version of the JSON string - self.assertEqual(obj, [Path('test', 'path1'), Path('test', 'path2')]) + assert obj == [Path('test', 'path1'), Path('test', 'path2')] class TestOpenLPJsonEncoder(TestCase): @@ -91,7 +91,7 @@ class TestOpenLPJsonEncoder(TestCase): result = instance.default(Path('test', 'path')) # THEN: A dictionary object that can be JSON encoded should be returned - self.assertEqual(result, {'__Path__': ('test', 'path')}) + assert result == {'__Path__': ('test', 'path')} def test_default_non_path_object(self): """ @@ -119,4 +119,4 @@ class TestOpenLPJsonEncoder(TestCase): json_string = json.dumps(obj, cls=OpenLPJsonEncoder) # THEN: The JSON string return should be a representation of the object encoded - self.assertEqual(json_string, '[{"__Path__": ["test", "path1"]}, {"__Path__": ["test", "path2"]}]') + assert json_string == '[{"__Path__": ["test", "path1"]}, {"__Path__": ["test", "path2"]}]' diff --git a/tests/functional/openlp_core/common/test_mixins.py b/tests/functional/openlp_core/common/test_mixins.py index 8eee60c6f..db9606d4e 100644 --- a/tests/functional/openlp_core/common/test_mixins.py +++ b/tests/functional/openlp_core/common/test_mixins.py @@ -46,7 +46,7 @@ class TestRegistryProperties(TestCase, RegistryProperties): # GIVEN an Empty Registry # WHEN there is no Application # THEN the application should be none - self.assertEqual(self.application, None, 'The application value should be None') + assert not self.application, 'The application value should be None' def test_application(self): """ @@ -59,7 +59,7 @@ class TestRegistryProperties(TestCase, RegistryProperties): Registry().register('application', application) # THEN the application should be none - self.assertEqual(self.application, application, 'The application value should match') + assert self.application == application, 'The application value should match' @patch('openlp.core.common.mixins.is_win') def test_application_on_windows(self, mocked_is_win): @@ -74,7 +74,7 @@ class TestRegistryProperties(TestCase, RegistryProperties): Registry().register('application', application) # THEN the application should be none - self.assertEqual(self.application, application, 'The application value should match') + assert self.application == application, 'The application value should match' @patch('openlp.core.common.mixins.is_win') def test_get_application_on_windows(self, mocked_is_win): @@ -93,6 +93,6 @@ class TestRegistryProperties(TestCase, RegistryProperties): actual_application = reg_props.application # THEN the application should be the mock object, and the correct function should have been called - self.assertEqual(mock_application, actual_application, 'The application value should match') + assert mock_application == actual_application, 'The application value should match' mocked_is_win.assert_called_with() mocked_get.assert_called_with('application') diff --git a/tests/functional/openlp_core/common/test_path.py b/tests/functional/openlp_core/common/test_path.py index 498b7aaa0..d7c20ab6f 100644 --- a/tests/functional/openlp_core/common/test_path.py +++ b/tests/functional/openlp_core/common/test_path.py @@ -47,8 +47,8 @@ class TestShutil(TestCase): result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params) # THEN: The positional and keyword args should not have changed - self.assertEqual(test_args, result_args) - self.assertEqual(test_kwargs, result_kwargs) + assert test_args == result_args + assert test_kwargs == result_kwargs def test_replace_params_params(self): """ @@ -63,8 +63,8 @@ class TestShutil(TestCase): result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params) # THEN: The positional and keyword args should have have changed - self.assertEqual(result_args, (1, '2')) - self.assertEqual(result_kwargs, {'arg3': '3', 'arg4': 4}) + assert result_args == (1, '2') + assert result_kwargs == {'arg3': '3', 'arg4': 4} def test_copy(self): """ @@ -82,7 +82,7 @@ class TestShutil(TestCase): # :func:`shutil.copy` as a Path object. mocked_shutil_copy.assert_called_once_with(os.path.join('source', 'test', 'path'), os.path.join('destination', 'test', 'path')) - self.assertEqual(result, Path('destination', 'test', 'path')) + assert result == Path('destination', 'test', 'path') def test_copy_follow_optional_params(self): """ @@ -114,7 +114,7 @@ class TestShutil(TestCase): # :func:`shutil.copyfile` as a Path object. mocked_shutil_copyfile.assert_called_once_with(os.path.join('source', 'test', 'path'), os.path.join('destination', 'test', 'path')) - self.assertEqual(result, Path('destination', 'test', 'path')) + assert result == Path('destination', 'test', 'path') def test_copyfile_optional_params(self): """ @@ -147,7 +147,7 @@ class TestShutil(TestCase): # :func:`shutil.copytree` as a Path object. mocked_shutil_copytree.assert_called_once_with(os.path.join('source', 'test', 'path'), os.path.join('destination', 'test', 'path')) - self.assertEqual(result, Path('destination', 'test', 'path')) + assert result == Path('destination', 'test', 'path') def test_copytree_optional_params(self): """ @@ -182,7 +182,7 @@ class TestShutil(TestCase): # THEN: :func:`shutil.rmtree` should have been called with the str equivalents of the Path object. mocked_shutil_rmtree.assert_called_once_with( os.path.join('test', 'path'), False, None) - self.assertIsNone(result) + assert not result def test_rmtree_optional_params(self): """ @@ -214,7 +214,7 @@ class TestShutil(TestCase): # THEN: :func:`shutil.which` should have been called with the command, and :func:`which` should return None. mocked_shutil_which.assert_called_once_with('no_command') - self.assertIsNone(result) + assert not result def test_which_command(self): """ @@ -230,7 +230,7 @@ class TestShutil(TestCase): # THEN: :func:`shutil.which` should have been called with the command, and :func:`which` should return a # Path object equivalent of the command path. mocked_shutil_which.assert_called_once_with('command') - self.assertEqual(result, Path('path', 'to', 'command')) + assert result == Path('path', 'to', 'command') class TestPath(TestCase): @@ -257,7 +257,7 @@ class TestPath(TestCase): result = path_to_str(None) # THEN: `path_to_str` should return an empty string - self.assertEqual(result, '') + assert not result def test_path_to_str_path_object(self): """ @@ -268,7 +268,7 @@ class TestPath(TestCase): result = path_to_str(Path('test/path')) # THEN: `path_to_str` should return a string representation of the Path object - self.assertEqual(result, os.path.join('test', 'path')) + assert result == os.path.join('test', 'path') def test_str_to_path_type_error(self): """ @@ -289,7 +289,7 @@ class TestPath(TestCase): result = str_to_path('') # THEN: `path_to_str` should return None - self.assertEqual(result, None) + assert not result def test_path_encode_json(self): """ @@ -301,7 +301,7 @@ class TestPath(TestCase): path = Path.encode_json({'__Path__': ['path', 'to', 'fi.le']}, extra=1, args=2) # THEN: A Path object should have been returned - self.assertEqual(path, Path('path', 'to', 'fi.le')) + assert path == Path('path', 'to', 'fi.le') def test_path_encode_json_base_path(self): """ @@ -313,7 +313,7 @@ class TestPath(TestCase): path = Path.encode_json({'__Path__': ['path', 'to', 'fi.le']}, base_path=Path('/base')) # THEN: A Path object should have been returned with an absolute path - self.assertEqual(path, Path('/', 'base', 'path', 'to', 'fi.le')) + assert path == Path('/', 'base', 'path', 'to', 'fi.le') def test_path_json_object(self): """ @@ -326,7 +326,7 @@ class TestPath(TestCase): obj = path.json_object(extra=1, args=2) # THEN: A JSON decodable object should have been returned. - self.assertEqual(obj, {'__Path__': ('/', 'base', 'path', 'to', 'fi.le')}) + assert obj == {'__Path__': ('/', 'base', 'path', 'to', 'fi.le')} def test_path_json_object_base_path(self): """ @@ -340,7 +340,7 @@ class TestPath(TestCase): obj = path.json_object(base_path=Path('/', 'base')) # THEN: A JSON decodable object should have been returned. - self.assertEqual(obj, {'__Path__': ('path', 'to', 'fi.le')}) + assert obj == {'__Path__': ('path', 'to', 'fi.le')} def test_create_paths_dir_exists(self): """ diff --git a/tests/functional/openlp_core/common/test_projector_utilities.py b/tests/functional/openlp_core/common/test_projector_utilities.py index cbdfec238..45e13c04d 100644 --- a/tests/functional/openlp_core/common/test_projector_utilities.py +++ b/tests/functional/openlp_core/common/test_projector_utilities.py @@ -57,7 +57,7 @@ class testProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip4_loopback) # THEN: Verify we received True - self.assertTrue(valid, 'IPv4 loopback address should have been valid') + assert valid, 'IPv4 loopback address should have been valid' def test_ip4_local_valid(self): """ @@ -67,7 +67,7 @@ class testProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip4_local) # THEN: Verify we received True - self.assertTrue(valid, 'IPv4 local address should have been valid') + assert valid, 'IPv4 local address should have been valid' def test_ip4_broadcast_valid(self): """ @@ -77,7 +77,7 @@ class testProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip4_broadcast) # THEN: Verify we received True - self.assertTrue(valid, 'IPv4 broadcast address should have been valid') + assert valid, 'IPv4 broadcast address should have been valid' def test_ip4_address_invalid(self): """ @@ -87,7 +87,7 @@ class testProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip4_bad) # THEN: Verify we received True - self.assertFalse(valid, 'Bad IPv4 address should not have been valid') + assert not valid, 'Bad IPv4 address should not have been valid' def test_ip6_loopback_valid(self): """ @@ -97,7 +97,7 @@ class testProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip6_loopback) # THEN: Validate return - self.assertTrue(valid, 'IPv6 loopback address should have been valid') + assert valid, 'IPv6 loopback address should have been valid' def test_ip6_local_valid(self): """ @@ -107,7 +107,7 @@ class testProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip6_link_local) # THEN: Validate return - self.assertTrue(valid, 'IPv6 link-local address should have been valid') + assert valid, 'IPv6 link-local address should have been valid' def test_ip6_address_invalid(self): """ @@ -117,7 +117,7 @@ class testProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip6_bad) # THEN: Validate bad return - self.assertFalse(valid, 'IPv6 bad address should have been invalid') + assert not valid, 'IPv6 bad address should have been invalid' def test_md5_hash(self): """ @@ -127,7 +127,7 @@ class testProjectorUtilities(TestCase): hash_ = md5_hash(salt=salt.encode('utf-8'), data=pin.encode('utf-8')) # THEN: Validate return has is same - self.assertEquals(hash_, test_hash, 'MD5 should have returned a good hash') + assert hash_ == test_hash, 'MD5 should have returned a good hash' def test_md5_hash_bad(self): """ @@ -137,7 +137,7 @@ class testProjectorUtilities(TestCase): hash_ = md5_hash(salt=pin.encode('utf-8'), data=salt.encode('utf-8')) # THEN: return data is different - self.assertNotEquals(hash_, test_hash, 'MD5 should have returned a bad hash') + assert hash_ is not test_hash, 'MD5 should have returned a bad hash' def test_qmd5_hash(self): """ @@ -147,7 +147,7 @@ class testProjectorUtilities(TestCase): hash_ = qmd5_hash(salt=salt.encode('utf-8'), data=pin.encode('utf-8')) # THEN: Validate return has is same - self.assertEquals(hash_, test_hash, 'Qt-MD5 should have returned a good hash') + assert hash_ == test_hash, 'Qt-MD5 should have returned a good hash' def test_qmd5_hash_bad(self): """ @@ -157,7 +157,7 @@ class testProjectorUtilities(TestCase): hash_ = qmd5_hash(salt=pin.encode('utf-8'), data=salt.encode('utf-8')) # THEN: return data is different - self.assertNotEquals(hash_, test_hash, 'Qt-MD5 should have returned a bad hash') + assert hash_ is not test_hash, 'Qt-MD5 should have returned a bad hash' def test_md5_non_ascii_string(self): """ @@ -167,7 +167,7 @@ class testProjectorUtilities(TestCase): hash_ = md5_hash(salt=test_non_ascii_string.encode('utf-8'), data=None) # THEN: Valid MD5 hash should be returned - self.assertEqual(hash_, test_non_ascii_hash, 'MD5 should have returned a valid hash') + assert hash_ == test_non_ascii_hash, 'MD5 should have returned a valid hash' def test_qmd5_non_ascii_string(self): """ @@ -177,4 +177,4 @@ class testProjectorUtilities(TestCase): hash_ = md5_hash(data=test_non_ascii_string.encode('utf-8')) # THEN: Valid MD5 hash should be returned - self.assertEqual(hash_, test_non_ascii_hash, 'Qt-MD5 should have returned a valid hash') + assert hash_ == test_non_ascii_hash, 'Qt-MD5 should have returned a valid hash' diff --git a/tests/functional/openlp_core/common/test_registry.py b/tests/functional/openlp_core/common/test_registry.py index a274243bd..81b4230a1 100644 --- a/tests/functional/openlp_core/common/test_registry.py +++ b/tests/functional/openlp_core/common/test_registry.py @@ -51,19 +51,19 @@ class TestRegistry(TestCase): # THEN and I will get an exception with self.assertRaises(KeyError) as context: Registry().register('test1', mock_1) - self.assertEqual(context.exception.args[0], 'Duplicate service exception test1', - 'KeyError exception should have been thrown for duplicate service') + assert context.exception.args[0] == 'Duplicate service exception test1', \ + 'KeyError exception should have been thrown for duplicate service' # WHEN I try to get back a non existent component # THEN I will get an exception temp = Registry().get('test2') - self.assertEqual(temp, None, 'None should have been returned for missing service') + assert not temp, 'None should have been returned for missing service' # WHEN I try to replace a component I should be allowed Registry().remove('test1') # THEN I will get an exception temp = Registry().get('test1') - self.assertEqual(temp, None, 'None should have been returned for deleted service') + assert not temp, 'None should have been returned for deleted service' def test_registry_function(self): """ @@ -77,21 +77,21 @@ class TestRegistry(TestCase): return_value = Registry().execute('test1') # THEN: I expect then function to have been called and a return given - self.assertEqual(return_value[0], 'function_1', 'A return value is provided and matches') + assert return_value[0] == 'function_1', 'A return value is provided and matches' # WHEN: I execute the a function with the same reference and execute the function Registry().register_function('test1', self.dummy_function_1) return_value = Registry().execute('test1') # THEN: I expect then function to have been called and a return given - self.assertEqual(return_value, ['function_1', 'function_1'], 'A return value list is provided and matches') + assert return_value == ['function_1', 'function_1'], 'A return value list is provided and matches' # WHEN: I execute the a 2nd function with the different reference and execute the function Registry().register_function('test2', self.dummy_function_2) return_value = Registry().execute('test2') # THEN: I expect then function to have been called and a return given - self.assertEqual(return_value[0], 'function_2', 'A return value is provided and matches') + assert return_value[0] == 'function_2', 'A return value is provided and matches' def test_registry_working_flags(self): """ @@ -107,28 +107,28 @@ class TestRegistry(TestCase): # THEN: we should be able retrieve the saved component temp = Registry().get_flag('test1') - self.assertEquals(temp, my_data, 'The value should have been saved') + assert temp == my_data, 'The value should have been saved' # WHEN: I add a component for the second time I am not mad. # THEN and I will not get an exception Registry().set_flag('test1', my_data2) temp = Registry().get_flag('test1') - self.assertEquals(temp, my_data2, 'The value should have been updated') + assert temp == my_data2, 'The value should have been updated' # WHEN I try to get back a non existent Working Flag # THEN I will get an exception with self.assertRaises(KeyError) as context1: temp = Registry().get_flag('test2') - self.assertEqual(context1.exception.args[0], 'Working Flag test2 not found in list', - 'KeyError exception should have been thrown for missing working flag') + assert context1.exception.args[0] == 'Working Flag test2 not found in list', \ + 'KeyError exception should have been thrown for missing working flag' # WHEN I try to replace a working flag I should be allowed Registry().remove_flag('test1') # THEN I will get an exception with self.assertRaises(KeyError) as context: temp = Registry().get_flag('test1') - self.assertEqual(context.exception.args[0], 'Working Flag test1 not found in list', - 'KeyError exception should have been thrown for duplicate working flag') + assert context.exception.args[0] == 'Working Flag test1 not found in list', \ + 'KeyError exception should have been thrown for duplicate working flag' def test_remove_function(self): """ @@ -174,7 +174,7 @@ class TestRegistryBase(TestCase): PlainStub() # THEN: Nothing is registered with the registry - self.assertEqual(len(Registry().functions_list), 0), 'The function should not be in the dict anymore.' + assert len(Registry().functions_list) == 0, 'The function should not be in the dict anymore.' def test_registry_mixin_present(self): """ @@ -187,4 +187,4 @@ class TestRegistryBase(TestCase): RegistryStub() # THEN: The bootstrap methods should be registered - self.assertEqual(len(Registry().functions_list), 2), 'The bootstrap functions should be in the dict.' + assert len(Registry().functions_list) == 2, 'The bootstrap functions should be in the dict.' diff --git a/tests/functional/openlp_core/common/test_settings.py b/tests/functional/openlp_core/common/test_settings.py index ff6ee8765..4cad58af0 100644 --- a/tests/functional/openlp_core/common/test_settings.py +++ b/tests/functional/openlp_core/common/test_settings.py @@ -139,13 +139,13 @@ class TestSettings(TestCase, TestMixin): extend = settings.value('extend') # THEN the default value is returned - self.assertEqual('very wide', extend, 'The default value defined should be returned') + assert 'very wide' == extend, 'The default value defined should be returned' # WHEN a new value is saved into config Settings().setValue('test/extend', 'very short') # THEN the new value is returned when re-read - self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned') + assert 'very short' == Settings().value('test/extend'), 'The saved value should be returned' def test_settings_nonexisting(self): """Test the Settings on query for non-existing value""" @@ -155,7 +155,7 @@ class TestSettings(TestCase, TestMixin): Settings().value('core/does not exists') # THEN: An exception with the non-existing key should be thrown - self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception') + assert str(cm.exception) == "'core/does not exists'", 'We should get an exception' def test_extend_default_settings(self): """Test that the extend_default_settings method extends the default settings""" @@ -167,9 +167,8 @@ class TestSettings(TestCase, TestMixin): Settings.extend_default_settings({'test/setting 3': 4, 'test/extended 1': 1, 'test/extended 2': 2}) # THEN: The _default_settings__ dictionary_ should have the new keys - self.assertEqual( - Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4, - 'test/extended 1': 1, 'test/extended 2': 2}) + assert Settings.__default_settings__ == {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4, + 'test/extended 1': 1, 'test/extended 2': 2} @patch('openlp.core.common.settings.QtCore.QSettings.contains') @patch('openlp.core.common.settings.QtCore.QSettings.value') From c25a446839a075f434905c9b3186bc990160f7f4 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 9 Dec 2017 16:37:26 +0000 Subject: [PATCH 88/96] Finish common again --- tests/functional/openlp_core/common/test_common.py | 2 +- tests/functional/openlp_core/common/test_path.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/functional/openlp_core/common/test_common.py b/tests/functional/openlp_core/common/test_common.py index 334ceb21b..5ffc664cd 100644 --- a/tests/functional/openlp_core/common/test_common.py +++ b/tests/functional/openlp_core/common/test_common.py @@ -70,7 +70,7 @@ class TestCommonFunctions(TestCase): # THEN: `extension_loader` should only try to import the files that are matched by the blob, excluding the # files listed in the `excluded_files` argument mocked_import_module.assert_has_calls([call('openlp.import_dir.file1'), - call('openlp.import_dir.file4')]) + call('openlp.import_dir.file4')]) def test_extension_loader_import_error(self): """ diff --git a/tests/functional/openlp_core/common/test_path.py b/tests/functional/openlp_core/common/test_path.py index d7c20ab6f..42d78cc7e 100644 --- a/tests/functional/openlp_core/common/test_path.py +++ b/tests/functional/openlp_core/common/test_path.py @@ -177,12 +177,11 @@ class TestShutil(TestCase): path = Path('test', 'path') # WHEN: Calling :func:`openlp.core.common.path.rmtree` with the path parameter as Path object type - result = path.rmtree() + path.rmtree() # THEN: :func:`shutil.rmtree` should have been called with the str equivalents of the Path object. mocked_shutil_rmtree.assert_called_once_with( os.path.join('test', 'path'), False, None) - assert not result def test_rmtree_optional_params(self): """ @@ -268,7 +267,7 @@ class TestPath(TestCase): result = path_to_str(Path('test/path')) # THEN: `path_to_str` should return a string representation of the Path object - assert result == os.path.join('test', 'path') + assert result == os.path.join('test', 'path') def test_str_to_path_type_error(self): """ From ca936f5e1a3bf8fd1e877964a249a4c941fd1b4c Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Fri, 15 Dec 2017 16:19:42 +0000 Subject: [PATCH 89/96] fixes --- tests/functional/openlp_core/common/test_actions.py | 4 ++-- tests/functional/openlp_core/common/test_httputils.py | 2 +- tests/functional/openlp_core/common/test_init.py | 6 +++--- tests/functional/openlp_core/common/test_json.py | 2 +- tests/functional/openlp_core/common/test_mixins.py | 2 +- tests/functional/openlp_core/common/test_path.py | 6 +++--- .../openlp_core/common/test_projector_utilities.py | 6 +++--- tests/functional/openlp_core/common/test_registry.py | 6 +++--- tests/functional/openlp_core/ui/test_mainwindow.py | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/functional/openlp_core/common/test_actions.py b/tests/functional/openlp_core/common/test_actions.py index d5d1abcf0..7d5ac6fca 100644 --- a/tests/functional/openlp_core/common/test_actions.py +++ b/tests/functional/openlp_core/common/test_actions.py @@ -124,8 +124,8 @@ class TestCategoryActionList(TestCase): local_list = [a for a in self.list] # THEN: Make sure they are returned in correct order assert len(self.list) == 2 - assert local_list[0] == self.action1 - assert local_list[1] == self.action2 + assert local_list[0] is self.action1 + assert local_list[1] is self.action2 def test_remove(self): """ diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py index ad1ab6e46..0b39033d9 100644 --- a/tests/functional/openlp_core/common/test_httputils.py +++ b/tests/functional/openlp_core/common/test_httputils.py @@ -240,4 +240,4 @@ class TestHttpUtils(TestCase, TestMixin): # THEN: socket.timeout should have been caught # NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files - assert not os.path.exists(self.tempfile), 'tempfile should have been deleted' + assert os.path.exists(self.tempfile) is False, 'tempfile should have been deleted' diff --git a/tests/functional/openlp_core/common/test_init.py b/tests/functional/openlp_core/common/test_init.py index fbeaec957..5292f414e 100644 --- a/tests/functional/openlp_core/common/test_init.py +++ b/tests/functional/openlp_core/common/test_init.py @@ -258,7 +258,7 @@ class TestInit(TestCase, TestMixin): result = delete_file(None) # THEN: delete_file should return False - assert not result, "delete_file should return False when called with None" + assert result is False, "delete_file should return False when called with None" def test_delete_file_path_success(self): """ @@ -271,7 +271,7 @@ class TestInit(TestCase, TestMixin): result = delete_file(Path('path', 'file.ext')) # THEN: delete_file should return True - assert result, 'delete_file should return True when it successfully deletes a file' + assert result is True, 'delete_file should return True when it successfully deletes a file' def test_delete_file_path_no_file_exists(self): """ @@ -363,4 +363,4 @@ class TestInit(TestCase, TestMixin): # THEN: log.exception should be called and get_file_encoding should return None mocked_log.exception.assert_called_once_with('Error detecting file encoding') - assert not result + assert result is None diff --git a/tests/functional/openlp_core/common/test_json.py b/tests/functional/openlp_core/common/test_json.py index 392b30c9b..18cba3b4e 100644 --- a/tests/functional/openlp_core/common/test_json.py +++ b/tests/functional/openlp_core/common/test_json.py @@ -60,7 +60,7 @@ class TestOpenLPJsonDecoder(TestCase): # THEN: The object should be returned unchanged and a Path object should not have been initiated assert result == {'key': 'value'} - assert not mocked_path.called + assert mocked_path.called is False def test_json_decode(self): """ diff --git a/tests/functional/openlp_core/common/test_mixins.py b/tests/functional/openlp_core/common/test_mixins.py index db9606d4e..f49c2b797 100644 --- a/tests/functional/openlp_core/common/test_mixins.py +++ b/tests/functional/openlp_core/common/test_mixins.py @@ -46,7 +46,7 @@ class TestRegistryProperties(TestCase, RegistryProperties): # GIVEN an Empty Registry # WHEN there is no Application # THEN the application should be none - assert not self.application, 'The application value should be None' + assert self.application is None, 'The application value should be None' def test_application(self): """ diff --git a/tests/functional/openlp_core/common/test_path.py b/tests/functional/openlp_core/common/test_path.py index 42d78cc7e..b069c251d 100644 --- a/tests/functional/openlp_core/common/test_path.py +++ b/tests/functional/openlp_core/common/test_path.py @@ -213,7 +213,7 @@ class TestShutil(TestCase): # THEN: :func:`shutil.which` should have been called with the command, and :func:`which` should return None. mocked_shutil_which.assert_called_once_with('no_command') - assert not result + assert result is None def test_which_command(self): """ @@ -256,7 +256,7 @@ class TestPath(TestCase): result = path_to_str(None) # THEN: `path_to_str` should return an empty string - assert not result + assert result == '' def test_path_to_str_path_object(self): """ @@ -288,7 +288,7 @@ class TestPath(TestCase): result = str_to_path('') # THEN: `path_to_str` should return None - assert not result + assert result is None def test_path_encode_json(self): """ diff --git a/tests/functional/openlp_core/common/test_projector_utilities.py b/tests/functional/openlp_core/common/test_projector_utilities.py index 45e13c04d..db163c152 100644 --- a/tests/functional/openlp_core/common/test_projector_utilities.py +++ b/tests/functional/openlp_core/common/test_projector_utilities.py @@ -45,7 +45,7 @@ ip6_link_local = 'fe80::223:14ff:fe99:d315' ip6_bad = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' -class testProjectorUtilities(TestCase): +class TestProjectorUtilities(TestCase): """ Validate functions in the projector utilities module """ @@ -87,7 +87,7 @@ class testProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip4_bad) # THEN: Verify we received True - assert not valid, 'Bad IPv4 address should not have been valid' + assert valid is False, 'Bad IPv4 address should not have been valid' def test_ip6_loopback_valid(self): """ @@ -117,7 +117,7 @@ class testProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip6_bad) # THEN: Validate bad return - assert not valid, 'IPv6 bad address should have been invalid' + assert valid is False, 'IPv6 bad address should have been invalid' def test_md5_hash(self): """ diff --git a/tests/functional/openlp_core/common/test_registry.py b/tests/functional/openlp_core/common/test_registry.py index 81b4230a1..7e8a80e14 100644 --- a/tests/functional/openlp_core/common/test_registry.py +++ b/tests/functional/openlp_core/common/test_registry.py @@ -57,13 +57,13 @@ class TestRegistry(TestCase): # WHEN I try to get back a non existent component # THEN I will get an exception temp = Registry().get('test2') - assert not temp, 'None should have been returned for missing service' + assert temp is False, 'None should have been returned for missing service' # WHEN I try to replace a component I should be allowed Registry().remove('test1') # THEN I will get an exception temp = Registry().get('test1') - assert not temp, 'None should have been returned for deleted service' + assert temp is False, 'None should have been returned for deleted service' def test_registry_function(self): """ @@ -142,7 +142,7 @@ class TestRegistry(TestCase): Registry().remove_function('test1', self.dummy_function_1) # THEN: The method should not be available. - assert not Registry().functions_list['test1'], 'The function should not be in the dict anymore.' + assert Registry().functions_list['test1'] is None, 'The function should not be in the dict anymore.' def dummy_function_1(self): return "function_1" diff --git a/tests/functional/openlp_core/ui/test_mainwindow.py b/tests/functional/openlp_core/ui/test_mainwindow.py index 4a0404eab..9dee80c0d 100644 --- a/tests/functional/openlp_core/ui/test_mainwindow.py +++ b/tests/functional/openlp_core/ui/test_mainwindow.py @@ -106,7 +106,7 @@ class TestMainWindow(TestCase, TestMixin): self.main_window.open_cmd_line_files("") # THEN the file should not be opened - assert not mocked_load_file.called, 'load_file should not have been called' + assert mocked_load_file.called is False, 'load_file should not have been called' def test_main_window_title(self): """ From eb208380469ad0cc4ebff7bd19b96a2ba6add226 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Fri, 15 Dec 2017 16:30:10 +0000 Subject: [PATCH 90/96] fixes --- tests/functional/openlp_core/common/test_registry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/functional/openlp_core/common/test_registry.py b/tests/functional/openlp_core/common/test_registry.py index 7e8a80e14..6f3cc5752 100644 --- a/tests/functional/openlp_core/common/test_registry.py +++ b/tests/functional/openlp_core/common/test_registry.py @@ -57,13 +57,13 @@ class TestRegistry(TestCase): # WHEN I try to get back a non existent component # THEN I will get an exception temp = Registry().get('test2') - assert temp is False, 'None should have been returned for missing service' + assert temp is None, 'None should have been returned for missing service' # WHEN I try to replace a component I should be allowed Registry().remove('test1') # THEN I will get an exception temp = Registry().get('test1') - assert temp is False, 'None should have been returned for deleted service' + assert temp is None, 'None should have been returned for deleted service' def test_registry_function(self): """ @@ -142,7 +142,7 @@ class TestRegistry(TestCase): Registry().remove_function('test1', self.dummy_function_1) # THEN: The method should not be available. - assert Registry().functions_list['test1'] is None, 'The function should not be in the dict anymore.' + assert Registry().functions_list['test1'] == [], 'The function should not be in the dict anymore.' def dummy_function_1(self): return "function_1" From efe7129f214ce37b5c4875e6ad3bdb6fa88c5e7f Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sat, 16 Dec 2017 08:43:33 +0000 Subject: [PATCH 91/96] fixes --- tests/functional/openlp_core/common/test_common.py | 6 +++--- tests/functional/openlp_core/common/test_httputils.py | 2 +- tests/functional/openlp_core/common/test_init.py | 4 ++-- .../openlp_core/common/test_projector_utilities.py | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/functional/openlp_core/common/test_common.py b/tests/functional/openlp_core/common/test_common.py index 5ffc664cd..368705821 100644 --- a/tests/functional/openlp_core/common/test_common.py +++ b/tests/functional/openlp_core/common/test_common.py @@ -175,7 +175,7 @@ class TestCommonFunctions(TestCase): mocked_sys.platform = 'win32' # THEN: The three platform functions should perform properly - assert is_win(), 'is_win() should return True' + assert is_win() is True, 'is_win() should return True' assert is_macosx() is False, 'is_macosx() should return False' assert is_linux() is False, 'is_linux() should return False' @@ -191,7 +191,7 @@ class TestCommonFunctions(TestCase): mocked_sys.platform = 'darwin' # THEN: The three platform functions should perform properly - assert is_macosx(), 'is_macosx() should return True' + assert is_macosx() is True, 'is_macosx() should return True' assert is_win() is False, 'is_win() should return False' assert is_linux() is False, 'is_linux() should return False' @@ -207,7 +207,7 @@ class TestCommonFunctions(TestCase): mocked_sys.platform = 'linux3' # THEN: The three platform functions should perform properly - assert is_linux(), 'is_linux() should return True' + assert is_linux() is True, 'is_linux() should return True' assert is_win() is False, 'is_win() should return False' assert is_macosx() is False, 'is_macosx() should return False' diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py index 0b39033d9..b7c08993f 100644 --- a/tests/functional/openlp_core/common/test_httputils.py +++ b/tests/functional/openlp_core/common/test_httputils.py @@ -59,7 +59,7 @@ class TestHttpUtils(TestCase, TestMixin): # THEN: The user agent is a Linux (or ChromeOS) user agent result = 'Linux' in user_agent or 'CrOS' in user_agent - assert result, 'The user agent should be a valid Linux user agent' + assert result is True, 'The user agent should be a valid Linux user agent' def test_get_user_agent_windows(self): """ diff --git a/tests/functional/openlp_core/common/test_init.py b/tests/functional/openlp_core/common/test_init.py index 5292f414e..9965a07ee 100644 --- a/tests/functional/openlp_core/common/test_init.py +++ b/tests/functional/openlp_core/common/test_init.py @@ -286,7 +286,7 @@ class TestInit(TestCase, TestMixin): # THEN: The function should not attempt to delete the file and it should return True assert mocked_unlink.called is False - assert result, 'delete_file should return True when the file doesnt exist' + assert result is True, 'delete_file should return True when the file doesnt exist' def test_delete_file_path_exception(self): """ @@ -346,7 +346,7 @@ class TestInit(TestCase, TestMixin): mocked_open.assert_called_once_with('rb') assert mocked_universal_detector_inst.feed.mock_calls == [call(b"data" * 256), call(b"data" * 4)] mocked_universal_detector_inst.close.assert_called_once_with() - assert result, encoding_result + assert result == encoding_result def test_get_file_encoding_oserror(self): """ diff --git a/tests/functional/openlp_core/common/test_projector_utilities.py b/tests/functional/openlp_core/common/test_projector_utilities.py index db163c152..5a3f886cf 100644 --- a/tests/functional/openlp_core/common/test_projector_utilities.py +++ b/tests/functional/openlp_core/common/test_projector_utilities.py @@ -67,7 +67,7 @@ class TestProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip4_local) # THEN: Verify we received True - assert valid, 'IPv4 local address should have been valid' + assert valid is True, 'IPv4 local address should have been valid' def test_ip4_broadcast_valid(self): """ @@ -77,7 +77,7 @@ class TestProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip4_broadcast) # THEN: Verify we received True - assert valid, 'IPv4 broadcast address should have been valid' + assert valid is True, 'IPv4 broadcast address should have been valid' def test_ip4_address_invalid(self): """ @@ -97,7 +97,7 @@ class TestProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip6_loopback) # THEN: Validate return - assert valid, 'IPv6 loopback address should have been valid' + assert valid is True, 'IPv6 loopback address should have been valid' def test_ip6_local_valid(self): """ @@ -107,7 +107,7 @@ class TestProjectorUtilities(TestCase): valid = verify_ip_address(addr=ip6_link_local) # THEN: Validate return - assert valid, 'IPv6 link-local address should have been valid' + assert valid is True, 'IPv6 link-local address should have been valid' def test_ip6_address_invalid(self): """ From fbaeca26f251634727fc495dc16fc121d947048c Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 17 Dec 2017 14:12:27 +0000 Subject: [PATCH 92/96] 2 more done --- .../openlp_core/display/test_renderer.py | 10 +++++----- .../openlp_core/display/test_screens.py | 6 +++--- tests/functional/openlp_core/lib/test_db.py | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/functional/openlp_core/display/test_renderer.py b/tests/functional/openlp_core/display/test_renderer.py index c30fe083b..b1478c7b9 100644 --- a/tests/functional/openlp_core/display/test_renderer.py +++ b/tests/functional/openlp_core/display/test_renderer.py @@ -113,7 +113,7 @@ class TestRenderer(TestCase): result = get_start_tags(given_raw_text) # THEN: Check if the correct tuple is returned. - self.assertEqual(result, expected_tuple), 'A tuple should be returned containing the text with correct ' \ + assert result == expected_tuple, 'A tuple should be returned containing the text with correct ' \ 'tags, the opening tags, and the opening html tags.' def test_word_split(self): @@ -128,7 +128,7 @@ class TestRenderer(TestCase): result_words = words_split(given_line) # THEN: The word lists should be the same. - self.assertListEqual(result_words, expected_words) + assert result_words == expected_words def test_format_slide_logical_split(self): """ @@ -145,7 +145,7 @@ class TestRenderer(TestCase): result_words = renderer.format_slide(given_line, service_item) # THEN: The word lists should be the same. - self.assertListEqual(result_words, expected_words) + assert result_words == expected_words def test_format_slide_blank_before_split(self): """ @@ -162,7 +162,7 @@ class TestRenderer(TestCase): result_words = renderer.format_slide(given_line, service_item) # THEN: The blanks have been removed. - self.assertListEqual(result_words, expected_words) + assert result_words == expected_words def test_format_slide_blank_after_split(self): """ @@ -179,7 +179,7 @@ class TestRenderer(TestCase): result_words = renderer.format_slide(given_line, service_item) # THEN: The blanks have been removed. - self.assertListEqual(result_words, expected_words) + assert result_words == expected_words @patch('openlp.core.display.renderer.QtWebKitWidgets.QWebView') @patch('openlp.core.display.renderer.build_lyrics_format_css') diff --git a/tests/functional/openlp_core/display/test_screens.py b/tests/functional/openlp_core/display/test_screens.py index 258f3b69a..960f9d0a8 100644 --- a/tests/functional/openlp_core/display/test_screens.py +++ b/tests/functional/openlp_core/display/test_screens.py @@ -75,6 +75,6 @@ class TestScreenList(TestCase): # THEN: The screen should have been added and the screens should be identical new_screen_count = len(self.screens.screen_list) - self.assertEqual(old_screen_count + 1, new_screen_count, 'The new_screens list should be bigger') - self.assertEqual(SCREEN, self.screens.screen_list.pop(), - 'The 2nd screen should be identical to the first screen') + assert old_screen_count + 1 == new_screen_count, 'The new_screens list should be bigger' + assert SCREEN == self.screens.screen_list.pop(), \ + 'The 2nd screen should be identical to the first screen' diff --git a/tests/functional/openlp_core/lib/test_db.py b/tests/functional/openlp_core/lib/test_db.py index 871980498..033508965 100644 --- a/tests/functional/openlp_core/lib/test_db.py +++ b/tests/functional/openlp_core/lib/test_db.py @@ -80,8 +80,8 @@ class TestDB(TestCase): MockedMetaData.assert_called_with(bind=mocked_engine) mocked_sessionmaker.assert_called_with(autoflush=True, autocommit=False, bind=mocked_engine) mocked_scoped_session.assert_called_with(mocked_sessionmaker_object) - self.assertIs(session, mocked_scoped_session_object, 'The ``session`` object should be the mock') - self.assertIs(metadata, mocked_metadata, 'The ``metadata`` object should be the mock') + assert session is mocked_scoped_session_object, 'The ``session`` object should be the mock' + assert metadata is mocked_metadata, 'The ``metadata`` object should be the mock' def test_init_db_defaults(self): """ @@ -94,8 +94,8 @@ class TestDB(TestCase): session, metadata = init_db(db_url) # THEN: Valid session and metadata objects should be returned - self.assertIsInstance(session, ScopedSession, 'The ``session`` object should be a ``ScopedSession`` instance') - self.assertIsInstance(metadata, MetaData, 'The ``metadata`` object should be a ``MetaData`` instance') + assert isinstance(session, ScopedSession), 'The ``session`` object should be a ``ScopedSession`` instance' + assert isinstance(metadata, MetaData), 'The ``metadata`` object should be a ``MetaData`` instance' def test_get_upgrade_op(self): """ @@ -116,7 +116,7 @@ class TestDB(TestCase): op = get_upgrade_op(mocked_session) # THEN: The op object should be mocked_op, and the correction function calls should have been made - self.assertIs(op, mocked_op, 'The return value should be the mocked object') + assert op is mocked_op, 'The return value should be the mocked object' mocked_session.bind.connect.assert_called_with() MockedMigrationContext.configure.assert_called_with(mocked_connection) MockedOperations.assert_called_with(mocked_context) @@ -139,7 +139,7 @@ class TestDB(TestCase): # THEN: The AppLocation.get_section_data_path and delete_file methods should have been called MockedAppLocation.get_section_data_path.assert_called_with(test_plugin) mocked_delete_file.assert_called_with(test_location) - self.assertTrue(result, 'The result of delete_file should be True (was rigged that way)') + assert result is True, 'The result of delete_file should be True (was rigged that way)' def test_delete_database_with_db_file_name(self): """ @@ -160,7 +160,7 @@ class TestDB(TestCase): # THEN: The AppLocation.get_section_data_path and delete_file methods should have been called MockedAppLocation.get_section_data_path.assert_called_with(test_plugin) mocked_delete_file.assert_called_with(test_location) - self.assertFalse(result, 'The result of delete_file should be False (was rigged that way)') + assert result is False, 'The result of delete_file should be False (was rigged that way)' def test_skip_db_upgrade_with_no_database(self): """ @@ -174,4 +174,4 @@ class TestDB(TestCase): upgrade_db(url, mocked_upgrade) # THEN: upgrade should NOT have been called - self.assertFalse(mocked_upgrade.called, 'Database upgrade function should NOT have been called') + assert mocked_upgrade.called is False, 'Database upgrade function should NOT have been called' From 9f735e0cc5524b559d4e08f92bc0960fe6e085a3 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 17 Dec 2017 15:35:35 +0000 Subject: [PATCH 93/96] 1 more done --- .../openlp_core/lib/test_exceptions.py | 2 +- .../openlp_core/lib/test_htmlbuilder.py | 22 +-- .../openlp_core/lib/test_image_manager.py | 30 ++-- tests/functional/openlp_core/lib/test_lib.py | 149 +++++++++--------- 4 files changed, 97 insertions(+), 106 deletions(-) diff --git a/tests/functional/openlp_core/lib/test_exceptions.py b/tests/functional/openlp_core/lib/test_exceptions.py index c0de323b7..7ffc122c1 100644 --- a/tests/functional/openlp_core/lib/test_exceptions.py +++ b/tests/functional/openlp_core/lib/test_exceptions.py @@ -42,4 +42,4 @@ class TestValidationError(TestCase): # THEN: Then calling str on the error should return the correct text and it should be an instance of `Exception` assert str(error) == 'Test ValidationError' - assert isinstance(error, Exception) + assert isinstance(error, Exception) is True diff --git a/tests/functional/openlp_core/lib/test_htmlbuilder.py b/tests/functional/openlp_core/lib/test_htmlbuilder.py index 7c8d7809f..ffd796ecb 100644 --- a/tests/functional/openlp_core/lib/test_htmlbuilder.py +++ b/tests/functional/openlp_core/lib/test_htmlbuilder.py @@ -309,7 +309,7 @@ class Htmbuilder(TestCase, TestMixin): html = build_html(item, screen, is_live, background, plugins=plugins) # THEN: The returned html should match. - self.assertEqual(html, HTML, 'The returned html should match') + assert html == HTML, 'The returned html should match' def test_build_background_css_radial(self): """ @@ -325,7 +325,7 @@ class Htmbuilder(TestCase, TestMixin): css = build_background_css(item, width) # THEN: The returned css should match. - self.assertEqual(BACKGROUND_CSS_RADIAL, css, 'The background css should be equal.') + assert BACKGROUND_CSS_RADIAL == css, 'The background css should be equal.' def test_build_lyrics_css(self): """ @@ -346,7 +346,7 @@ class Htmbuilder(TestCase, TestMixin): css = build_lyrics_css(item) # THEN: The css should be equal. - self.assertEqual(LYRICS_CSS, css, 'The lyrics css should be equal.') + assert LYRICS_CSS == css, 'The lyrics css should be equal.' def test_build_lyrics_outline_css(self): """ @@ -363,7 +363,7 @@ class Htmbuilder(TestCase, TestMixin): css = build_lyrics_outline_css(theme_data) # THEN: The css should be equal. - self.assertEqual(LYRICS_OUTLINE_CSS, css, 'The outline css should be equal.') + assert LYRICS_OUTLINE_CSS == css, 'The outline css should be equal.' def test_build_lyrics_format_css(self): """ @@ -386,7 +386,7 @@ class Htmbuilder(TestCase, TestMixin): css = build_lyrics_format_css(theme_data, width, height) # THEN: They should be equal. - self.assertEqual(LYRICS_FORMAT_CSS, css, 'The lyrics format css should be equal.') + assert LYRICS_FORMAT_CSS == css, 'The lyrics format css should be equal.' def test_build_footer_css(self): """ @@ -404,7 +404,7 @@ class Htmbuilder(TestCase, TestMixin): css = build_footer_css(item, height) # THEN: THE css should be the same. - self.assertEqual(FOOTER_CSS, css, 'The footer strings should be equal.') + assert FOOTER_CSS == css, 'The footer strings should be equal.' def test_build_footer_css_wrap(self): """ @@ -423,7 +423,7 @@ class Htmbuilder(TestCase, TestMixin): css = build_footer_css(item, height) # THEN: Footer should wrap - self.assertEqual(FOOTER_CSS_WRAP, css, 'The footer strings should be equal.') + assert FOOTER_CSS_WRAP == css, 'The footer strings should be equal.' def test_build_footer_invalid(self): """ @@ -443,8 +443,8 @@ class Htmbuilder(TestCase, TestMixin): css.append(build_footer_css(item, height)) # THEN: Footer should wrap - self.assertEqual(FOOTER_CSS_INVALID, css[0], 'The footer strings should be blank.') - self.assertEqual(FOOTER_CSS_INVALID, css[1], 'The footer strings should be blank.') + assert FOOTER_CSS_INVALID == css[0], 'The footer strings should be blank.' + assert FOOTER_CSS_INVALID == css[1], 'The footer strings should be blank.' def test_webkit_version(self): """ @@ -454,7 +454,7 @@ class Htmbuilder(TestCase, TestMixin): webkit_ver = float(QtWebKit.qWebKitVersion()) # WHEN: Retrieving the webkit version # THEN: Webkit versions should match - self.assertEquals(webkit_version(), webkit_ver, "The returned webkit version doesn't match the installed one") + assert webkit_version() == webkit_ver, "The returned webkit version doesn't match the installed one" def test_build_chords_css(self): """ @@ -468,4 +468,4 @@ class Htmbuilder(TestCase, TestMixin): chord_css = build_chords_css() # THEN: The build css should look as expected - self.assertEqual(CHORD_CSS_ENABLED, chord_css, 'The chord CSS should look as expected') + assert CHORD_CSS_ENABLED == chord_css, 'The chord CSS should look as expected' diff --git a/tests/functional/openlp_core/lib/test_image_manager.py b/tests/functional/openlp_core/lib/test_image_manager.py index c39f6e8f4..0b37b412d 100644 --- a/tests/functional/openlp_core/lib/test_image_manager.py +++ b/tests/functional/openlp_core/lib/test_image_manager.py @@ -84,7 +84,7 @@ class TestImageManager(TestCase, TestMixin): # THEN a KeyError is thrown with self.assertRaises(KeyError) as context: self.image_manager.get_image(TEST_PATH, 'church1.jpg') - self.assertNotEquals(context.exception, '', 'KeyError exception should have been thrown for missing image') + assert context.exception is not '', 'KeyError exception should have been thrown for missing image' def test_different_dimension_image(self): """ @@ -98,7 +98,7 @@ class TestImageManager(TestCase, TestMixin): image = self.image_manager.get_image(full_path, 'church.jpg', 80, 80) # THEN: The return should be of type image - self.assertEqual(isinstance(image, QtGui.QImage), True, 'The returned object should be a QImage') + assert isinstance(image, QtGui.QImage) is True, 'The returned object should be a QImage' # WHEN: adding the same image with different dimensions self.image_manager.add_image(full_path, 'church.jpg', None, 100, 100) @@ -116,7 +116,7 @@ class TestImageManager(TestCase, TestMixin): # WHEN: calling with correct image, but wrong dimensions with self.assertRaises(KeyError) as context: self.image_manager.get_image(full_path, 'church.jpg', 120, 120) - self.assertNotEquals(context.exception, '', 'KeyError exception should have been thrown for missing dimension') + assert context.exception is not '', 'KeyError exception should have been thrown for missing dimension' def test_process_cache(self): """ @@ -141,10 +141,8 @@ class TestImageManager(TestCase, TestMixin): # is being processed (see mocked methods/functions). # Note: Priority.Normal means, that the resize_image() was not completed yet (because afterwards the # # priority is adjusted to Priority.Lowest). - self.assertEqual(self.get_image_priority(image1), Priority.Normal, - "image1's priority should be 'Priority.Normal'") - self.assertEqual(self.get_image_priority(image2), Priority.Normal, - "image2's priority should be 'Priority.Normal'") + assert self.get_image_priority(image1) == Priority.Normal, "image1's priority should be 'Priority.Normal'" + assert self.get_image_priority(image2) == Priority.Normal, "image2's priority should be 'Priority.Normal'" # WHEN: Add more images. self.image_manager.add_image(TEST_PATH, image3, None) @@ -162,15 +160,15 @@ class TestImageManager(TestCase, TestMixin): # Because empty() is not reliable, wait a litte; just to make sure. time.sleep(0.1) # THEN: The images' priority reflect how they were processed. - self.assertEqual(self.image_manager._conversion_queue.qsize(), 0, "The queue should be empty.") - self.assertEqual(self.get_image_priority(image1), Priority.Lowest, - "The image should have not been requested (=Lowest)") - self.assertEqual(self.get_image_priority(image2), Priority.Lowest, - "The image should have not been requested (=Lowest)") - self.assertEqual(self.get_image_priority(image3), Priority.Low, - "Only the QImage should have been requested (=Low).") - self.assertEqual(self.get_image_priority(image4), Priority.Urgent, - "The image bytes should have been requested (=Urgent).") + assert self.image_manager._conversion_queue.qsize() == 0, "The queue should be empty." + assert self.get_image_priority(image1) == Priority.Lowest, \ + "The image should have not been requested (=Lowest)" + assert self.get_image_priority(image2) == Priority.Lowest, \ + "The image should have not been requested (=Lowest)" + assert self.get_image_priority(image3) == Priority.Low, \ + "Only the QImage should have been requested (=Low)." + assert self.get_image_priority(image4) == Priority.Urgent, \ + "The image bytes should have been requested (=Urgent)." def get_image_priority(self, image): """ diff --git a/tests/functional/openlp_core/lib/test_lib.py b/tests/functional/openlp_core/lib/test_lib.py index e9788060e..9230ca5b0 100644 --- a/tests/functional/openlp_core/lib/test_lib.py +++ b/tests/functional/openlp_core/lib/test_lib.py @@ -49,8 +49,8 @@ class TestLib(TestCase): true_result = str_to_bool(true_boolean) # THEN: We should get back a True bool - self.assertIsInstance(true_result, bool, 'The result should be a boolean') - self.assertTrue(true_result, 'The result should be True') + assert isinstance(true_result, bool), 'The result should be a boolean' + assert true_result is True, 'The result should be True' def test_str_to_bool_with_bool_false(self): """ @@ -63,8 +63,8 @@ class TestLib(TestCase): false_result = str_to_bool(false_boolean) # THEN: We should get back a True bool - self.assertIsInstance(false_result, bool, 'The result should be a boolean') - self.assertFalse(false_result, 'The result should be True') + assert isinstance(false_result, bool), 'The result should be a boolean' + assert false_result is False, 'The result should be True' def test_str_to_bool_with_integer(self): """ @@ -77,7 +77,7 @@ class TestLib(TestCase): int_result = str_to_bool(int_string) # THEN: we should get back a false - self.assertFalse(int_result, 'The result should be False') + assert int_result is False, 'The result should be False' def test_str_to_bool_with_invalid_string(self): """ @@ -90,7 +90,7 @@ class TestLib(TestCase): str_result = str_to_bool(invalid_string) # THEN: we should get back a false - self.assertFalse(str_result, 'The result should be False') + assert str_result is False, 'The result should be False' def test_str_to_bool_with_string_false(self): """ @@ -103,7 +103,7 @@ class TestLib(TestCase): false_result = str_to_bool(false_string) # THEN: we should get back a false - self.assertFalse(false_result, 'The result should be False') + assert false_result is False, 'The result should be False' def test_str_to_bool_with_string_no(self): """ @@ -116,7 +116,7 @@ class TestLib(TestCase): str_result = str_to_bool(no_string) # THEN: we should get back a false - self.assertFalse(str_result, 'The result should be False') + assert str_result is False, 'The result should be False' def test_str_to_bool_with_true_string_value(self): """ @@ -129,7 +129,7 @@ class TestLib(TestCase): true_result = str_to_bool(true_string) # THEN: we should get back a true - self.assertTrue(true_result, 'The result should be True') + assert true_result is True, 'The result should be True' def test_str_to_bool_with_yes_string_value(self): """ @@ -142,7 +142,7 @@ class TestLib(TestCase): str_result = str_to_bool(yes_string) # THEN: we should get back a true - self.assertTrue(str_result, 'The result should be True') + assert str_result is True, 'The result should be True' def test_get_text_file_string_no_file(self): """ @@ -157,7 +157,7 @@ class TestLib(TestCase): # THEN: The result should be False file_path.is_file.assert_called_with() - self.assertFalse(result, 'False should be returned if no file exists') + assert result is False, 'False should be returned if no file exists' def test_get_text_file_string_read_error(self): """ @@ -176,7 +176,7 @@ class TestLib(TestCase): # THEN: None should be returned file_path.is_file.assert_called_once_with() file_path.open.assert_called_once_with('r', encoding='utf-8') - self.assertIsNone(result, 'None should be returned if the file cannot be opened') + assert result is None, 'None should be returned if the file cannot be opened' def test_get_text_file_string_decode_error(self): """ @@ -195,7 +195,7 @@ class TestLib(TestCase): result = build_icon(icon) # THEN: The result should be the same icon as we passed in - self.assertIs(icon, result, 'The result should be the same icon as we passed in') + assert icon is result, 'The result should be the same icon as we passed in' def test_build_icon_with_resource(self): """ @@ -217,7 +217,7 @@ class TestLib(TestCase): MockedQPixmap.assert_called_with(resource_uri) # There really should be more assert statements here but due to type checking and things they all break. The # best we can do is to assert that we get back a MagicMock object. - self.assertIsInstance(result, MagicMock, 'The result should be a MagicMock, because we mocked it out') + assert isinstance(result, MagicMock), 'The result should be a MagicMock, because we mocked it out' def test_image_to_byte(self): """ @@ -240,8 +240,8 @@ class TestLib(TestCase): MockedQtCore.QBuffer.assert_called_with(mocked_byte_array) mocked_buffer.open.assert_called_with('writeonly') mocked_image.save.assert_called_with(mocked_buffer, "PNG") - self.assertFalse(mocked_byte_array.toBase64.called) - self.assertEqual(mocked_byte_array, result, 'The mocked out byte array should be returned') + assert mocked_byte_array.toBase64.called is False + assert mocked_byte_array == result, 'The mocked out byte array should be returned' def test_image_to_byte_base_64(self): """ @@ -266,8 +266,7 @@ class TestLib(TestCase): mocked_buffer.open.assert_called_with('writeonly') mocked_image.save.assert_called_with(mocked_buffer, "PNG") mocked_byte_array.toBase64.assert_called_with() - self.assertEqual('base64mock', result, 'The result should be the return value of the mocked out ' - 'base64 method') + assert 'base64mock' == result, 'The result should be the return value of the mocked out base64 method' def test_create_thumb_with_size(self): """ @@ -286,16 +285,16 @@ class TestLib(TestCase): pass # Only continue when the thumb does not exist. - self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') + assert thumb_path.exists() is False, 'Test was not run, because the thumb already exists.' # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created and scaled to the given size. self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') - self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') - self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(thumb_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + assert isinstance(icon, QtGui.QIcon), 'The icon should be a QIcon' + assert icon.isNull()is False, 'The icon should not be null' + assert thumb_size == QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size' # Remove the thumb so that the test actually tests if the thumb will be created. try: @@ -320,17 +319,16 @@ class TestLib(TestCase): pass # Only continue when the thumb does not exist. - self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') + assert thumb_path.exists() is False, 'Test was not run, because the thumb already exists.' # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path) # THEN: Check if the thumb was created, retaining its aspect ratio. self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') - self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') - self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), - 'The thumb should have the given size') + assert isinstance(icon, QtGui.QIcon), 'The icon should be a QIcon' + assert icon.isNull() is False, 'The icon should not be null' + assert expected_size == QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size' # Remove the thumb so that the test actually tests if the thumb will be created. try: @@ -356,17 +354,16 @@ class TestLib(TestCase): pass # Only continue when the thumb does not exist. - self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') + assert thumb_path.exists() is False, 'Test was not run, because the thumb already exists.' # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created, retaining its aspect ratio. - self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') - self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') - self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(), - 'The thumb should have the given size') + assert thumb_path.exists() is True, 'Test was not ran, because the thumb already exists' + assert isinstance(icon, QtGui.QIcon), 'The icon should be a QIcon' + assert icon.isNull() is False, 'The icon should not be null' + assert expected_size == QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size' # Remove the thumb so that the test actually tests if the thumb will be created. try: @@ -392,17 +389,16 @@ class TestLib(TestCase): pass # Only continue when the thumb does not exist. - self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') + assert thumb_path.exists() is False, 'Test was not run, because the thumb already exists.' # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created, retaining its aspect ratio. - self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') - self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') - self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual( - expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + assert thumb_path.exists() is True, 'Test was not ran, because the thumb already exists' + assert isinstance(icon, QtGui.QIcon), 'The icon should be a QIcon' + assert icon.isNull() is False, 'The icon should not be null' + assert expected_size == QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size' # Remove the thumb so that the test actually tests if the thumb will be created. try: @@ -428,17 +424,16 @@ class TestLib(TestCase): pass # Only continue when the thumb does not exist. - self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') + assert thumb_path.exists() is False, 'Test was not run, because the thumb already exists.' # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created, retaining its aspect ratio. self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') - self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') - self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual( - expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + assert isinstance(icon, QtGui.QIcon), 'The icon should be a QIcon' + assert icon.isNull() is False, 'The icon should not be null' + assert expected_size == QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size' # Remove the thumb so that the test actually tests if the thumb will be created. try: @@ -465,7 +460,7 @@ class TestLib(TestCase): pass # Only continue when the thumb does not exist. - self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.') + assert thumb_path.exists() is False, 'Test was not run, because the thumb already exists.' # WHEN: Create the thumb. with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size: @@ -474,10 +469,9 @@ class TestLib(TestCase): # THEN: Check if the thumb was created with aspect ratio of 1. self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') - self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') - self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual( - expected_size_1, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + assert isinstance(icon, QtGui.QIcon), 'The icon should be a QIcon' + assert icon.isNull() is False, 'The icon should not be null' + assert expected_size_1 == QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size' # WHEN: Create the thumb. with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size: @@ -485,10 +479,9 @@ class TestLib(TestCase): icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created with aspect ratio of 1. - self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') - self.assertFalse(icon.isNull(), 'The icon should not be null') - self.assertEqual( - expected_size_2, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size') + assert isinstance(icon, QtGui.QIcon), 'The icon should be a QIcon' + assert icon.isNull() is False, 'The icon should not be null' + assert expected_size_2 == QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size' # Remove the thumb so that the test actually tests if the thumb will be created. try: @@ -511,7 +504,7 @@ class TestLib(TestCase): # THEN: The selectedIndexes function should have been called and the result should be true mocked_list_widget.selectedIndexes.assert_called_with() - self.assertTrue(result, 'The result should be True') + assert result is True, 'The result should be True' def test_check_item_selected_false(self): """ @@ -532,7 +525,7 @@ class TestLib(TestCase): # THEN: The selectedIndexes function should have been called and the result should be true mocked_list_widget.selectedIndexes.assert_called_with() MockedQtWidgets.QMessageBox.information.assert_called_with('parent', 'mocked translate', 'message') - self.assertFalse(result, 'The result should be False') + assert result is False, 'The result should be False' def test_clean_tags(self): """ @@ -554,7 +547,7 @@ class TestLib(TestCase): result_string = clean_tags(string_to_pass) # THEN: The strings should be identical. - self.assertEqual(wanted_string, result_string, 'The strings should be identical') + assert wanted_string == result_string, 'The strings should be identical' def test_expand_tags(self): """ @@ -593,7 +586,7 @@ class TestLib(TestCase): result_string = expand_tags(string_to_pass) # THEN: The strings should be identical. - self.assertEqual(wanted_string, result_string, 'The strings should be identical.') + assert wanted_string == result_string, 'The strings should be identical.' def test_validate_thumb_file_does_not_exist(self): """ @@ -609,7 +602,7 @@ class TestLib(TestCase): # THEN: we should have called a few functions, and the result should be False thumb_path.exists.assert_called_once_with() - self.assertFalse(result, 'The result should be False') + assert result is False, 'The result should be False' def test_validate_thumb_file_exists_and_newer(self): """ @@ -624,7 +617,7 @@ class TestLib(TestCase): result = validate_thumb(file_path, thumb_path) # THEN: `validate_thumb` should return True - self.assertTrue(result) + assert result is True def test_validate_thumb_file_exists_and_older(self): """ @@ -639,7 +632,7 @@ class TestLib(TestCase): # THEN: `validate_thumb` should return False thumb_path.stat.assert_called_once_with() - self.assertFalse(result, 'The result should be False') + assert result is False, 'The result should be False' def test_resize_thumb(self): """ @@ -658,9 +651,9 @@ class TestLib(TestCase): # THEN: Check if the size is correct and the background was set. result_size = image.size() - self.assertEqual(wanted_height, result_size.height(), 'The image should have the requested height.') - self.assertEqual(wanted_width, result_size.width(), 'The image should have the requested width.') - self.assertEqual(image.pixel(0, 0), wanted_background_rgb, 'The background should be white.') + assert wanted_height == result_size.height(), 'The image should have the requested height.' + assert wanted_width == result_size.width(), 'The image should have the requested width.' + assert image.pixel(0, 0) == wanted_background_rgb, 'The background should be white.' def test_resize_thumb_ignoring_aspect_ratio(self): """ @@ -679,9 +672,9 @@ class TestLib(TestCase): # THEN: Check if the size is correct and the background was set. result_size = image.size() - self.assertEqual(wanted_height, result_size.height(), 'The image should have the requested height.') - self.assertEqual(wanted_width, result_size.width(), 'The image should have the requested width.') - self.assertEqual(image.pixel(0, 0), wanted_background_rgb, 'The background should be white.') + assert wanted_height == result_size.height(), 'The image should have the requested height.' + assert wanted_width == result_size.width(), 'The image should have the requested width.' + assert image.pixel(0, 0) == wanted_background_rgb, 'The background should be white.' @patch('openlp.core.lib.QtCore.QLocale.createSeparatedList') def test_create_separated_list_qlocate(self, mocked_createSeparatedList): @@ -696,8 +689,8 @@ class TestLib(TestCase): string_result = create_separated_list(string_list) # THEN: We should have "Author 1, Author 2, and Author 3" - self.assertEqual(string_result, 'Author 1, Author 2 and Author 3', 'The string should be "Author 1, ' - 'Author 2, and Author 3".') + assert string_result == 'Author 1, Author 2 and Author 3', \ + 'The string should be "Author 1, Author 2, and Author 3".' def test_create_separated_list_empty_list(self): """ @@ -710,7 +703,7 @@ class TestLib(TestCase): string_result = create_separated_list(string_list) # THEN: We shoud have an emptry string. - self.assertEqual(string_result, '', 'The string sould be empty.') + assert string_result == '', 'The string sould be empty.' def test_create_separated_list_with_one_item(self): """ @@ -723,7 +716,7 @@ class TestLib(TestCase): string_result = create_separated_list(string_list) # THEN: We should have "Author 1" - self.assertEqual(string_result, 'Author 1', 'The string should be "Author 1".') + assert string_result == 'Author 1', 'The string should be "Author 1".' def test_create_separated_list_with_two_items(self): """ @@ -736,7 +729,7 @@ class TestLib(TestCase): string_result = create_separated_list(string_list) # THEN: We should have "Author 1 and Author 2" - self.assertEqual(string_result, 'Author 1 and Author 2', 'The string should be "Author 1 and Author 2".') + assert string_result == 'Author 1 and Author 2', 'The string should be "Author 1 and Author 2".' def test_create_separated_list_with_three_items(self): """ @@ -749,8 +742,8 @@ class TestLib(TestCase): string_result = create_separated_list(string_list) # THEN: We should have "Author 1, Author 2 and Author 3" - self.assertEqual(string_result, 'Author 1, Author 2 and Author 3', 'The string should be "Author 1, ' - 'Author 2, and Author 3".') + assert string_result == 'Author 1, Author 2 and Author 3', \ + 'The string should be "Author 1, Author 2, and Author 3".' def test_expand_chords(self): """ @@ -766,7 +759,7 @@ class TestLib(TestCase): expected_html = '<span class="chordline firstchordline">H<span class="chord"><span><strong>C</strong></span>' \ '</span>alleluya.<span class="chord"><span><strong>F</strong></span></span><span class="ws">' \ '  </span> <span class="chord"><span><strong>G</strong></span></span></span>' - self.assertEqual(expected_html, text_with_expanded_chords, 'The expanded chords should look as expected!') + assert expected_html == text_with_expanded_chords, 'The expanded chords should look as expected!' def test_expand_chords2(self): """ @@ -782,7 +775,7 @@ class TestLib(TestCase): expected_html = '<span class="chordline firstchordline">I<span class="chord"><span><strong>D</strong></span>' \ '</span>'M NOT MOVED BY WHAT I SEE HALLE<span class="chord"><span><strong>F</strong>' \ '</span></span>LUJA<span class="chord"><span><strong>C</strong></span></span>H</span>' - self.assertEqual(expected_html, text_with_expanded_chords, 'The expanded chords should look as expected!') + assert expected_html == text_with_expanded_chords, 'The expanded chords should look as expected!' def test_compare_chord_lyric_short_chord(self): """ @@ -810,7 +803,7 @@ class TestLib(TestCase): ret = compare_chord_lyric(chord, lyrics) # THEN: The returned value should 4 because the chord is longer than the lyric - self.assertEquals(4, ret, 'The returned value should 4 because the chord is longer than the lyric') + assert 4 == ret, 'The returned value should 4 because the chord is longer than the lyric' def test_find_formatting_tags(self): """ @@ -825,7 +818,7 @@ class TestLib(TestCase): active_tags = find_formatting_tags(lyrics, tags) # THEN: The list of active tags should contain only 'st' - self.assertListEqual(['st'], active_tags, 'The list of active tags should contain only "st"') + assert ['st'] == active_tags, 'The list of active tags should contain only "st"' def test_expand_chords_for_printing(self): """ @@ -862,4 +855,4 @@ class TestLib(TestCase): '<table class="segment" cellpadding="0" cellspacing="0" border="0" align="left"><tr ' \ 'class="chordrow"><td class="chord">F</td></tr><tr><td class="lyrics">{st}{/st} </td>' \ '</tr></table></td></tr></table>' - self.assertEqual(expected_html, text_with_expanded_chords, 'The expanded chords should look as expected!') + assert expected_html == text_with_expanded_chords, 'The expanded chords should look as expected!' From f76f04994d229ea5584cacc9bb87cf1fe81fd7ea Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 17 Dec 2017 17:52:17 +0000 Subject: [PATCH 94/96] lib done --- .../openlp_core/lib/test_mediamanageritem.py | 14 +- .../openlp_core/lib/test_pluginmanager.py | 52 ++--- .../openlp_core/lib/test_serviceitem.py | 178 +++++++++--------- .../functional/openlp_core/lib/test_theme.py | 16 +- tests/functional/openlp_core/lib/test_ui.py | 92 ++++----- 5 files changed, 180 insertions(+), 172 deletions(-) diff --git a/tests/functional/openlp_core/lib/test_mediamanageritem.py b/tests/functional/openlp_core/lib/test_mediamanageritem.py index a6152cbdf..6c659a5bc 100644 --- a/tests/functional/openlp_core/lib/test_mediamanageritem.py +++ b/tests/functional/openlp_core/lib/test_mediamanageritem.py @@ -69,11 +69,11 @@ class TestMediaManagerItem(TestCase, TestMixin): # WHEN: Object is created mmi.required_icons() # THEN: Default icons should be populated - self.assertFalse(mmi.has_import_icon, 'There should be no import icon by default') - self.assertTrue(mmi.has_new_icon, 'By default a new icon should be present') - self.assertFalse(mmi.has_file_icon, 'There should be no file icon by default') - self.assertTrue(mmi.has_delete_icon, 'By default a delete icon should be present') - self.assertFalse(mmi.add_to_service_item, 'There should be no add_to_service icon by default') + assert mmi.has_import_icon is False, 'There should be no import icon by default' + assert mmi.has_new_icon is True, 'By default a new icon should be present' + assert mmi.has_file_icon is False, 'There should be no file icon by default' + assert mmi.has_delete_icon is True, 'By default a delete icon should be present' + assert mmi.add_to_service_item is False, 'There should be no add_to_service icon by default' @patch('openlp.core.lib.mediamanageritem.Settings') @patch('openlp.core.lib.mediamanageritem.MediaManagerItem.on_live_click') @@ -111,5 +111,5 @@ class TestMediaManagerItem(TestCase, TestMixin): mmi.on_double_clicked() # THEN: on_live_click() should have been called - self.assertEqual(0, mocked_on_live_click.call_count, 'on_live_click() should not have been called') - self.assertEqual(0, mocked_on_preview_click.call_count, 'on_preview_click() should not have been called') + assert 0 == mocked_on_live_click.call_count, 'on_live_click() should not have been called' + assert 0 == mocked_on_preview_click.call_count, 'on_preview_click() should not have been called' diff --git a/tests/functional/openlp_core/lib/test_pluginmanager.py b/tests/functional/openlp_core/lib/test_pluginmanager.py index 52e9d91bf..163f52877 100644 --- a/tests/functional/openlp_core/lib/test_pluginmanager.py +++ b/tests/functional/openlp_core/lib/test_pluginmanager.py @@ -64,8 +64,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_media_manager() # THEN: The create_media_manager_item() method should have been called - self.assertEqual(0, mocked_plugin.create_media_manager_item.call_count, - 'The create_media_manager_item() method should not have been called.') + assert 0 == mocked_plugin.create_media_manager_item.call_count, \ + 'The create_media_manager_item() method should not have been called.' def test_hook_media_manager_with_active_plugin(self): """ @@ -97,8 +97,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_settings_tabs() # THEN: The hook_settings_tabs() method should have been called - self.assertEqual(0, mocked_plugin.create_media_manager_item.call_count, - 'The create_media_manager_item() method should not have been called.') + assert 0 == mocked_plugin.create_media_manager_item.call_count, \ + 'The create_media_manager_item() method should not have been called.' def test_hook_settings_tabs_with_disabled_plugin_and_mocked_form(self): """ @@ -117,10 +117,10 @@ class TestPluginManager(TestCase): plugin_manager.hook_settings_tabs() # THEN: The create_settings_tab() method should not have been called, but the plugins lists should be the same - self.assertEqual(0, mocked_plugin.create_settings_tab.call_count, - 'The create_media_manager_item() method should not have been called.') - self.assertEqual(mocked_settings_form.plugin_manager.plugins, plugin_manager.plugins, - 'The plugins on the settings form should be the same as the plugins in the plugin manager') + assert 0 == mocked_plugin.create_settings_tab.call_count, \ + 'The create_media_manager_item() method should not have been called.' + assert mocked_settings_form.plugin_manager.plugins == plugin_manager.plugins, \ + 'The plugins on the settings form should be the same as the plugins in the plugin manager' def test_hook_settings_tabs_with_active_plugin_and_mocked_form(self): """ @@ -139,10 +139,10 @@ class TestPluginManager(TestCase): plugin_manager.hook_settings_tabs() # THEN: The create_media_manager_item() method should have been called with the mocked settings form - self.assertEqual(1, mocked_plugin.create_settings_tab.call_count, - 'The create_media_manager_item() method should have been called once.') - self.assertEqual(plugin_manager.plugins, mocked_settings_form.plugin_manager.plugins, - 'The plugins on the settings form should be the same as the plugins in the plugin manager') + assert 1 == mocked_plugin.create_settings_tab.call_count, \ + 'The create_media_manager_item() method should have been called once.' + assert plugin_manager.plugins == mocked_settings_form.plugin_manager.plugins, \ + 'The plugins on the settings form should be the same as the plugins in the plugin manager' def test_hook_settings_tabs_with_active_plugin_and_no_form(self): """ @@ -174,8 +174,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_import_menu() # THEN: The create_media_manager_item() method should have been called - self.assertEqual(0, mocked_plugin.add_import_menu_item.call_count, - 'The add_import_menu_item() method should not have been called.') + assert 0 == mocked_plugin.add_import_menu_item.call_count, \ + 'The add_import_menu_item() method should not have been called.' def test_hook_import_menu_with_active_plugin(self): """ @@ -207,8 +207,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_export_menu() # THEN: The add_export_menu_item() method should not have been called - self.assertEqual(0, mocked_plugin.add_export_menu_item.call_count, - 'The add_export_menu_item() method should not have been called.') + assert 0 == mocked_plugin.add_export_menu_item.call_count, \ + 'The add_export_menu_item() method should not have been called.' def test_hook_export_menu_with_active_plugin(self): """ @@ -241,8 +241,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_upgrade_plugin_settings(settings) # THEN: The upgrade_settings() method should not have been called - self.assertEqual(0, mocked_plugin.upgrade_settings.call_count, - 'The upgrade_settings() method should not have been called.') + assert 0 == mocked_plugin.upgrade_settings.call_count, \ + 'The upgrade_settings() method should not have been called.' def test_hook_upgrade_plugin_settings_with_active_plugin(self): """ @@ -275,8 +275,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_tools_menu() # THEN: The add_tools_menu_item() method should have been called - self.assertEqual(0, mocked_plugin.add_tools_menu_item.call_count, - 'The add_tools_menu_item() method should not have been called.') + assert 0 == mocked_plugin.add_tools_menu_item.call_count, \ + 'The add_tools_menu_item() method should not have been called.' def test_hook_tools_menu_with_active_plugin(self): """ @@ -310,7 +310,7 @@ class TestPluginManager(TestCase): # THEN: The is_active() method should have been called, and initialise() method should NOT have been called mocked_plugin.is_active.assert_called_with() - self.assertEqual(0, mocked_plugin.initialise.call_count, 'The initialise() method should not have been called.') + assert 0 == mocked_plugin.initialise.call_count, 'The initialise() method should not have been called.' def test_initialise_plugins_with_active_plugin(self): """ @@ -346,7 +346,7 @@ class TestPluginManager(TestCase): # THEN: The is_active() method should have been called, and initialise() method should NOT have been called mocked_plugin.is_active.assert_called_with() - self.assertEqual(0, mocked_plugin.finalise.call_count, 'The finalise() method should not have been called.') + assert 0 == mocked_plugin.finalise.call_count, 'The finalise() method should not have been called.' def test_finalise_plugins_with_active_plugin(self): """ @@ -380,7 +380,7 @@ class TestPluginManager(TestCase): result = plugin_manager.get_plugin_by_name('Missing Plugin') # THEN: The is_active() and finalise() methods should have been called - self.assertIsNone(result, 'The result for get_plugin_by_name should be None') + assert result is None, 'The result for get_plugin_by_name should be None' def test_get_plugin_by_name_exists(self): """ @@ -396,7 +396,7 @@ class TestPluginManager(TestCase): result = plugin_manager.get_plugin_by_name('Mocked Plugin') # THEN: The is_active() and finalise() methods should have been called - self.assertEqual(result, mocked_plugin, 'The result for get_plugin_by_name should be the mocked plugin') + assert result == mocked_plugin, 'The result for get_plugin_by_name should be the mocked plugin' def test_new_service_created_with_disabled_plugin(self): """ @@ -414,8 +414,8 @@ class TestPluginManager(TestCase): # THEN: The isActive() method should have been called, and initialise() method should NOT have been called mocked_plugin.is_active.assert_called_with() - self.assertEqual(0, mocked_plugin.new_service_created.call_count, - 'The new_service_created() method should not have been called.') + assert 0 == mocked_plugin.new_service_created.call_count, \ + 'The new_service_created() method should not have been called.' def test_new_service_created_with_active_plugin(self): """ diff --git a/tests/functional/openlp_core/lib/test_serviceitem.py b/tests/functional/openlp_core/lib/test_serviceitem.py index 78ad018fb..2ee142be4 100644 --- a/tests/functional/openlp_core/lib/test_serviceitem.py +++ b/tests/functional/openlp_core/lib/test_serviceitem.py @@ -28,7 +28,9 @@ from unittest.mock import MagicMock, patch from openlp.core.common import md5_hash from openlp.core.common.registry import Registry +from openlp.core.common.settings import Settings from openlp.core.lib import ItemCapabilities, ServiceItem, ServiceItemType, FormattingTags +from tests.helpers.testmixin import TestMixin from tests.utils import assert_length, convert_file_service_item @@ -59,19 +61,31 @@ RENDERED_VERSE = 'The Lord said to <span style="-webkit-text-fill-color:red">Noa FOOTER = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456'] TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'service')) +__default_settings__ = { + 'songs/enable chords': True, +} -class TestServiceItem(TestCase): + +class TestServiceItem(TestCase, TestMixin): def setUp(self): """ Set up the Registry """ + self.build_settings() + Settings().extend_default_settings(__default_settings__) Registry.create() mocked_renderer = MagicMock() mocked_renderer.format_slide.return_value = [VERSE] Registry().register('renderer', mocked_renderer) Registry().register('image_manager', MagicMock()) + def tearDown(self): + """ + Clean up + """ + self.destroy_settings() + def test_service_item_basic(self): """ Test the Service Item - basic test @@ -82,8 +96,8 @@ class TestServiceItem(TestCase): service_item = ServiceItem(None) # THEN: We should get back a valid service item - self.assertTrue(service_item.is_valid, 'The new service item should be valid') - self.assertTrue(service_item.missing_frames(), 'There should not be any frames in the service item') + assert service_item.is_valid is True, 'The new service item should be valid' + assert service_item.missing_frames() is True, 'There should not be any frames in the service item' def test_service_item_load_custom_from_service(self): """ @@ -99,7 +113,7 @@ class TestServiceItem(TestCase): service_item.set_from_service(line) # THEN: We should get back a valid service item - self.assertTrue(service_item.is_valid, 'The new service item should be valid') + assert service_item.is_valid, 'The new service item should be valid' assert_length(0, service_item._display_frames, 'The service item should have no display frames') assert_length(5, service_item.capabilities, 'There should be 5 default custom item capabilities') @@ -107,14 +121,14 @@ class TestServiceItem(TestCase): service_item.render(True) # THEN: The frames should also be valid - self.assertEqual('Test Custom', service_item.get_display_title(), 'The title should be "Test Custom"') - self.assertEqual(CLEANED_VERSE[:-1], service_item.get_frames()[0]['text'], - 'The returned text matches the input, except the last line feed') - self.assertEqual(RENDERED_VERSE.split('\n', 1)[0], service_item.get_rendered_frame(1), - 'The first line has been returned') - self.assertEqual('Slide 1', service_item.get_frame_title(0), '"Slide 1" has been returned as the title') - self.assertEqual('Slide 2', service_item.get_frame_title(1), '"Slide 2" has been returned as the title') - self.assertEqual('', service_item.get_frame_title(2), 'Blank has been returned as the title of slide 3') + assert 'Test Custom' == service_item.get_display_title(), 'The title should be "Test Custom"' + assert CLEANED_VERSE[:-1] == service_item.get_frames()[0]['text'], \ + 'The returned text matches the input, except the last line feed' + assert RENDERED_VERSE.split('\n', 1)[0] == service_item.get_rendered_frame(1), \ + 'The first line has been returned' + assert 'Slide 1' == service_item.get_frame_title(0), '"Slide 1" has been returned as the title' + assert 'Slide 2' == service_item.get_frame_title(1), '"Slide 2" has been returned as the title' + assert '' == service_item.get_frame_title(2), 'Blank has been returned as the title of slide 3' def test_service_item_load_image_from_service(self): """ @@ -138,26 +152,22 @@ class TestServiceItem(TestCase): service_item.set_from_service(line, TEST_PATH) # THEN: We should get back a valid service item - self.assertTrue(service_item.is_valid, 'The new service item should be valid') - self.assertEqual(os.path.normpath(test_file), os.path.normpath(service_item.get_rendered_frame(0)), - 'The first frame should match the path to the image') - self.assertEqual(frame_array, service_item.get_frames()[0], - 'The return should match frame array1') - self.assertEqual(test_file, service_item.get_frame_path(0), - 'The frame path should match the full path to the image') - self.assertEqual(image_name, service_item.get_frame_title(0), - 'The frame title should match the image name') - self.assertEqual(image_name, service_item.get_display_title(), - 'The display title should match the first image name') - self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type') - self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain), - 'This service item should be able to be Maintained') - self.assertTrue(service_item.is_capable(ItemCapabilities.CanPreview), - 'This service item should be able to be be Previewed') - self.assertTrue(service_item.is_capable(ItemCapabilities.CanLoop), - 'This service item should be able to be run in a can be made to Loop') - self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend), - 'This service item should be able to have new items added to it') + assert service_item.is_valid is True, 'The new service item should be valid' + assert os.path.normpath(test_file) == os.path.normpath(service_item.get_rendered_frame(0)), \ + 'The first frame should match the path to the image' + assert frame_array == service_item.get_frames()[0], 'The return should match frame array1' + assert test_file == service_item.get_frame_path(0), 'The frame path should match the full path to the image' + assert image_name == service_item.get_frame_title(0), 'The frame title should match the image name' + assert image_name == service_item.get_display_title(), 'The display title should match the first image name' + assert service_item.is_image() is True, 'This service item should be of an "image" type' + assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \ + 'This service item should be able to be Maintained' + assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \ + 'This service item should be able to be be Previewed' + assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \ + 'This service item should be able to be run in a can be made to Loop' + assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \ + 'This service item should be able to have new items added to it' def test_service_item_load_image_from_local_service(self): """ @@ -193,35 +203,33 @@ class TestServiceItem(TestCase): # This test is copied from service_item.py, but is changed since to conform to # new layout of service item. The layout use in serviceitem_image_2.osd is actually invalid now. - self.assertTrue(service_item.is_valid, 'The first service item should be valid') - self.assertTrue(service_item2.is_valid, 'The second service item should be valid') + assert service_item.is_valid is True, 'The first service item should be valid' + assert service_item2.is_valid is True, 'The second service item should be valid' # These test will fail on windows due to the difference in folder seperators if os.name != 'nt': - self.assertEqual(test_file1, service_item.get_rendered_frame(0), - 'The first frame should match the path to the image') - self.assertEqual(test_file2, service_item2.get_rendered_frame(0), - 'The Second frame should match the path to the image') - self.assertEqual(frame_array1, service_item.get_frames()[0], 'The return should match the frame array1') - self.assertEqual(frame_array2, service_item2.get_frames()[0], 'The return should match the frame array2') - self.assertEqual(test_file1, service_item.get_frame_path(0), - 'The frame path should match the full path to the image') - self.assertEqual(test_file2, service_item2.get_frame_path(0), - 'The frame path should match the full path to the image') - self.assertEqual(image_name1, service_item.get_frame_title(0), - 'The 1st frame title should match the image name') - self.assertEqual(image_name2, service_item2.get_frame_title(0), - 'The 2nd frame title should match the image name') - self.assertEqual(service_item.name, service_item.title.lower(), - 'The plugin name should match the display title, as there are > 1 Images') - self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type') - self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain), - 'This service item should be able to be Maintained') - self.assertTrue(service_item.is_capable(ItemCapabilities.CanPreview), - 'This service item should be able to be be Previewed') - self.assertTrue(service_item.is_capable(ItemCapabilities.CanLoop), - 'This service item should be able to be run in a can be made to Loop') - self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend), - 'This service item should be able to have new items added to it') + assert test_file1 == service_item.get_rendered_frame(0), \ + 'The first frame should match the path to the image' + assert test_file2 == service_item2.get_rendered_frame(0), \ + 'The Second frame should match the path to the image' + assert frame_array1 == service_item.get_frames()[0], 'The return should match the frame array1' + assert frame_array2 == service_item2.get_frames()[0], 'The return should match the frame array2' + assert test_file1 == service_item.get_frame_path(0), \ + 'The frame path should match the full path to the image' + assert test_file2 == service_item2.get_frame_path(0), \ + 'The frame path should match the full path to the image' + assert image_name1 == service_item.get_frame_title(0), 'The 1st frame title should match the image name' + assert image_name2 == service_item2.get_frame_title(0), 'The 2nd frame title should match the image name' + assert service_item.name == service_item.title.lower(), \ + 'The plugin name should match the display title, as there are > 1 Images' + assert service_item.is_image() is True, 'This service item should be of an "image" type' + assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \ + 'This service item should be able to be Maintained' + assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \ + 'This service item should be able to be be Previewed' + assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \ + 'This service item should be able to be run in a can be made to Loop' + assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \ + 'This service item should be able to have new items added to it' def test_add_from_command_for_a_presentation(self): """ @@ -240,8 +248,8 @@ class TestServiceItem(TestCase): service_item.add_from_command(TEST_PATH, presentation_name, image, display_title, notes) # THEN: verify that it is setup as a Command and that the frame data matches - self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command') - self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match') + assert service_item.service_item_type == ServiceItemType.Command, 'It should be a Command' + assert service_item.get_frames()[0] == frame, 'Frames should match' def test_add_from_comamnd_without_display_title_and_notes(self): """ @@ -258,8 +266,8 @@ class TestServiceItem(TestCase): service_item.add_from_command(TEST_PATH, image_name, image) # THEN: verify that it is setup as a Command and that the frame data matches - self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command') - self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match') + assert service_item.service_item_type == ServiceItemType.Command, 'It should be a Command' + assert service_item.get_frames()[0] == frame, 'Frames should match' @patch(u'openlp.core.lib.serviceitem.ServiceItem.image_manager') @patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') @@ -287,9 +295,9 @@ class TestServiceItem(TestCase): service_item.add_from_command(TEST_PATH, presentation_name, thumb, display_title, notes) # THEN: verify that it is setup as a Command and that the frame data matches - self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command') - self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match') - self.assertEqual(1, mocked_image_manager.add_image.call_count, 'image_manager should be used') + assert service_item.service_item_type == ServiceItemType.Command, 'It should be a Command' + assert service_item.get_frames()[0] == frame, 'Frames should match' + assert 1 == mocked_image_manager.add_image.call_count, 'image_manager should be used' def test_service_item_load_optical_media_from_service(self): """ @@ -306,11 +314,11 @@ class TestServiceItem(TestCase): service_item.set_from_service(line) # THEN: We should get back a valid service item with optical media info - self.assertTrue(service_item.is_valid, 'The service item should be valid') - self.assertTrue(service_item.is_capable(ItemCapabilities.IsOptical), 'The item should be Optical') - self.assertEqual(service_item.start_time, 654.375, 'Start time should be 654.375') - self.assertEqual(service_item.end_time, 672.069, 'End time should be 672.069') - self.assertEqual(service_item.media_length, 17.694, 'Media length should be 17.694') + assert service_item.is_valid is True, 'The service item should be valid' + assert service_item.is_capable(ItemCapabilities.IsOptical) is True, 'The item should be Optical' + assert service_item.start_time == 654.375, 'Start time should be 654.375' + assert service_item.end_time == 672.069, 'End time should be 672.069' + assert service_item.media_length == 17.694, 'Media length should be 17.694' def test_service_item_load_song_and_audio_from_service(self): """ @@ -326,22 +334,22 @@ class TestServiceItem(TestCase): service_item.set_from_service(line, '/test/') # THEN: We should get back a valid service item - self.assertTrue(service_item.is_valid, 'The new service item should be valid') - assert_length(0, service_item._display_frames, 'The service item should have no display frames') - assert_length(7, service_item.capabilities, 'There should be 7 default custom item capabilities') + assert service_item.is_valid, 'The new service item should be valid' + assert 0 == len(service_item._display_frames), 'The service item should have no display frames' + assert 7 == len(service_item.capabilities), 'There should be 7 default custom item capabilities' # WHEN: We render the frames of the service item service_item.render(True) # THEN: The frames should also be valid - self.assertEqual('Amazing Grace', service_item.get_display_title(), 'The title should be "Amazing Grace"') - self.assertEqual(CLEANED_VERSE[:-1], service_item.get_frames()[0]['text'], - 'The returned text matches the input, except the last line feed') - self.assertEqual(RENDERED_VERSE.split('\n', 1)[0], service_item.get_rendered_frame(1), - 'The first line has been returned') - self.assertEqual('Amazing Grace! how sweet the s', service_item.get_frame_title(0), - '"Amazing Grace! how sweet the s" has been returned as the title') - self.assertEqual('’Twas grace that taught my hea', service_item.get_frame_title(1), - '"’Twas grace that taught my hea" has been returned as the title') - self.assertEqual('/test/amazing_grace.mp3', service_item.background_audio[0], - '"/test/amazing_grace.mp3" should be in the background_audio list') + assert 'Amazing Grace' == service_item.get_display_title(), 'The title should be "Amazing Grace"' + assert CLEANED_VERSE[:-1] == service_item.get_frames()[0]['text'], \ + 'The returned text matches the input, except the last line feed' + assert RENDERED_VERSE.split('\n', 1)[0] == service_item.get_rendered_frame(1), \ + 'The first line has been returned' + assert 'Amazing Grace! how sweet the s' == service_item.get_frame_title(0), \ + '"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], \ + '"/test/amazing_grace.mp3" should be in the background_audio list' diff --git a/tests/functional/openlp_core/lib/test_theme.py b/tests/functional/openlp_core/lib/test_theme.py index 93bc06f24..4e6407d1c 100644 --- a/tests/functional/openlp_core/lib/test_theme.py +++ b/tests/functional/openlp_core/lib/test_theme.py @@ -90,8 +90,8 @@ class TestTheme(TestCase): # THEN: The filename of the background should be correct expected_filename = path / 'MyBeautifulTheme' / 'video.mp4' - self.assertEqual(expected_filename, theme.background_filename) - self.assertEqual('MyBeautifulTheme', theme.theme_name) + assert expected_filename == theme.background_filename + assert 'MyBeautifulTheme' == theme.theme_name def test_save_retrieve(self): """ @@ -107,9 +107,9 @@ class TestTheme(TestCase): self.check_theme(lt) def check_theme(self, theme): - self.assertEqual('#000000', theme.background_border_color, 'background_border_color should be "#000000"') - self.assertEqual('solid', theme.background_type, 'background_type should be "solid"') - self.assertEqual(0, theme.display_vertical_align, 'display_vertical_align should be 0') - self.assertFalse(theme.font_footer_bold, 'font_footer_bold should be False') - self.assertEqual('Arial', theme.font_main_name, 'font_main_name should be "Arial"') - self.assertEqual(47, len(theme.__dict__), 'The theme should have 47 attributes') + assert '#000000' == theme.background_border_color, 'background_border_color should be "#000000"' + assert 'solid' == theme.background_type, 'background_type should be "solid"' + assert 0 == theme.display_vertical_align, 'display_vertical_align should be 0' + assert theme.font_footer_bold is False, 'font_footer_bold should be False' + assert 'Arial' == theme.font_main_name, 'font_main_name should be "Arial"' + assert 47 == len(theme.__dict__), 'The theme should have 47 attributes' diff --git a/tests/functional/openlp_core/lib/test_ui.py b/tests/functional/openlp_core/lib/test_ui.py index 8aec310ba..b17597306 100644 --- a/tests/functional/openlp_core/lib/test_ui.py +++ b/tests/functional/openlp_core/lib/test_ui.py @@ -49,8 +49,8 @@ class TestUi(TestCase): add_welcome_page(wizard, ':/wizards/wizard_firsttime.bmp') # THEN: The wizard should have one page with a pixmap. - self.assertEqual(1, len(wizard.pageIds()), 'The wizard should have one page.') - self.assertIsInstance(wizard.page(0).pixmap(QtWidgets.QWizard.WatermarkPixmap), QtGui.QPixmap) + assert 1 == len(wizard.pageIds()), 'The wizard should have one page.' + assert isinstance(wizard.page(0).pixmap(QtWidgets.QWizard.WatermarkPixmap), QtGui.QPixmap) def test_create_button_box(self): """ @@ -63,22 +63,22 @@ class TestUi(TestCase): btnbox = create_button_box(dialog, 'my_btns', ['ok', 'save', 'cancel', 'close', 'defaults']) # THEN: We should get a QDialogButtonBox with five buttons - self.assertIsInstance(btnbox, QtWidgets.QDialogButtonBox) - self.assertEqual(5, len(btnbox.buttons())) + assert isinstance(btnbox, QtWidgets.QDialogButtonBox) + assert 5 == len(btnbox.buttons()) # WHEN: We create the button box with a custom button btnbox = create_button_box(dialog, 'my_btns', None, [QtWidgets.QPushButton('Custom')]) # THEN: We should get a QDialogButtonBox with one button - self.assertIsInstance(btnbox, QtWidgets.QDialogButtonBox) - self.assertEqual(1, len(btnbox.buttons())) + assert isinstance(btnbox, QtWidgets.QDialogButtonBox) + assert 1 == len(btnbox.buttons()) # WHEN: We create the button box with a custom button and a custom role btnbox = create_button_box(dialog, 'my_btns', None, [(QtWidgets.QPushButton('Help'), QtWidgets.QDialogButtonBox.HelpRole)]) # THEN: We should get a QDialogButtonBox with one button with a certain role - self.assertIsInstance(btnbox, QtWidgets.QDialogButtonBox) - self.assertEqual(1, len(btnbox.buttons())) - self.assertEqual(QtWidgets.QDialogButtonBox.HelpRole, btnbox.buttonRole(btnbox.buttons()[0])) + assert isinstance(btnbox, QtWidgets.QDialogButtonBox) + assert 1 == len(btnbox.buttons()) + assert QtWidgets.QDialogButtonBox.HelpRole, btnbox.buttonRole(btnbox.buttons()[0]) def test_create_horizontal_adjusting_combo_box(self): """ @@ -91,9 +91,9 @@ class TestUi(TestCase): combo = create_horizontal_adjusting_combo_box(dialog, 'combo1') # THEN: We should get a ComboBox - self.assertIsInstance(combo, QtWidgets.QComboBox) - self.assertEqual('combo1', combo.objectName()) - self.assertEqual(QtWidgets.QComboBox.AdjustToMinimumContentsLength, combo.sizeAdjustPolicy()) + assert isinstance(combo, QtWidgets.QComboBox) + assert 'combo1' == combo.objectName() + assert QtWidgets.QComboBox.AdjustToMinimumContentsLength == combo.sizeAdjustPolicy() def test_create_button(self): """ @@ -106,26 +106,26 @@ class TestUi(TestCase): btn = create_button(dialog, 'my_btn') # THEN: We should get a button with a name - self.assertIsInstance(btn, QtWidgets.QPushButton) - self.assertEqual('my_btn', btn.objectName()) - self.assertTrue(btn.isEnabled()) + assert isinstance(btn, QtWidgets.QPushButton) + assert 'my_btn' == btn.objectName() + assert btn.isEnabled() is True # WHEN: We create a button with some attributes btn = create_button(dialog, 'my_btn', text='Hello', tooltip='How are you?', enabled=False) # THEN: We should get a button with those attributes - self.assertIsInstance(btn, QtWidgets.QPushButton) - self.assertEqual('Hello', btn.text()) - self.assertEqual('How are you?', btn.toolTip()) - self.assertFalse(btn.isEnabled()) + assert isinstance(btn, QtWidgets.QPushButton) + assert 'Hello' == btn.text() + assert 'How are you?' == btn.toolTip() + assert btn.isEnabled() is False # WHEN: We create a toolbutton btn = create_button(dialog, 'my_btn', btn_class='toolbutton') # THEN: We should get a toolbutton - self.assertIsInstance(btn, QtWidgets.QToolButton) - self.assertEqual('my_btn', btn.objectName()) - self.assertTrue(btn.isEnabled()) + assert isinstance(btn, QtWidgets.QToolButton) + assert 'my_btn' == btn.objectName() + assert btn.isEnabled() is True def test_create_action(self): """ @@ -138,19 +138,19 @@ class TestUi(TestCase): action = create_action(dialog, 'my_action') # THEN: We should get a QAction - self.assertIsInstance(action, QtWidgets.QAction) - self.assertEqual('my_action', action.objectName()) + assert isinstance(action, QtWidgets.QAction) + assert 'my_action' == action.objectName() # WHEN: We create an action with some properties action = create_action(dialog, 'my_action', text='my text', icon=':/wizards/wizard_firsttime.bmp', tooltip='my tooltip', statustip='my statustip') # THEN: These properties should be set - self.assertIsInstance(action, QtWidgets.QAction) - self.assertEqual('my text', action.text()) - self.assertIsInstance(action.icon(), QtGui.QIcon) - self.assertEqual('my tooltip', action.toolTip()) - self.assertEqual('my statustip', action.statusTip()) + assert isinstance(action, QtWidgets.QAction) + assert 'my text' == action.text() + assert isinstance(action.icon(), QtGui.QIcon) + assert 'my tooltip' == action.toolTip() + assert 'my statustip' == action.statusTip() def test_create_action_on_mac_osx(self): """ @@ -186,8 +186,8 @@ class TestUi(TestCase): create_action(dialog, 'my_action') # THEN: setIconVisibleInMenu should not be called - self.assertEqual(0, mocked_action.setIconVisibleInMenu.call_count, - 'setIconVisibleInMenu should not have been called') + assert 0 == mocked_action.setIconVisibleInMenu.call_count, \ + 'setIconVisibleInMenu should not have been called' def test_create_checked_disabled_invisible_action(self): """ @@ -200,9 +200,9 @@ class TestUi(TestCase): action = create_action(dialog, 'my_action', checked=True, enabled=False, visible=False) # THEN: These properties should be set - self.assertTrue(action.isChecked(), 'The action should be checked') - self.assertFalse(action.isEnabled(), 'The action should be disabled') - self.assertFalse(action.isVisible(), 'The action should be invisble') + assert action.isChecked() is True, 'The action should be checked' + assert action.isEnabled() is False, 'The action should be disabled' + assert action.isVisible() is False, 'The action should be invisble' def test_create_action_separator(self): """ @@ -215,7 +215,7 @@ class TestUi(TestCase): action = create_action(dialog, 'my_action', separator=True) # THEN: The action should be a separator - self.assertTrue(action.isSeparator(), 'The action should be a separator') + assert action.isSeparator() is True, 'The action should be a separator' def test_create_valign_selection_widgets(self): """ @@ -228,11 +228,11 @@ class TestUi(TestCase): label, combo = create_valign_selection_widgets(dialog) # THEN: We should get a label and a combobox. - self.assertEqual(translate('OpenLP.Ui', '&Vertical Align:'), label.text()) - self.assertIsInstance(combo, QtWidgets.QComboBox) - self.assertEqual(combo, label.buddy()) + assert translate('OpenLP.Ui', '&Vertical Align:') == label.text() + assert isinstance(combo, QtWidgets.QComboBox) + assert combo == label.buddy() for text in [UiStrings().Top, UiStrings().Middle, UiStrings().Bottom]: - self.assertTrue(combo.findText(text) >= 0) + assert combo.findText(text) >= 0 def test_find_and_set_in_combo_box(self): """ @@ -247,19 +247,19 @@ class TestUi(TestCase): find_and_set_in_combo_box(combo, 'Four', set_missing=False) # THEN: The index should not have changed - self.assertEqual(1, combo.currentIndex()) + assert 1 == combo.currentIndex() # WHEN: We call the method with a non-existing value find_and_set_in_combo_box(combo, 'Four') # THEN: The index should have been reset - self.assertEqual(0, combo.currentIndex()) + assert 0 == combo.currentIndex() # WHEN: We call the method with the default behavior find_and_set_in_combo_box(combo, 'Three') # THEN: The index should have changed - self.assertEqual(2, combo.currentIndex()) + assert 2 == combo.currentIndex() def test_create_widget_action(self): """ @@ -272,8 +272,8 @@ class TestUi(TestCase): action = create_widget_action(button, 'some action') # THEN: The action should be returned - self.assertIsInstance(action, QtWidgets.QAction) - self.assertEqual(action.objectName(), 'some action') + assert isinstance(action, QtWidgets.QAction) + assert action.objectName() == 'some action' def test_set_case_insensitive_completer(self): """ @@ -288,5 +288,5 @@ class TestUi(TestCase): # THEN: The Combobox should have a completer which is case insensitive completer = line_edit.completer() - self.assertIsInstance(completer, QtWidgets.QCompleter) - self.assertEqual(completer.caseSensitivity(), QtCore.Qt.CaseInsensitive) + assert isinstance(completer, QtWidgets.QCompleter) + assert completer.caseSensitivity() == QtCore.Qt.CaseInsensitive From c2215d1a1e3ec4ec33288e7f8516778a1bef0da9 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Sun, 17 Dec 2017 20:19:19 +0000 Subject: [PATCH 95/96] lib done pep 8 --- tests/functional/openlp_core/lib/test_serviceitem.py | 6 +++--- tests/functional/openlp_core/lib/test_ui.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/functional/openlp_core/lib/test_serviceitem.py b/tests/functional/openlp_core/lib/test_serviceitem.py index 2ee142be4..f07efd2b4 100644 --- a/tests/functional/openlp_core/lib/test_serviceitem.py +++ b/tests/functional/openlp_core/lib/test_serviceitem.py @@ -315,9 +315,9 @@ class TestServiceItem(TestCase, TestMixin): # THEN: We should get back a valid service item with optical media info assert service_item.is_valid is True, 'The service item should be valid' - assert service_item.is_capable(ItemCapabilities.IsOptical) is True, 'The item should be Optical' - assert service_item.start_time == 654.375, 'Start time should be 654.375' - assert service_item.end_time == 672.069, 'End time should be 672.069' + assert service_item.is_capable(ItemCapabilities.IsOptical) is True, 'The item should be Optical' + assert service_item.start_time == 654.375, 'Start time should be 654.375' + assert service_item.end_time == 672.069, 'End time should be 672.069' assert service_item.media_length == 17.694, 'Media length should be 17.694' def test_service_item_load_song_and_audio_from_service(self): diff --git a/tests/functional/openlp_core/lib/test_ui.py b/tests/functional/openlp_core/lib/test_ui.py index b17597306..7c1e94f36 100644 --- a/tests/functional/openlp_core/lib/test_ui.py +++ b/tests/functional/openlp_core/lib/test_ui.py @@ -149,7 +149,7 @@ class TestUi(TestCase): assert isinstance(action, QtWidgets.QAction) assert 'my text' == action.text() assert isinstance(action.icon(), QtGui.QIcon) - assert 'my tooltip' == action.toolTip() + assert 'my tooltip' == action.toolTip() assert 'my statustip' == action.statusTip() def test_create_action_on_mac_osx(self): @@ -187,7 +187,7 @@ class TestUi(TestCase): # THEN: setIconVisibleInMenu should not be called assert 0 == mocked_action.setIconVisibleInMenu.call_count, \ - 'setIconVisibleInMenu should not have been called' + 'setIconVisibleInMenu should not have been called' def test_create_checked_disabled_invisible_action(self): """ @@ -247,7 +247,7 @@ class TestUi(TestCase): find_and_set_in_combo_box(combo, 'Four', set_missing=False) # THEN: The index should not have changed - assert 1 == combo.currentIndex() + assert 1 == combo.currentIndex() # WHEN: We call the method with a non-existing value find_and_set_in_combo_box(combo, 'Four') From 054e6e08b0a79d5a0997f25e64d9554e56f23152 Mon Sep 17 00:00:00 2001 From: Tim Bentley <tim.bentley@gmail.com> Date: Mon, 18 Dec 2017 17:10:04 +0000 Subject: [PATCH 96/96] Fixes --- tests/functional/openlp_core/lib/test_exceptions.py | 2 +- tests/functional/openlp_core/lib/test_image_manager.py | 2 +- tests/functional/openlp_core/lib/test_lib.py | 2 +- tests/functional/openlp_core/lib/test_serviceitem.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/functional/openlp_core/lib/test_exceptions.py b/tests/functional/openlp_core/lib/test_exceptions.py index 7ffc122c1..c0de323b7 100644 --- a/tests/functional/openlp_core/lib/test_exceptions.py +++ b/tests/functional/openlp_core/lib/test_exceptions.py @@ -42,4 +42,4 @@ class TestValidationError(TestCase): # THEN: Then calling str on the error should return the correct text and it should be an instance of `Exception` assert str(error) == 'Test ValidationError' - assert isinstance(error, Exception) is True + assert isinstance(error, Exception) diff --git a/tests/functional/openlp_core/lib/test_image_manager.py b/tests/functional/openlp_core/lib/test_image_manager.py index 0b37b412d..3c47e81a1 100644 --- a/tests/functional/openlp_core/lib/test_image_manager.py +++ b/tests/functional/openlp_core/lib/test_image_manager.py @@ -98,7 +98,7 @@ class TestImageManager(TestCase, TestMixin): image = self.image_manager.get_image(full_path, 'church.jpg', 80, 80) # THEN: The return should be of type image - assert isinstance(image, QtGui.QImage) is True, 'The returned object should be a QImage' + assert isinstance(image, QtGui.QImage), 'The returned object should be a QImage' # WHEN: adding the same image with different dimensions self.image_manager.add_image(full_path, 'church.jpg', None, 100, 100) diff --git a/tests/functional/openlp_core/lib/test_lib.py b/tests/functional/openlp_core/lib/test_lib.py index 9230ca5b0..7cc44ec4e 100644 --- a/tests/functional/openlp_core/lib/test_lib.py +++ b/tests/functional/openlp_core/lib/test_lib.py @@ -293,7 +293,7 @@ class TestLib(TestCase): # THEN: Check if the thumb was created and scaled to the given size. self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists') assert isinstance(icon, QtGui.QIcon), 'The icon should be a QIcon' - assert icon.isNull()is False, 'The icon should not be null' + assert icon.isNull() is False, 'The icon should not be null' assert thumb_size == QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size' # Remove the thumb so that the test actually tests if the thumb will be created. diff --git a/tests/functional/openlp_core/lib/test_serviceitem.py b/tests/functional/openlp_core/lib/test_serviceitem.py index f07efd2b4..d069f18bf 100644 --- a/tests/functional/openlp_core/lib/test_serviceitem.py +++ b/tests/functional/openlp_core/lib/test_serviceitem.py @@ -113,7 +113,7 @@ class TestServiceItem(TestCase, TestMixin): service_item.set_from_service(line) # THEN: We should get back a valid service item - assert service_item.is_valid, 'The new service item should be valid' + assert service_item.is_valid is True, 'The new service item should be valid' assert_length(0, service_item._display_frames, 'The service item should have no display frames') assert_length(5, service_item.capabilities, 'There should be 5 default custom item capabilities') @@ -334,7 +334,7 @@ class TestServiceItem(TestCase, TestMixin): service_item.set_from_service(line, '/test/') # THEN: We should get back a valid service item - assert service_item.is_valid, 'The new service item should be valid' + assert service_item.is_valid is True, 'The new service item should be valid' assert 0 == len(service_item._display_frames), 'The service item should have no display frames' assert 7 == len(service_item.capabilities), 'There should be 7 default custom item capabilities'