This commit is contained in:
Tim Bentley 2013-12-06 19:00:55 +00:00
commit d0a9423982
26 changed files with 863 additions and 165 deletions

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
############################################################################### ###############################################################################
# OpenLP - Open Source Lyrics Projection # # OpenLP - Open Source Lyrics Projection #

View File

@ -184,7 +184,8 @@ class OpenLP(QtGui.QApplication):
``traceback`` ``traceback``
A traceback object with the details of where the exception occurred. A traceback object with the details of where the exception occurred.
""" """
log.exception(''.join(format_exception(exctype, value, traceback))) # We can't log.exception here because the last exception no longer exists, we're actually busy handling it.
log.critical(''.join(format_exception(exctype, value, traceback)))
if not hasattr(self, 'exception_form'): if not hasattr(self, 'exception_form'):
self.exception_form = ExceptionForm() self.exception_form = ExceptionForm()
self.exception_form.exception_text_edit.setPlainText(''.join(format_exception(exctype, value, traceback))) self.exception_form.exception_text_edit.setPlainText(''.join(format_exception(exctype, value, traceback)))

View File

@ -83,6 +83,8 @@ class UiStrings(object):
self.Error = translate('OpenLP.Ui', 'Error') self.Error = translate('OpenLP.Ui', 'Error')
self.Export = translate('OpenLP.Ui', 'Export') self.Export = translate('OpenLP.Ui', 'Export')
self.File = translate('OpenLP.Ui', 'File') self.File = translate('OpenLP.Ui', 'File')
self.FileNotFound = translate('OpenLP.Ui', 'File Not Found')
self.FileNotFoundMessage = translate('OpenLP.Ui', 'File %s not found.\nPlease try selecting it individually.')
self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit') self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit')
self.Help = translate('OpenLP.Ui', 'Help') self.Help = translate('OpenLP.Ui', 'Help')
self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours') self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours')

View File

@ -330,6 +330,7 @@ def create_separated_list(string_list):
from .registry import Registry from .registry import Registry
from .filedialog import FileDialog
from .screen import ScreenList from .screen import ScreenList
from .listwidgetwithdnd import ListWidgetWithDnD from .listwidgetwithdnd import ListWidgetWithDnD
from .treewidgetwithdnd import TreeWidgetWithDnD from .treewidgetwithdnd import TreeWidgetWithDnD
@ -345,4 +346,3 @@ from .dockwidget import OpenLPDockWidget
from .imagemanager import ImageManager from .imagemanager import ImageManager
from .renderer import Renderer from .renderer import Renderer
from .mediamanageritem import MediaManagerItem from .mediamanageritem import MediaManagerItem

View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Provide a work around for a bug in QFileDialog <https://bugs.launchpad.net/openlp/+bug/1209515>
"""
import logging
import os
from urllib import parse
from PyQt4 import QtGui
from openlp.core.common import UiStrings
log = logging.getLogger(__name__)
class FileDialog(QtGui.QFileDialog):
"""
Subclass QFileDialog to work round a bug
"""
@staticmethod
def getOpenFileNames(parent, *args, **kwargs):
"""
Reimplement getOpenFileNames to fix the way it returns some file names that url encoded when selecting multiple
files
"""
files = QtGui.QFileDialog.getOpenFileNames(parent, *args, **kwargs)
file_list = []
for file in files:
if not os.path.exists(file):
log.info('File %s not found. Attempting to unquote.')
file = parse.unquote(file)
if not os.path.exists(file):
log.error('File %s not found.' % file)
QtGui.QMessageBox.information(parent, UiStrings().FileNotFound,
UiStrings().FileNotFoundMessage % file)
continue
log.info('File %s found.')
file_list.append(file)
return file_list

View File

@ -36,7 +36,7 @@ import re
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.common import Settings, UiStrings, translate from openlp.core.common import Settings, UiStrings, translate
from openlp.core.lib import OpenLPToolbar, ServiceItem, StringContent, ListWidgetWithDnD, \ from openlp.core.lib import FileDialog, OpenLPToolbar, ServiceItem, StringContent, ListWidgetWithDnD, \
ServiceItemContext, Registry ServiceItemContext, Registry
from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.searchedit import SearchEdit
from openlp.core.lib.ui import create_widget_action, critical_error_message_box from openlp.core.lib.ui import create_widget_action, critical_error_message_box
@ -319,7 +319,7 @@ class MediaManagerItem(QtGui.QWidget):
""" """
Add a file to the list widget to make it available for showing Add a file to the list widget to make it available for showing
""" """
files = QtGui.QFileDialog.getOpenFileNames(self, self.on_new_prompt, files = FileDialog.getOpenFileNames(self, self.on_new_prompt,
Settings().value(self.settings_section + '/last directory'), self.on_new_file_masks) Settings().value(self.settings_section + '/last directory'), self.on_new_file_masks)
log.info('New files(s) %s', files) log.info('New files(s) %s', files)
if files: if files:

View File

@ -283,7 +283,7 @@ class AdvancedTab(SettingsTab):
self.service_name_day.setItemText(0, translate('OpenLP.AdvancedTab', 'Monday')) self.service_name_day.setItemText(0, translate('OpenLP.AdvancedTab', 'Monday'))
self.service_name_day.setItemText(1, translate('OpenLP.AdvancedTab', 'Tuesday')) self.service_name_day.setItemText(1, translate('OpenLP.AdvancedTab', 'Tuesday'))
self.service_name_day.setItemText(2, translate('OpenLP.AdvancedTab', 'Wednesday')) self.service_name_day.setItemText(2, translate('OpenLP.AdvancedTab', 'Wednesday'))
self.service_name_day.setItemText(3, translate('OpenLP.AdvancedTab', 'Thurdsday')) self.service_name_day.setItemText(3, translate('OpenLP.AdvancedTab', 'Thursday'))
self.service_name_day.setItemText(4, translate('OpenLP.AdvancedTab', 'Friday')) self.service_name_day.setItemText(4, translate('OpenLP.AdvancedTab', 'Friday'))
self.service_name_day.setItemText(5, translate('OpenLP.AdvancedTab', 'Saturday')) self.service_name_day.setItemText(5, translate('OpenLP.AdvancedTab', 'Saturday'))
self.service_name_day.setItemText(6, translate('OpenLP.AdvancedTab', 'Sunday')) self.service_name_day.setItemText(6, translate('OpenLP.AdvancedTab', 'Sunday'))

View File

@ -101,7 +101,7 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
""" """
Constructor. Constructor.
""" """
super(ExceptionForm, self).__init__(self.main_window) super(ExceptionForm, self).__init__()
self.setupUi(self) self.setupUi(self)
self.settings_section = 'crashreport' self.settings_section = 'crashreport'

View File

@ -38,7 +38,7 @@ from xml.etree.ElementTree import ElementTree, XML
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.common import AppLocation, Settings, check_directory_exists, UiStrings, translate from openlp.core.common import AppLocation, Settings, check_directory_exists, UiStrings, translate
from openlp.core.lib import ImageSource, OpenLPToolbar, Registry, get_text_file_string, build_icon, \ from openlp.core.lib import FileDialog, ImageSource, OpenLPToolbar, Registry, get_text_file_string, build_icon, \
check_item_selected, create_thumb, validate_thumb check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.theme import ThemeXML, BackgroundType from openlp.core.lib.theme import ThemeXML, BackgroundType
from openlp.core.lib.ui import critical_error_message_box, create_widget_action from openlp.core.lib.ui import critical_error_message_box, create_widget_action
@ -365,7 +365,7 @@ class ThemeManager(QtGui.QWidget, ThemeManagerHelper):
Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from
those files. This process will load both OpenLP version 1 and version 2 themes. those files. This process will load both OpenLP version 1 and version 2 themes.
""" """
files = QtGui.QFileDialog.getOpenFileNames(self, files = FileDialog.getOpenFileNames(self,
translate('OpenLP.ThemeManager', 'Select Theme Import File'), translate('OpenLP.ThemeManager', 'Select Theme Import File'),
Settings().value(self.settings_section + '/last directory import'), Settings().value(self.settings_section + '/last directory import'),
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.theme *.otz)')) translate('OpenLP.ThemeManager', 'OpenLP Themes (*.theme *.otz)'))

View File

@ -379,7 +379,7 @@ class BSExtract(object):
send_error_message('parse') send_error_message('parse')
return None return None
content = content.find_all('li') content = content.find_all('li')
return [book.contents[0].contents[0] for book in content] return [book.contents[0].contents[0] for book in content if len(book.contents[0].contents)]
def _get_application(self): def _get_application(self):
""" """

View File

@ -128,6 +128,15 @@ from openlp.core.common import AppLocation, Settings, translate
from openlp.core.lib import Registry, PluginStatus, StringContent, image_to_byte from openlp.core.lib import Registry, PluginStatus, StringContent, image_to_byte
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
FILE_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.ico': 'image/x-icon',
'.png': 'image/png'
}
class HttpRouter(object): class HttpRouter(object):
@ -346,30 +355,13 @@ class HttpRouter(object):
path = os.path.normpath(os.path.join(self.html_dir, file_name)) path = os.path.normpath(os.path.join(self.html_dir, file_name))
if not path.startswith(self.html_dir): if not path.startswith(self.html_dir):
return self.do_not_found() return self.do_not_found()
ext = os.path.splitext(file_name)[1] content = None
html = None ext, content_type = self.get_content_type(path)
if ext == '.html':
self.send_header('Content-type', 'text/html')
variables = self.template_vars
html = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables)
elif ext == '.css':
self.send_header('Content-type', 'text/css')
elif ext == '.js':
self.send_header('Content-type', 'application/javascript')
elif ext == '.jpg':
self.send_header('Content-type', 'image/jpeg')
elif ext == '.gif':
self.send_header('Content-type', 'image/gif')
elif ext == '.ico':
self.send_header('Content-type', 'image/x-icon')
elif ext == '.png':
self.send_header('Content-type', 'image/png')
else:
self.send_header('Content-type', 'text/plain')
file_handle = None file_handle = None
try: try:
if html: if ext == '.html':
content = html variables = self.template_vars
content = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables)
else: else:
file_handle = open(path, 'rb') file_handle = open(path, 'rb')
log.debug('Opened %s' % path) log.debug('Opened %s' % path)
@ -380,8 +372,22 @@ class HttpRouter(object):
finally: finally:
if file_handle: if file_handle:
file_handle.close() file_handle.close()
self.send_response(200)
self.send_header('Content-type', content_type)
self.end_headers()
return content return content
def get_content_type(self, file_name):
"""
Examines the extension of the file and determines
what the content_type should be, defaults to text/plain
Returns the extension and the content_type
"""
content_type = 'text/plain'
ext = os.path.splitext(file_name)[1]
content_type = FILE_TYPES.get(ext, 'text/plain')
return ext, content_type
def poll(self): def poll(self):
""" """
Poll OpenLP to determine the current slide number and item name. Poll OpenLP to determine the current slide number and item name.

View File

@ -39,7 +39,7 @@ import shutil
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.common import AppLocation, UiStrings, check_directory_exists, translate from openlp.core.common import AppLocation, UiStrings, check_directory_exists, translate
from openlp.core.lib import Registry, PluginStatus, MediaType, create_separated_list from openlp.core.lib import FileDialog, Registry, PluginStatus, MediaType, create_separated_list
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
from openlp.plugins.songs.lib import VerseType, clean_song from openlp.plugins.songs.lib import VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, Topic, MediaFile from openlp.plugins.songs.lib.db import Book, Song, Author, Topic, MediaFile
@ -758,7 +758,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
Loads file(s) from the filesystem. Loads file(s) from the filesystem.
""" """
filters = '%s (*)' % UiStrings().AllFiles filters = '%s (*)' % UiStrings().AllFiles
filenames = QtGui.QFileDialog.getOpenFileNames(self, filenames = FileDialog.getOpenFileNames(self,
translate('SongsPlugin.EditSongForm', 'Open File(s)'), '', filters) translate('SongsPlugin.EditSongForm', 'Open File(s)'), '', filters)
for filename in filenames: for filename in filenames:
item = QtGui.QListWidgetItem(os.path.split(str(filename))[1]) item = QtGui.QListWidgetItem(os.path.split(str(filename))[1])

View File

@ -37,7 +37,7 @@ from PyQt4 import QtCore, QtGui
from openlp.core.common import UiStrings, translate from openlp.core.common import UiStrings, translate
from openlp.core.common import Settings from openlp.core.common import Settings
from openlp.core.lib import Registry from openlp.core.lib import FileDialog, Registry
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
@ -246,7 +246,7 @@ class SongImportForm(OpenLPWizard):
if filters: if filters:
filters += ';;' filters += ';;'
filters += '%s (*)' % UiStrings().AllFiles filters += '%s (*)' % UiStrings().AllFiles
filenames = QtGui.QFileDialog.getOpenFileNames(self, title, filenames = FileDialog.getOpenFileNames(self, title,
Settings().value(self.plugin.settings_section + '/last directory import'), filters) Settings().value(self.plugin.settings_section + '/last directory import'), filters)
if filenames: if filenames:
listbox.addItems(filenames) listbox.addItems(filenames)

View File

@ -102,10 +102,10 @@ class SongBeamerImport(SongImport):
""" """
Receive a single file or a list of files to import. Receive a single file or a list of files to import.
""" """
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
if not isinstance(self.import_source, list): if not isinstance(self.import_source, list):
return return
for file in self.import_source: self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for import_file in self.import_source:
# TODO: check that it is a valid SongBeamer file # TODO: check that it is a valid SongBeamer file
if self.stop_import_flag: if self.stop_import_flag:
return return
@ -113,12 +113,13 @@ class SongBeamerImport(SongImport):
self.currentVerse = '' self.currentVerse = ''
self.currentVerseType = VerseType.tags[VerseType.Verse] self.currentVerseType = VerseType.tags[VerseType.Verse]
read_verses = False read_verses = False
file_name = os.path.split(file)[1] file_name = os.path.split(import_file)[1]
if os.path.isfile(file): if os.path.isfile(import_file):
detect_file = open(file, 'r') # First open in binary mode to detect the encoding
detect_file = open(import_file, 'rb')
details = chardet.detect(detect_file.read()) details = chardet.detect(detect_file.read())
detect_file.close() detect_file.close()
infile = codecs.open(file, 'r', details['encoding']) infile = codecs.open(import_file, 'r', details['encoding'])
song_data = infile.readlines() song_data = infile.readlines()
infile.close() infile.close()
else: else:
@ -149,7 +150,7 @@ class SongBeamerImport(SongImport):
self.replaceHtmlTags() self.replaceHtmlTags()
self.addVerse(self.currentVerse, self.currentVerseType) self.addVerse(self.currentVerse, self.currentVerseType)
if not self.finish(): if not self.finish():
self.logError(file) self.logError(import_file)
def replaceHtmlTags(self): def replaceHtmlTags(self):
""" """

View File

@ -0,0 +1,73 @@
"""
Package to test the openlp.core.lib.filedialog package.
"""
from unittest import TestCase
from openlp.core.common import UiStrings
from openlp.core.lib.filedialog import FileDialog
from tests.functional import MagicMock, patch
class TestFileDialog(TestCase):
"""
Test the functions in the :mod:`filedialog` module.
"""
def setUp(self):
self.os_patcher = patch('openlp.core.lib.filedialog.os')
self.qt_gui_patcher = patch('openlp.core.lib.filedialog.QtGui')
self.ui_strings_patcher = patch('openlp.core.lib.filedialog.UiStrings')
self.mocked_os = self.os_patcher.start()
self.mocked_qt_gui = self.qt_gui_patcher.start()
self.mocked_ui_strings = self.ui_strings_patcher.start()
self.mocked_parent = MagicMock()
def tearDown(self):
self.os_patcher.stop()
self.qt_gui_patcher.stop()
self.ui_strings_patcher.stop()
def get_open_file_names_canceled_test(self):
"""
Test that FileDialog.getOpenFileNames() returns and empty QStringList when QFileDialog is canceled
(returns an empty QStringList)
"""
self.mocked_os.reset()
# GIVEN: An empty QStringList as a return value from QFileDialog.getOpenFileNames
self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = []
# WHEN: FileDialog.getOpenFileNames is called
result = FileDialog.getOpenFileNames(self.mocked_parent)
# THEN: The returned value should be an empty QStringList and os.path.exists should not have been called
assert not self.mocked_os.path.exists.called
self.assertEqual(result, [],
'FileDialog.getOpenFileNames should return and empty list when QFileDialog.getOpenFileNames is canceled')
def returned_file_list_test(self):
"""
Test that FileDialog.getOpenFileNames handles a list of files properly when QFileList.getOpenFileNames
returns a good file name, a urlencoded file name and a non-existing file
"""
self.mocked_os.rest()
self.mocked_qt_gui.reset()
# GIVEN: A List of known values as a return value from QFileDialog.getOpenFileNames and a list of valid
# file names.
self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = [
'/Valid File', '/url%20encoded%20file%20%231', '/non-existing']
self.mocked_os.path.exists.side_effect = lambda file_name: file_name in [
'/Valid File', '/url encoded file #1']
# WHEN: FileDialog.getOpenFileNames is called
result = FileDialog.getOpenFileNames(self.mocked_parent)
# THEN: os.path.exists should have been called with known args. QmessageBox.information should have been
# called. The returned result should corrilate with the input.
self.mocked_os.path.exists.assert_callde_with('/Valid File')
self.mocked_os.path.exists.assert_callde_with('/url%20encoded%20file%20%231')
self.mocked_os.path.exists.assert_callde_with('/url encoded file #1')
self.mocked_os.path.exists.assert_callde_with('/non-existing')
self.mocked_os.path.exists.assert_callde_with('/non-existing')
self.mocked_qt_gui.QmessageBox.information.called_with(self.mocked_parent, UiStrings().FileNotFound,
UiStrings().FileNotFoundMessage % '/non-existing')
self.assertEqual(result, ['/Valid File', '/url encoded file #1'], 'The returned file list is incorrect')

View File

@ -3,13 +3,13 @@ Package to test the openlp.core.lib.htmlbuilder module.
""" """
from unittest import TestCase from unittest import TestCase
from mock import MagicMock, patch
from PyQt4 import QtCore from PyQt4 import QtCore
from openlp.core.lib.htmlbuilder import build_html, build_background_css, build_lyrics_css, build_lyrics_outline_css, \ from openlp.core.lib.htmlbuilder import build_html, build_background_css, build_lyrics_css, build_lyrics_outline_css, \
build_lyrics_format_css, build_footer_css build_lyrics_format_css, build_footer_css
from openlp.core.lib.theme import HorizontalType, VerticalType from openlp.core.lib.theme import HorizontalType, VerticalType
from tests.functional import MagicMock, patch
HTML = """ HTML = """
@ -216,9 +216,9 @@ class Htmbuilder(TestCase):
is_live = False is_live = False
background = None background = None
plugin = MagicMock() plugin = MagicMock()
plugin.get_display_css = MagicMock(return_value='plugin CSS') plugin.get_display_css.return_value = 'plugin CSS'
plugin.get_display_javascript = MagicMock(return_value='plugin JS') plugin.get_display_javascript.return_value = 'plugin JS'
plugin.get_display_html = MagicMock(return_value='plugin HTML') plugin.get_display_html.return_value = 'plugin HTML'
plugins = [plugin] plugins = [plugin]
# WHEN: Create the html. # WHEN: Create the html.

View File

@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
This module contains tests for the http module of the Bibles plugin.
"""
from unittest import TestCase
from bs4 import BeautifulSoup
from tests.functional import patch, MagicMock
from openlp.plugins.bibles.lib.http import BSExtract
#TODO: Items left to test
# BGExtract
# __init__
# _remove_elements
# _extract_verse
# _clean_soup
# _extract_verses
# _extract_verses_old
# get_bible_chapter
# get_books_from_http
# _get_application
# CWExtract
# __init__
# get_bible_chapter
# get_books_from_http
# _get_application
# HTTPBible
# __init__
# do_import
# get_verses
# get_chapter
# get_books
# get_chapter_count
# get_verse_count
# _get_application
# get_soup_for_bible_ref
# send_error_message
class TestBSExtract(TestCase):
"""
Test the BSExtractClass
"""
#TODO: Items left to test
# BSExtract
# __init__
# get_bible_chapter
# get_books_from_http
# _get_application
def setUp(self):
self.get_soup_for_bible_ref_patcher = patch('openlp.plugins.bibles.lib.http.get_soup_for_bible_ref')
self.log_patcher = patch('openlp.plugins.bibles.lib.http.log')
self.send_error_message_patcher = patch('openlp.plugins.bibles.lib.http.send_error_message')
self.socket_patcher = patch('openlp.plugins.bibles.lib.http.socket')
self.urllib_patcher = patch('openlp.plugins.bibles.lib.http.urllib')
self.mock_get_soup_for_bible_ref = self.get_soup_for_bible_ref_patcher.start()
self.mock_log = self.log_patcher.start()
self.mock_send_error_message = self.send_error_message_patcher.start()
self.mock_socket = self.socket_patcher.start()
self.mock_soup = MagicMock()
self.mock_urllib = self.urllib_patcher.start()
def tearDown(self):
self.get_soup_for_bible_ref_patcher.stop()
self.log_patcher.stop()
self.send_error_message_patcher.stop()
self.socket_patcher.stop()
self.urllib_patcher.stop()
def get_books_from_http_no_soup_test(self):
"""
Test the get_books_from_http method when get_soup_for_bible_ref returns a falsey value
"""
# GIVEN: An instance of BSExtract, and reset log, urllib & get_soup_for_bible_ref mocks
instance = BSExtract()
self.mock_log.debug.reset_mock()
self.mock_urllib.reset_mock()
self.mock_get_soup_for_bible_ref.reset_mock()
# WHEN: get_books_from_http is called with 'NIV' and get_soup_for_bible_ref returns a None value
self.mock_urllib.parse.quote.return_value = 'NIV'
self.mock_get_soup_for_bible_ref.return_value = None
result = instance.get_books_from_http('NIV')
# THEN: The rest mocks should be called with known values and get_books_from_http should return None
self.mock_log.debug.assert_called_once_with('BSExtract.get_books_from_http("%s")', 'NIV')
self.mock_urllib.parse.quote.assert_called_once_with(b'NIV')
self.mock_get_soup_for_bible_ref.assert_called_once_with(
'http://m.bibleserver.com/overlay/selectBook?translation=NIV')
self.assertIsNone(result,
'BSExtract.get_books_from_http should return None when get_soup_for_bible_ref returns a false value')
def get_books_from_http_no_content_test(self):
"""
Test the get_books_from_http method when the specified element cannot be found in the tag object returned from
get_soup_for_bible_ref
"""
# GIVEN: An instance of BSExtract, and reset log, urllib, get_soup_for_bible_ref & soup mocks
instance = BSExtract()
self.mock_log.reset_mock()
self.mock_urllib.reset_mock()
self.mock_get_soup_for_bible_ref.reset_mock()
self.mock_soup.reset_mock()
# WHEN: get_books_from_http is called with 'NIV', get_soup_for_bible_ref returns a mocked_soup object and
# mocked_soup.find returns None
self.mock_urllib.parse.quote.return_value = 'NIV'
self.mock_soup.find.return_value = None
self.mock_get_soup_for_bible_ref.return_value = self.mock_soup
result = instance.get_books_from_http('NIV')
# THEN: The rest mocks should be called with known values and get_books_from_http should return None
self.mock_log.debug.assert_called_once_with('BSExtract.get_books_from_http("%s")', 'NIV')
self.mock_urllib.parse.quote.assert_called_once_with(b'NIV')
self.mock_get_soup_for_bible_ref.assert_called_once_with(
'http://m.bibleserver.com/overlay/selectBook?translation=NIV')
self.mock_soup.find.assert_called_once_with('ul')
self.mock_log.error.assert_called_once_with('No books found in the Bibleserver response.')
self.mock_send_error_message.assert_called_once_with('parse')
self.assertIsNone(result,
'BSExtract.get_books_from_http should return None when get_soup_for_bible_ref returns a false value')
def get_books_from_http_content_test(self):
"""
Test the get_books_from_http method with sample HTML
Also a regression test for bug #1184869. (The anchor tag in the second list item is empty)
"""
# GIVEN: An instance of BSExtract, and reset log, urllib & get_soup_for_bible_ref mocks and sample HTML data
self.test_html = '<ul><li><a href="/overlay/selectChapter?tocBook=1">Genesis</a></li>' \
'<li><a href="/overlay/selectChapter?tocBook=2"></a></li>' \
'<li><a href="/overlay/selectChapter?tocBook=3">Leviticus</a></li></ul>'
self.test_soup = BeautifulSoup(self.test_html)
instance = BSExtract()
self.mock_log.reset_mock()
self.mock_urllib.reset_mock()
self.mock_get_soup_for_bible_ref.reset_mock()
self.mock_send_error_message.reset_mock()
# WHEN: get_books_from_http is called with 'NIV' and get_soup_for_bible_ref returns tag object based on the
# supplied test data.
self.mock_urllib.parse.quote.return_value = 'NIV'
self.mock_get_soup_for_bible_ref.return_value = self.test_soup
result = instance.get_books_from_http('NIV')
# THEN: The rest mocks should be called with known values and get_books_from_http should return the two books
# in the test data
self.mock_log.debug.assert_called_once_with('BSExtract.get_books_from_http("%s")', 'NIV')
self.mock_urllib.parse.quote.assert_called_once_with(b'NIV')
self.mock_get_soup_for_bible_ref.assert_called_once_with(
'http://m.bibleserver.com/overlay/selectBook?translation=NIV')
self.assertFalse(self.mock_log.error.called, 'log.error should not have been called')
self.assertFalse(self.mock_send_error_message.called, 'send_error_message should not have been called')
self.assertEquals(result, ['Genesis', 'Leviticus'])

View File

@ -62,7 +62,7 @@ class TestRemoteTab(TestCase):
""" """
Create the UI Create the UI
""" """
fd, self.ini_file = mkstemp('.ini') self.fd, self.ini_file = mkstemp('.ini')
Settings().set_filename(self.ini_file) Settings().set_filename(self.ini_file)
self.application = QtGui.QApplication.instance() self.application = QtGui.QApplication.instance()
Settings().extend_default_settings(__default_settings__) Settings().extend_default_settings(__default_settings__)
@ -76,6 +76,7 @@ class TestRemoteTab(TestCase):
del self.application del self.application
del self.parent del self.parent
del self.form del self.form
os.close(self.fd)
os.unlink(self.ini_file) os.unlink(self.ini_file)
def get_ip_address_default_test(self): def get_ip_address_default_test(self):

View File

@ -37,7 +37,8 @@ from PyQt4 import QtGui
from openlp.core.common import Settings from openlp.core.common import Settings
from openlp.plugins.remotes.lib.httpserver import HttpRouter from openlp.plugins.remotes.lib.httpserver import HttpRouter
from tests.functional import MagicMock from tests.functional import MagicMock, patch
from mock import mock_open
__default_settings__ = { __default_settings__ = {
'remotes/twelve hour': True, 'remotes/twelve hour': True,
@ -50,6 +51,7 @@ __default_settings__ = {
'remotes/ip address': '0.0.0.0' 'remotes/ip address': '0.0.0.0'
} }
TEST_PATH = os.path.abspath(os.path.dirname(__file__))
class TestRouter(TestCase): class TestRouter(TestCase):
""" """
@ -59,7 +61,7 @@ class TestRouter(TestCase):
""" """
Create the UI Create the UI
""" """
fd, self.ini_file = mkstemp('.ini') self.fd, self.ini_file = mkstemp('.ini')
Settings().set_filename(self.ini_file) Settings().set_filename(self.ini_file)
self.application = QtGui.QApplication.instance() self.application = QtGui.QApplication.instance()
Settings().extend_default_settings(__default_settings__) Settings().extend_default_settings(__default_settings__)
@ -70,6 +72,7 @@ class TestRouter(TestCase):
Delete all the C++ objects at the end so that we don't have a segfault Delete all the C++ objects at the end so that we don't have a segfault
""" """
del self.application del self.application
os.close(self.fd)
os.unlink(self.ini_file) os.unlink(self.ini_file)
def password_encrypter_test(self): def password_encrypter_test(self):
@ -109,4 +112,63 @@ class TestRouter(TestCase):
assert function['function'] == mocked_function, \ assert function['function'] == mocked_function, \
'The mocked function should match defined value.' 'The mocked function should match defined value.'
assert function['secure'] == False, \ assert function['secure'] == False, \
'The mocked function should not require any security.' 'The mocked function should not require any security.'
def get_content_type_test(self):
"""
Test the get_content_type logic
"""
# GIVEN: a set of files and their corresponding types
headers = [ ['test.html', 'text/html'], ['test.css', 'text/css'],
['test.js', 'application/javascript'], ['test.jpg', 'image/jpeg'],
['test.gif', 'image/gif'], ['test.ico', 'image/x-icon'],
['test.png', 'image/png'], ['test.whatever', 'text/plain'],
['test', 'text/plain'], ['', 'text/plain'],
[os.path.join(TEST_PATH,'test.html'), 'text/html']]
# WHEN: calling each file type
for header in headers:
ext, content_type = self.router.get_content_type(header[0])
# THEN: all types should match
self.assertEqual(content_type, header[1], 'Mismatch of content type')
def serve_file_without_params_test(self):
"""
Test the serve_file method without params
"""
# GIVEN: mocked environment
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
self.router.html_dir = os.path.normpath('test/dir')
self.router.template_vars = MagicMock()
# WHEN: call serve_file with no file_name
self.router.serve_file()
# THEN: it should return a 404
self.router.send_response.assert_called_once_with(404)
self.router.send_header.assert_called_once_with('Content-type','text/html')
self.assertEqual(self.router.end_headers.call_count, 1,
'end_headers called once')
def serve_file_with_valid_params_test(self):
"""
Test the serve_file method with an existing file
"""
# GIVEN: mocked environment
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
self.router.html_dir = os.path.normpath('test/dir')
self.router.template_vars = MagicMock()
with patch('openlp.core.lib.os.path.exists') as mocked_exists, \
patch('builtins.open', mock_open(read_data='123')):
mocked_exists.return_value = True
# WHEN: call serve_file with an existing html file
self.router.serve_file(os.path.normpath('test/dir/test.html'))
# THEN: it should return a 200 and the file
self.router.send_response.assert_called_once_with(200)
self.router.send_header.assert_called_once_with(
'Content-type','text/html')
self.assertEqual(self.router.end_headers.call_count, 1,
'end_headers called once')

View File

@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
This module contains tests for the Songbeamer song importer.
"""
import os
from unittest import TestCase
from tests.functional import MagicMock, patch
from openlp.plugins.songs.lib.songbeamerimport import SongBeamerImport
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', '..', '..', 'resources', 'songbeamersongs'))
SONG_TEST_DATA = {'Lobsinget dem Herrn.sng':
{'title': 'GL 1 - Lobsinget dem Herrn',
'verses':
[('1. Lobsinget dem Herrn,\no preiset Ihn gern!\n'
'Anbetung und Lob Ihm gebühret.\n', 'v'),
('2. Lobsingt Seiner Lieb´,\ndie einzig ihn trieb,\n'
'zu sterben für unsere Sünden!\n', 'v'),
('3. Lobsingt Seiner Macht!\nSein Werk ist vollbracht:\n'
'Er sitzet zur Rechten des Vaters.\n', 'v'),
('4. Lobsingt seiner Treu´,\ndie immerdar neu,\n'
'bis Er uns zur Herrlichket führet!\n\n', 'v')],
'song_book_name': 'Glaubenslieder I',
'song_number': "1"}
}
class TestSongBeamerImport(TestCase):
"""
Test the functions in the :mod:`songbeamerimport` module.
"""
def create_importer_test(self):
"""
Test creating an instance of the SongBeamer file importer
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.songbeamerimport.SongImport'):
mocked_manager = MagicMock()
# WHEN: An importer object is created
importer = SongBeamerImport(mocked_manager)
# THEN: The importer object should not be None
self.assertIsNotNone(importer, 'Import should not be none')
def invalid_import_source_test(self):
"""
Test SongBeamerImport.doImport handles different invalid import_source values
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.songbeamerimport.SongImport'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = SongBeamerImport(mocked_manager)
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True
# WHEN: Import source is not a list
for source in ['not a list', 0]:
importer.import_source = source
# THEN: doImport should return none and the progress bar maximum should not be set.
self.assertIsNone(importer.doImport(), 'doImport should return None when import_source is not a list')
self.assertEquals(mocked_import_wizard.progress_bar.setMaximum.called, False,
'setMaxium on import_wizard.progress_bar should not have been called')
def valid_import_source_test(self):
"""
Test SongBeamerImport.doImport handles different invalid import_source values
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch('openlp.plugins.songs.lib.songbeamerimport.SongImport'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = SongBeamerImport(mocked_manager)
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True
# WHEN: Import source is a list
importer.import_source = ['List', 'of', 'files']
# THEN: doImport should return none and the progress bar setMaximum should be called with the length of
# import_source.
self.assertIsNone(importer.doImport(),
'doImport should return None when import_source is a list and stop_import_flag is True')
mocked_import_wizard.progress_bar.setMaximum.assert_called_with(len(importer.import_source))
def file_import_test(self):
"""
Test the actual import of real song files and check that the imported data is correct.
"""
# GIVEN: Test files with a mocked out SongImport class, a mocked out "manager", a mocked out "import_wizard",
# and mocked out "author", "add_copyright", "add_verse", "finish" methods.
with patch('openlp.plugins.songs.lib.songbeamerimport.SongImport'):
for song_file in SONG_TEST_DATA:
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
mocked_add_verse = MagicMock()
mocked_finish = MagicMock()
mocked_finish.return_value = True
importer = SongBeamerImport(mocked_manager)
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = False
importer.addVerse = mocked_add_verse
importer.finish = mocked_finish
# WHEN: Importing each file
importer.import_source = [os.path.join(TEST_PATH, song_file)]
title = SONG_TEST_DATA[song_file]['title']
add_verse_calls = SONG_TEST_DATA[song_file]['verses']
song_book_name = SONG_TEST_DATA[song_file]['song_book_name']
song_number = SONG_TEST_DATA[song_file]['song_number']
# THEN: doImport should return none, the song data should be as expected, and finish should have been
# called.
self.assertIsNone(importer.doImport(), 'doImport should return None when it has completed')
self.assertEquals(importer.title, title, 'title for %s should be "%s"' % (song_file, title))
for verse_text, verse_tag in add_verse_calls:
mocked_add_verse.assert_any_call(verse_text, verse_tag)
if song_book_name:
self.assertEquals(importer.songBookName, song_book_name, 'songBookName for %s should be "%s"'
% (song_file, song_book_name))
if song_number:
self.assertEquals(importer.songNumber, song_number, 'songNumber for %s should be %s'
% (song_file, song_number))
mocked_finish.assert_called_with()

View File

@ -33,51 +33,25 @@ This module contains tests for the SongShow Plus song importer.
import os import os
from unittest import TestCase from unittest import TestCase
from tests.helpers.songfileimport import SongImportTestHelper
from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.songshowplusimport import SongShowPlusImport from openlp.plugins.songs.lib.songshowplusimport import SongShowPlusImport
from tests.functional import patch, MagicMock from tests.functional import patch, MagicMock
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../resources/songshowplussongs')) TEST_PATH = os.path.abspath(
SONG_TEST_DATA = {'Amazing Grace.sbsong': os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'songshowplussongs'))
{'title': 'Amazing Grace (Demonstration)',
'authors': ['John Newton', 'Edwin Excell', 'John P. Rees'], class TestSongShowPlusFileImport(SongImportTestHelper):
'copyright': 'Public Domain ', def __init__(self, *args, **kwargs):
'ccli_number': 22025, self.importer_class_name = 'SongShowPlusImport'
'verses': self.importer_module_name = 'songshowplusimport'
[('Amazing grace! How sweet the sound!\r\nThat saved a wretch like me!\r\n' super(TestSongShowPlusFileImport, self).__init__(*args, **kwargs)
'I once was lost, but now am found;\r\nWas blind, but now I see.', 'v1'),
('\'Twas grace that taught my heart to fear,\r\nAnd grace my fears relieved.\r\n' def test_song_import(self):
'How precious did that grace appear,\r\nThe hour I first believed.', 'v2'), test_import = self.file_import(os.path.join(TEST_PATH, 'Amazing Grace.sbsong'),
('The Lord has promised good to me,\r\nHis Word my hope secures.\r\n' self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
'He will my shield and portion be\r\nAs long as life endures.', 'v3'), test_import = self.file_import(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.sbsong'),
('Thro\' many dangers, toils and snares\r\nI have already come.\r\n' self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
'\'Tis grace that brought me safe thus far,\r\nAnd grace will lead me home.', 'v4'),
('When we\'ve been there ten thousand years,\r\nBright shining as the sun,\r\n'
'We\'ve no less days to sing God\'s praise,\r\nThan when we first begun.', 'v5')],
'topics': ['Assurance', 'Grace', 'Praise', 'Salvation'],
'comments': '\n\n\n',
'song_book_name': 'Demonstration Songs',
'song_number': 0,
'verse_order_list': []},
'Beautiful Garden Of Prayer.sbsong':
{'title': 'Beautiful Garden Of Prayer (Demonstration)',
'authors': ['Eleanor Allen Schroll', 'James H. Fillmore'],
'copyright': 'Public Domain ',
'ccli_number': 60252,
'verses':
[('There\'s a garden where Jesus is waiting,\r\nThere\'s a place that is wondrously fair.\r\n'
'For it glows with the light of His presence,\r\n\'Tis the beautiful garden of prayer.', 'v1'),
('There\'s a garden where Jesus is waiting,\r\nAnd I go with my burden and care.\r\n'
'Just to learn from His lips, words of comfort,\r\nIn the beautiful garden of prayer.', 'v2'),
('There\'s a garden where Jesus is waiting,\r\nAnd He bids you to come meet Him there,\r\n'
'Just to bow and receive a new blessing,\r\nIn the beautiful garden of prayer.', 'v3'),
('O the beautiful garden, the garden of prayer,\r\nO the beautiful garden of prayer.\r\n'
'There my Savior awaits, and He opens the gates\r\nTo the beautiful garden of prayer.', 'c1')],
'topics': ['Devotion', 'Prayer'],
'comments': '',
'song_book_name': '',
'song_number': 0,
'verse_order_list': []}}
class TestSongShowPlusImport(TestCase): class TestSongShowPlusImport(TestCase):
@ -117,7 +91,7 @@ class TestSongShowPlusImport(TestCase):
# THEN: doImport should return none and the progress bar maximum should not be set. # THEN: doImport should return none and the progress bar maximum should not be set.
self.assertIsNone(importer.doImport(), 'doImport should return None when import_source is not a list') self.assertIsNone(importer.doImport(), 'doImport should return None when import_source is not a list')
self.assertEquals(mocked_import_wizard.progress_bar.setMaximum.called, False, self.assertEquals(mocked_import_wizard.progress_bar.setMaximum.called, False,
'setMaxium on import_wizard.progress_bar should not have been called') 'setMaximum on import_wizard.progress_bar should not have been called')
def valid_import_source_test(self): def valid_import_source_test(self):
""" """
@ -194,70 +168,3 @@ class TestSongShowPlusImport(TestCase):
self.assertEquals(importer.to_openlp_verse_tag(original_tag, ignore_unique=True), openlp_tag, self.assertEquals(importer.to_openlp_verse_tag(original_tag, ignore_unique=True), openlp_tag,
'SongShowPlusImport.to_openlp_verse_tag should return "%s" when called with "%s"' 'SongShowPlusImport.to_openlp_verse_tag should return "%s" when called with "%s"'
% (openlp_tag, original_tag)) % (openlp_tag, original_tag))
def file_import_test(self):
"""
Test the actual import of real song files and check that the imported data is correct.
"""
# GIVEN: Test files with a mocked out SongImport class, a mocked out "manager", a mocked out "import_wizard",
# and mocked out "author", "add_copyright", "add_verse", "finish" methods.
with patch('openlp.plugins.songs.lib.songshowplusimport.SongImport'):
for song_file in SONG_TEST_DATA:
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
mocked_parse_author = MagicMock()
mocked_add_copyright = MagicMock()
mocked_add_verse = MagicMock()
mocked_finish = MagicMock()
mocked_finish.return_value = True
importer = SongShowPlusImport(mocked_manager)
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = False
importer.parse_author = mocked_parse_author
importer.addCopyright = mocked_add_copyright
importer.addVerse = mocked_add_verse
importer.finish = mocked_finish
importer.topics = []
# WHEN: Importing each file
importer.import_source = [os.path.join(TEST_PATH, song_file)]
title = SONG_TEST_DATA[song_file]['title']
author_calls = SONG_TEST_DATA[song_file]['authors']
song_copyright = SONG_TEST_DATA[song_file]['copyright']
ccli_number = SONG_TEST_DATA[song_file]['ccli_number']
add_verse_calls = SONG_TEST_DATA[song_file]['verses']
topics = SONG_TEST_DATA[song_file]['topics']
comments = SONG_TEST_DATA[song_file]['comments']
song_book_name = SONG_TEST_DATA[song_file]['song_book_name']
song_number = SONG_TEST_DATA[song_file]['song_number']
verse_order_list = SONG_TEST_DATA[song_file]['verse_order_list']
# THEN: doImport should return none, the song data should be as expected, and finish should have been
# called.
self.assertIsNone(importer.doImport(), 'doImport should return None when it has completed')
self.assertEquals(importer.title, title, 'title for %s should be "%s"' % (song_file, title))
for author in author_calls:
mocked_parse_author.assert_any_call(author)
if song_copyright:
mocked_add_copyright.assert_called_with(song_copyright)
if ccli_number:
self.assertEquals(importer.ccliNumber, ccli_number, 'ccliNumber for %s should be %s'
% (song_file, ccli_number))
for verse_text, verse_tag in add_verse_calls:
mocked_add_verse.assert_any_call(verse_text, verse_tag)
if topics:
self.assertEquals(importer.topics, topics, 'topics for %s should be %s' % (song_file, topics))
if comments:
self.assertEquals(importer.comments, comments, 'comments for %s should be "%s"'
% (song_file, comments))
if song_book_name:
self.assertEquals(importer.songBookName, song_book_name, 'songBookName for %s should be "%s"'
% (song_file, song_book_name))
if song_number:
self.assertEquals(importer.songNumber, song_number, 'songNumber for %s should be %s'
% (song_file, song_number))
if verse_order_list:
self.assertEquals(importer.verseOrderList, [], 'verseOrderList for %s should be %s'
% (song_file, verse_order_list))
mocked_finish.assert_called_with()

View File

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`songfileimporthelper` modules provides a helper class and methods to easily enable testing the import of
song files from third party applications.
"""
import json
from unittest import TestCase
from tests.functional import patch, MagicMock
class SongImportTestHelper(TestCase):
"""
This class is designed to be a helper class to reduce repition when testing the import of song files.
"""
def __init__(self, *args, **kwargs):
super(SongImportTestHelper, self).__init__(*args, **kwargs)
self.importer_module = __import__(
'openlp.plugins.songs.lib.%s' % self.importer_module_name, fromlist=[self.importer_class_name])
self.importer_class = getattr(self.importer_module, self.importer_class_name)
def setUp(self):
"""
Patch and set up the mocks required.
"""
self.add_copyright_patcher = patch(
'openlp.plugins.songs.lib.%s.%s.addCopyright' % (self.importer_module_name, self.importer_class_name))
self.add_verse_patcher = patch(
'openlp.plugins.songs.lib.%s.%s.addVerse' % (self.importer_module_name, self.importer_class_name))
self.finish_patcher = patch(
'openlp.plugins.songs.lib.%s.%s.finish' % (self.importer_module_name, self.importer_class_name))
self.parse_author_patcher = patch(
'openlp.plugins.songs.lib.%s.%s.parse_author' % (self.importer_module_name, self.importer_class_name))
self.song_import_patcher = patch('openlp.plugins.songs.lib.%s.SongImport' % self.importer_module_name)
self.mocked_add_copyright = self.add_copyright_patcher.start()
self.mocked_add_verse = self.add_verse_patcher.start()
self.mocked_finish = self.finish_patcher.start()
self.mocked_parse_author = self.parse_author_patcher.start()
self.mocked_song_importer = self.song_import_patcher.start()
self.mocked_manager = MagicMock()
self.mocked_import_wizard = MagicMock()
self.mocked_finish.return_value = True
def tearDown(self):
"""
Clean up
"""
self.add_copyright_patcher.stop()
self.add_verse_patcher.stop()
self.finish_patcher.stop()
self.parse_author_patcher.stop()
self.song_import_patcher.stop()
def load_external_result_data(self, file_name):
"""
A method to load and return an object containing the song data from an external file.
"""
result_file = open(file_name, 'rb')
return json.loads(result_file.read().decode())
def file_import(self, source_file_name, result_data):
"""
Import the given file and check that it has imported correctly
"""
importer = self.importer_class(self.mocked_manager)
importer.import_wizard = self.mocked_import_wizard
importer.stop_import_flag = False
importer.topics = []
# WHEN: Importing the source file
importer.import_source = [source_file_name]
add_verse_calls = self._get_data(result_data, 'verses')
author_calls = self._get_data(result_data, 'authors')
ccli_number = self._get_data(result_data, 'ccli_number')
comments = self._get_data(result_data, 'comments')
song_book_name = self._get_data(result_data, 'song_book_name')
song_copyright = self._get_data(result_data, 'copyright')
song_number = self._get_data(result_data, 'song_number')
title = self._get_data(result_data, 'title')
topics = self._get_data(result_data, 'topics')
verse_order_list = self._get_data(result_data, 'verse_order_list')
# THEN: doImport should return none, the song data should be as expected, and finish should have been
# called.
self.assertIsNone(importer.doImport(), 'doImport should return None when it has completed')
self.assertEquals(importer.title, title, 'title for %s should be "%s"' % (source_file_name, title))
for author in author_calls:
self.mocked_parse_author.assert_any_call(author)
if song_copyright:
self.mocked_add_copyright.assert_called_with(song_copyright)
if ccli_number:
self.assertEquals(importer.ccliNumber, ccli_number, 'ccliNumber for %s should be %s'
% (source_file_name, ccli_number))
for verse_text, verse_tag in add_verse_calls:
self.mocked_add_verse.assert_any_call(verse_text, verse_tag)
if topics:
self.assertEquals(importer.topics, topics, 'topics for %s should be %s' % (source_file_name, topics))
if comments:
self.assertEquals(importer.comments, comments, 'comments for %s should be "%s"'
% (source_file_name, comments))
if song_book_name:
self.assertEquals(importer.songBookName, song_book_name, 'songBookName for %s should be "%s"'
% (source_file_name, song_book_name))
if song_number:
self.assertEquals(importer.songNumber, song_number, 'songNumber for %s should be %s'
% (source_file_name, song_number))
if verse_order_list:
self.assertEquals(importer.verseOrderList, [], 'verseOrderList for %s should be %s'
% (source_file_name, verse_order_list))
self.mocked_finish.assert_called_with()
def _get_data(self, data, key):
if key in data:
return data[key]
return ''

View File

@ -55,9 +55,9 @@ class TestEditSongForm(TestCase):
self.form.verse_list_widget.rowCount = MagicMock(return_value=2) self.form.verse_list_widget.rowCount = MagicMock(return_value=2)
# Mock out the verse. # Mock out the verse.
first_verse = MagicMock() first_verse = MagicMock()
first_verse.data = MagicMock(return_value='V1') first_verse.data.return_value = 'V1'
second_verse = MagicMock() second_verse = MagicMock()
second_verse.data = MagicMock(return_value= 'V2') second_verse.data.return_value = 'V2'
self.form.verse_list_widget.item = MagicMock(side_effect=[first_verse, second_verse]) self.form.verse_list_widget.item = MagicMock(side_effect=[first_verse, second_verse])
self.form._extract_verse_order = MagicMock(return_value=given_verse_order.split()) self.form._extract_verse_order = MagicMock(return_value=given_verse_order.split())
@ -76,9 +76,9 @@ class TestEditSongForm(TestCase):
self.form.verse_list_widget.rowCount = MagicMock(return_value=2) self.form.verse_list_widget.rowCount = MagicMock(return_value=2)
# Mock out the verse. # Mock out the verse.
first_verse = MagicMock() first_verse = MagicMock()
first_verse.data = MagicMock(return_value='V1') first_verse.data.return_value = 'V1'
second_verse = MagicMock() second_verse = MagicMock()
second_verse.data = MagicMock(return_value= 'V2') second_verse.data.return_value = 'V2'
self.form.verse_list_widget.item = MagicMock(side_effect=[first_verse, second_verse]) self.form.verse_list_widget.item = MagicMock(side_effect=[first_verse, second_verse])
self.form._extract_verse_order = MagicMock(return_value=[given_verse_order]) self.form._extract_verse_order = MagicMock(return_value=[given_verse_order])
@ -98,7 +98,7 @@ class TestEditSongForm(TestCase):
self.form.verse_list_widget.rowCount = MagicMock(return_value=1) self.form.verse_list_widget.rowCount = MagicMock(return_value=1)
# Mock out the verse. (We want a verse type to be returned). # Mock out the verse. (We want a verse type to be returned).
mocked_verse = MagicMock() mocked_verse = MagicMock()
mocked_verse.data = MagicMock(return_value='V1') mocked_verse.data.return_value = 'V1'
self.form.verse_list_widget.item = MagicMock(return_value=mocked_verse) self.form.verse_list_widget.item = MagicMock(return_value=mocked_verse)
self.form._extract_verse_order = MagicMock(return_value=[]) self.form._extract_verse_order = MagicMock(return_value=[])
self.form.verse_order_edit.text = MagicMock(return_value=given_verse_order) self.form.verse_order_edit.text = MagicMock(return_value=given_verse_order)

View File

@ -0,0 +1,25 @@
#LangCount=1
#Title=GL 1 - Lobsinget dem Herrn
#Editor=SongBeamer 4.20
#Version=3
#Format=F/K//
#TitleFormat=U
#ChurchSongID=0001
#Songbook=Glaubenslieder I / 1
---
1. Lobsinget dem Herrn,
o preiset Ihn gern!
Anbetung und Lob Ihm gebühret.
---
2. Lobsingt Seiner Lieb´,
die einzig ihn trieb,
zu sterben für unsere Sünden!
---
3. Lobsingt Seiner Macht!
Sein Werk ist vollbracht:
Er sitzet zur Rechten des Vaters.
---
4. Lobsingt seiner Treu´,
die immerdar neu,
bis Er uns zur Herrlichket führet!

View File

@ -0,0 +1,42 @@
{
"authors": [
"John Newton",
"Edwin Excell",
"John P. Rees"
],
"ccli_number": 22025,
"comments": "\n\n\n",
"copyright": "Public Domain ",
"song_book_name": "Demonstration Songs",
"song_number": 0,
"title": "Amazing Grace (Demonstration)",
"topics": [
"Assurance",
"Grace",
"Praise",
"Salvation"
],
"verse_order_list": [],
"verses": [
[
"Amazing grace! How sweet the sound!\r\nThat saved a wretch like me!\r\nI once was lost, but now am found;\r\nWas blind, but now I see.",
"v1"
],
[
"'Twas grace that taught my heart to fear,\r\nAnd grace my fears relieved.\r\nHow precious did that grace appear,\r\nThe hour I first believed.",
"v2"
],
[
"The Lord has promised good to me,\r\nHis Word my hope secures.\r\nHe will my shield and portion be\r\nAs long as life endures.",
"v3"
],
[
"Thro' many dangers, toils and snares\r\nI have already come.\r\n'Tis grace that brought me safe thus far,\r\nAnd grace will lead me home.",
"v4"
],
[
"When we've been there ten thousand years,\r\nBright shining as the sun,\r\nWe've no less days to sing God's praise,\r\nThan when we first begun.",
"v5"
]
]
}

View File

@ -0,0 +1,35 @@
{
"authors": [
"Eleanor Allen Schroll",
"James H. Fillmore"
],
"ccli_number": 60252,
"comments": "",
"copyright": "Public Domain ",
"song_book_name": "",
"song_number": 0,
"title": "Beautiful Garden Of Prayer (Demonstration)",
"topics": [
"Devotion",
"Prayer"
],
"verse_order_list": [],
"verses": [
[
"There's a garden where Jesus is waiting,\r\nThere's a place that is wondrously fair.\r\nFor it glows with the light of His presence,\r\n'Tis the beautiful garden of prayer.",
"v1"
],
[
"There's a garden where Jesus is waiting,\r\nAnd I go with my burden and care.\r\nJust to learn from His lips, words of comfort,\r\nIn the beautiful garden of prayer.",
"v2"
],
[
"There's a garden where Jesus is waiting,\r\nAnd He bids you to come meet Him there,\r\nJust to bow and receive a new blessing,\r\nIn the beautiful garden of prayer.",
"v3"
],
[
"O the beautiful garden, the garden of prayer,\r\nO the beautiful garden of prayer.\r\nThere my Savior awaits, and He opens the gates\r\nTo the beautiful garden of prayer.",
"c1"
]
]
}