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)