diff --git a/openlp/core/api/deploy.py b/openlp/core/api/deploy.py index 2aef03ec3..89f4e2afa 100644 --- a/openlp/core/api/deploy.py +++ b/openlp/core/api/deploy.py @@ -23,16 +23,64 @@ Download and "install" the remote web client """ import json import logging +from datetime import date +from distutils.version import LooseVersion from zipfile import ZipFile +from PyQt5 import QtCore + from openlp.core.common.applocation import AppLocation from openlp.core.common.httputils import download_file, get_web_page, get_openlp_user_agent +from openlp.core.common.registry import Registry +from openlp.core.threading import ThreadWorker, run_thread REMOTE_URL = 'https://get.openlp.org/remote/' log = logging.getLogger(__name__) +class RemoteVersionWorker(ThreadWorker): + """ + A worker class to fetch the version of the web remote. This is run from within a thread so that it + doesn't affect the loading time of OpenLP. + """ + new_version = QtCore.pyqtSignal(str) + no_internet = QtCore.pyqtSignal() + + def __init__(self, current_version): + """ + Constructor for the version check worker. + + :param string current_version: The current version of the web remote + """ + log.debug('VersionWorker - Initialise') + super().__init__(None) + self.current_version = current_version or '0.0' + + def start(self): + """ + Check the latest version of the web remote against the version file on the OpenLP server. + """ + log.debug('RemoteVersionWorker - Start') + version_info = None + retries = 0 + while retries < 3: + try: + version_info = download_version_info() + log.debug('New version found: %s', version_info['latest']['version']) + break + except OSError: + log.exception('Unable to connect to OpenLP server to download version file') + retries += 1 + else: + self.no_internet.emit() + if version_info and LooseVersion(version_info['latest']['version']) > LooseVersion(self.current_version): + Registry().get('settings').setValue('api/last version test', date.today().strftime('%Y-%m-%d')) + Registry().get('settings_form').api_tab.master_version = version_info['latest']['version'] + self.new_version.emit(version_info['latest']['version']) + self.quit.emit() + + def deploy_zipfile(app_root_path, zip_name): """ Process the downloaded zip file and add to the correct directory @@ -60,7 +108,18 @@ def download_version_info(): return json.loads(file_contents) -def download_and_check(callback=None): +def get_latest_size(): + """ + Download the version info file and get the size of the latest file + """ + version_info = download_version_info() + if not version_info: + log.warning('Unable to access the version information, abandoning download') + return 0 + return version_info['latest']['size'] + + +def download_and_check(callback=None, can_update_range=True): """ Download the web site and deploy it. """ @@ -70,9 +129,27 @@ def download_and_check(callback=None): # Show the user an error message return None file_size = version_info['latest']['size'] - callback.setRange(0, file_size) + if can_update_range: + callback.setRange(0, file_size) if download_file(callback, REMOTE_URL + '{version}/{filename}'.format(**version_info['latest']), AppLocation.get_section_data_path('remotes') / 'remote.zip'): deploy_zipfile(AppLocation.get_section_data_path('remotes'), 'remote.zip') return version_info['latest']['version'] return None + + +def check_for_remote_update(main_window): + """ + Run a thread to download and check the version of OpenLP + + :param MainWindow main_window: The OpenLP main window. + """ + last_check_date = Registry().get('settings').value('api/last version test') + if date.today().strftime('%Y-%m-%d') <= last_check_date: + log.debug('Version check skipped, last checked today') + return + worker = RemoteVersionWorker(Registry().get('settings').value('api/download version')) + worker.new_version.connect(main_window.on_new_remote_version) + # TODO: Use this to figure out if there's an Internet connection? + # worker.no_internet.connect(main_window.on_no_internet) + run_thread(worker, 'remote-version') diff --git a/openlp/core/api/tab.py b/openlp/core/api/tab.py index 0cc63610f..48c869a05 100644 --- a/openlp/core/api/tab.py +++ b/openlp/core/api/tab.py @@ -44,7 +44,7 @@ class ApiTab(SettingsTab): def __init__(self, parent): self.icon_path = UiIcons().remote advanced_translated = translate('OpenLP.APITab', 'API') - self.master_version = None + self._master_version = None super(ApiTab, self).__init__(parent, 'api', advanced_translated) def setup_ui(self): @@ -192,7 +192,37 @@ class ApiTab(SettingsTab): self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:')) self.current_version_label.setText(translate('RemotePlugin.RemoteTab', 'Current version:')) self.master_version_label.setText(translate('RemotePlugin.RemoteTab', 'Latest version:')) - self.unknown_version = translate('RemotePlugin.RemoteTab', '(unknown)') + self._unknown_version = translate('RemotePlugin.RemoteTab', '(unknown)') + + @property + def master_version(self): + """ + Property getter for the remote master version + """ + return self._master_version + + @master_version.setter + def master_version(self, value): + """ + Property setter for the remote master version + """ + self._master_version = value + self.master_version_value.setText(self._master_version or self._unknown_version) + self.upgrade_button.setEnabled(self.can_enable_upgrade_button()) + + def can_enable_upgrade_button(self): + """ + Do a couple checks to set the upgrade button state + """ + return self.master_version_value.text() != self._unknown_version and \ + self.master_version_value.text() != self.current_version_value.text() + + def set_master_version(self): + """ + Check if the master version is not set, and set it to None to invoke the "unknown version" label + """ + if not self._master_version: + self.master_version = None def set_urls(self): """ @@ -222,13 +252,6 @@ class ApiTab(SettingsTab): break return ip_address - def can_enable_upgrade_button(self): - """ - Do a couple checks to set the upgrade button state - """ - return self.master_version_value.text() != self.unknown_version and \ - self.master_version_value.text() != self.current_version_value.text() - def load(self): """ Load the configuration and update the server configuration if necessary @@ -243,8 +266,7 @@ class ApiTab(SettingsTab): self.user_id.setText(self.settings.value(self.settings_section + '/user id')) self.password.setText(self.settings.value(self.settings_section + '/password')) self.current_version_value.setText(self.settings.value(self.settings_section + '/download version')) - self.master_version_value.setText(self.master_version or self.unknown_version) - self.upgrade_button.setEnabled(self.can_enable_upgrade_button()) + self.set_master_version() self.set_urls() def save(self): @@ -287,8 +309,7 @@ class ApiTab(SettingsTab): app.process_events() version_info = download_version_info() app.process_events() - self.master_version_value.setText(version_info['latest']['version']) - self.upgrade_button.setEnabled(self.can_enable_upgrade_button()) + self.master_version = version_info['latest']['version'] app.process_events() app.set_normal_cursor() app.process_events() diff --git a/openlp/core/app.py b/openlp/core/app.py index 5d3a919e4..7fa4cc06c 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -37,18 +37,19 @@ from traceback import format_exception from PyQt5 import QtCore, QtWebEngineWidgets, QtWidgets # noqa -from openlp.core.state import State +from openlp.core.api.deploy import check_for_remote_update from openlp.core.common import is_macosx, is_win from openlp.core.common.applocation import AppLocation -from openlp.core.common.mixins import LogMixin -from openlp.core.loader import loader from openlp.core.common.i18n import LanguageManager, UiStrings, translate +from openlp.core.common.mixins import LogMixin from openlp.core.common.path import create_paths from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.display.screens import ScreenList +from openlp.core.loader import loader from openlp.core.resources import qInitResources from openlp.core.server import Server +from openlp.core.state import State from openlp.core.ui.exceptionform import ExceptionForm from openlp.core.ui.firsttimeform import FirstTimeForm from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm @@ -140,6 +141,8 @@ class OpenLP(QtCore.QObject, LogMixin): self.main_window.first_time() if self.settings.value('core/update check'): check_for_update(self.main_window) + if self.settings.value('api/update check'): + check_for_remote_update(self.main_window) self.main_window.is_display_blank() Registry().execute('bootstrap_completion') return self.exec() diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index a509da25e..798eb48c1 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -196,6 +196,8 @@ class Settings(QtCore.QSettings): 'api/ip address': '0.0.0.0', 'api/thumbnails': True, 'api/download version': '0.0', + 'api/last version test': '', + 'api/update check': True, 'bibles/db type': 'sqlite', 'bibles/db username': '', 'bibles/db password': '', diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 51e590600..51099a5db 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -32,6 +32,7 @@ from tempfile import gettempdir from PyQt5 import QtCore, QtWidgets +from openlp.core.api.deploy import get_latest_size, download_and_check from openlp.core.common import trace_error_handler from openlp.core.common.applocation import AppLocation from openlp.core.common.httputils import DownloadWorker, download_file, get_url_file_size, get_web_page @@ -113,13 +114,13 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): """ Returns the id of the next FirstTimePage to go to based on enabled plugins """ - if FirstTimePage.Download < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked(): + if FirstTimePage.Remote < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked(): # If the songs plugin is enabled then go to the songs page return FirstTimePage.Songs - elif FirstTimePage.Download < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked(): + elif FirstTimePage.Remote < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked(): # Otherwise, if the Bibles plugin is enabled then go to the Bibles page return FirstTimePage.Bibles - elif FirstTimePage.Download < self.currentId() < FirstTimePage.Themes: + elif FirstTimePage.Remote < self.currentId() < FirstTimePage.Themes: # Otherwise, if the current page is somewhere between the Welcome and the Themes pages, go to the themes return FirstTimePage.Themes else: @@ -135,7 +136,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): if not self.has_web_access: return FirstTimePage.NoInternet else: - return FirstTimePage.Songs + return FirstTimePage.Remote elif self.currentId() == FirstTimePage.Progress: return -1 elif self.currentId() == FirstTimePage.NoInternet: @@ -237,6 +238,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): self.has_run_wizard = self.settings.value('core/has run wizard') create_paths(Path(gettempdir(), 'openlp')) self.theme_combo_box.clear() + self.remote_page.can_download_remote = False self.button(QtWidgets.QWizard.CustomButton1).setVisible(False) if self.has_run_wizard: self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active()) @@ -418,6 +420,9 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): for item in self.themes_list_widget.selectedItems(): size = get_url_file_size('{url}{file}'.format(url=self.themes_url, file=item.file_name)) self.max_progress += size + # If we're downloading the remote, add it in here too + if self.remote_page.can_download_remote: + self.max_progress += get_latest_size() except urllib.error.URLError: trace_error_handler(log) critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'), @@ -517,6 +522,15 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): if not download_file(self, '{url}{file}'.format(url=self.themes_url, file=item.file_name), themes_destination_path / item.file_name, item.sha256): missed_files.append('Theme: name'.format(name=item.file_name)) + # Remote + if self.remote_page.can_download_remote: + self._increment_progress_bar(self.downloading.format(name='Web Remote'), 0) + self.previous_size = 0 + remote_version = download_and_check(self, can_update_range=False) + if remote_version: + self.settings.setValue('api/download version', remote_version) + else: + missed_files.append('Web Remote') if missed_files: file_list = '' for entry in missed_files: diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index aabe542c8..f35a7ab32 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -29,6 +29,7 @@ from openlp.core.lib.ui import add_welcome_page from openlp.core.ui.icons import UiIcons from openlp.core.display.screens import ScreenList +from openlp.core.pages import GridLayoutPage from openlp.core.widgets.widgets import ScreenSelectionWidget @@ -42,10 +43,53 @@ class FirstTimePage(object): SampleOption = 3 Download = 4 NoInternet = 5 - Songs = 6 - Bibles = 7 - Themes = 8 - Progress = 9 + Remote = 6 + Songs = 7 + Bibles = 8 + Themes = 9 + Progress = 10 + + +class RemotePage(GridLayoutPage): + """ + A page for the web remote + """ + def setup_ui(self): + """ + Set up the page + """ + self.remote_label = QtWidgets.QLabel(self) + self.remote_label.setWordWrap(True) + self.remote_label.setObjectName('remote_label') + self.layout.addWidget(self.remote_label, 0, 0, 1, 4) + self.download_checkbox = QtWidgets.QCheckBox(self) + self.setObjectName('download_checkbox') + self.layout.addWidget(self.download_checkbox, 1, 1, 1, 3) + + def retranslate_ui(self): + """ + Translate the interface + """ + self.remote_label.setText(translate('OpenLP.FirstTimeWizard', 'OpenLP has a web remote, which enables you to ' + 'control OpenLP from another computer, phone or tablet on the same network ' + 'as the OpenLP computer. OpenLP can download this web remote for you now, ' + 'or you can download it later via the remote settings.')) + self.download_checkbox.setText(translate('OpenLP.FirstTimeWizard', 'Yes, download the remote now')) + self.setTitle(translate('OpenLP.FirstTimeWizard', 'Web-based Remote Interface')) + self.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please confirm if you want to download the web remote.')) + + @property + def can_download_remote(self): + """ + The get method of a property to determine if the user selected the "Download remote now" checkbox + """ + return self.download_checkbox.isChecked() + + @can_download_remote.setter + def can_download_remote(self, value): + if not isinstance(value, bool): + raise TypeError('Must be a bool') + self.download_checkbox.setChecked(value) class ThemeListWidget(QtWidgets.QListWidget): @@ -187,6 +231,9 @@ class UiFirstTimeWizard(object): self.alert_check_box.setObjectName('alert_check_box') self.plugin_layout.addWidget(self.alert_check_box) first_time_wizard.setPage(FirstTimePage.Plugins, self.plugin_page) + # Web Remote page + self.remote_page = RemotePage(self) + first_time_wizard.setPage(FirstTimePage.Remote, self.remote_page) # The song samples page self.songs_page = QtWidgets.QWizardPage() self.songs_page.setObjectName('songs_page') diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index dfaf6f6e3..63490dc49 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -622,8 +622,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert def on_new_version(self, version): """ - Notifies the user that a newer version of OpenLP is available. - Triggered by delay thread and cannot display popup. + Notifies the user that a newer version of OpenLP is available. Triggered by delay thread and cannot display + popup. :param version: The Version to be displayed. """ @@ -632,6 +632,18 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert 'https://openlp.org/.').format(new=version, current=get_version()[u'full']) QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Version Updated'), version_text) + def on_new_remote_version(self, version): + """ + Notifies the user that a newer version of the web remote is available. Triggered by delay thread and cannot + display popup. + + :param version: The Version to be displayed. + """ + version_text = translate('OpenLP.MainWindow', 'Version {version} of the web remote is now available for ' + 'download.\nTo download this version, go to the Remote settings and click the Upgrade ' + 'button.').format(version=version) + self.information_message(translate('OpenLP.MainWindow', 'New Web Remote Version Available'), version_text) + def show(self): """ Show the main form, as well as the display form diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index 6fad6bd59..2efd7c65c 100644 --- a/openlp/plugins/custom/customplugin.py +++ b/openlp/plugins/custom/customplugin.py @@ -61,7 +61,7 @@ class CustomPlugin(Plugin): @staticmethod def about(): - about_text = translate('CustomPlugin', 'Custom Slide Plugin
The custom slide plugin ' + about_text = translate('CustomPlugin', 'Custom Slide Plugin
The custom slide plugin ' 'provides the ability to set up custom text slides that can be displayed on the screen ' 'the same way songs are. This plugin provides greater freedom over the songs plugin.') return about_text diff --git a/openlp/plugins/songs/lib/openlyricsexport.py b/openlp/plugins/songs/lib/openlyricsexport.py index 066b4097e..d10a01b67 100644 --- a/openlp/plugins/songs/lib/openlyricsexport.py +++ b/openlp/plugins/songs/lib/openlyricsexport.py @@ -70,7 +70,9 @@ class OpenLyricsExport(RegistryProperties): xml = open_lyrics.song_to_xml(song) tree = etree.ElementTree(etree.fromstring(xml.encode())) filename = '{title} ({author})'.format(title=song.title, - author=', '.join([author.display_name for author in song.authors])) + author=', '.join([author.display_name for author in + sorted(song.authors, + key=lambda a: a.display_name)])) filename = clean_filename(filename) # Ensure the filename isn't too long for some filesystems path_length = len(str(self.save_path)) diff --git a/tests/functional/openlp_core/common/test_json.py b/tests/functional/openlp_core/common/test_json.py index 327dcc5e6..6095353b7 100644 --- a/tests/functional/openlp_core/common/test_json.py +++ b/tests/functional/openlp_core/common/test_json.py @@ -187,7 +187,22 @@ class TestOpenLPJSONDecoder(TestCase): """ # GIVEN: A JSON encoded string json_string = '[{"parts": ["test", "path1"], "json_meta": {"class": "Path", "version": 1}}, ' \ - '{"parts": ["test", "path2"], "json_meta": {"class": "Path", "version": 1}}]' + '{"parts": ["test", "path2"], "json_meta": {"class": "Path", "version": 1}}, ' \ + '{"key": "value", "json_meta": {"class": "Object"}}]' + + # WHEN: Decoding the string using the OpenLPJsonDecoder class + obj = json.loads(json_string, cls=OpenLPJSONDecoder) + + # THEN: The object returned should be a python version of the JSON string + assert obj == [Path('test', 'path1'), Path('test', 'path2'), {'key': 'value', 'json_meta': {'class': 'Object'}}] + + def test_json_decode_old_style(self): + """ + Test the OpenLPJsonDecoder when decoding a JSON string with an old-style Path object + """ + # GIVEN: A JSON encoded string + json_string = '[{"__Path__": ["test", "path1"]}, ' \ + '{"__Path__": ["test", "path2"]}]' # WHEN: Decoding the string using the OpenLPJsonDecoder class obj = json.loads(json_string, cls=OpenLPJSONDecoder) @@ -284,6 +299,19 @@ class TestPathSerializer(TestCase): # THEN: A JSON decodeable object should have been returned. assert obj == {'parts': (os.sep, 'base', 'path', 'to', 'fi.le'), "json_meta": {"class": "Path", "version": 1}} + def test_path_json_object_is_js(self): + """ + Test that `Path.json_object` creates a JSON decode-able object from a Path object + """ + # GIVEN: A Path object from openlp.core.common.path + path = Path('/base', 'path', 'to', 'fi.le') + + # WHEN: Calling json_object + obj = PathSerializer().json_object(path, is_js=True, extra=1, args=2) + + # THEN: A URI should be returned + assert obj == 'file:///base/path/to/fi.le' + def test_path_json_object_base_path(self): """ Test that `Path.json_object` creates a JSON decode-able object from a Path object, that is relative to the diff --git a/tests/functional/openlp_core/ui/test_firsttimeform.py b/tests/functional/openlp_core/ui/test_firsttimeform.py index 7b214ec8e..a5ad9d8b5 100644 --- a/tests/functional/openlp_core/ui/test_firsttimeform.py +++ b/tests/functional/openlp_core/ui/test_firsttimeform.py @@ -29,7 +29,7 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.common.registry import Registry from openlp.core.ui.firsttimeform import FirstTimeForm, ThemeListWidgetItem -from openlp.core.ui.firsttimewizard import ThemeListWidget +from openlp.core.ui.firsttimewizard import RemotePage, ThemeListWidget INVALID_CONFIG = """ @@ -367,6 +367,50 @@ def test_on_projectors_check_box_unchecked(mock_settings): mock_settings.setValue.assert_called_once_with('projector/show after wizard', True) +def test_remote_page_get_can_download_remote(ftf_app): + """ + Test that the `can_download_remote` property returns the correct value + """ + # GIVEN: A RemotePage object with a mocked out download_checkbox + remote_page = RemotePage(None) + remote_page.download_checkbox = MagicMock(**{"isChecked.return_value": True}) + + # WHEN: The can_download_remote property is accessed + result = remote_page.can_download_remote + + # THEN: The result should be True + assert result is True + + +def test_remote_page_set_can_download_remote(ftf_app): + """ + Test that the `can_download_remote` property sets the correct value + """ + # GIVEN: A RemotePage object with a mocked out download_checkbox + remote_page = RemotePage(None) + remote_page.download_checkbox = MagicMock() + + # WHEN: The can_download_remote property is set + remote_page.can_download_remote = False + + # THEN: The result should be True + remote_page.download_checkbox.setChecked.assert_called_once_with(False) + + +def test_remote_page_set_can_download_remote_not_bool(ftf_app): + """ + Test that the `can_download_remote` property throws an exception when the value is not a boolean + """ + # GIVEN: A RemotePage object with a mocked out download_checkbox + remote_page = RemotePage(None) + remote_page.download_checkbox = MagicMock() + + # WHEN: The can_download_remote property is set + # THEN: An exception is thrown + with pytest.raises(TypeError, match='Must be a bool'): + remote_page.can_download_remote = 'not a bool' + + def test_theme_list_widget_resize(ftf_app): """ Test that the resizeEvent() method in the ThemeListWidget works correctly @@ -382,5 +426,3 @@ def test_theme_list_widget_resize(ftf_app): # THEN: Check that the correct calculations were done mocked_setGridSize.assert_called_once_with(QtCore.QSize(149, 140)) - - # THEN: everything resizes correctly diff --git a/tests/functional/openlp_plugins/alerts/test_plugin.py b/tests/functional/openlp_plugins/alerts/test_plugin.py new file mode 100644 index 000000000..2690895d4 --- /dev/null +++ b/tests/functional/openlp_plugins/alerts/test_plugin.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +########################################################################## +""" +This module contains tests for the plugin class Alerts plugin. +""" +from openlp.plugins.alerts.alertsplugin import AlertsPlugin + + +def test_plugin_about(): + result = AlertsPlugin.about() + + assert result == ( + 'Alerts Plugin' + '
The alert plugin controls the displaying of alerts on the display screen.' + ) diff --git a/tests/functional/openlp_plugins/bibles/test_mediaitem.py b/tests/functional/openlp_plugins/bibles/test_mediaitem.py old mode 100755 new mode 100644 diff --git a/tests/functional/openlp_plugins/bibles/test_plugin.py b/tests/functional/openlp_plugins/bibles/test_plugin.py new file mode 100644 index 000000000..eb416c8ee --- /dev/null +++ b/tests/functional/openlp_plugins/bibles/test_plugin.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +########################################################################## +""" +This module contains tests for the plugin class Bibles plugin. +""" +from openlp.plugins.bibles.bibleplugin import BiblePlugin + + +def test_plugin_about(): + result = BiblePlugin.about() + + assert result == ( + 'Bible Plugin' + '
The Bible plugin provides the ability to display Bible ' + 'verses from different sources during the service.' + ) diff --git a/tests/functional/openlp_plugins/custom/test_plugin.py b/tests/functional/openlp_plugins/custom/test_plugin.py new file mode 100644 index 000000000..d9e113042 --- /dev/null +++ b/tests/functional/openlp_plugins/custom/test_plugin.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +########################################################################## +""" +This module contains tests for the plugin class Custom plugin. +""" +from openlp.plugins.custom.customplugin import CustomPlugin + + +def test_plugin_about(): + result = CustomPlugin.about() + + assert result == ( + 'Custom Slide Plugin
The custom slide plugin ' + 'provides the ability to set up custom text slides that can be displayed on the screen ' + 'the same way songs are. This plugin provides greater freedom over the songs plugin.' + ) diff --git a/tests/functional/openlp_plugins/images/test_plugin.py b/tests/functional/openlp_plugins/images/test_plugin.py new file mode 100644 index 000000000..fbb378ccd --- /dev/null +++ b/tests/functional/openlp_plugins/images/test_plugin.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +########################################################################## +""" +This module contains tests for the plugin class Images plugin. +""" +from openlp.plugins.images.imageplugin import ImagePlugin + + +def test_image_plugin_about(): + result = ImagePlugin.about() + + assert result == ( + 'Image Plugin' + '
The image plugin provides displaying of images.
One ' + 'of the distinguishing features of this plugin is the ability to ' + 'group a number of images together in the service manager, making ' + 'the displaying of multiple images easier. This plugin can also ' + 'make use of OpenLP\'s "timed looping" feature to create a slide ' + 'show that runs automatically. In addition to this, images from ' + 'the plugin can be used to override the current theme\'s ' + 'background, which renders text-based items like songs with the ' + 'selected image as a background instead of the background ' + 'provided by the theme.' + ) diff --git a/tests/functional/openlp_plugins/media/test_plugin.py b/tests/functional/openlp_plugins/media/test_plugin.py new file mode 100644 index 000000000..047466e9f --- /dev/null +++ b/tests/functional/openlp_plugins/media/test_plugin.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +########################################################################## +""" +This module contains tests for the plugin class Media plugin. +""" +from openlp.plugins.media.mediaplugin import MediaPlugin + + +def test_plugin_about(): + result = MediaPlugin.about() + + assert result == ( + 'Media Plugin' + '
The media plugin provides playback of audio and video.' + ) diff --git a/tests/functional/openlp_plugins/presentations/test_plugin.py b/tests/functional/openlp_plugins/presentations/test_plugin.py new file mode 100644 index 000000000..b7b2211c7 --- /dev/null +++ b/tests/functional/openlp_plugins/presentations/test_plugin.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +########################################################################## +""" +This module contains tests for the plugin class Presentation plugin. +""" +from openlp.plugins.presentations.presentationplugin import PresentationPlugin + + +def test_plugin_about(): + result = PresentationPlugin.about() + + assert result == ( + 'Presentation ' + 'Plugin
The presentation plugin provides the ' + 'ability to show presentations using a number of different ' + 'programs. The choice of available presentation programs is ' + 'available to the user in a drop down box.' + ) diff --git a/tests/functional/openlp_plugins/songs/test_openlyricsexport.py b/tests/functional/openlp_plugins/songs/test_openlyricsexport.py index 93b05c229..4faf1e11d 100644 --- a/tests/functional/openlp_plugins/songs/test_openlyricsexport.py +++ b/tests/functional/openlp_plugins/songs/test_openlyricsexport.py @@ -24,93 +24,108 @@ This module contains tests for the OpenLyrics song importer. import shutil from pathlib import Path 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.settings import Settings +import pytest + +# from openlp.core.common.registry import Registry from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport -from tests.helpers.testmixin import TestMixin -class TestOpenLyricsExport(TestCase, TestMixin): +@pytest.yield_fixture +def temp_folder(): + temp_path = Path(mkdtemp()) + yield temp_path + shutil.rmtree(temp_path) + + +def test_export_same_filename(registry, settings, temp_folder): """ - Test the functions in the :mod:`openlyricsexport` module. + Test that files is not overwritten if songs has same title and author """ - def setUp(self): - """ - Create the registry - """ - Registry.create() - Registry().register('settings', Settings()) - self.temp_folder = Path(mkdtemp()) + # GIVEN: A mocked song_to_xml, 2 mocked songs, a mocked application and an OpenLyricsExport instance + with patch('openlp.plugins.songs.lib.openlyricsexport.OpenLyrics.song_to_xml') as mocked_song_to_xml: + mocked_song_to_xml.return_value = '\n' + author = MagicMock() + author.display_name = 'Test Author' + song = MagicMock() + song.authors = [author] + song.title = 'Test Title' + parent = MagicMock() + parent.stop_export_flag = False + # mocked_application_object = MagicMock() + # Registry().register('application', mocked_application_object) + ol_export = OpenLyricsExport(parent, [song, song], temp_folder) - def tearDown(self): - """ - Cleanup - """ - shutil.rmtree(self.temp_folder) + # WHEN: Doing the export + ol_export.do_export() - def test_export_same_filename(self): - """ - Test that files is not overwritten if songs has same title and author - """ - # GIVEN: A mocked song_to_xml, 2 mocked songs, a mocked application and an OpenLyricsExport instance - with patch('openlp.plugins.songs.lib.openlyricsexport.OpenLyrics.song_to_xml') as mocked_song_to_xml: - mocked_song_to_xml.return_value = '\n' - author = MagicMock() - author.display_name = 'Test Author' - song = MagicMock() - song.authors = [author] - song.title = 'Test Title' - parent = MagicMock() - parent.stop_export_flag = False - mocked_application_object = MagicMock() - Registry().register('application', mocked_application_object) - ol_export = OpenLyricsExport(parent, [song, song], self.temp_folder) + # THEN: The exporter should have created 2 files + assert (temp_folder / '{title} ({display_name}).xml'.format( + title=song.title, display_name=author.display_name)).exists() is True + assert (temp_folder / '{title} ({display_name})-1.xml'.format( + title=song.title, display_name=author.display_name)).exists() is True - # WHEN: Doing the export - ol_export.do_export() - # THEN: The exporter should have created 2 files - assert (self.temp_folder / '{title} ({display_name}).xml'.format( - title=song.title, display_name=author.display_name)).exists() is True - assert (self.temp_folder / '{title} ({display_name})-1.xml'.format( - title=song.title, display_name=author.display_name)).exists() is True +def test_export_sort_of_authers_filename(registry, settings, temp_folder): + """ + Test that files is not overwritten if songs has same title and author + """ + # GIVEN: A mocked song_to_xml, 1 mocked songs, a mocked application and an OpenLyricsExport instance + with patch('openlp.plugins.songs.lib.openlyricsexport.OpenLyrics.song_to_xml') as mocked_song_to_xml: + mocked_song_to_xml.return_value = '\n' + authorA = MagicMock() + authorA.display_name = 'a Author' + authorB = MagicMock() + authorB.display_name = 'b Author' + songA = MagicMock() + songA.authors = [authorA, authorB] + songA.title = 'Test Title' + songB = MagicMock() + songB.authors = [authorB, authorA] + songB.title = 'Test Title' - def test_export_sort_of_authers_filename(self): - """ - Test that files is not overwritten if songs has same title and author - """ - # GIVEN: A mocked song_to_xml, 1 mocked songs, a mocked application and an OpenLyricsExport instance - with patch('openlp.plugins.songs.lib.openlyricsexport.OpenLyrics.song_to_xml') as mocked_song_to_xml: - mocked_song_to_xml.return_value = '\n' - authorA = MagicMock() - authorA.display_name = 'a Author' - authorB = MagicMock() - authorB.display_name = 'b Author' - songA = MagicMock() - songA.authors = [authorA, authorB] - songA.title = 'Test Title' - songB = MagicMock() - songB.authors = [authorB, authorA] - songB.title = 'Test Title' + parent = MagicMock() + parent.stop_export_flag = False + # mocked_application_object = MagicMock() + # Registry().register('application', mocked_application_object) + ol_export = OpenLyricsExport(parent, [songA, songB], temp_folder) - parent = MagicMock() - parent.stop_export_flag = False - mocked_application_object = MagicMock() - Registry().register('application', mocked_application_object) - ol_export = OpenLyricsExport(parent, [songA, songB], self.temp_folder) + # WHEN: Doing the export + ol_export.do_export() - # WHEN: Doing the export - ol_export.do_export() + # THEN: The exporter orders authers + assert (temp_folder / '{title} ({display_name}).xml'.format( + title=songA.title, + display_name=", ".join([authorA.display_name, authorB.display_name]) + )).exists() is True + assert (temp_folder / '{title} ({display_name})-1.xml'.format( + title=songB.title, + display_name=", ".join([authorA.display_name, authorB.display_name]) + )).exists() is True - # THEN: The exporter orders authers - assert (self.temp_folder / '{title} ({display_name}).xml'.format( - title=song.title, - display_name=", ".join([authorA.display_name, authorB.display_name]) - )).exists() is True - assert (self.temp_folder / '{title} ({display_name})-1.xml'.format( - title=song.title, - display_name=", ".join([authorA.display_name, authorB.display_name]) - )).exists() is True + +def test_export_is_stopped(registry, settings, temp_folder): + """ + Test that the exporter stops when the flag is set + """ + # GIVEN: A mocked song_to_xml, a mocked song, a mocked application and an OpenLyricsExport instance + with patch('openlp.plugins.songs.lib.openlyricsexport.OpenLyrics.song_to_xml') as mocked_song_to_xml: + mocked_song_to_xml.return_value = '\n' + author = MagicMock() + author.display_name = 'Test Author' + song = MagicMock() + song.authors = [author] + song.title = 'Test Title' + parent = MagicMock() + parent.stop_export_flag = True + # mocked_application_object = MagicMock() + # Registry().register('application', mocked_application_object) + ol_export = OpenLyricsExport(parent, [song, song], temp_folder) + + # WHEN: Doing the export + ol_export.do_export() + + # THEN: The exporter should not have created any files + assert (temp_folder / '{title} ({display_name}).xml'.format( + title=song.title, display_name=author.display_name)).exists() is False diff --git a/tests/functional/openlp_plugins/songs/test_plugin.py b/tests/functional/openlp_plugins/songs/test_plugin.py new file mode 100644 index 000000000..b66814577 --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_plugin.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +########################################################################## +""" +This module contains tests for the plugin class Song plugin. +""" +from openlp.plugins.songs.songsplugin import SongsPlugin + + +def test_plugin_about(): + result = SongsPlugin.about() + + assert result == ( + 'Songs Plugin' + '
The songs plugin provides the ability to display and manage songs.' + ) diff --git a/tests/interfaces/openlp_plugins/planningcenter/test_planningcenterplugin.py b/tests/interfaces/openlp_plugins/planningcenter/test_planningcenterplugin.py index b4bd3a55d..441575590 100644 --- a/tests/interfaces/openlp_plugins/planningcenter/test_planningcenterplugin.py +++ b/tests/interfaces/openlp_plugins/planningcenter/test_planningcenterplugin.py @@ -22,7 +22,7 @@ Package to test the openlp.plugins.planningcenter.planningcenterplugin package. """ from unittest import TestCase -from unittest.mock import patch +from unittest.mock import MagicMock, patch from PyQt5 import QtWidgets @@ -152,3 +152,16 @@ class TestPlanningCenterPlugin(TestCase, TestMixin): return_value = self.plugin.about() # THEN: self.assertGreater(len(return_value), 0, "About function returned some text") + + def test_finalise(self): + """ + Test that the finalise function cleans up after the plugin + """ + # GIVEN: A PlanningcenterPlugin Class with a bunch of mocks + self.plugin.import_planning_center = MagicMock() + + # WHEN: finalise has been called on the class + self.plugin.finalise() + + # THEN: it cleans up after itself + self.plugin.import_planning_center.setVisible.assert_called_once_with(False)