diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 023bd76fe..a3d9cec4b 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -30,6 +30,7 @@ The :mod:`lib` module contains most of the components and libraries that make OpenLP work. """ +from distutils.version import LooseVersion import logging import os @@ -366,23 +367,23 @@ def create_separated_list(stringlist): ``stringlist`` List of unicode strings """ - if Qt.PYQT_VERSION_STR >= u'4.9' and Qt.qVersion() >= u'4.8': + if LooseVersion(Qt.PYQT_VERSION_STR) >= LooseVersion(u'4.9') and \ + LooseVersion(Qt.qVersion()) >= LooseVersion(u'4.8'): return QtCore.QLocale().createSeparatedList(stringlist) if not stringlist: return u'' elif len(stringlist) == 1: return stringlist[0] elif len(stringlist) == 2: - return translate('OpenLP.core.lib', '%1 and %2', + return translate('OpenLP.core.lib', '%s and %s', 'Locale list separator: 2 items') % (stringlist[0], stringlist[1]) else: - merged = translate('OpenLP.core.lib', '%1, and %2', + merged = translate('OpenLP.core.lib', '%s, and %s', u'Locale list separator: end') % (stringlist[-2], stringlist[-1]) for index in reversed(range(1, len(stringlist) - 2)): - merged = translate('OpenLP.core.lib', '%1, %2', - u'Locale list separator: middle') % (stringlist[index], merged) - return translate('OpenLP.core.lib', '%1, %2', - u'Locale list separator: start') % (stringlist[0], merged) + merged = translate('OpenLP.core.lib', '%s, %s', + u'Locale list separator: middle') % (stringlist[index], merged) + return translate('OpenLP.core.lib', '%s, %s', u'Locale list separator: start') % (stringlist[0], merged) from registry import Registry diff --git a/openlp/core/lib/formattingtags.py b/openlp/core/lib/formattingtags.py index b7bbd7322..b2d8f6ea7 100644 --- a/openlp/core/lib/formattingtags.py +++ b/openlp/core/lib/formattingtags.py @@ -36,8 +36,7 @@ from openlp.core.lib import Settings, translate class FormattingTags(object): """ - Static Class to HTML Tags to be access around the code the list is managed - by the Options Tab. + Static Class to HTML Tags to be access around the code the list is managed by the Options Tab. """ html_expands = [] @@ -56,12 +55,11 @@ class FormattingTags(object): tags = [] for tag in FormattingTags.html_expands: if not tag[u'protected'] and not tag.get(u'temporary'): - # Using dict ensures that copy is made and encoding of values - # a little later does not affect tags in the original list + # Using dict ensures that copy is made and encoding of values a little later does not affect tags in + # the original list tags.append(dict(tag)) tag = tags[-1] - # Remove key 'temporary' from tags. - # It is not needed to be saved. + # Remove key 'temporary' from tags. It is not needed to be saved. if u'temporary' in tag: del tag[u'temporary'] for element in tag: @@ -73,15 +71,12 @@ class FormattingTags(object): @staticmethod def load_tags(): """ - Load the Tags from store so can be used in the system or used to - update the display. + Load the Tags from store so can be used in the system or used to update the display. """ - temporary_tags = [tag for tag in FormattingTags.html_expands - if tag.get(u'temporary')] + temporary_tags = [tag for tag in FormattingTags.html_expands if tag.get(u'temporary')] FormattingTags.html_expands = [] base_tags = [] # Append the base tags. - # Hex Color tags from http://www.w3schools.com/html/html_colornames.asp base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Red'), u'start tag': u'{r}', u'start html': u'', @@ -195,19 +190,17 @@ class FormattingTags(object): The end tag, e. g. ``{/r}`` * start html - The start html tag. For instance ```` + The start html tag. For instance ```` * end html The end html tag. For example ```` * protected - A boolean stating whether this is a build-in tag or not. Should be - ``True`` in most cases. + A boolean stating whether this is a build-in tag or not. Should be ``True`` in most cases. * temporary - A temporary tag will not be saved, but is also considered when - displaying text containing the tag. It has to be a ``boolean``. + A temporary tag will not be saved, but is also considered when displaying text containing the tag. It has + to be a ``boolean``. """ FormattingTags.html_expands.extend(tags) diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index a6fd2a848..06f0e36eb 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -32,6 +32,7 @@ Provide plugin management import os import sys import logging +import imp from openlp.core.lib import Plugin, PluginStatus, Registry @@ -55,55 +56,44 @@ class PluginManager(object): """ log.info(u'Plugin manager Initialising') Registry().register(u'plugin_manager', self) - if not plugin_dir in sys.path: - log.debug(u'Inserting %s into sys.path', plugin_dir) - sys.path.insert(0, plugin_dir) - self.basepath = os.path.abspath(plugin_dir) - log.debug(u'Base path %s ', self.basepath) + self.base_path = os.path.abspath(plugin_dir) + log.debug(u'Base path %s ', self.base_path) self.plugins = [] log.info(u'Plugin manager Initialised') - def find_plugins(self, plugin_dir): + def find_plugins(self): """ - Scan the directory ``plugin_dir`` for objects inheriting from the - ``Plugin`` class. - - ``plugin_dir`` - The directory to scan. - + Scan a directory for objects inheriting from the ``Plugin`` class. """ log.info(u'Finding plugins') - startdepth = len(os.path.abspath(plugin_dir).split(os.sep)) - log.debug(u'finding plugins in %s at depth %d', - unicode(plugin_dir), startdepth) - for root, dirs, files in os.walk(plugin_dir): - # TODO Presentation plugin is not yet working on Mac OS X. - # For now just ignore it. The following code will hide it - # in settings dialog. - if sys.platform == 'darwin': - present_plugin_dir = os.path.join(plugin_dir, 'presentations') - # Ignore files from the presentation plugin directory. - if root.startswith(present_plugin_dir): - continue + start_depth = len(os.path.abspath(self.base_path).split(os.sep)) + present_plugin_dir = os.path.join(self.base_path, 'presentations') + log.debug(u'finding plugins in %s at depth %d', unicode(self.base_path), start_depth) + for root, dirs, files in os.walk(self.base_path): + if sys.platform == 'darwin' and root.startswith(present_plugin_dir): + # TODO Presentation plugin is not yet working on Mac OS X. + # For now just ignore it. The following code will ignore files from the presentation plugin directory + # and thereby never import the plugin. + continue for name in files: if name.endswith(u'.py') and not name.startswith(u'__'): path = os.path.abspath(os.path.join(root, name)) - thisdepth = len(path.split(os.sep)) - if thisdepth - startdepth > 2: + this_depth = len(path.split(os.sep)) + if this_depth - start_depth > 2: # skip anything lower down break - modulename = os.path.splitext(path)[0] - prefix = os.path.commonprefix([self.basepath, path]) - # hack off the plugin base path - modulename = modulename[len(prefix) + 1:] - modulename = modulename.replace(os.path.sep, '.') + module_name = name[:-3] # import the modules - log.debug(u'Importing %s from %s. Depth %d', modulename, path, thisdepth) + log.debug(u'Importing %s from %s. Depth %d', module_name, root, this_depth) try: - __import__(modulename, globals(), locals(), []) + # Use the "imp" library to try to get around a problem with the PyUNO library which + # monkey-patches the __import__ function to do some magic. This causes issues with our tests. + # First, try to find the module we want to import, searching the directory in root + fp, path_name, description = imp.find_module(module_name, [root]) + # Then load the module (do the actual import) using the details from find_module() + imp.load_module(module_name, fp, path_name, description) except ImportError, e: - log.exception(u'Failed to import module %s on path %s for reason %s', - modulename, path, e.args[0]) + log.exception(u'Failed to import module %s on path %s: %s', module_name, path, e.args[0]) plugin_classes = Plugin.__subclasses__() plugin_objects = [] for p in plugin_classes: @@ -142,7 +132,8 @@ class PluginManager(object): for plugin in self.plugins: if plugin.status is not PluginStatus.Disabled: plugin.createSettingsTab(settings_form) - settings_form.plugins = self.plugins + if settings_form: + settings_form.plugins = self.plugins def hook_import_menu(self, import_menu): """ diff --git a/openlp/core/ui/filerenameform.py b/openlp/core/ui/filerenameform.py index 0743578a1..934a8dee8 100644 --- a/openlp/core/ui/filerenameform.py +++ b/openlp/core/ui/filerenameform.py @@ -34,18 +34,18 @@ from PyQt4 import QtGui from filerenamedialog import Ui_FileRenameDialog -from openlp.core.lib import translate +from openlp.core.lib import translate, Registry class FileRenameForm(QtGui.QDialog, Ui_FileRenameDialog): """ The file rename dialog """ - def __init__(self, parent): + def __init__(self): """ Constructor """ - QtGui.QDialog.__init__(self, parent) + QtGui.QDialog.__init__(self, self.main_window) self.setupUi(self) def exec_(self, copy=False): @@ -56,4 +56,15 @@ class FileRenameForm(QtGui.QDialog, Ui_FileRenameDialog): self.setWindowTitle(translate('OpenLP.FileRenameForm', 'File Copy')) else: self.setWindowTitle(translate('OpenLP.FileRenameForm', 'File Rename')) + self.fileNameEdit.setFocus() return QtGui.QDialog.exec_(self) + + def _get_main_window(self): + """ + Adds the main window to the class dynamically + """ + if not hasattr(self, u'_main_window'): + self._main_window = Registry().get(u'main_window') + return self._main_window + + main_window = property(_get_main_window) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index e86f068a8..695c4073a 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -487,8 +487,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.timer_id = 0 self.timer_version_id = 0 # Set up the path with plugins - plugin_path = AppLocation.get_directory(AppLocation.PluginsDir) - self.plugin_manager = PluginManager(plugin_path) + self.plugin_manager = PluginManager(AppLocation.get_directory(AppLocation.PluginsDir)) self.imageManager = ImageManager() # Set up the interface self.setupUi(self) @@ -541,7 +540,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): # Define the media Dock Manager self.mediaDockManager = MediaDockManager(self.mediaToolBox) log.info(u'Load Plugins') - self.plugin_manager.find_plugins(plugin_path) + self.plugin_manager.find_plugins() # hook methods have to happen after find_plugins. Find plugins needs # the controllers hence the hooks have moved from setupUI() to here # Find and insert settings tabs diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index c58a8aefd..3e5ce56d4 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -62,7 +62,7 @@ class ThemeManager(QtGui.QWidget): Registry().register(u'theme_manager', self) self.settingsSection = u'themes' self.themeForm = ThemeForm(self) - self.fileRenameForm = FileRenameForm(self) + self.fileRenameForm = FileRenameForm() # start with the layout self.layout = QtGui.QVBoxLayout(self) self.layout.setSpacing(0) diff --git a/openlp/plugins/alerts/forms/alertform.py b/openlp/plugins/alerts/forms/alertform.py index c09180702..aba13ddb9 100644 --- a/openlp/plugins/alerts/forms/alertform.py +++ b/openlp/plugins/alerts/forms/alertform.py @@ -199,7 +199,7 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): self.parameterEdit.setFocus() return False text = text.replace(u'<>', self.parameterEdit.text()) - self.plugin.alertsmanager.displayAlert(text) + self.plugin.alertsmanager.display_alert(text) return True def onCurrentRowChanged(self, row): diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index 03f36f09e..6db4f2d70 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -37,8 +37,10 @@ from PyQt4 import QtCore from openlp.core.lib import Registry, translate + log = logging.getLogger(__name__) + class AlertsManager(QtCore.QObject): """ AlertsManager manages the settings of Alerts. @@ -87,7 +89,7 @@ class AlertsManager(QtCore.QObject): return text = self.alert_list.pop(0) alertTab = self.parent().settingsTab - self.parent().liveController.display.alert(text, alertTab.location) + self.live_controller.display.alert(text, alertTab.location) # Check to see if we have a timer running. if self.timer_id == 0: self.timer_id = self.startTimer(int(alertTab.timeout) * 1000) @@ -103,11 +105,21 @@ class AlertsManager(QtCore.QObject): log.debug(u'timer event') if event.timerId() == self.timer_id: alertTab = self.parent().settingsTab - self.parent().liveController.display.alert(u'', alertTab.location) + self.live_controller.display.alert(u'', alertTab.location) self.killTimer(self.timer_id) self.timer_id = 0 self.generate_alert() + def _get_live_controller(self): + """ + Adds the live controller to the class dynamically + """ + if not hasattr(self, u'_live_controller'): + self._live_controller = Registry().get(u'live_controller') + return self._live_controller + + live_controller = property(_get_live_controller) + def _get_main_window(self): """ Adds the main window to the class dynamically @@ -116,4 +128,4 @@ class AlertsManager(QtCore.QObject): self._main_window = Registry().get(u'main_window') return self._main_window - main_window = property(_get_main_window) \ No newline at end of file + main_window = property(_get_main_window) diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index 7f7a56dc0..61cc29e01 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.py @@ -27,7 +27,7 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui import logging @@ -98,4 +98,14 @@ class ImagePlugin(Plugin): last part of saving the config. """ background = QtGui.QColor(Settings().value(self.settingsSection + u'/background color')) - self.liveController.imageManager.update_images_border(ImageSource.ImagePlugin, background) + self.image_manager.update_images_border(ImageSource.ImagePlugin, background) + + def _get_image_manager(self): + """ + Adds the image manager to the class dynamically + """ + if not hasattr(self, u'_image_manager'): + self._image_manager = Registry().get(u'image_manager') + return self._image_manager + + image_manager = property(_get_image_manager) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 5a8106f7d..d4c933d33 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -58,6 +58,7 @@ else: from PyQt4 import QtCore +from openlp.core.lib import ScreenList from openlp.core.utils import delete_file, get_uno_command, get_uno_instance from presentationcontroller import PresentationController, PresentationDocument @@ -254,7 +255,7 @@ class ImpressDocument(PresentationDocument): window = self.document.getCurrentController().getFrame().getContainerWindow() window.setVisible(False) self.presentation = self.document.getPresentation() - self.presentation.Display = self.controller.plugin.renderer.screens.current[u'number'] + 1 + self.presentation.Display = ScreenList().current[u'number'] + 1 self.control = None self.create_thumbnails() return True diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index d62465ee7..7a9f548ee 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -36,6 +36,7 @@ if os.name == u'nt': import win32ui import pywintypes +from openlp.core.lib import ScreenList from presentationcontroller import PresentationController, PresentationDocument log = logging.getLogger(__name__) @@ -252,8 +253,7 @@ class PowerpointDocument(PresentationDocument): dpi = win32ui.GetForegroundWindow().GetDC().GetDeviceCaps(88) except win32ui.error: dpi = 96 - renderer = self.controller.plugin.renderer - rect = renderer.screens.current[u'size'] + rect = ScreenList().current[u'size'] ppt_window = self.presentation.SlideShowSettings.Run() if not ppt_window: return diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index ec3adebe4..a2dc56f52 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -34,6 +34,7 @@ if os.name == u'nt': from ctypes import cdll from ctypes.wintypes import RECT +from openlp.core.lib import ScreenList from presentationcontroller import PresentationController, PresentationDocument log = logging.getLogger(__name__) @@ -83,7 +84,7 @@ class PptviewController(PresentationController): if self.process: return log.debug(u'start PPTView') - dllpath = os.path.join(self.plugin_manager.basepath, u'presentations', u'lib', u'pptviewlib', + dllpath = os.path.join(self.plugin_manager.base_path, u'presentations', u'lib', u'pptviewlib', u'pptviewlib.dll') self.process = cdll.LoadLibrary(dllpath) if log.isEnabledFor(logging.DEBUG): @@ -120,8 +121,7 @@ class PptviewDocument(PresentationDocument): PptView task started earlier. """ log.debug(u'LoadPresentation') - renderer = self.controller.plugin.renderer - rect = renderer.screens.current[u'size'] + rect = ScreenList().current[u'size'] rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom()) filepath = str(self.filepath.replace(u'/', u'\\')) if not os.path.isdir(self.get_temp_folder()): diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index 70df2bcd9..3b2c7439a 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -124,6 +124,7 @@ from PyQt4 import QtCore, QtNetwork from mako.template import Template from openlp.core.lib import Registry, Settings, PluginStatus, StringContent + from openlp.core.utils import AppLocation, translate log = logging.getLogger(__name__) @@ -250,12 +251,11 @@ class HttpConnection(object): def _get_service_items(self): service_items = [] - service_manager = self.parent.plugin.serviceManager if self.parent.current_item: current_unique_identifier = self.parent.current_item.unique_identifier else: current_unique_identifier = None - for item in service_manager.serviceItems: + for item in self.service_manager.serviceItems: service_item = item[u'service_item'] service_items.append({ u'id': unicode(service_item.unique_identifier), @@ -386,13 +386,13 @@ class HttpConnection(object): Poll OpenLP to determine the current slide number and item name. """ result = { - u'service': self.parent.plugin.serviceManager.service_id, + u'service': self.service_manager.service_id, u'slide': self.parent.current_slide or 0, u'item': self.parent.current_item.unique_identifier if self.parent.current_item else u'', u'twelve':Settings().value(u'remotes/twelve hour'), - u'blank': self.parent.plugin.liveController.blankScreen.isChecked(), - u'theme': self.parent.plugin.liveController.themeScreen.isChecked(), - u'display': self.parent.plugin.liveController.desktopScreen.isChecked() + u'blank': self.live_controller.blankScreen.isChecked(), + u'theme': self.live_controller.themeScreen.isChecked(), + u'display': self.live_controller.desktopScreen.isChecked() } return HttpResponse(json.dumps({u'results': result}), {u'Content-Type': u'application/json'}) @@ -412,7 +412,7 @@ class HttpConnection(object): """ Send an alert. """ - plugin = self.parent.plugin.pluginManager.get_plugin_by_name("alerts") + plugin = self.plugin_manager.get_plugin_by_name("alerts") if plugin.status == PluginStatus.Active: try: text = json.loads(self.url_params[u'data'][0])[u'request'][u'text'] @@ -504,7 +504,7 @@ class HttpConnection(object): """ if action == u'search': searches = [] - for plugin in self.parent.plugin.pluginManager.plugins: + for plugin in self.plugin_manager.plugins: if plugin.status == PluginStatus.Active and plugin.mediaItem and plugin.mediaItem.hasSearch: searches.append([plugin.name, unicode(plugin.textStrings[StringContent.Name][u'plural'])]) return HttpResponse( @@ -523,7 +523,7 @@ class HttpConnection(object): except KeyError, ValueError: return HttpResponse(code=u'400 Bad Request') text = urllib.unquote(text) - plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type) + plugin = self.plugin_manager.get_plugin_by_name(type) if plugin.status == PluginStatus.Active and plugin.mediaItem and plugin.mediaItem.hasSearch: results = plugin.mediaItem.search(text, False) else: @@ -539,7 +539,7 @@ class HttpConnection(object): id = json.loads(self.url_params[u'data'][0])[u'request'][u'id'] except KeyError, ValueError: return HttpResponse(code=u'400 Bad Request') - plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type) + plugin = self.plugin_manager.get_plugin_by_name(type) if plugin.status == PluginStatus.Active and plugin.mediaItem: plugin.mediaItem.goLive(id, remote=True) return HttpResponse(code=u'200 OK') @@ -552,7 +552,7 @@ class HttpConnection(object): id = json.loads(self.url_params[u'data'][0])[u'request'][u'id'] except KeyError, ValueError: return HttpResponse(code=u'400 Bad Request') - plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type) + plugin = self.plugin_manager.get_plugin_by_name(type) if plugin.status == PluginStatus.Active and plugin.mediaItem: item_id = plugin.mediaItem.createItemFromId(id) plugin.mediaItem.addToService(item_id, remote=True) @@ -583,3 +583,33 @@ class HttpConnection(object): self.socket.close() self.socket = None self.parent.close_connection(self) + + def _get_service_manager(self): + """ + Adds the service manager to the class dynamically + """ + if not hasattr(self, u'_service_manager'): + self._service_manager = Registry().get(u'service_manager') + return self._service_manager + + service_manager = property(_get_service_manager) + + def _get_live_controller(self): + """ + Adds the live controller to the class dynamically + """ + if not hasattr(self, u'_live_controller'): + self._live_controller = Registry().get(u'live_controller') + return self._live_controller + + live_controller = property(_get_live_controller) + + def _get_plugin_manager(self): + """ + Adds the plugin manager to the class dynamically + """ + if not hasattr(self, u'_plugin_manager'): + self._plugin_manager = Registry().get(u'plugin_manager') + return self._plugin_manager + + plugin_manager = property(_get_plugin_manager) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index e5421990f..f77f8d18e 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -116,6 +116,23 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.findVerseSplit = re.compile(u'---\[\]---\n', re.UNICODE) self.whitespace = re.compile(r'\W+', re.UNICODE) + def keyPressEvent(self, event): + """ + Reimplement the keyPressEvent to react on Return/Enter keys. When some combo boxes have focus we do not want + dialog's default action be triggered but instead our own. + + ``event`` + A QtGui.QKeyEvent event. + """ + if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): + if self.authorsComboBox.hasFocus() and self.authorsComboBox.currentText(): + self.onAuthorAddButtonClicked() + return + if self.topicsComboBox.hasFocus() and self.topicsComboBox.currentText(): + self.onTopicAddButtonClicked() + return + QtGui.QDialog.keyPressEvent(self, event) + def initialise(self): """ Set up the form for when it is displayed. diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index be15414b4..3485b8505 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -40,7 +40,13 @@ import os import sys from distutils.version import LooseVersion -is_win = sys.platform.startswith('win') +# If we try to import uno before nose this will greate a warning. Just try to import nose first to supress the warning. +try: + import nose +except ImportError: + pass + +IS_WIN = sys.platform.startswith('win') VERS = { 'Python': '2.6', @@ -48,7 +54,7 @@ VERS = { 'Qt4': '4.6', 'sqlalchemy': '0.5', # pyenchant 1.6 required on Windows - 'enchant': '1.6' if is_win else '1.3' + 'enchant': '1.6' if IS_WIN else '1.3' } # pywin32 @@ -84,7 +90,7 @@ OPTIONAL_MODULES = [ ('sqlite', ' (SQLite 2 support)'), ('MySQLdb', ' (MySQL support)'), ('psycopg2', ' (PostgreSQL support)'), - ('pytest', ' (testing framework)'), + ('nose', ' (testing framework)'), ] w = sys.stdout.write @@ -176,7 +182,7 @@ def main(): for m in OPTIONAL_MODULES: check_module(m[0], text=m[1]) - if is_win: + if IS_WIN: print('Checking for Windows specific modules...') for m in WIN32_MODULES: check_module(m) diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py new file mode 100644 index 000000000..0b5157b46 --- /dev/null +++ b/tests/functional/__init__.py @@ -0,0 +1,13 @@ +import sip +sip.setapi(u'QDate', 2) +sip.setapi(u'QDateTime', 2) +sip.setapi(u'QString', 2) +sip.setapi(u'QTextStream', 2) +sip.setapi(u'QTime', 2) +sip.setapi(u'QUrl', 2) +sip.setapi(u'QVariant', 2) + +from PyQt4 import QtGui + +# Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication. +application = QtGui.QApplication([]) diff --git a/tests/functional/openlp_core_lib/test_formattingtags.py b/tests/functional/openlp_core_lib/test_formattingtags.py new file mode 100644 index 000000000..335650112 --- /dev/null +++ b/tests/functional/openlp_core_lib/test_formattingtags.py @@ -0,0 +1,78 @@ +""" +Package to test the openlp.core.lib.formattingtags package. +""" +import copy +from unittest import TestCase + +from mock import patch + +from openlp.core.lib import FormattingTags + + +TAG = { + u'end tag': '{/aa}', + u'start html': '', + u'start tag': '{aa}', + u'protected': False, + u'end html': '', + u'desc': 'name' +} + + +class TestFormattingTags(TestCase): + + def tearDown(self): + """ + Clean up the FormattingTags class. + """ + FormattingTags.html_expands = [] + + def get_html_tags_no_user_tags_test(self): + """ + Test the FormattingTags class' get_html_tags static method. + """ + with patch(u'openlp.core.lib.translate') as mocked_translate, \ + patch(u'openlp.core.lib.settings') as mocked_settings, \ + patch(u'openlp.core.lib.formattingtags.cPickle') as mocked_cPickle: + # GIVEN: Our mocked modules and functions. + mocked_translate.side_effect = lambda module, string_to_translate, comment: string_to_translate + mocked_settings.value.return_value = u'' + mocked_cPickle.load.return_value = [] + + # WHEN: Get the display tags. + FormattingTags.load_tags() + old_tags_list = copy.deepcopy(FormattingTags.get_html_tags()) + FormattingTags.load_tags() + new_tags_list = FormattingTags.get_html_tags() + + # THEN: Lists should be identically. + assert old_tags_list == new_tags_list, u'The formatting tag lists should be identically.' + + def get_html_tags_with_user_tags_test(self): + """ + Test the FormattingTags class' get_html_tags static method in combination with user tags. + """ + with patch(u'openlp.core.lib.translate') as mocked_translate, \ + patch(u'openlp.core.lib.settings') as mocked_settings, \ + patch(u'openlp.core.lib.formattingtags.cPickle') as mocked_cPickle: + # GIVEN: Our mocked modules and functions. + mocked_translate.side_effect = lambda module, string_to_translate: string_to_translate + mocked_settings.value.return_value = u'' + mocked_cPickle.loads.side_effect = [[], [TAG]] + + # WHEN: Get the display tags. + FormattingTags.load_tags() + old_tags_list = copy.deepcopy(FormattingTags.get_html_tags()) + + # WHEN: Add our tag and get the tags again. + FormattingTags.load_tags() + FormattingTags.add_html_tags([TAG]) + new_tags_list = FormattingTags.get_html_tags() + + # THEN: Lists should not be identically. + assert old_tags_list != new_tags_list, u'The lists should be different.' + + # THEN: Added tag and last tag should be the same. + new_tag = new_tags_list.pop() + assert TAG == new_tag, u'Tags should be identically.' + diff --git a/tests/functional/openlp_core_lib/test_image_manager.py b/tests/functional/openlp_core_lib/test_image_manager.py new file mode 100644 index 000000000..3256ef86f --- /dev/null +++ b/tests/functional/openlp_core_lib/test_image_manager.py @@ -0,0 +1,50 @@ +""" + Package to test the openlp.core.ui package. +""" + +import os +from unittest import TestCase + +from PyQt4 import QtGui + +from openlp.core.lib import Registry, ImageManager, ScreenList + + +TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources')) + + +class TestImageManager(TestCase): + + def setUp(self): + """ + Create the UI + """ + Registry.create() + self.app = QtGui.QApplication.instance() + ScreenList.create(self.app.desktop()) + self.image_manager = ImageManager() + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + del self.app + + def basic_image_manager_test(self): + """ + Test the Image Manager setup basic functionality + """ + # GIVEN: the an image add to the image manager + self.image_manager.add_image(TEST_PATH, u'church.jpg', None) + + # WHEN the image is retrieved + image = self.image_manager.get_image(TEST_PATH, u'church.jpg') + + # THEN returned record is a type of image + self.assertEqual(isinstance(image, QtGui.QImage), True, u'The returned object should be a QImage') + + # WHEN the image is retrieved has not been loaded + # THEN a KeyError is thrown + with self.assertRaises(KeyError) as context: + self.image_manager.get_image(TEST_PATH, u'church1.jpg') + self.assertNotEquals(context.exception[0], u'', u'KeyError exception should have been thrown for missing image') diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py index 176197e24..8527373dc 100644 --- a/tests/functional/openlp_core_lib/test_lib.py +++ b/tests/functional/openlp_core_lib/test_lib.py @@ -7,7 +7,7 @@ from datetime import datetime, timedelta from mock import MagicMock, patch from openlp.core.lib import str_to_bool, translate, check_directory_exists, get_text_file_string, build_icon, \ - image_to_byte, check_item_selected, validate_thumb + image_to_byte, check_item_selected, validate_thumb, create_separated_list class TestLib(TestCase): @@ -308,14 +308,14 @@ class TestLib(TestCase): file_path = u'path/to/file' thumb_path = u'path/to/thumb' mocked_os.path.exists.return_value = False - + # WHEN: we run the validate_thumb() function result = validate_thumb(file_path, thumb_path) - + # THEN: we should have called a few functions, and the result should be False mocked_os.path.exists.assert_called_with(thumb_path) assert result is False, u'The result should be False' - + def validate_thumb_file_exists_and_newer_test(self): """ Test the validate_thumb() function when the thumbnail exists and has a newer timestamp than the file @@ -350,7 +350,7 @@ class TestLib(TestCase): thumb_mocked_stat.st_mtime = datetime.now() - timedelta(seconds=10) mocked_os.path.exists.return_value = True mocked_os.stat.side_effect = lambda fname: file_mocked_stat if fname == file_path else thumb_mocked_stat - + # WHEN: we run the validate_thumb() function result = validate_thumb(file_path, thumb_path) @@ -359,3 +359,90 @@ class TestLib(TestCase): mocked_os.stat.assert_any_call(file_path) mocked_os.stat.assert_any_call(thumb_path) assert result is False, u'The result should be False' + + def create_separated_list_qlocate_test(self): + """ + Test the create_separated_list function using the Qt provided method. + """ + with patch(u'openlp.core.lib.Qt') as mocked_qt, \ + patch(u'openlp.core.lib.QtCore.QLocale.createSeparatedList') as mocked_createSeparatedList: + # GIVEN: A list of strings and the mocked Qt module. + mocked_qt.PYQT_VERSION_STR = u'4.9' + mocked_qt.qVersion.return_value = u'4.8' + mocked_createSeparatedList.return_value = u'Author 1, Author 2, and Author 3' + string_list = [u'Author 1', u'Author 2', u'Author 3'] + + # WHEN: We get a string build from the entries it the list and a seperator. + string_result = create_separated_list(string_list) + + # THEN: We should have "Author 1, Author 2, and Author 3" + assert string_result == u'Author 1, Author 2, and Author 3', u'The string should be u\'Author 1, ' \ + 'Author 2, and Author 3\'.' + + def create_separated_list_empty_list_test(self): + """ + Test the create_separated_list function with an empty list. + """ + with patch(u'openlp.core.lib.Qt') as mocked_qt: + # GIVEN: An empty list and the mocked Qt module. + mocked_qt.PYQT_VERSION_STR = u'4.8' + mocked_qt.qVersion.return_value = u'4.7' + string_list = [] + + # WHEN: We get a string build from the entries it the list and a seperator. + string_result = create_separated_list(string_list) + + # THEN: We shoud have an emptry string. + assert string_result == u'', u'The string sould be empty.' + + def create_separated_list_with_one_item_test(self): + """ + Test the create_separated_list function with a list consisting of only one entry. + """ + with patch(u'openlp.core.lib.Qt') as mocked_qt: + # GIVEN: A list with a string and the mocked Qt module. + mocked_qt.PYQT_VERSION_STR = u'4.8' + mocked_qt.qVersion.return_value = u'4.7' + string_list = [u'Author 1'] + + # WHEN: We get a string build from the entries it the list and a seperator. + string_result = create_separated_list(string_list) + + # THEN: We should have "Author 1" + assert string_result == u'Author 1', u'The string should be u\'Author 1\'.' + + def create_separated_list_with_two_items_test(self): + """ + Test the create_separated_list function with a list of two entries. + """ + with patch(u'openlp.core.lib.Qt') as mocked_qt, patch(u'openlp.core.lib.translate') as mocked_translate: + # GIVEN: A list of strings and the mocked Qt module. + mocked_qt.PYQT_VERSION_STR = u'4.8' + mocked_qt.qVersion.return_value = u'4.7' + mocked_translate.return_value = u'%s and %s' + string_list = [u'Author 1', u'Author 2'] + + # WHEN: We get a string build from the entries it the list and a seperator. + string_result = create_separated_list(string_list) + + # THEN: We should have "Author 1 and Author 2" + assert string_result == u'Author 1 and Author 2', u'The string should be u\'Author 1 and Author 2\'.' + + def create_separated_list_with_three_items_test(self): + """ + Test the create_separated_list function with a list of three items. + """ + with patch(u'openlp.core.lib.Qt') as mocked_qt, patch(u'openlp.core.lib.translate') as mocked_translate: + # GIVEN: A list with a string and the mocked Qt module. + mocked_qt.PYQT_VERSION_STR = u'4.8' + mocked_qt.qVersion.return_value = u'4.7' + # Always return the untranslated string. + mocked_translate.side_effect = lambda module, string_to_translate, comment: string_to_translate + string_list = [u'Author 1', u'Author 2', u'Author 3'] + + # WHEN: We get a string build from the entries it the list and a seperator. + string_result = create_separated_list(string_list) + + # THEN: We should have "Author 1, Author 2, and Author 3" + assert string_result == u'Author 1, Author 2, and Author 3', u'The string should be u\'Author 1, ' \ + 'Author 2, and Author 3\'.' diff --git a/tests/functional/openlp_core_lib/test_pluginmanager.py b/tests/functional/openlp_core_lib/test_pluginmanager.py new file mode 100644 index 000000000..bad38d721 --- /dev/null +++ b/tests/functional/openlp_core_lib/test_pluginmanager.py @@ -0,0 +1,373 @@ +""" +Package to test the openlp.core.lib.pluginmanager package. +""" +from unittest import TestCase + +from mock import MagicMock + +from openlp.core.lib.pluginmanager import PluginManager +from openlp.core.lib import Registry, PluginStatus + + +class TestPluginManager(TestCase): + """ + Test the PluginManager class + """ + + def setUp(self): + """ + Some pre-test setup required. + """ + Registry.create() + Registry().register(u'service_list', MagicMock()) + + def hook_media_manager_with_disabled_plugin_test(self): + """ + Test running the hook_media_manager() method with a disabled plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Disabled + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_media_manager() + plugin_manager.hook_media_manager() + + # THEN: The createMediaManagerItem() method should have been called + assert mocked_plugin.createMediaManagerItem.call_count == 0, \ + u'The createMediaManagerItem() method should not have been called.' + + def hook_media_manager_with_active_plugin_test(self): + """ + Test running the hook_media_manager() method with an active plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Active + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_media_manager() + plugin_manager.hook_media_manager() + + # THEN: The createMediaManagerItem() method should have been called + mocked_plugin.createMediaManagerItem.assert_called_with() + + def hook_settings_tabs_with_disabled_plugin_and_no_form_test(self): + """ + Test running the hook_settings_tabs() method with a disabled plugin and no form + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Disabled + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_settings_tabs() + plugin_manager.hook_settings_tabs() + + # THEN: The createSettingsTab() method should have been called + assert mocked_plugin.createMediaManagerItem.call_count == 0, \ + u'The createMediaManagerItem() method should not have been called.' + + def hook_settings_tabs_with_disabled_plugin_and_mocked_form_test(self): + """ + Test running the hook_settings_tabs() method with a disabled plugin and a mocked form + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Disabled + mocked_settings_form = MagicMock() + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_settings_tabs() + plugin_manager.hook_settings_tabs(mocked_settings_form) + + # THEN: The createSettingsTab() method should not have been called, but the plugins lists should be the same + assert mocked_plugin.createSettingsTab.call_count == 0, \ + u'The createMediaManagerItem() method should not have been called.' + self.assertEqual(mocked_settings_form.plugins, plugin_manager.plugins, + u'The plugins on the settings form should be the same as the plugins in the plugin manager') + + def hook_settings_tabs_with_active_plugin_and_no_form_test(self): + """ + Test running the hook_settings_tabs() method with an active plugin and no settings form + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Active + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_settings_tabs() + plugin_manager.hook_settings_tabs() + + # THEN: The createSettingsTab() method should have been called + mocked_plugin.createSettingsTab.assert_called_with(None) + + def hook_settings_tabs_with_active_plugin_and_mocked_form_test(self): + """ + Test running the hook_settings_tabs() method with an active plugin and a mocked settings form + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Active + mocked_settings_form = MagicMock() + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_settings_tabs() + plugin_manager.hook_settings_tabs(mocked_settings_form) + + # THEN: The createMediaManagerItem() method should have been called with the mocked settings form + mocked_plugin.createSettingsTab.assert_called_with(mocked_settings_form) + self.assertEqual(mocked_settings_form.plugins, plugin_manager.plugins, + u'The plugins on the settings form should be the same as the plugins in the plugin manager') + + def hook_import_menu_with_disabled_plugin_test(self): + """ + Test running the hook_import_menu() method with a disabled plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Disabled + mocked_import_menu = MagicMock() + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_import_menu() + plugin_manager.hook_import_menu(mocked_import_menu) + + # THEN: The createMediaManagerItem() method should have been called + assert mocked_plugin.addImportMenuItem.call_count == 0, \ + u'The addImportMenuItem() method should not have been called.' + + def hook_import_menu_with_active_plugin_test(self): + """ + Test running the hook_import_menu() method with an active plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Active + mocked_import_menu = MagicMock() + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_import_menu() + plugin_manager.hook_import_menu(mocked_import_menu) + + # THEN: The addImportMenuItem() method should have been called + mocked_plugin.addImportMenuItem.assert_called_with(mocked_import_menu) + + def hook_export_menu_with_disabled_plugin_test(self): + """ + Test running the hook_export_menu() method with a disabled plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Disabled + mocked_export_menu = MagicMock() + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_export_menu() + plugin_manager.hook_export_menu(mocked_export_menu) + + # THEN: The addExportMenuItem() method should have been called + assert mocked_plugin.addExportMenuItem.call_count == 0, \ + u'The addExportMenuItem() method should not have been called.' + + def hook_export_menu_with_active_plugin_test(self): + """ + Test running the hook_export_menu() method with an active plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Active + mocked_export_menu = MagicMock() + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_export_menu() + plugin_manager.hook_export_menu(mocked_export_menu) + + # THEN: The addExportMenuItem() method should have been called + mocked_plugin.addExportMenuItem.assert_called_with(mocked_export_menu) + + def hook_tools_menu_with_disabled_plugin_test(self): + """ + Test running the hook_tools_menu() method with a disabled plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Disabled + mocked_tools_menu = MagicMock() + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_tools_menu() + plugin_manager.hook_tools_menu(mocked_tools_menu) + + # THEN: The addToolsMenuItem() method should have been called + assert mocked_plugin.addToolsMenuItem.call_count == 0, \ + u'The addToolsMenuItem() method should not have been called.' + + def hook_tools_menu_with_active_plugin_test(self): + """ + Test running the hook_tools_menu() method with an active plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Active + mocked_tools_menu = MagicMock() + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run hook_tools_menu() + plugin_manager.hook_tools_menu(mocked_tools_menu) + + # THEN: The addToolsMenuItem() method should have been called + mocked_plugin.addToolsMenuItem.assert_called_with(mocked_tools_menu) + + def initialise_plugins_with_disabled_plugin_test(self): + """ + Test running the initialise_plugins() method with a disabled plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Disabled + mocked_plugin.isActive.return_value = False + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run initialise_plugins() + plugin_manager.initialise_plugins() + + # THEN: The isActive() method should have been called, and initialise() method should NOT have been called + mocked_plugin.isActive.assert_called_with() + assert mocked_plugin.initialise.call_count == 0, u'The initialise() method should not have been called.' + + def initialise_plugins_with_active_plugin_test(self): + """ + Test running the initialise_plugins() method with an active plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Active + mocked_plugin.isActive.return_value = True + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run initialise_plugins() + plugin_manager.initialise_plugins() + + # THEN: The isActive() and initialise() methods should have been called + mocked_plugin.isActive.assert_called_with() + mocked_plugin.initialise.assert_called_with() + + def finalise_plugins_with_disabled_plugin_test(self): + """ + Test running the finalise_plugins() method with a disabled plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Disabled + mocked_plugin.isActive.return_value = False + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run finalise_plugins() + plugin_manager.finalise_plugins() + + # THEN: The isActive() method should have been called, and initialise() method should NOT have been called + mocked_plugin.isActive.assert_called_with() + assert mocked_plugin.finalise.call_count == 0, u'The finalise() method should not have been called.' + + def finalise_plugins_with_active_plugin_test(self): + """ + Test running the finalise_plugins() method with an active plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Active + mocked_plugin.isActive.return_value = True + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run finalise_plugins() + plugin_manager.finalise_plugins() + + # THEN: The isActive() and finalise() methods should have been called + mocked_plugin.isActive.assert_called_with() + mocked_plugin.finalise.assert_called_with() + + def get_plugin_by_name_does_not_exist_test(self): + """ + Test running the get_plugin_by_name() method to find a plugin that does not exist + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active + mocked_plugin = MagicMock() + mocked_plugin.name = 'Mocked Plugin' + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run finalise_plugins() + result = plugin_manager.get_plugin_by_name('Missing Plugin') + + # THEN: The isActive() and finalise() methods should have been called + self.assertIsNone(result, u'The result for get_plugin_by_name should be None') + + def get_plugin_by_name_exists_test(self): + """ + Test running the get_plugin_by_name() method to find a plugin that exists + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active + mocked_plugin = MagicMock() + mocked_plugin.name = 'Mocked Plugin' + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run finalise_plugins() + result = plugin_manager.get_plugin_by_name('Mocked Plugin') + + # THEN: The isActive() and finalise() methods should have been called + self.assertEqual(result, mocked_plugin, u'The result for get_plugin_by_name should be the mocked plugin') + + def new_service_created_with_disabled_plugin_test(self): + """ + Test running the new_service_created() method with a disabled plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Disabled + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Disabled + mocked_plugin.isActive.return_value = False + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run finalise_plugins() + plugin_manager.new_service_created() + + # THEN: The isActive() method should have been called, and initialise() method should NOT have been called + mocked_plugin.isActive.assert_called_with() + assert mocked_plugin.new_service_created.call_count == 0,\ + u'The new_service_created() method should not have been called.' + + def new_service_created_with_active_plugin_test(self): + """ + Test running the new_service_created() method with an active plugin + """ + # GIVEN: A PluginManager instance and a list with a mocked up plugin whose status is set to Active + mocked_plugin = MagicMock() + mocked_plugin.status = PluginStatus.Active + mocked_plugin.isActive.return_value = True + plugin_manager = PluginManager('') + plugin_manager.plugins = [mocked_plugin] + + # WHEN: We run new_service_created() + plugin_manager.new_service_created() + + # THEN: The isActive() and finalise() methods should have been called + mocked_plugin.isActive.assert_called_with() + mocked_plugin.new_service_created.assert_called_with() diff --git a/tests/functional/openlp_core_lib/test_registry.py b/tests/functional/openlp_core_lib/test_registry.py index a2fe60627..f272010c5 100644 --- a/tests/functional/openlp_core_lib/test_registry.py +++ b/tests/functional/openlp_core_lib/test_registry.py @@ -1,13 +1,16 @@ """ - Package to test the openlp.core.lib package. +Package to test the openlp.core.lib package. """ import os - from unittest import TestCase + from mock import MagicMock + from openlp.core.lib import Registry -TESTPATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources')) +TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources')) + + class TestRegistry(TestCase): diff --git a/tests/functional/openlp_core_lib/test_screen.py b/tests/functional/openlp_core_lib/test_screen.py new file mode 100644 index 000000000..92dd48172 --- /dev/null +++ b/tests/functional/openlp_core_lib/test_screen.py @@ -0,0 +1,55 @@ +""" +Package to test the openlp.core.lib.screenlist package. +""" +import copy +from unittest import TestCase + +from mock import MagicMock +from PyQt4 import QtGui, QtCore + +from openlp.core.lib import ScreenList + + +SCREEN = { + u'primary': False, + u'number': 1, + u'size': QtCore.QRect(0, 0, 1024, 768) +} + + +class TestScreenList(TestCase): + + def setUp(self): + """ + Set up the components need for all tests. + """ + self.application = QtGui.QApplication.instance() + self.screens = ScreenList.create(self.application.desktop()) + + def tearDown(self): + """ + Delete QApplication. + """ + del self.application + + def add_desktop_test(self): + """ + Test the ScreenList class' screen_count_changed method to check if new monitors are detected by OpenLP. + """ + # GIVEN: The screen list. + old_screens = copy.deepcopy(self.screens.screen_list) + # Mock the attributes. + self.screens.desktop.primaryScreen = MagicMock(return_value=SCREEN[u'primary']) + self.screens.desktop.screenCount = MagicMock(return_value=SCREEN[u'number'] + 1) + self.screens.desktop.screenGeometry = MagicMock(return_value=SCREEN[u'size']) + + # WHEN: Add a new screen. + self.screens.screen_count_changed(len(old_screens)) + + # THEN: The screen should have been added. + new_screens = self.screens.screen_list + assert len(old_screens) + 1 == len(new_screens), u'The new_screens list should be bigger.' + + # THEN: The screens should be identically. + assert SCREEN == new_screens.pop(), u'The new screen should be identically to the screen defined above.' + diff --git a/tests/functional/openlp_core_lib/test_serviceitem.py b/tests/functional/openlp_core_lib/test_serviceitem.py index a50752cce..b731eaf00 100644 --- a/tests/functional/openlp_core_lib/test_serviceitem.py +++ b/tests/functional/openlp_core_lib/test_serviceitem.py @@ -3,10 +3,9 @@ """ import os import cPickle - from unittest import TestCase - from mock import MagicMock + from openlp.core.lib import ServiceItem, Registry @@ -18,7 +17,6 @@ VERSE = u'The Lord said to {r}Noah{/r}: \n'\ '{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}'\ 'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n' FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456'] - TESTPATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources')) @@ -40,7 +38,7 @@ class TestServiceItem(TestCase): """ # GIVEN: A new service item - # WHEN:A service item is created (without a plugin) + # WHEN: A service item is created (without a plugin) service_item = ServiceItem(None) # THEN: We should get back a valid service item @@ -210,4 +208,5 @@ class TestServiceItem(TestCase): first_line = items[0] except: first_line = u'' - return first_line \ No newline at end of file + return first_line + diff --git a/tests/functional/openlp_core_lib/test_uistrings.py b/tests/functional/openlp_core_lib/test_uistrings.py new file mode 100644 index 000000000..3351657d1 --- /dev/null +++ b/tests/functional/openlp_core_lib/test_uistrings.py @@ -0,0 +1,22 @@ +""" +Package to test the openlp.core.lib.uistrings package. +""" + +from unittest import TestCase + +from openlp.core.lib import UiStrings + +class TestUiStrings(TestCase): + + def check_same_instance_test(self): + """ + Test the UiStrings class - we always should have only one instance of the UiStrings class. + """ + # WHEN: Create two instances of the UiStrings class. + first_instance = UiStrings() + second_instance = UiStrings() + + # THEN: Check if the instances are the same. + assert first_instance is second_instance, "They should be the same instance!" + + diff --git a/tests/interfaces/__init__.py b/tests/interfaces/__init__.py new file mode 100644 index 000000000..0b5157b46 --- /dev/null +++ b/tests/interfaces/__init__.py @@ -0,0 +1,13 @@ +import sip +sip.setapi(u'QDate', 2) +sip.setapi(u'QDateTime', 2) +sip.setapi(u'QString', 2) +sip.setapi(u'QTextStream', 2) +sip.setapi(u'QTime', 2) +sip.setapi(u'QUrl', 2) +sip.setapi(u'QVariant', 2) + +from PyQt4 import QtGui + +# Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication. +application = QtGui.QApplication([]) diff --git a/tests/interfaces/openlp_core_lib/__init__.py b/tests/interfaces/openlp_core_lib/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/interfaces/openlp_core_lib/test_pluginmanager.py b/tests/interfaces/openlp_core_lib/test_pluginmanager.py new file mode 100644 index 000000000..ca8ed3f07 --- /dev/null +++ b/tests/interfaces/openlp_core_lib/test_pluginmanager.py @@ -0,0 +1,61 @@ +""" +Package to test the openlp.core.lib.pluginmanager package. +""" +import os +import sys +from tempfile import mkstemp +from unittest import TestCase + +from mock import MagicMock, patch +from PyQt4 import QtGui + +from openlp.core.lib.pluginmanager import PluginManager +from openlp.core.lib import Registry, Settings + + +class TestPluginManager(TestCase): + """ + Test the PluginManager class + """ + + def setUp(self): + """ + Some pre-test setup required. + """ + fd, self.ini_file = mkstemp(u'.ini') + Settings().set_filename(self.ini_file) + Registry.create() + Registry().register(u'service_list', MagicMock()) + self.app = QtGui.QApplication.instance() + self.main_window = QtGui.QMainWindow() + Registry().register(u'main_window', self.main_window) + self.plugins_dir = os.path.abspath(os.path.join(os.path.basename(__file__), u'..', u'openlp', u'plugins')) + + def tearDown(self): + os.unlink(self.ini_file) + del self.app + + def find_plugins_test(self): + """ + Test the find_plugins() method to ensure it imports the correct plugins. + """ + # GIVEN: A plugin manager + plugin_manager = PluginManager(self.plugins_dir) + + # WHEN: We mock out sys.platform to make it return "darwin" and then find the plugins + old_platform = sys.platform + sys.platform = u'darwin' + plugin_manager.find_plugins() + sys.platform = old_platform + + # THEN: We should find the "Songs", "Bibles", etc in the plugins list + plugin_names = [plugin.name for plugin in plugin_manager.plugins] + assert u'songs' in plugin_names, u'There should be a "songs" plugin.' + assert u'bibles' in plugin_names, u'There should be a "bibles" plugin.' + assert u'presentations' not in plugin_names, u'There should NOT be a "presentations" plugin.' + assert u'images' in plugin_names, u'There should be a "images" plugin.' + assert u'media' in plugin_names, u'There should be a "media" plugin.' + assert u'custom' in plugin_names, u'There should be a "custom" plugin.' + assert u'songusage' in plugin_names, u'There should be a "songusage" plugin.' + assert u'alerts' in plugin_names, u'There should be a "alerts" plugin.' + assert u'remotes' in plugin_names, u'There should be a "remotes" plugin.' diff --git a/tests/interfaces/openlp_core_ui/__init__.py b/tests/interfaces/openlp_core_ui/__init__.py index e69de29bb..8b1378917 100644 --- a/tests/interfaces/openlp_core_ui/__init__.py +++ b/tests/interfaces/openlp_core_ui/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/interfaces/openlp_core_ui/test_filerenamedialog.py b/tests/interfaces/openlp_core_ui/test_filerenamedialog.py new file mode 100644 index 000000000..941b1485a --- /dev/null +++ b/tests/interfaces/openlp_core_ui/test_filerenamedialog.py @@ -0,0 +1,83 @@ +""" + Package to test the openlp.core.ui package. +""" +from unittest import TestCase + +from mock import MagicMock, patch +from openlp.core.lib import Registry +from openlp.core.ui import filerenameform +from PyQt4 import QtGui, QtTest + +class TestStartFileRenameForm(TestCase): + + def setUp(self): + """ + Create the UI + """ + registry = Registry.create() + self.app = QtGui.QApplication.instance() + self.main_window = QtGui.QMainWindow() + Registry().register(u'main_window', self.main_window) + self.form = filerenameform.FileRenameForm() + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + del self.form + del self.main_window + del self.app + + def window_title_test(self): + """ + Test the windowTitle of the FileRenameDialog + """ + # GIVEN: A mocked QDialog.exec_() method + with patch(u'PyQt4.QtGui.QDialog.exec_') as mocked_exec: + + # WHEN: The form is executed with no args + self.form.exec_() + + # THEN: the window title is set correctly + self.assertEqual(self.form.windowTitle(), u'File Rename', u'The window title should be "File Rename"') + + # WHEN: The form is executed with False arg + self.form.exec_(False) + + # THEN: the window title is set correctly + self.assertEqual(self.form.windowTitle(), u'File Rename', u'The window title should be "File Rename"') + + # WHEN: The form is executed with True arg + self.form.exec_(True) + + # THEN: the window title is set correctly + self.assertEqual(self.form.windowTitle(), u'File Copy', u'The window title should be "File Copy"') + + def line_edit_focus_test(self): + """ + Regression test for bug1067251 + Test that the fileNameEdit setFocus has called with True when executed + """ + # GIVEN: A mocked QDialog.exec_() method and mocked fileNameEdit.setFocus() method. + with patch(u'PyQt4.QtGui.QDialog.exec_') as mocked_exec: + mocked_set_focus = MagicMock() + self.form.fileNameEdit.setFocus = mocked_set_focus + + # WHEN: The form is executed + self.form.exec_() + + # THEN: the setFocus method of the fileNameEdit has been called with True + mocked_set_focus.assert_called_with() + + def file_name_validation_test(self): + """ + Test the fileNameEdit validation + """ + # GIVEN: QLineEdit with a validator set with illegal file name characters. + + # WHEN: 'Typing' a string containing invalid file characters. + QtTest.QTest.keyClicks(self.form.fileNameEdit, u'I/n\\v?a*l|i \F[i\l]e" :N+a%me') + + # THEN: The text in the QLineEdit should be the same as the input string with the invalid chatacters filtered + # out. + self.assertEqual(self.form.fileNameEdit.text(), u'Invalid File Name') diff --git a/tests/interfaces/openlp_core_ui/test_servicemanager.py b/tests/interfaces/openlp_core_ui/test_servicemanager.py new file mode 100644 index 000000000..e06edd4a6 --- /dev/null +++ b/tests/interfaces/openlp_core_ui/test_servicemanager.py @@ -0,0 +1,43 @@ +""" + Package to test the openlp.core.lib package. +""" + +from unittest import TestCase +from mock import MagicMock + +from PyQt4 import QtGui + +from openlp.core.lib import Registry, ScreenList +from openlp.core.ui.mainwindow import MainWindow + + +class TestStartNoteDialog(TestCase): + + def setUp(self): + """ + Create the UI + """ + Registry.create() + self.app = QtGui.QApplication.instance() + ScreenList.create(self.app.desktop()) + Registry().register(u'application', MagicMock()) + self.main_window = MainWindow() + self.service_manager = Registry().get(u'service_manager') + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + del self.main_window + del self.app + + def basic_service_manager_test(self): + """ + Test the Service Manager display functionality + """ + # GIVEN: A New Service Manager instance + + # WHEN I have an empty display + # THEN the count of items should be zero + self.assertEqual(self.service_manager.service_manager_list.topLevelItemCount(), 0, + u'The service manager list should be empty ') diff --git a/tests/interfaces/openlp_core_ui/test_servicenotedialog.py b/tests/interfaces/openlp_core_ui/test_servicenotedialog.py index f3694fdea..e04f9975d 100644 --- a/tests/interfaces/openlp_core_ui/test_servicenotedialog.py +++ b/tests/interfaces/openlp_core_ui/test_servicenotedialog.py @@ -2,11 +2,13 @@ Package to test the openlp.core.ui package. """ from unittest import TestCase - from mock import patch + +from PyQt4 import QtCore, QtGui, QtTest + from openlp.core.lib import Registry from openlp.core.ui import servicenoteform -from PyQt4 import QtCore, QtGui, QtTest + class TestStartNoteDialog(TestCase): @@ -15,7 +17,7 @@ class TestStartNoteDialog(TestCase): Create the UI """ Registry.create() - self.app = QtGui.QApplication([]) + self.app = QtGui.QApplication.instance() self.main_window = QtGui.QMainWindow() Registry().register(u'main_window', self.main_window) self.form = servicenoteform.ServiceNoteForm() @@ -64,4 +66,5 @@ class TestStartNoteDialog(TestCase): QtTest.QTest.mouseClick(okWidget, QtCore.Qt.LeftButton) # THEN the following text is returned - self.assertEqual(self.form.text_edit.toPlainText(), text, u'The new text should be returned') \ No newline at end of file + self.assertEqual(self.form.text_edit.toPlainText(), text, u'The new text should be returned') + diff --git a/tests/interfaces/openlp_core_ui/test_starttimedialog.py b/tests/interfaces/openlp_core_ui/test_starttimedialog.py index a9b33a30c..c09426a1f 100644 --- a/tests/interfaces/openlp_core_ui/test_starttimedialog.py +++ b/tests/interfaces/openlp_core_ui/test_starttimedialog.py @@ -2,11 +2,13 @@ Package to test the openlp.core.ui package. """ from unittest import TestCase - from mock import MagicMock, patch + +from PyQt4 import QtCore, QtGui, QtTest + from openlp.core.lib import Registry from openlp.core.ui import starttimeform -from PyQt4 import QtCore, QtGui, QtTest + class TestStartTimeDialog(TestCase): @@ -15,7 +17,7 @@ class TestStartTimeDialog(TestCase): Create the UI """ Registry.create() - self.app = QtGui.QApplication([]) + self.app = QtGui.QApplication.instance() self.main_window = QtGui.QMainWindow() Registry().register(u'main_window', self.main_window) self.form = starttimeform.StartTimeForm() @@ -91,4 +93,4 @@ class TestStartTimeDialog(TestCase): self.assertEqual(self.form.hourSpinBox.value(), 0) self.assertEqual(self.form.minuteSpinBox.value(), 2) self.assertEqual(self.form.secondSpinBox.value(), 3) - self.assertEqual(self.form.item[u'service_item'].start_time, 123, u'The start time should have changed') \ No newline at end of file + self.assertEqual(self.form.item[u'service_item'].start_time, 123, u'The start time should have changed')