Tests plugin 3 - and Fix #272

This commit is contained in:
Tim Bentley 2020-03-08 21:45:42 +00:00
parent b51829d4d6
commit d43811f38d
12 changed files with 1502 additions and 1572 deletions

View File

@ -189,8 +189,8 @@ class BackgroundPage(GridLayoutPage):
self.stream_label.setText('{text}:'.format(text=UiStrings().LiveStream))
self.image_path_edit.filters = \
'{name};;{text} (*)'.format(name=get_images_filter(), text=UiStrings().AllFiles)
visible_formats = '(*.{name})'.format(name='; *.'.join(VIDEO_EXT))
actual_formats = '(*.{name})'.format(name=' *.'.join(VIDEO_EXT))
visible_formats = '({name})'.format(name='; '.join(VIDEO_EXT))
actual_formats = '({name})'.format(name=' '.join(VIDEO_EXT))
video_filter = '{trans} {visible} {actual}'.format(trans=translate('OpenLP', 'Video Files'),
visible=visible_formats, actual=actual_formats)
self.video_path_edit.filters = '{video};;{ui} (*)'.format(video=video_filter, ui=UiStrings().AllFiles)

View File

@ -23,9 +23,10 @@ Functional tests to test the Mac LibreOffice class and related methods.
"""
import shutil
from tempfile import mkdtemp
from unittest import TestCase, SkipTest
from unittest import TestCase, skipIf, SkipTest
from unittest.mock import MagicMock, patch, call
from openlp.core.common.registry import Registry
from openlp.core.common import is_macosx
from openlp.core.common.path import Path
from openlp.plugins.presentations.lib.maclocontroller import MacLOController, MacLODocument
@ -41,6 +42,7 @@ if not is_macosx():
raise SkipTest('Not on macOS, skipping testing the Mac LibreOffice controller')
@skipIf(is_macosx(), 'Skip on macOS until we can figure out what the problem is or the tests are refactored')
class TestMacLOController(TestCase, TestMixin):
"""
Test the MacLOController Class
@ -50,8 +52,10 @@ class TestMacLOController(TestCase, TestMixin):
"""
Set up the patches and mocks need for all tests.
"""
Registry.create()
self.setup_application()
self.build_settings()
Registry().register('settings', self.settings)
self.mock_plugin = MagicMock()
self.temp_folder = mkdtemp()
self.mock_plugin.settings_section = self.temp_folder

View File

@ -163,14 +163,14 @@ def test_load_pdf(pdf_env):
load_pdf_pictures(exe_path, pdf_env)
def test_loading_pdf_using_pymupdf():
def test_loading_pdf_using_pymupdf(pdf_env):
try:
import fitz # noqa: F401
except ImportError:
pytest.skip('PyMuPDF is not installed')
load_pdf(None)
load_pdf_pictures(None)
load_pdf(None, pdf_env)
load_pdf_pictures(None, pdf_env)
@patch('openlp.plugins.presentations.lib.pdfcontroller.check_binary_exists')

View File

@ -21,213 +21,208 @@
"""
This module contains tests for the db submodule of the Songs plugin.
"""
import pytest
import os
import shutil
from tempfile import mkdtemp
from unittest import TestCase
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib.db import upgrade_db
from openlp.plugins.songs.lib import upgrade
from openlp.plugins.songs.lib.db import Author, AuthorType, Book, Song
from tests.utils.constants import TEST_RESOURCES_PATH
class TestDB(TestCase):
@pytest.yield_fixture()
def tmp_folder():
t_folder = mkdtemp()
yield t_folder
shutil.rmtree(t_folder, ignore_errors=True)
def test_add_author():
"""
Test the functions in the :mod:`db` module.
Test adding an author to a song
"""
# GIVEN: A song and an author
song = Song()
song.authors_songs = []
author = Author()
author.first_name = "Max"
author.last_name = "Mustermann"
def setUp(self):
"""
Setup for tests
"""
self.tmp_folder = mkdtemp()
Registry.create()
Registry().register('settings', Settings())
# WHEN: We add an author to the song
song.add_author(author)
def tearDown(self):
"""
Clean up after tests
"""
# Ignore errors since windows can have problems with locked files
shutil.rmtree(self.tmp_folder, ignore_errors=True)
# THEN: The author should have been added with author_type=None
assert 1 == len(song.authors_songs)
assert "Max" == song.authors_songs[0].author.first_name
assert "Mustermann" == song.authors_songs[0].author.last_name
assert song.authors_songs[0].author_type is None
def test_add_author(self):
"""
Test adding an author to a song
"""
# GIVEN: A song and an author
song = Song()
song.authors_songs = []
author = Author()
author.first_name = "Max"
author.last_name = "Mustermann"
# WHEN: We add an author to the song
song.add_author(author)
def test_add_author_with_type():
"""
Test adding an author with a type specified to a song
"""
# GIVEN: A song and an author
song = Song()
song.authors_songs = []
author = Author()
author.first_name = "Max"
author.last_name = "Mustermann"
# THEN: The author should have been added with author_type=None
assert 1 == len(song.authors_songs)
assert "Max" == song.authors_songs[0].author.first_name
assert "Mustermann" == song.authors_songs[0].author.last_name
assert song.authors_songs[0].author_type is None
# WHEN: We add an author to the song
song.add_author(author, AuthorType.Words)
def test_add_author_with_type(self):
"""
Test adding an author with a type specified to a song
"""
# GIVEN: A song and an author
song = Song()
song.authors_songs = []
author = Author()
author.first_name = "Max"
author.last_name = "Mustermann"
# THEN: The author should have been added with author_type=None
assert 1 == len(song.authors_songs)
assert "Max" == song.authors_songs[0].author.first_name
assert "Mustermann" == song.authors_songs[0].author.last_name
assert AuthorType.Words == song.authors_songs[0].author_type
# WHEN: We add an author to the song
song.add_author(author, AuthorType.Words)
# THEN: The author should have been added with author_type=None
assert 1 == len(song.authors_songs)
assert "Max" == song.authors_songs[0].author.first_name
assert "Mustermann" == song.authors_songs[0].author.last_name
assert AuthorType.Words == song.authors_songs[0].author_type
def test_remove_author():
"""
Test removing an author from a song
"""
# GIVEN: A song with an author
song = Song()
song.authors_songs = []
author = Author()
song.add_author(author)
def test_remove_author(self):
"""
Test removing an author from a song
"""
# GIVEN: A song with an author
song = Song()
song.authors_songs = []
author = Author()
song.add_author(author)
# WHEN: We remove the author
song.remove_author(author)
# WHEN: We remove the author
song.remove_author(author)
# THEN: It should have been removed
assert 0 == len(song.authors_songs)
# THEN: It should have been removed
assert 0 == len(song.authors_songs)
def test_remove_author_with_type(self):
"""
Test removing an author with a type specified from a song
"""
# GIVEN: A song with two authors
song = Song()
song.authors_songs = []
author = Author()
song.add_author(author)
song.add_author(author, AuthorType.Translation)
def test_remove_author_with_type():
"""
Test removing an author with a type specified from a song
"""
# GIVEN: A song with two authors
song = Song()
song.authors_songs = []
author = Author()
song.add_author(author)
song.add_author(author, AuthorType.Translation)
# WHEN: We remove the author with a certain type
song.remove_author(author, AuthorType.Translation)
# WHEN: We remove the author with a certain type
song.remove_author(author, AuthorType.Translation)
# THEN: It should have been removed and the other author should still be there
assert 1 == len(song.authors_songs)
assert song.authors_songs[0].author_type is None
# THEN: It should have been removed and the other author should still be there
assert 1 == len(song.authors_songs)
assert song.authors_songs[0].author_type is None
def test_get_author_type_from_translated_text(self):
"""
Test getting an author type from translated text
"""
# GIVEN: A string with an author type
author_type_name = AuthorType.Types[AuthorType.Words]
# WHEN: We call the method
author_type = AuthorType.from_translated_text(author_type_name)
def test_get_author_type_from_translated_text():
"""
Test getting an author type from translated text
"""
# GIVEN: A string with an author type
author_type_name = AuthorType.Types[AuthorType.Words]
# THEN: The type should be correct
assert author_type == AuthorType.Words
# WHEN: We call the method
author_type = AuthorType.from_translated_text(author_type_name)
def test_author_get_display_name(self):
"""
Test that the display name of an author is correct
"""
# GIVEN: An author
author = Author()
author.display_name = "John Doe"
# THEN: The type should be correct
assert author_type == AuthorType.Words
# WHEN: We call the get_display_name() function
display_name = author.get_display_name()
# THEN: It should return only the name
assert "John Doe" == display_name
def test_author_get_display_name():
"""
Test that the display name of an author is correct
"""
# GIVEN: An author
author = Author()
author.display_name = "John Doe"
def test_author_get_display_name_with_type_words(self):
"""
Test that the display name of an author with a type is correct (Words)
"""
# GIVEN: An author
author = Author()
author.display_name = "John Doe"
# WHEN: We call the get_display_name() function
display_name = author.get_display_name()
# WHEN: We call the get_display_name() function
display_name = author.get_display_name(AuthorType.Words)
# THEN: It should return only the name
assert "John Doe" == display_name
# THEN: It should return the name with the type in brackets
assert "John Doe (Words)" == display_name
def test_author_get_display_name_with_type_translation(self):
"""
Test that the display name of an author with a type is correct (Translation)
"""
# GIVEN: An author
author = Author()
author.display_name = "John Doe"
def test_author_get_display_name_with_type_words():
"""
Test that the display name of an author with a type is correct (Words)
"""
# GIVEN: An author
author = Author()
author.display_name = "John Doe"
# WHEN: We call the get_display_name() function
display_name = author.get_display_name(AuthorType.Translation)
# WHEN: We call the get_display_name() function
display_name = author.get_display_name(AuthorType.Words)
# THEN: It should return the name with the type in brackets
assert "John Doe (Translation)" == display_name
# THEN: It should return the name with the type in brackets
assert "John Doe (Words)" == display_name
def test_add_songbooks(self):
"""
Test that adding songbooks to a song works correctly
"""
# GIVEN: A mocked song and songbook
song = Song()
song.songbook_entries = []
songbook = Book()
songbook.name = "Thy Word"
# WHEN: We add two songbooks to a Song
song.add_songbook_entry(songbook, "120")
song.add_songbook_entry(songbook, "550A")
def test_author_get_display_name_with_type_translation():
"""
Test that the display name of an author with a type is correct (Translation)
"""
# GIVEN: An author
author = Author()
author.display_name = "John Doe"
# THEN: The song should have two songbook entries
assert len(song.songbook_entries) == 2, 'There should be two Songbook entries.'
# WHEN: We call the get_display_name() function
display_name = author.get_display_name(AuthorType.Translation)
def test_upgrade_old_song_db(self):
"""
Test that we can upgrade an old song db to the current schema
"""
# GIVEN: An old song db
old_db_path = os.path.join(TEST_RESOURCES_PATH, "songs", 'songs-1.9.7.sqlite')
old_db_tmp_path = os.path.join(self.tmp_folder, 'songs-1.9.7.sqlite')
shutil.copyfile(old_db_path, old_db_tmp_path)
db_url = 'sqlite:///' + old_db_tmp_path
# THEN: It should return the name with the type in brackets
assert "John Doe (Translation)" == display_name
# WHEN: upgrading the db
updated_to_version, latest_version = upgrade_db(db_url, upgrade)
# THEN: the song db should have been upgraded to the latest version
assert updated_to_version == latest_version, 'The song DB should have been upgrade to the latest version'
def test_add_songbooks():
"""
Test that adding songbooks to a song works correctly
"""
# GIVEN: A mocked song and songbook
song = Song()
song.songbook_entries = []
songbook = Book()
songbook.name = "Thy Word"
def test_upgrade_invalid_song_db(self):
"""
Test that we can upgrade an invalid song db to the current schema
"""
# GIVEN: A song db with invalid version
invalid_db_path = os.path.join(TEST_RESOURCES_PATH, "songs", 'songs-2.2-invalid.sqlite')
invalid_db_tmp_path = os.path.join(self.tmp_folder, 'songs-2.2-invalid.sqlite')
shutil.copyfile(invalid_db_path, invalid_db_tmp_path)
db_url = 'sqlite:///' + invalid_db_tmp_path
# WHEN: We add two songbooks to a Song
song.add_songbook_entry(songbook, "120")
song.add_songbook_entry(songbook, "550A")
# WHEN: upgrading the db
updated_to_version, latest_version = upgrade_db(db_url, upgrade)
# THEN: The song should have two songbook entries
assert len(song.songbook_entries) == 2, 'There should be two Songbook entries.'
# THEN: the song db should have been upgraded to the latest version without errors
assert updated_to_version == latest_version, 'The song DB should have been upgrade to the latest version'
def test_upgrade_old_song_db(settings, tmp_folder):
"""
Test that we can upgrade an old song db to the current schema
"""
# GIVEN: An old song db
old_db_path = os.path.join(TEST_RESOURCES_PATH, "songs", 'songs-1.9.7.sqlite')
old_db_tmp_path = os.path.join(tmp_folder, 'songs-1.9.7.sqlite')
shutil.copyfile(old_db_path, old_db_tmp_path)
db_url = 'sqlite:///' + old_db_tmp_path
# WHEN: upgrading the db
updated_to_version, latest_version = upgrade_db(db_url, upgrade)
# THEN: the song db should have been upgraded to the latest version
assert updated_to_version == latest_version, 'The song DB should have been upgrade to the latest version'
def test_upgrade_invalid_song_db(settings, tmp_folder):
"""
Test that we can upgrade an invalid song db to the current schema
"""
# GIVEN: A song db with invalid version
invalid_db_path = os.path.join(TEST_RESOURCES_PATH, "songs", 'songs-2.2-invalid.sqlite')
invalid_db_tmp_path = os.path.join(tmp_folder, 'songs-2.2-invalid.sqlite')
shutil.copyfile(invalid_db_path, invalid_db_tmp_path)
db_url = 'sqlite:///' + invalid_db_tmp_path
# WHEN: upgrading the db
updated_to_version, latest_version = upgrade_db(db_url, upgrade)
# THEN: the song db should have been upgraded to the latest version without errors
assert updated_to_version == latest_version, 'The song DB should have been upgrade to the latest version'

File diff suppressed because it is too large Load Diff

View File

@ -22,17 +22,14 @@
This module contains tests for the lib submodule of the Songs plugin.
"""
import pytest
from unittest import TestCase
from unittest.mock import MagicMock, patch
from PyQt5 import QtCore
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib.serviceitem import ServiceItem
from openlp.plugins.songs.lib.db import AuthorType, Song
from openlp.plugins.songs.lib.mediaitem import SongMediaItem
from tests.helpers.testmixin import TestMixin
__default_settings__ = {
'songs/footer template': """
@ -92,7 +89,7 @@ ${title}<br/>
@pytest.yield_fixture
def mocked_media_item(mock_settings):
def media_item(settings):
Registry().register('service_list', MagicMock())
Registry().register('main_window', MagicMock())
mocked_plugin = MagicMock()
@ -114,505 +111,482 @@ def mocked_media_item(mock_settings):
yield media_item
class TestMediaItem(TestCase, TestMixin):
def test_display_results_song(media_item):
"""
Test the functions in the :mod:`lib` module.
Test displaying song search results with basic song
"""
def setUp(self):
"""
Set up the components need for all tests.
"""
Registry.create()
Registry().register('service_list', MagicMock())
Registry().register('main_window', MagicMock())
self.mocked_plugin = MagicMock()
with patch('openlp.core.lib.mediamanageritem.MediaManagerItem._setup'), \
patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'):
self.media_item = SongMediaItem(None, self.mocked_plugin)
self.media_item.save_auto_select_id = MagicMock()
self.media_item.list_view = MagicMock()
self.media_item.list_view.save_auto_select_id = MagicMock()
self.media_item.list_view.clear = MagicMock()
self.media_item.list_view.addItem = MagicMock()
self.media_item.list_view.setCurrentItem = MagicMock()
self.media_item.auto_select_id = -1
self.media_item.display_songbook = False
self.media_item.display_copyright_symbol = False
self.setup_application()
self.build_settings()
Settings().extend_default_settings(__default_settings__)
self.settings = self.setting
Registry().register('settings', self.settings)
QtCore.QLocale.setDefault(QtCore.QLocale('en_GB'))
# GIVEN: Search results, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_song = MagicMock()
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.authors = []
mock_song_temp = MagicMock()
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.authors = []
mock_author = MagicMock()
mock_author.display_name = 'My Author'
mock_song.authors.append(mock_author)
mock_song_temp.authors.append(mock_author)
mock_song.temporary = False
mock_song_temp.temporary = True
mock_search_results.append(mock_song)
mock_search_results.append(mock_song_temp)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
media_item.auto_select_id = 1
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
self.destroy_settings()
# WHEN: I display song search results
media_item.display_results_song(mock_search_results)
def test_display_results_song(self):
"""
Test displaying song search results with basic song
"""
# GIVEN: Search results, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_song = MagicMock()
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.authors = []
mock_song_temp = MagicMock()
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.authors = []
mock_author = MagicMock()
mock_author.display_name = 'My Author'
mock_song.authors.append(mock_author)
mock_song_temp.authors.append(mock_author)
mock_song.temporary = False
mock_song_temp.temporary = True
mock_search_results.append(mock_song)
mock_search_results.append(mock_song_temp)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
self.media_item.auto_select_id = 1
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
media_item.list_view.clear.assert_called_with()
media_item.save_auto_select_id.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Song (My Author)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
media_item.list_view.setCurrentItem.assert_called_with(mock_qlist_widget)
# WHEN: I display song search results
self.media_item.display_results_song(mock_search_results)
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
self.media_item.save_auto_select_id.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Song (My Author)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
self.media_item.list_view.setCurrentItem.assert_called_with(mock_qlist_widget)
def test_display_results_author(media_item):
"""
Test displaying song search results grouped by author with basic song
"""
# GIVEN: Search results grouped by author, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_author = MagicMock()
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_author.display_name = 'My Author'
mock_author.songs = []
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.temporary = True
mock_author.songs.append(mock_song)
mock_author.songs.append(mock_song_temp)
mock_search_results.append(mock_author)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
def test_display_results_author(self):
"""
Test displaying song search results grouped by author with basic song
"""
# GIVEN: Search results grouped by author, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_author = MagicMock()
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_author.display_name = 'My Author'
mock_author.songs = []
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.temporary = True
mock_author.songs.append(mock_song)
mock_author.songs.append(mock_song_temp)
mock_search_results.append(mock_author)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by author
media_item.display_results_author(mock_search_results)
# WHEN: I display song search results grouped by author
self.media_item.display_results_author(mock_search_results)
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Author (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Author (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def test_display_results_book(self):
"""
Test displaying song search results grouped by book and entry with basic song
"""
# GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = [('1', 'My Book', 'My Song', 1)]
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by book
self.media_item.display_results_book(mock_search_results)
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Book #1: My Song')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, 1)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def test_songbook_natural_sorting(self):
"""
Test that songbooks are sorted naturally
"""
# GIVEN: Search results grouped by book and entry
search_results = [('2', 'Thy Book', 'Thy Song', 50),
('2', 'My Book', 'Your Song', 7),
('10', 'My Book', 'Our Song', 12),
('1', 'My Book', 'My Song', 1),
('2', 'Thy Book', 'A Song', 8)]
def test_display_results_book(media_item):
"""
Test displaying song search results grouped by book and entry with basic song
"""
# GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = [('1', 'My Book', 'My Song', 1)]
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by book
self.media_item.display_results_book(search_results)
media_item.display_results_book(mock_search_results)
# THEN: The songbooks are sorted inplace in the right (natural) order,
# grouped first by book, then by number, then by song title
assert search_results == [('1', 'My Book', 'My Song', 1),
('2', 'My Book', 'Your Song', 7),
('10', 'My Book', 'Our Song', 12),
('2', 'Thy Book', 'A Song', 8),
('2', 'Thy Book', 'Thy Song', 50)]
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Book #1: My Song')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, 1)
media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def test_display_results_topic(self):
"""
Test displaying song search results grouped by topic with basic song
"""
# GIVEN: Search results grouped by topic, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_topic = MagicMock()
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_topic.name = 'My Topic'
mock_topic.songs = []
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.temporary = True
mock_topic.songs.append(mock_song)
mock_topic.songs.append(mock_song_temp)
mock_search_results.append(mock_topic)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by topic
self.media_item.display_results_topic(mock_search_results)
def test_songbook_natural_sorting(media_item):
"""
Test that songbooks are sorted naturally
"""
# GIVEN: Search results grouped by book and entry
search_results = [('2', 'Thy Book', 'Thy Song', 50),
('2', 'My Book', 'Your Song', 7),
('10', 'My Book', 'Our Song', 12),
('1', 'My Book', 'My Song', 1),
('2', 'Thy Book', 'A Song', 8)]
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Topic (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
# WHEN: I display song search results grouped by book
media_item.display_results_book(search_results)
def test_display_results_themes(self):
"""
Test displaying song search results sorted by theme with basic song
"""
# GIVEN: Search results sorted by theme, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.theme_name = 'My Theme'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.theme_name = 'My Theme'
mock_song_temp.temporary = True
mock_search_results.append(mock_song)
mock_search_results.append(mock_song_temp)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
# THEN: The songbooks are sorted inplace in the right (natural) order,
# grouped first by book, then by number, then by song title
assert search_results == [('1', 'My Book', 'My Song', 1),
('2', 'My Book', 'Your Song', 7),
('10', 'My Book', 'Our Song', 12),
('2', 'Thy Book', 'A Song', 8),
('2', 'Thy Book', 'Thy Song', 50)]
# WHEN: I display song search results sorted by theme
self.media_item.display_results_themes(mock_search_results)
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Theme (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def test_display_results_cclinumber(self):
"""
Test displaying song search results sorted by CCLI number with basic song
"""
# GIVEN: Search results sorted by CCLI number, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.ccli_number = '12345'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.ccli_number = '12346'
mock_song_temp.temporary = True
mock_search_results.append(mock_song)
mock_search_results.append(mock_song_temp)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results sorted by CCLI number
self.media_item.display_results_cclinumber(mock_search_results)
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('12345 (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def test_build_song_footer_two_authors(self):
"""
Test build songs footer with basic song and two authors
"""
# GIVEN: A Song and a Service Item
def test_display_results_topic(media_item):
"""
Test displaying song search results grouped by topic with basic song
"""
# GIVEN: Search results grouped by topic, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_topic = MagicMock()
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_topic.name = 'My Topic'
mock_topic.songs = []
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.authors_songs = []
mock_author = MagicMock()
mock_author.display_name = 'my author'
mock_author_song = MagicMock()
mock_author_song.author = mock_author
mock_author_song.author_type = AuthorType.Music
mock_song.authors_songs.append(mock_author_song)
mock_author = MagicMock()
mock_author.display_name = 'another author'
mock_author_song = MagicMock()
mock_author_song.author = mock_author
mock_author_song.author_type = AuthorType.Words
mock_song.authors_songs.append(mock_author_song)
mock_author = MagicMock()
mock_author.display_name = 'translator'
mock_author_song = MagicMock()
mock_author_song.author = mock_author
mock_author_song.author_type = AuthorType.Translation
mock_song.authors_songs.append(mock_author_song)
mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None)
mock_song.sort_key = 'My Song'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.temporary = True
mock_topic.songs.append(mock_song)
mock_topic.songs.append(mock_song_temp)
mock_search_results.append(mock_topic)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I generate the Footer with default settings
author_list = self.media_item.generate_footer(service_item, mock_song)
# WHEN: I display song search results grouped by topic
media_item.display_results_topic(mock_search_results)
# THEN: I get the following Array returned
assert service_item.raw_footer == ['My Song', 'Words: another author', 'Music: my author',
'Translation: translator', '© My copyright'], \
'The array should be returned correctly with a song, two authors and copyright'
assert author_list == ['another author', 'my author', 'translator'], \
'The author list should be returned correctly with two authors'
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Topic (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def test_build_song_footer_base_ccli(self):
"""
Test build songs footer with basic song and a CCLI number
"""
# GIVEN: A Song and a Service Item and a configured CCLI license
def test_display_results_themes(media_item):
"""
Test displaying song search results sorted by theme with basic song
"""
# GIVEN: Search results sorted by theme, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None)
self.settings.setValue('core/ccli number', '1234')
mock_song.sort_key = 'My Song'
mock_song.theme_name = 'My Theme'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.theme_name = 'My Theme'
mock_song_temp.temporary = True
mock_search_results.append(mock_song)
mock_search_results.append(mock_song_temp)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, mock_song)
# WHEN: I display song search results sorted by theme
media_item.display_results_themes(mock_search_results)
# THEN: I get the following Array returned
assert service_item.raw_footer == ['My Song', '© My copyright', 'CCLI License: 1234'], \
'The array should be returned correctly with a song, an author, copyright and ccli'
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Theme (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
# WHEN: I amend the CCLI value
self.settings.setValue('core/ccli number', '4321')
self.media_item.generate_footer(service_item, mock_song)
# THEN: I would get an amended footer string
assert service_item.raw_footer == ['My Song', '© My copyright', 'CCLI License: 4321'], \
'The array should be returned correctly with a song, an author, copyright and amended ccli'
def test_build_song_footer_base_songbook(self):
"""
Test build songs footer with basic song and multiple songbooks
"""
# GIVEN: A Song and a Service Item
song = Song()
song.title = 'My Song'
song.alternate_title = ''
song.copyright = 'My copyright'
song.authors_songs = []
song.songbook_entries = []
song.alternate_title = ''
song.topics = []
song.ccli_number = ''
book1 = MagicMock()
book1.name = 'My songbook'
book2 = MagicMock()
book2.name = 'Thy songbook'
song.songbookentries = []
song.add_songbook_entry(book1, '12')
song.add_songbook_entry(book2, '502A')
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, song)
# THEN: The songbook should be in the footer
assert service_item.raw_footer == ['My Song', '© My copyright', 'My songbook #12, Thy songbook #502A']
def test_build_song_footer_copyright_enabled(self):
"""
Test building song footer with displaying the copyright symbol
"""
# GIVEN: A Song and a Service Item; displaying the copyright symbol is enabled
self.media_item.display_copyright_symbol = True
def test_display_results_cclinumber(media_item):
"""
Test displaying song search results sorted by CCLI number with basic song
"""
# GIVEN: Search results sorted by CCLI number, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = []
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None)
mock_song.sort_key = 'My Song'
mock_song.ccli_number = '12345'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.ccli_number = '12346'
mock_song_temp.temporary = True
mock_search_results.append(mock_song)
mock_search_results.append(mock_song_temp)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, mock_song)
# WHEN: I display song search results sorted by CCLI number
media_item.display_results_cclinumber(mock_search_results)
# THEN: The copyright symbol should be in the footer
assert service_item.raw_footer == ['My Song', '© My copyright']
def test_build_song_footer_copyright_disabled(self):
"""
Test building song footer without displaying the copyright symbol
"""
# GIVEN: A Song and a Service Item; displaying the copyright symbol should be disabled by default
mock_song = MagicMock()
mock_song.title = 'My Song'
mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, mock_song)
# THEN: The copyright symbol should not be in the footer
assert service_item.raw_footer == ['My Song', '© My copyright']
def test_authors_match(self):
"""
Test the author matching when importing a song from a service
"""
# GIVEN: A song and a string with authors
song = MagicMock()
song.authors = []
author = MagicMock()
author.display_name = "Hans Wurst"
song.authors.append(author)
author2 = MagicMock()
author2.display_name = "Max Mustermann"
song.authors.append(author2)
# There are occasions where an author appears twice in a song (with different types).
# We need to make sure that this case works (lp#1313538)
author3 = MagicMock()
author3.display_name = "Max Mustermann"
song.authors.append(author3)
authors_str = "Hans Wurst, Max Mustermann, Max Mustermann"
# WHEN: Checking for matching
result = self.media_item._authors_match(song, authors_str)
# THEN: They should match
assert result is True, "Authors should match"
def test_authors_dont_match(self):
# GIVEN: A song and a string with authors
song = MagicMock()
song.authors = []
author = MagicMock()
author.display_name = "Hans Wurst"
song.authors.append(author)
author2 = MagicMock()
author2.display_name = "Max Mustermann"
song.authors.append(author2)
# There are occasions where an author appears twice in a song (with different types).
# We need to make sure that this case works (lp#1313538)
author3 = MagicMock()
author3.display_name = "Max Mustermann"
song.authors.append(author3)
# WHEN: An author is missing in the string
authors_str = "Hans Wurst, Max Mustermann"
result = self.media_item._authors_match(song, authors_str)
# THEN: They should not match
assert result is False, "Authors should not match"
def test_build_remote_search(self):
"""
Test results for the remote search api
"""
# GIVEN: A Song and a search a JSON array should be returned.
mock_song = MagicMock()
mock_song.id = 123
mock_song.title = 'My Song'
mock_song.search_title = 'My Song'
mock_song.alternate_title = 'My alternative'
self.media_item.search_entire = MagicMock()
self.media_item.search_entire.return_value = [mock_song]
# WHEN: I process a search
search_results = self.media_item.search('My Song', False)
# THEN: The correct formatted results are returned
assert search_results == [[123, 'My Song', 'My alternative']]
@patch('openlp.plugins.songs.lib.mediaitem.Book')
@patch('openlp.plugins.songs.lib.mediaitem.SongBookEntry')
@patch('openlp.plugins.songs.lib.mediaitem.Song')
@patch('openlp.plugins.songs.lib.mediaitem.or_')
def test_entire_song_search(self, mocked_or, MockedSong, MockedSongBookEntry, MockedBook):
"""
Test that searching the entire song does the right queries
"""
# GIVEN: A song media item, a keyword and some mocks
keyword = 'Jesus'
mocked_or.side_effect = lambda a, b, c, d, e: ' '.join([a, b, c, d, e])
MockedSong.search_title.like.side_effect = lambda a: a
MockedSong.search_lyrics.like.side_effect = lambda a: a
MockedSong.comments.like.side_effect = lambda a: a
MockedSongBookEntry.entry.like.side_effect = lambda a: a
MockedBook.name.like.side_effect = lambda a: a
# WHEN: search_entire_song() is called with the keyword
self.media_item.search_entire(keyword)
# THEN: The correct calls were made
MockedSong.search_title.like.assert_called_once_with('%jesus%')
MockedSong.search_lyrics.like.assert_called_once_with('%jesus%')
MockedSong.comments.like.assert_called_once_with('%jesus%')
MockedSongBookEntry.entry.like.assert_called_once_with('%jesus%')
MockedBook.name.like.assert_called_once_with('%jesus%')
mocked_or.assert_called_once_with('%jesus%', '%jesus%', '%jesus%', '%jesus%', '%jesus%')
self.mocked_plugin.manager.session.query.assert_called_once_with(MockedSong)
assert self.mocked_plugin.manager.session.query.mock_calls[4][0] == '().join().join().filter().all'
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('12345 (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def test_build_song_footer_one_author_show_written_by(mocked_media_item):
def test_build_song_footer_two_authors(media_item):
"""
Test build songs footer with basic song and two authors
"""
# GIVEN: A Song and a Service Item
mock_song = MagicMock()
mock_song.title = 'My Song'
mock_song.authors_songs = []
mock_author = MagicMock()
mock_author.display_name = 'my author'
mock_author_song = MagicMock()
mock_author_song.author = mock_author
mock_author_song.author_type = AuthorType.Music
mock_song.authors_songs.append(mock_author_song)
mock_author = MagicMock()
mock_author.display_name = 'another author'
mock_author_song = MagicMock()
mock_author_song.author = mock_author
mock_author_song.author_type = AuthorType.Words
mock_song.authors_songs.append(mock_author_song)
mock_author = MagicMock()
mock_author.display_name = 'translator'
mock_author_song = MagicMock()
mock_author_song.author = mock_author
mock_author_song.author_type = AuthorType.Translation
mock_song.authors_songs.append(mock_author_song)
mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
author_list = media_item.generate_footer(service_item, mock_song)
# THEN: I get the following Array returned
assert service_item.raw_footer == ['My Song', 'Words: another author', 'Music: my author',
'Translation: translator', '© My copyright'], \
'The array should be returned correctly with a song, two authors and copyright'
assert author_list == ['another author', 'my author', 'translator'], \
'The author list should be returned correctly with two authors'
def test_build_song_footer_base_ccli(media_item):
"""
Test build songs footer with basic song and a CCLI number
"""
# GIVEN: A Song and a Service Item and a configured CCLI license
mock_song = MagicMock()
mock_song.title = 'My Song'
mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None)
media_item.settings.setValue('core/ccli number', '1234')
# WHEN: I generate the Footer with default settings
media_item.generate_footer(service_item, mock_song)
# THEN: I get the following Array returned
assert service_item.raw_footer == ['My Song', '© My copyright', 'CCLI License: 1234'], \
'The array should be returned correctly with a song, an author, copyright and ccli'
# WHEN: I amend the CCLI value
media_item.settings.setValue('core/ccli number', '4321')
media_item.generate_footer(service_item, mock_song)
# THEN: I would get an amended footer string
assert service_item.raw_footer == ['My Song', '© My copyright', 'CCLI License: 4321'], \
'The array should be returned correctly with a song, an author, copyright and amended ccli'
def test_build_song_footer_base_songbook(media_item):
"""
Test build songs footer with basic song and multiple songbooks
"""
# GIVEN: A Song and a Service Item
song = Song()
song.title = 'My Song'
song.alternate_title = ''
song.copyright = 'My copyright'
song.authors_songs = []
song.songbook_entries = []
song.alternate_title = ''
song.topics = []
song.ccli_number = ''
book1 = MagicMock()
book1.name = 'My songbook'
book2 = MagicMock()
book2.name = 'Thy songbook'
song.songbookentries = []
song.add_songbook_entry(book1, '12')
song.add_songbook_entry(book2, '502A')
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
media_item.generate_footer(service_item, song)
# THEN: The songbook should be in the footer
assert service_item.raw_footer == ['My Song', '© My copyright', 'My songbook #12, Thy songbook #502A']
def test_build_song_footer_copyright_enabled(media_item):
"""
Test building song footer with displaying the copyright symbol
"""
# GIVEN: A Song and a Service Item; displaying the copyright symbol is enabled
media_item.display_copyright_symbol = True
mock_song = MagicMock()
mock_song.title = 'My Song'
mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
media_item.generate_footer(service_item, mock_song)
# THEN: The copyright symbol should be in the footer
assert service_item.raw_footer == ['My Song', '© My copyright']
def test_build_song_footer_copyright_disabled(media_item):
"""
Test building song footer without displaying the copyright symbol
"""
# GIVEN: A Song and a Service Item; displaying the copyright symbol should be disabled by default
mock_song = MagicMock()
mock_song.title = 'My Song'
mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
media_item.generate_footer(service_item, mock_song)
# THEN: The copyright symbol should not be in the footer
assert service_item.raw_footer == ['My Song', '© My copyright']
def test_authors_match(media_item):
"""
Test the author matching when importing a song from a service
"""
# GIVEN: A song and a string with authors
song = MagicMock()
song.authors = []
author = MagicMock()
author.display_name = "Hans Wurst"
song.authors.append(author)
author2 = MagicMock()
author2.display_name = "Max Mustermann"
song.authors.append(author2)
# There are occasions where an author appears twice in a song (with different types).
# We need to make sure that this case works (lp#1313538)
author3 = MagicMock()
author3.display_name = "Max Mustermann"
song.authors.append(author3)
authors_str = "Hans Wurst, Max Mustermann, Max Mustermann"
# WHEN: Checking for matching
result = media_item._authors_match(song, authors_str)
# THEN: They should match
assert result is True, "Authors should match"
def test_authors_dont_match(media_item):
# GIVEN: A song and a string with authors
song = MagicMock()
song.authors = []
author = MagicMock()
author.display_name = "Hans Wurst"
song.authors.append(author)
author2 = MagicMock()
author2.display_name = "Max Mustermann"
song.authors.append(author2)
# There are occasions where an author appears twice in a song (with different types).
# We need to make sure that this case works (lp#1313538)
author3 = MagicMock()
author3.display_name = "Max Mustermann"
song.authors.append(author3)
# WHEN: An author is missing in the string
authors_str = "Hans Wurst, Max Mustermann"
result = media_item._authors_match(song, authors_str)
# THEN: They should not match
assert result is False, "Authors should not match"
def test_build_remote_search(media_item):
"""
Test results for the remote search api
"""
# GIVEN: A Song and a search a JSON array should be returned.
mock_song = MagicMock()
mock_song.id = 123
mock_song.title = 'My Song'
mock_song.search_title = 'My Song'
mock_song.alternate_title = 'My alternative'
media_item.search_entire = MagicMock()
media_item.search_entire.return_value = [mock_song]
# WHEN: I process a search
search_results = media_item.search('My Song', False)
# THEN: The correct formatted results are returned
assert search_results == [[123, 'My Song', 'My alternative']]
@patch('openlp.plugins.songs.lib.mediaitem.Book')
@patch('openlp.plugins.songs.lib.mediaitem.SongBookEntry')
@patch('openlp.plugins.songs.lib.mediaitem.Song')
@patch('openlp.plugins.songs.lib.mediaitem.or_')
def test_entire_song_search(mocked_or, MockedSong, MockedSongBookEntry, MockedBook, media_item):
"""
Test that searching the entire song does the right queries
"""
# GIVEN: A song media item, a keyword and some mocks
keyword = 'Jesus'
mocked_or.side_effect = lambda a, b, c, d, e: ' '.join([a, b, c, d, e])
MockedSong.search_title.like.side_effect = lambda a: a
MockedSong.search_lyrics.like.side_effect = lambda a: a
MockedSong.comments.like.side_effect = lambda a: a
MockedSongBookEntry.entry.like.side_effect = lambda a: a
MockedBook.name.like.side_effect = lambda a: a
# WHEN: search_entire_song() is called with the keyword
media_item.search_entire(keyword)
# THEN: The correct calls were made
MockedSong.search_title.like.assert_called_once_with('%jesus%')
MockedSong.search_lyrics.like.assert_called_once_with('%jesus%')
MockedSong.comments.like.assert_called_once_with('%jesus%')
MockedSongBookEntry.entry.like.assert_called_once_with('%jesus%')
MockedBook.name.like.assert_called_once_with('%jesus%')
mocked_or.assert_called_once_with('%jesus%', '%jesus%', '%jesus%', '%jesus%', '%jesus%')
media_item.plugin.manager.session.query.assert_called_once_with(MockedSong)
assert media_item.plugin.manager.session.query.mock_calls[4][0] == '().join().join().filter().all'
def test_build_song_footer_one_author_show_written_by(media_item):
"""
Test build songs footer with basic song and one author
"""
# GIVEN: A Song and a Service Item, mocked settings
mocked_media_item.settings.value.side_effect = [False, "", "0"]
media_item.settings.setValue('core/ccli number', "0")
media_item.settings.setValue('songs/footer template', "")
with patch('mako.template.Template.render_unicode') as MockedRenderer:
mock_song = MagicMock()
mock_song.title = 'My Song'
@ -629,7 +603,7 @@ def test_build_song_footer_one_author_show_written_by(mocked_media_item):
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
author_list = mocked_media_item.generate_footer(service_item, mock_song)
author_list = media_item.generate_footer(service_item, mock_song)
# THEN: The mako function was called with the following arguments
args = {'authors_translation': [], 'authors_music_label': 'Music',

View File

@ -21,73 +21,70 @@
"""
This module contains tests for the SongFormat class
"""
from unittest import TestCase
from openlp.plugins.songs.lib.importer import SongFormat
class TestSongFormat(TestCase):
def test_get_format_list():
"""
Test the functions in the :class:`SongFormat` class.
Test that get_format_list() returns all available formats
"""
# GIVEN: The SongFormat class
# WHEN: Retrieving the format list
# THEN: All SongFormats should be returned
assert len(SongFormat.get_format_list()) == len(SongFormat.__attributes__), \
"The returned SongFormats don't match the stored ones"
def test_get_format_list(self):
"""
Test that get_format_list() returns all available formats
"""
# GIVEN: The SongFormat class
# WHEN: Retrieving the format list
# THEN: All SongFormats should be returned
assert len(SongFormat.get_format_list()) == len(SongFormat.__attributes__), \
"The returned SongFormats don't match the stored ones"
def test_get_attributed_no_attributes(self):
"""
Test that SongFormat.get(song_format) returns all attributes associated with the given song_format
"""
# GIVEN: A SongFormat
# WHEN: Retrieving all attributes of a SongFormat
for song_format in SongFormat.get_format_list():
# THEN: All attributes associated with the SongFormat should be returned
assert SongFormat.get(song_format) == SongFormat.__attributes__[song_format], \
"The returned attributes don't match the stored ones"
def test_get_attributed_no_attributes():
"""
Test that SongFormat.get(song_format) returns all attributes associated with the given song_format
"""
# GIVEN: A SongFormat
# WHEN: Retrieving all attributes of a SongFormat
for song_format in SongFormat.get_format_list():
# THEN: All attributes associated with the SongFormat should be returned
assert SongFormat.get(song_format) == SongFormat.__attributes__[song_format], \
"The returned attributes don't match the stored ones"
def test_get_attributed_single_attribute(self):
"""
Test that SongFormat.get(song_format, attribute) returns only one -and the correct- attribute
"""
# GIVEN: A SongFormat
for song_format in SongFormat.get_format_list():
# WHEN: Retrieving an attribute that overrides the default values
for attribute in SongFormat.get(song_format).keys():
# THEN: Return the attribute
assert SongFormat.get(song_format, attribute) == SongFormat.get(song_format)[attribute], \
"The returned attribute doesn't match the stored one"
# WHEN: Retrieving an attribute that was not overridden
for attribute in SongFormat.__defaults__.keys():
if attribute not in SongFormat.get(song_format).keys():
# THEN: Return the default value
assert SongFormat.get(song_format, attribute) == SongFormat.__defaults__[attribute], \
"The returned attribute does not match the default values stored"
def test_get_attributed_multiple_attributes(self):
"""
Test that multiple attributes can be retrieved for a song_format
"""
# GIVEN: A SongFormat
# WHEN: Retrieving multiple attributes at the same time
for song_format in SongFormat.get_format_list():
# THEN: Return all attributes that were specified
assert len(SongFormat.get(song_format, 'canDisable', 'availability')) == 2, \
"Did not return the correct number of attributes when retrieving multiple attributes at once"
def test_get_attributed_single_attribute():
"""
Test that SongFormat.get(song_format, attribute) returns only one -and the correct- attribute
"""
# GIVEN: A SongFormat
for song_format in SongFormat.get_format_list():
# WHEN: Retrieving an attribute that overrides the default values
for attribute in SongFormat.get(song_format).keys():
# THEN: Return the attribute
assert SongFormat.get(song_format, attribute) == SongFormat.get(song_format)[attribute], \
"The returned attribute doesn't match the stored one"
# WHEN: Retrieving an attribute that was not overridden
for attribute in SongFormat.__defaults__.keys():
if attribute not in SongFormat.get(song_format).keys():
# THEN: Return the default value
assert SongFormat.get(song_format, attribute) == SongFormat.__defaults__[attribute], \
"The returned attribute does not match the default values stored"
def test_get_format_list_returns_ordered_list(self):
"""
Test that get_format_list() returns a list that is ordered
according to the order specified in SongFormat
"""
# GIVEN: The SongFormat class
# WHEN: Retrieving all formats
# THEN: The returned list should be sorted according to the ordering defined in SongFormat
assert sorted(SongFormat.get_format_list()) == SongFormat.get_format_list(), \
"The list returned should be sorted according to the ordering in SongFormat"
def test_get_attributed_multiple_attributes():
"""
Test that multiple attributes can be retrieved for a song_format
"""
# GIVEN: A SongFormat
# WHEN: Retrieving multiple attributes at the same time
for song_format in SongFormat.get_format_list():
# THEN: Return all attributes that were specified
assert len(SongFormat.get(song_format, 'canDisable', 'availability')) == 2, \
"Did not return the correct number of attributes when retrieving multiple attributes at once"
def test_get_format_list_returns_ordered_list():
"""
Test that get_format_list() returns a list that is ordered
according to the order specified in SongFormat
"""
# GIVEN: The SongFormat class
# WHEN: Retrieving all formats
# THEN: The returned list should be sorted according to the ordering defined in SongFormat
assert sorted(SongFormat.get_format_list()) == SongFormat.get_format_list(), \
"The list returned should be sorted according to the ordering in SongFormat"

View File

@ -21,14 +21,13 @@
"""
This module contains tests for the lib submodule of the Images plugin.
"""
from unittest import TestCase
import pytest
from unittest.mock import MagicMock, patch
from PyQt5 import QtCore, QtWidgets
from PyQt5 import QtCore
from openlp.core.common.registry import Registry
from openlp.plugins.songs.lib.songstab import SongsTab
from tests.helpers.testmixin import TestMixin
__default_settings__ = {
'songs/footer template': """${title}""",
@ -36,175 +35,167 @@ __default_settings__ = {
}
class TestSongTab(TestCase, TestMixin):
@pytest.fixture()
def form(settings):
Registry().register('settings_form', MagicMock())
Registry().get('settings').extend_default_settings(__default_settings__)
frm = SongsTab(None, 'Songs', None, None)
frm.settings_form.register_post_process = MagicMock()
return frm
def test_german_notation_on_load(form):
"""
This is a test case to test various methods in the ImageTab.
Test the corrent notation is selected on load
"""
# GIVEN: German notation in the settings
form.settings.setValue('songs/chord notation', 'german')
# WHEN: Load is invoked
form.load()
# THEN: The german radio button should be checked
assert form.german_notation_radio_button.isChecked() is True
def setUp(self):
"""
Create the UI
"""
Registry.create()
Registry().register('settings_form', MagicMock())
self.setup_application()
self.build_settings()
Registry().register('settings', self.setting)
self.setting.extend_default_settings(__default_settings__)
self.parent = QtWidgets.QMainWindow()
self.form = SongsTab(self.parent, 'Songs', None, None)
self.form.settings_form.register_post_process = MagicMock()
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
del self.parent
del self.form
self.destroy_settings()
def test_neolatin_notation_on_load(form):
"""
Test the corrent notation is selected on load
"""
# GIVEN: neo-latin notation in the settings
form.settings.setValue('songs/chord notation', 'neo-latin')
# WHEN: Load is invoked
form.load()
# THEN: The neo-latin radio button should be checked
assert form.neolatin_notation_radio_button.isChecked() is True
def test_german_notation_on_load(self):
"""
Test the corrent notation is selected on load
"""
# GIVEN: German notation in the settings
self.setting.setValue('songs/chord notation', 'german')
# WHEN: Load is invoked
self.form.load()
# THEN: The german radio button should be checked
assert self.form.german_notation_radio_button.isChecked() is True
def test_neolatin_notation_on_load(self):
"""
Test the corrent notation is selected on load
"""
# GIVEN: neo-latin notation in the settings
self.setting.setValue('songs/chord notation', 'neo-latin')
# WHEN: Load is invoked
self.form.load()
# THEN: The neo-latin radio button should be checked
assert self.form.neolatin_notation_radio_button.isChecked() is True
def test_invalid_notation_on_load(form):
"""
Test the invalid notation in settings reverts to english
"""
# GIVEN: gibberish notation in the settings
form.settings.setValue('songs/chord notation', 'gibberish')
# WHEN: Load is invoked
form.load()
# THEN: The english radio button should be checked
assert form.english_notation_radio_button.isChecked() is True
def test_invalid_notation_on_load(self):
"""
Test the invalid notation in settings reverts to english
"""
# GIVEN: gibberish notation in the settings
self.setting.setValue('songs/chord notation', 'gibberish')
# WHEN: Load is invoked
self.form.load()
# THEN: The english radio button should be checked
assert self.form.english_notation_radio_button.isChecked() is True
def test_save_check_box_settings(self):
"""
Test check box options are correctly saved
"""
# GIVEN: A arrangement of enabled/disabled check boxes
self.form.on_search_as_type_check_box_changed(QtCore.Qt.Unchecked)
self.form.on_tool_bar_active_check_box_changed(QtCore.Qt.Checked)
self.form.on_update_on_edit_check_box_changed(QtCore.Qt.Unchecked)
self.form.on_add_from_service_check_box_changed(QtCore.Qt.Checked)
self.form.on_songbook_slide_check_box_changed(QtCore.Qt.Unchecked)
self.form.on_mainview_chords_check_box_changed(QtCore.Qt.Checked)
self.form.on_disable_chords_import_check_box_changed(QtCore.Qt.Unchecked)
# WHEN: Save is invoked
self.form.save()
# THEN: The correct values should be stored in the settings
# song_search is currently unused
assert self.setting.value('songs/display songbar') is True
assert self.setting.value('songs/update service on edit') is False
assert self.setting.value('songs/add song from service') is True
assert self.setting.value('songs/add songbook slide') is False
assert self.setting.value('songs/mainview chords') is True
assert self.setting.value('songs/disable chords import') is False
def test_save_check_box_settings(form):
"""
Test check box options are correctly saved
"""
# GIVEN: A arrangement of enabled/disabled check boxes
form.on_search_as_type_check_box_changed(QtCore.Qt.Unchecked)
form.on_tool_bar_active_check_box_changed(QtCore.Qt.Checked)
form.on_update_on_edit_check_box_changed(QtCore.Qt.Unchecked)
form.on_add_from_service_check_box_changed(QtCore.Qt.Checked)
form.on_songbook_slide_check_box_changed(QtCore.Qt.Unchecked)
form.on_mainview_chords_check_box_changed(QtCore.Qt.Checked)
form.on_disable_chords_import_check_box_changed(QtCore.Qt.Unchecked)
# WHEN: Save is invoked
form.save()
# THEN: The correct values should be stored in the settings
# song_search is currently unused
assert form.settings.value('songs/display songbar') is True
assert form.settings.value('songs/update service on edit') is False
assert form.settings.value('songs/add song from service') is True
assert form.settings.value('songs/add songbook slide') is False
assert form.settings.value('songs/mainview chords') is True
assert form.settings.value('songs/disable chords import') is False
def test_english_notation_button(self):
"""
Test notation button clicked handler
"""
# GIVEN: A normal song form
# WHEN: english notation clicked
self.form.on_english_notation_button_clicked()
# THEN: Chord notation should be correct
assert self.form.chord_notation == 'english'
def test_german_notation_button(self):
"""
Test notation button clicked handler
"""
# GIVEN: A normal song form
# WHEN: german notation clicked
self.form.on_german_notation_button_clicked()
# THEN: Chord notation should be correct
assert self.form.chord_notation == 'german'
def test_english_notation_button(form):
"""
Test notation button clicked handler
"""
# GIVEN: A normal song form
# WHEN: english notation clicked
form.on_english_notation_button_clicked()
# THEN: Chord notation should be correct
assert form.chord_notation == 'english'
def test_neolatin_notation_button(self):
"""
Test notation button clicked handler
"""
# GIVEN: A normal song form
# WHEN: neolatin notation clicked
self.form.on_neolatin_notation_button_clicked()
# THEN: Chord notation should be correct
assert self.form.chord_notation == 'neo-latin'
@patch('openlp.core.common.settings.Settings.setValue')
def test_footer_nochange(self, mocked_settings_set_val):
"""
Test the footer is not saved when not changed
"""
# GIVEN: A normal song form
# WHEN: save is invoked
self.form.save()
# THEN: footer should not have been saved (one less call than the change test below)
assert mocked_settings_set_val.call_count == 8
def test_german_notation_button(form):
"""
Test notation button clicked handler
"""
# GIVEN: A normal song form
# WHEN: german notation clicked
form.on_german_notation_button_clicked()
# THEN: Chord notation should be correct
assert form.chord_notation == 'german'
@patch('openlp.core.common.settings.Settings.setValue')
def test_footer_change(self, mocked_settings_set_val):
"""
Test the footer is saved when changed
"""
# GIVEN: Footer has changed
self.form.footer_edit_box.setPlainText('A new footer')
# WHEN: save is invoked
self.form.save()
# THEN: footer should have been saved (one more call to setValue than the nochange test)
assert mocked_settings_set_val.call_count == 9
assert self.form.footer_edit_box.toPlainText() == 'A new footer'
def test_footer_reset(self):
"""
Test the footer is reset when reset clicked
"""
# GIVEN: A default footer and different content in the edit box
self.setting.setValue('songs/footer template', 'hello')
self.form.footer_edit_box.setPlainText('A different footer')
# WHEN: reset is invoked
self.form.on_footer_reset_button_clicked()
# THEN: footer edit box should have been reset to the settings value
assert self.form.footer_edit_box.toPlainText() == self.setting.get_default_value('songs/footer template')
def test_neolatin_notation_button(form):
"""
Test notation button clicked handler
"""
# GIVEN: A normal song form
# WHEN: neolatin notation clicked
form.on_neolatin_notation_button_clicked()
# THEN: Chord notation should be correct
assert form.chord_notation == 'neo-latin'
def test_save_tab_nochange(self):
"""
Test no changes does not trigger post processing
"""
# GIVEN: No changes on the form.
self.initial_color = '#999999'
# WHEN: the save is invoked
self.form.save()
# THEN: the post process should not be requested
assert 0 == self.form.settings_form.register_post_process.call_count, \
'Songs Post processing should not have been requested'
def test_save_tab_change(self):
"""
Test save after visiting the page triggers post processing.
"""
# GIVEN: Form has been visited.
self.form.tab_visited = True
# WHEN: the save is invoked
self.form.save()
# THEN: the post process should be requested
assert 1 == self.form.settings_form.register_post_process.call_count, \
'Songs Post processing should have been requested'
@patch('openlp.core.common.settings.Settings.setValue')
def test_footer_nochange(mocked_settings_set_val, form):
"""
Test the footer is not saved when not changed
"""
# GIVEN: A normal song form
# WHEN: save is invoked
form.save()
# THEN: footer should not have been saved (one less call than the change test below)
assert mocked_settings_set_val.call_count == 8
@patch('openlp.core.common.settings.Settings.setValue')
def test_footer_change(mocked_settings_set_val, form):
"""
Test the footer is saved when changed
"""
# GIVEN: Footer has changed
form.footer_edit_box.setPlainText('A new footer')
# WHEN: save is invoked
form.save()
# THEN: footer should have been saved (one more call to setValue than the nochange test)
assert mocked_settings_set_val.call_count == 9
assert form.footer_edit_box.toPlainText() == 'A new footer'
def test_footer_reset(form):
"""
Test the footer is reset when reset clicked
"""
# GIVEN: A default footer and different content in the edit box
form.settings.setValue('songs/footer template', 'hello')
form.footer_edit_box.setPlainText('A different footer')
# WHEN: reset is invoked
form.on_footer_reset_button_clicked()
# THEN: footer edit box should have been reset to the settings value
assert form.footer_edit_box.toPlainText() == form.settings.get_default_value('songs/footer template')
def test_save_tab_nochange(form):
"""
Test no changes does not trigger post processing
"""
# GIVEN: No changes on the form.
# WHEN: the save is invoked
form.save()
# THEN: the post process should not be requested
assert 0 == form.settings_form.register_post_process.call_count, \
'Songs Post processing should not have been requested'
def test_save_tab_change(form):
"""
Test save after visiting the page triggers post processing.
"""
# GIVEN: Form has been visited.
form.tab_visited = True
# WHEN: the save is invoked
form.save()
# THEN: the post process should be requested
assert 1 == form.settings_form.register_post_process.call_count, \
'Songs Post processing should have been requested'

View File

@ -21,59 +21,48 @@
"""
Functional tests to test the AppLocation class and related methods.
"""
from unittest import TestCase
from openlp.core.common import is_not_image_file
from tests.utils.constants import RESOURCE_PATH
from tests.helpers.testmixin import TestMixin
class TestUtils(TestCase, TestMixin):
def test_is_not_image_empty():
"""
A test suite to test out various methods around the Utils functions.
Test the method handles an empty string
"""
# Given and empty string
file_name = ""
def setUp(self):
"""
Some pre-test setup required.
"""
self.setup_application()
# WHEN testing for it
result = is_not_image_file(file_name)
def test_is_not_image_empty(self):
"""
Test the method handles an empty string
"""
# Given and empty string
file_name = ""
# THEN the result is false
assert result is True, 'The missing file test should return True'
# WHEN testing for it
result = is_not_image_file(file_name)
# THEN the result is false
assert result is True, 'The missing file test should return True'
def test_is_not_image_with_image_file():
"""
Test the method handles an image file
"""
# Given and empty string
file_path = RESOURCE_PATH / 'church.jpg'
def test_is_not_image_with_image_file(self):
"""
Test the method handles an image file
"""
# Given and empty string
file_path = RESOURCE_PATH / 'church.jpg'
# WHEN testing for it
result = is_not_image_file(file_path)
# WHEN testing for it
result = is_not_image_file(file_path)
# THEN the result is false
assert result is False, 'The file is present so the test should return False'
# THEN the result is false
assert result is False, 'The file is present so the test should return False'
def test_is_not_image_with_none_image_file(self):
"""
Test the method handles a non image file
"""
# Given and empty string
file_path = RESOURCE_PATH / 'serviceitem_custom_1.osj'
def test_is_not_image_with_none_image_file():
"""
Test the method handles a non image file
"""
# Given and empty string
file_path = RESOURCE_PATH / 'serviceitem_custom_1.osj'
# WHEN testing for it
result = is_not_image_file(file_path)
# WHEN testing for it
result = is_not_image_file(file_path)
# THEN the result is false
assert result is True, 'The file is not an image file so the test should return True'
# THEN the result is false
assert result is True, 'The file is not an image file so the test should return True'

View File

@ -21,85 +21,72 @@
"""
Package to test the openlp.core.ui package.
"""
from unittest import TestCase
import pytest
from unittest.mock import MagicMock, patch
from PyQt5 import QtTest, QtWidgets
from PyQt5 import QtTest
from openlp.core.common.registry import Registry
from openlp.core.ui.filerenameform import FileRenameForm
from tests.helpers.testmixin import TestMixin
class TestStartFileRenameForm(TestCase, TestMixin):
@pytest.fixture()
def form(settings):
frm = FileRenameForm()
return frm
def setUp(self):
"""
Create the UI
"""
Registry.create()
self.setup_application()
self.main_window = QtWidgets.QMainWindow()
Registry().register('main_window', self.main_window)
self.form = 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
def test_window_title(form):
"""
Test the windowTitle of the FileRenameDialog
"""
# GIVEN: A mocked QDialog.exec() method
with patch('PyQt5.QtWidgets.QDialog.exec'):
def test_window_title(self):
"""
Test the windowTitle of the FileRenameDialog
"""
# GIVEN: A mocked QDialog.exec() method
with patch('PyQt5.QtWidgets.QDialog.exec'):
# WHEN: The form is executed with no args
form.exec()
# WHEN: The form is executed with no args
self.form.exec()
# THEN: the window title is set correctly
assert form.windowTitle() == 'File Rename', 'The window title should be "File Rename"'
# THEN: the window title is set correctly
assert self.form.windowTitle() == 'File Rename', 'The window title should be "File Rename"'
# WHEN: The form is executed with False arg
form.exec(False)
# WHEN: The form is executed with False arg
self.form.exec(False)
# THEN: the window title is set correctly
assert form.windowTitle() == 'File Rename', 'The window title should be "File Rename"'
# THEN: the window title is set correctly
assert self.form.windowTitle() == 'File Rename', 'The window title should be "File Rename"'
# WHEN: The form is executed with True arg
form.exec(True)
# WHEN: The form is executed with True arg
self.form.exec(True)
# THEN: the window title is set correctly
assert form.windowTitle() == 'File Copy', 'The window title should be "File Copy"'
# THEN: the window title is set correctly
assert self.form.windowTitle() == 'File Copy', 'The window title should be "File Copy"'
def test_line_edit_focus(self):
"""
Regression test for bug1067251
Test that the file_name_edit setFocus has called with True when executed
"""
# GIVEN: A mocked QDialog.exec() method and mocked file_name_edit.setFocus() method.
with patch('PyQt5.QtWidgets.QDialog.exec'):
mocked_set_focus = MagicMock()
self.form.file_name_edit.setFocus = mocked_set_focus
def test_line_edit_focus(form):
"""
Regression test for bug1067251
Test that the file_name_edit setFocus has called with True when executed
"""
# GIVEN: A mocked QDialog.exec() method and mocked file_name_edit.setFocus() method.
with patch('PyQt5.QtWidgets.QDialog.exec'):
mocked_set_focus = MagicMock()
form.file_name_edit.setFocus = mocked_set_focus
# WHEN: The form is executed
self.form.exec()
# WHEN: The form is executed
form.exec()
# THEN: the setFocus method of the file_name_edit has been called with True
mocked_set_focus.assert_called_with()
# THEN: the setFocus method of the file_name_edit has been called with True
mocked_set_focus.assert_called_with()
def test_file_name_validation(self):
"""
Test the file_name_edit 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.file_name_edit, r'I/n\\v?a*l|i<d> \F[i\l]e" :N+a%me')
def test_file_name_validation(form):
"""
Test the file_name_edit validation
"""
# GIVEN: QLineEdit with a validator set with illegal file name characters.
# THEN: The text in the QLineEdit should be the same as the input string with the invalid characters filtered
# out.
assert self.form.file_name_edit.text() == 'Invalid File Name'
# WHEN: 'Typing' a string containing invalid file characters.
QtTest.QTest.keyClicks(form.file_name_edit, r'I/n\\v?a*l|i<d> \F[i\l]e" :N+a%me')
# THEN: The text in the QLineEdit should be the same as the input string with the invalid characters filtered
# out.
assert form.file_name_edit.text() == 'Invalid File Name'

View File

@ -21,67 +21,64 @@
"""
Package to test the openlp.core.ui.firsttimeform package.
"""
import pytest
from pathlib import Path
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
from openlp.core.common.registry import Registry
from openlp.core.ui.firsttimeform import ThemeListWidgetItem
from openlp.core.ui.icons import UiIcons
from tests.helpers.testmixin import TestMixin
sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash',
'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'}
class TestThemeListWidgetItem(TestCase, TestMixin):
def setUp(self):
self.sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash',
'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'}
Registry.create()
self.registry = Registry()
mocked_app = MagicMock()
mocked_app.worker_threads = {}
Registry().register('application', mocked_app)
self.setup_application()
@pytest.yield_fixture()
def mocked_set_icon(mock_settings):
move_to_thread_patcher = patch('openlp.core.ui.firsttimeform.DownloadWorker.moveToThread').start()
set_icon_patcher = patch('openlp.core.ui.firsttimeform.ThemeListWidgetItem.setIcon').start()
q_thread_patcher = patch('openlp.core.ui.firsttimeform.QtCore.QThread').start()
mocked_app = MagicMock()
mocked_app.worker_threads = {}
Registry().remove('application')
Registry().register('application', mocked_app)
yield set_icon_patcher
move_to_thread_patcher.stop()
set_icon_patcher.stop()
q_thread_patcher.stop()
move_to_thread_patcher = patch('openlp.core.ui.firsttimeform.DownloadWorker.moveToThread')
self.addCleanup(move_to_thread_patcher.stop)
move_to_thread_patcher.start()
set_icon_patcher = patch('openlp.core.ui.firsttimeform.ThemeListWidgetItem.setIcon')
self.addCleanup(set_icon_patcher.stop)
self.mocked_set_icon = set_icon_patcher.start()
q_thread_patcher = patch('openlp.core.ui.firsttimeform.QtCore.QThread')
self.addCleanup(q_thread_patcher.stop)
q_thread_patcher.start()
def test_failed_download(self):
"""
Test that icon get set to indicate a failure when `DownloadWorker` emits the download_failed signal
"""
# GIVEN: An instance of `DownloadWorker`
instance = ThemeListWidgetItem('url', self.sample_theme_data, MagicMock()) # noqa Overcome GC issue
worker_threads = Registry().get('application').worker_threads
worker = worker_threads['thumbnail_download_BlueBurst.png']['worker']
def test_failed_download(mocked_set_icon):
"""
Test that icon get set to indicate a failure when `DownloadWorker` emits the download_failed signal
"""
# GIVEN: An instance of `DownloadWorker`
instance = ThemeListWidgetItem('url', sample_theme_data, MagicMock()) # noqa Overcome GC issue
worker_threads = Registry().get('application').worker_threads
worker = worker_threads['thumbnail_download_BlueBurst.png']['worker']
# WHEN: `DownloadWorker` emits the `download_failed` signal
worker.download_failed.emit()
# WHEN: `DownloadWorker` emits the `download_failed` signal
worker.download_failed.emit()
# THEN: Then the initial loading icon should have been replaced by the exception icon
self.mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(UiIcons().exception)])
# THEN: Then the initial loading icon should have been replaced by the exception icon
mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(UiIcons().exception)])
@patch('openlp.core.ui.firsttimeform.build_icon')
def test_successful_download(self, mocked_build_icon):
"""
Test that the downloaded thumbnail is set as the icon when `DownloadWorker` emits the `download_succeeded`
signal
"""
# GIVEN: An instance of `DownloadWorker`
instance = ThemeListWidgetItem('url', self.sample_theme_data, MagicMock()) # noqa Overcome GC issue
worker_threads = Registry().get('application').worker_threads
worker = worker_threads['thumbnail_download_BlueBurst.png']['worker']
test_path = Path('downlaoded', 'file')
# WHEN: `DownloadWorker` emits the `download_succeeded` signal
worker.download_succeeded.emit(test_path)
@patch('openlp.core.ui.firsttimeform.build_icon')
def test_successful_download(mocked_build_icon, mocked_set_icon):
"""
Test that the downloaded thumbnail is set as the icon when `DownloadWorker` emits the `download_succeeded`
signal
"""
# GIVEN: An instance of `DownloadWorker`
instance = ThemeListWidgetItem('url', sample_theme_data, MagicMock()) # noqa Overcome GC issue
worker_threads = Registry().get('application').worker_threads
worker = worker_threads['thumbnail_download_BlueBurst.png']['worker']
test_path = Path('downlaoded', 'file')
# THEN: An icon should have been built from the downloaded file and used to replace the loading icon
mocked_build_icon.assert_called_once_with(test_path)
self.mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(mocked_build_icon())])
# WHEN: `DownloadWorker` emits the `download_succeeded` signal
worker.download_succeeded.emit(test_path)
# THEN: An icon should have been built from the downloaded file and used to replace the loading icon
mocked_build_icon.assert_called_once_with(test_path)
mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(mocked_build_icon())])

View File

@ -21,101 +21,81 @@
"""
Package to test the openlp.core.ui.mainwindow package.
"""
from unittest import TestCase, skipIf, skip
import pytest
from unittest.mock import MagicMock, patch
from PyQt5 import QtGui
from openlp.core.state import State
from openlp.core.common import is_macosx
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib.plugin import PluginStatus
from openlp.core.ui.mainwindow import MainWindow
from tests.helpers.testmixin import TestMixin
@skipIf(is_macosx(), 'Skip on macOS until we can figure out what the problem is or the tests are refactored')
class TestMainWindow(TestCase, TestMixin):
@pytest.fixture()
def main_window(settings, state):
"""
Create the UI
"""
Registry().set_flag('no_web_server', True)
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
mocked_plugin.icon = QtGui.QIcon()
Registry().register('mock_plugin', mocked_plugin)
State().add_service("mock", 1, is_plugin=True, status=PluginStatus.Active)
# Mock classes and methods used by mainwindow.
with patch('openlp.core.ui.mainwindow.SettingsForm'), \
patch('openlp.core.ui.mainwindow.OpenLPDockWidget'), \
patch('openlp.core.ui.mainwindow.QtWidgets.QToolBox'), \
patch('openlp.core.ui.mainwindow.QtWidgets.QMainWindow.addDockWidget'), \
patch('openlp.core.ui.mainwindow.ServiceManager'), \
patch('openlp.core.ui.mainwindow.ThemeManager'), \
patch('openlp.core.ui.mainwindow.ProjectorManager'), \
patch('openlp.core.ui.mainwindow.HttpServer'), \
patch('openlp.core.ui.mainwindow.WebSocketServer'), \
patch('openlp.core.ui.mainwindow.start_zeroconf'), \
patch('openlp.core.ui.mainwindow.PluginForm'):
return MainWindow()
def setUp(self):
"""
Create the UI
"""
Registry.create()
self.registry = Registry()
self.setup_application()
# Mock cursor busy/normal methods.
self.app.set_busy_cursor = MagicMock()
self.app.set_normal_cursor = MagicMock()
self.app.args = []
Registry().register('application', self.app)
Registry().register('settings', Settings())
Registry().set_flag('no_web_server', True)
mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active
mocked_plugin.icon = QtGui.QIcon()
Registry().register('mock_plugin', mocked_plugin)
State().load_settings()
State().add_service("mock", 1, is_plugin=True, status=PluginStatus.Active)
# Mock classes and methods used by mainwindow.
with patch('openlp.core.ui.mainwindow.SettingsForm'), \
patch('openlp.core.ui.mainwindow.OpenLPDockWidget'), \
patch('openlp.core.ui.mainwindow.QtWidgets.QToolBox'), \
patch('openlp.core.ui.mainwindow.QtWidgets.QMainWindow.addDockWidget'), \
patch('openlp.core.ui.mainwindow.ServiceManager'), \
patch('openlp.core.ui.mainwindow.ThemeManager'), \
patch('openlp.core.ui.mainwindow.ProjectorManager'), \
patch('openlp.core.ui.mainwindow.HttpServer'), \
patch('openlp.core.ui.mainwindow.WebSocketServer'), \
patch('openlp.core.ui.mainwindow.start_zeroconf'), \
patch('openlp.core.ui.mainwindow.PluginForm'):
self.main_window = MainWindow()
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
del self.main_window
def test_restore_current_media_manager_item(main_window):
"""
Regression test for bug #1152509.
"""
# save current plugin: True; current media plugin: 2
main_window.settings.setValue('advanced/save current plugin', True)
main_window.settings.setValue('advanced/current media plugin', 2)
@skip('Fix when migrate to PyTest')
def test_restore_current_media_manager_item(self):
"""
Regression test for bug #1152509.
"""
# GIVEN: Mocked Settings().value method.
with patch('openlp.core.ui.mainwindow.Settings.value') as mocked_value:
# save current plugin: True; current media plugin: 2
mocked_value.side_effect = [True, 2]
# WHEN: Call the restore method.
main_window.restore_current_media_manager_item()
# WHEN: Call the restore method.
self.main_window.restore_current_media_manager_item()
# THEN: The current widget should have been set.
main_window.media_tool_box.setCurrentIndex.assert_called_with(2)
# THEN: The current widget should have been set.
self.main_window.media_tool_box.setCurrentIndex.assert_called_with(2)
def test_projector_manager_dock_locked(self):
"""
Projector Manager enable UI options - bug #1390702
"""
# GIVEN: A mocked projector manager dock item:
projector_dock = self.main_window.projector_manager_dock
def test_projector_manager_dock_locked(main_window):
"""
Projector Manager enable UI options - bug #1390702
"""
# GIVEN: A mocked projector manager dock item:
projector_dock = main_window.projector_manager_dock
# WHEN: main_window.lock_panel action is triggered
self.main_window.lock_panel.triggered.emit(True)
# WHEN: main_window.lock_panel action is triggered
main_window.lock_panel.triggered.emit(True)
# THEN: Projector manager dock should have been called with disable UI features
projector_dock.setFeatures.assert_called_with(0)
# THEN: Projector manager dock should have been called with disable UI features
projector_dock.setFeatures.assert_called_with(0)
def test_projector_manager_dock_unlocked(self):
"""
Projector Manager disable UI options - bug #1390702
"""
# GIVEN: A mocked projector manager dock item:
projector_dock = self.main_window.projector_manager_dock
# WHEN: main_window.lock_panel action is triggered
self.main_window.lock_panel.triggered.emit(False)
def test_projector_manager_dock_unlocked(main_window):
"""
Projector Manager disable UI options - bug #1390702
"""
# GIVEN: A mocked projector manager dock item:
projector_dock = main_window.projector_manager_dock
# THEN: Projector manager dock should have been called with enable UI features
projector_dock.setFeatures.assert_called_with(7)
# WHEN: main_window.lock_panel action is triggered
main_window.lock_panel.triggered.emit(False)
# THEN: Projector manager dock should have been called with enable UI features
projector_dock.setFeatures.assert_called_with(7)