Merge branch 'tests_plugin_3' into 'master'

Tests plugin 3 - and Fix #272

Closes #272

See merge request openlp/openlp!152
This commit is contained in:
Tim Bentley 2020-03-08 21:45:42 +00:00
commit 80a2cfc2d2
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.stream_label.setText('{text}:'.format(text=UiStrings().LiveStream))
self.image_path_edit.filters = \ self.image_path_edit.filters = \
'{name};;{text} (*)'.format(name=get_images_filter(), text=UiStrings().AllFiles) '{name};;{text} (*)'.format(name=get_images_filter(), text=UiStrings().AllFiles)
visible_formats = '(*.{name})'.format(name='; *.'.join(VIDEO_EXT)) visible_formats = '({name})'.format(name='; '.join(VIDEO_EXT))
actual_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'), video_filter = '{trans} {visible} {actual}'.format(trans=translate('OpenLP', 'Video Files'),
visible=visible_formats, actual=actual_formats) visible=visible_formats, actual=actual_formats)
self.video_path_edit.filters = '{video};;{ui} (*)'.format(video=video_filter, ui=UiStrings().AllFiles) 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 import shutil
from tempfile import mkdtemp from tempfile import mkdtemp
from unittest import TestCase, SkipTest from unittest import TestCase, skipIf, SkipTest
from unittest.mock import MagicMock, patch, call 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 import is_macosx
from openlp.core.common.path import Path from openlp.core.common.path import Path
from openlp.plugins.presentations.lib.maclocontroller import MacLOController, MacLODocument 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') 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): class TestMacLOController(TestCase, TestMixin):
""" """
Test the MacLOController Class Test the MacLOController Class
@ -50,8 +52,10 @@ class TestMacLOController(TestCase, TestMixin):
""" """
Set up the patches and mocks need for all tests. Set up the patches and mocks need for all tests.
""" """
Registry.create()
self.setup_application() self.setup_application()
self.build_settings() self.build_settings()
Registry().register('settings', self.settings)
self.mock_plugin = MagicMock() self.mock_plugin = MagicMock()
self.temp_folder = mkdtemp() self.temp_folder = mkdtemp()
self.mock_plugin.settings_section = self.temp_folder 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) load_pdf_pictures(exe_path, pdf_env)
def test_loading_pdf_using_pymupdf(): def test_loading_pdf_using_pymupdf(pdf_env):
try: try:
import fitz # noqa: F401 import fitz # noqa: F401
except ImportError: except ImportError:
pytest.skip('PyMuPDF is not installed') pytest.skip('PyMuPDF is not installed')
load_pdf(None) load_pdf(None, pdf_env)
load_pdf_pictures(None) load_pdf_pictures(None, pdf_env)
@patch('openlp.plugins.presentations.lib.pdfcontroller.check_binary_exists') @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. This module contains tests for the db submodule of the Songs plugin.
""" """
import pytest
import os import os
import shutil import shutil
from tempfile import mkdtemp 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.core.lib.db import upgrade_db
from openlp.plugins.songs.lib import upgrade from openlp.plugins.songs.lib import upgrade
from openlp.plugins.songs.lib.db import Author, AuthorType, Book, Song from openlp.plugins.songs.lib.db import Author, AuthorType, Book, Song
from tests.utils.constants import TEST_RESOURCES_PATH 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): # WHEN: We add an author to the song
""" song.add_author(author)
Setup for tests
"""
self.tmp_folder = mkdtemp()
Registry.create()
Registry().register('settings', Settings())
def tearDown(self): # THEN: The author should have been added with author_type=None
""" assert 1 == len(song.authors_songs)
Clean up after tests assert "Max" == song.authors_songs[0].author.first_name
""" assert "Mustermann" == song.authors_songs[0].author.last_name
# Ignore errors since windows can have problems with locked files assert song.authors_songs[0].author_type is None
shutil.rmtree(self.tmp_folder, ignore_errors=True)
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 def test_add_author_with_type():
song.add_author(author) """
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 # WHEN: We add an author to the song
assert 1 == len(song.authors_songs) song.add_author(author, AuthorType.Words)
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_with_type(self): # THEN: The author should have been added with author_type=None
""" assert 1 == len(song.authors_songs)
Test adding an author with a type specified to a song assert "Max" == song.authors_songs[0].author.first_name
""" assert "Mustermann" == song.authors_songs[0].author.last_name
# GIVEN: A song and an author assert AuthorType.Words == song.authors_songs[0].author_type
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, AuthorType.Words)
# THEN: The author should have been added with author_type=None def test_remove_author():
assert 1 == len(song.authors_songs) """
assert "Max" == song.authors_songs[0].author.first_name Test removing an author from a song
assert "Mustermann" == song.authors_songs[0].author.last_name """
assert AuthorType.Words == song.authors_songs[0].author_type # GIVEN: A song with an author
song = Song()
song.authors_songs = []
author = Author()
song.add_author(author)
def test_remove_author(self): # WHEN: We remove the author
""" song.remove_author(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)
# WHEN: We remove the author # THEN: It should have been removed
song.remove_author(author) 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): def test_remove_author_with_type():
""" """
Test removing an author with a type specified from a song Test removing an author with a type specified from a song
""" """
# GIVEN: A song with two authors # GIVEN: A song with two authors
song = Song() song = Song()
song.authors_songs = [] song.authors_songs = []
author = Author() author = Author()
song.add_author(author) song.add_author(author)
song.add_author(author, AuthorType.Translation) song.add_author(author, AuthorType.Translation)
# WHEN: We remove the author with a certain type # WHEN: We remove the author with a certain type
song.remove_author(author, AuthorType.Translation) song.remove_author(author, AuthorType.Translation)
# THEN: It should have been removed and the other author should still be there # THEN: It should have been removed and the other author should still be there
assert 1 == len(song.authors_songs) assert 1 == len(song.authors_songs)
assert song.authors_songs[0].author_type is None 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 def test_get_author_type_from_translated_text():
author_type = AuthorType.from_translated_text(author_type_name) """
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 # WHEN: We call the method
assert author_type == AuthorType.Words author_type = AuthorType.from_translated_text(author_type_name)
def test_author_get_display_name(self): # THEN: The type should be correct
""" assert author_type == AuthorType.Words
Test that the display name of an author is correct
"""
# GIVEN: An author
author = Author()
author.display_name = "John Doe"
# WHEN: We call the get_display_name() function
display_name = author.get_display_name()
# THEN: It should return only the name def test_author_get_display_name():
assert "John Doe" == 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): # WHEN: We call the get_display_name() function
""" display_name = author.get_display_name()
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 # THEN: It should return only the name
display_name = author.get_display_name(AuthorType.Words) 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): def test_author_get_display_name_with_type_words():
""" """
Test that the display name of an author with a type is correct (Translation) Test that the display name of an author with a type is correct (Words)
""" """
# GIVEN: An author # GIVEN: An author
author = Author() author = Author()
author.display_name = "John Doe" author.display_name = "John Doe"
# WHEN: We call the get_display_name() function # WHEN: We call the get_display_name() function
display_name = author.get_display_name(AuthorType.Translation) display_name = author.get_display_name(AuthorType.Words)
# THEN: It should return the name with the type in brackets # THEN: It should return the name with the type in brackets
assert "John Doe (Translation)" == display_name 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 def test_author_get_display_name_with_type_translation():
song.add_songbook_entry(songbook, "120") """
song.add_songbook_entry(songbook, "550A") 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 # WHEN: We call the get_display_name() function
assert len(song.songbook_entries) == 2, 'There should be two Songbook entries.' display_name = author.get_display_name(AuthorType.Translation)
def test_upgrade_old_song_db(self): # THEN: It should return the name with the type in brackets
""" assert "John Doe (Translation)" == display_name
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
# 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 def test_add_songbooks():
assert updated_to_version == latest_version, 'The song DB should have been upgrade to the latest version' """
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): # WHEN: We add two songbooks to a Song
""" song.add_songbook_entry(songbook, "120")
Test that we can upgrade an invalid song db to the current schema song.add_songbook_entry(songbook, "550A")
"""
# 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: upgrading the db # THEN: The song should have two songbook entries
updated_to_version, latest_version = upgrade_db(db_url, upgrade) 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. This module contains tests for the lib submodule of the Songs plugin.
""" """
import pytest import pytest
from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from PyQt5 import QtCore from PyQt5 import QtCore
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib.serviceitem import ServiceItem from openlp.core.lib.serviceitem import ServiceItem
from openlp.plugins.songs.lib.db import AuthorType, Song from openlp.plugins.songs.lib.db import AuthorType, Song
from openlp.plugins.songs.lib.mediaitem import SongMediaItem from openlp.plugins.songs.lib.mediaitem import SongMediaItem
from tests.helpers.testmixin import TestMixin
__default_settings__ = { __default_settings__ = {
'songs/footer template': """ 'songs/footer template': """
@ -92,7 +89,7 @@ ${title}<br/>
@pytest.yield_fixture @pytest.yield_fixture
def mocked_media_item(mock_settings): def media_item(settings):
Registry().register('service_list', MagicMock()) Registry().register('service_list', MagicMock())
Registry().register('main_window', MagicMock()) Registry().register('main_window', MagicMock())
mocked_plugin = MagicMock() mocked_plugin = MagicMock()
@ -114,505 +111,482 @@ def mocked_media_item(mock_settings):
yield media_item 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): # GIVEN: Search results, plus a mocked QtListWidgetItem
""" with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
Set up the components need for all tests. patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
""" mock_search_results = []
Registry.create() mock_song = MagicMock()
Registry().register('service_list', MagicMock()) mock_song.id = 1
Registry().register('main_window', MagicMock()) mock_song.title = 'My Song'
self.mocked_plugin = MagicMock() mock_song.sort_key = 'My Song'
with patch('openlp.core.lib.mediamanageritem.MediaManagerItem._setup'), \ mock_song.authors = []
patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'): mock_song_temp = MagicMock()
self.media_item = SongMediaItem(None, self.mocked_plugin) mock_song_temp.id = 2
self.media_item.save_auto_select_id = MagicMock() mock_song_temp.title = 'My Temporary'
self.media_item.list_view = MagicMock() mock_song_temp.sort_key = 'My Temporary'
self.media_item.list_view.save_auto_select_id = MagicMock() mock_song_temp.authors = []
self.media_item.list_view.clear = MagicMock() mock_author = MagicMock()
self.media_item.list_view.addItem = MagicMock() mock_author.display_name = 'My Author'
self.media_item.list_view.setCurrentItem = MagicMock() mock_song.authors.append(mock_author)
self.media_item.auto_select_id = -1 mock_song_temp.authors.append(mock_author)
self.media_item.display_songbook = False mock_song.temporary = False
self.media_item.display_copyright_symbol = False mock_song_temp.temporary = True
self.setup_application() mock_search_results.append(mock_song)
self.build_settings() mock_search_results.append(mock_song_temp)
Settings().extend_default_settings(__default_settings__) mock_qlist_widget = MagicMock()
self.settings = self.setting MockedQListWidgetItem.return_value = mock_qlist_widget
Registry().register('settings', self.settings) media_item.auto_select_id = 1
QtCore.QLocale.setDefault(QtCore.QLocale('en_GB'))
def tearDown(self): # WHEN: I display song search results
""" media_item.display_results_song(mock_search_results)
Delete all the C++ objects at the end so that we don't have a segfault
"""
self.destroy_settings()
def test_display_results_song(self): # THEN: The current list view is cleared, the widget is created, and the relevant attributes set
""" media_item.list_view.clear.assert_called_with()
Test displaying song search results with basic song media_item.save_auto_select_id.assert_called_with()
""" MockedQListWidgetItem.assert_called_once_with('My Song (My Author)')
# GIVEN: Search results, plus a mocked QtListWidgetItem mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \ media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole: media_item.list_view.setCurrentItem.assert_called_with(mock_qlist_widget)
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
# 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 def test_display_results_author(media_item):
self.media_item.list_view.clear.assert_called_with() """
self.media_item.save_auto_select_id.assert_called_with() Test displaying song search results grouped by author with basic song
MockedQListWidgetItem.assert_called_once_with('My Song (My Author)') """
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id) # GIVEN: Search results grouped by author, plus a mocked QtListWidgetItem
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget) with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
self.media_item.list_view.setCurrentItem.assert_called_with(mock_qlist_widget) 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): # WHEN: I display song search results grouped by author
""" media_item.display_results_author(mock_search_results)
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 # THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.display_results_author(mock_search_results) 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): def test_display_results_book(media_item):
""" """
Test displaying song search results grouped by book and entry with basic song 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 # GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \ with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole: patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = [('1', 'My Book', 'My Song', 1)] mock_search_results = [('1', 'My Book', 'My Song', 1)]
mock_qlist_widget = MagicMock() mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget 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)]
# WHEN: I display song search results grouped by book # 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, # THEN: The current list view is cleared, the widget is created, and the relevant attributes set
# grouped first by book, then by number, then by song title media_item.list_view.clear.assert_called_with()
assert search_results == [('1', 'My Book', 'My Song', 1), MockedQListWidgetItem.assert_called_once_with('My Book #1: My Song')
('2', 'My Book', 'Your Song', 7), mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, 1)
('10', 'My Book', 'Our Song', 12), media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
('2', 'Thy Book', 'A Song', 8),
('2', 'Thy Book', 'Thy Song', 50)]
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 def test_songbook_natural_sorting(media_item):
self.media_item.display_results_topic(mock_search_results) """
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 # WHEN: I display song search results grouped by book
self.media_item.list_view.clear.assert_called_with() media_item.display_results_book(search_results)
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)
def test_display_results_themes(self): # THEN: The songbooks are sorted inplace in the right (natural) order,
""" # grouped first by book, then by number, then by song title
Test displaying song search results sorted by theme with basic song assert search_results == [('1', 'My Book', 'My Song', 1),
""" ('2', 'My Book', 'Your Song', 7),
# GIVEN: Search results sorted by theme, plus a mocked QtListWidgetItem ('10', 'My Book', 'Our Song', 12),
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \ ('2', 'Thy Book', 'A Song', 8),
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole: ('2', 'Thy Book', 'Thy Song', 50)]
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
# 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 def test_display_results_topic(media_item):
self.media_item.list_view.clear.assert_called_with() """
MockedQListWidgetItem.assert_called_once_with('My Theme (My Song)') Test displaying song search results grouped by topic with basic 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) # GIVEN: Search results grouped by topic, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
def test_display_results_cclinumber(self): patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
""" mock_search_results = []
Test displaying song search results sorted by CCLI number with basic song mock_topic = MagicMock()
"""
# 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
mock_song = 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.title = 'My Song'
mock_song.authors_songs = [] mock_song.sort_key = 'My Song'
mock_author = MagicMock() mock_song.temporary = False
mock_author.display_name = 'my author' mock_song_temp.id = 2
mock_author_song = MagicMock() mock_song_temp.title = 'My Temporary'
mock_author_song.author = mock_author mock_song_temp.sort_key = 'My Temporary'
mock_author_song.author_type = AuthorType.Music mock_song_temp.temporary = True
mock_song.authors_songs.append(mock_author_song) mock_topic.songs.append(mock_song)
mock_author = MagicMock() mock_topic.songs.append(mock_song_temp)
mock_author.display_name = 'another author' mock_search_results.append(mock_topic)
mock_author_song = MagicMock() mock_qlist_widget = MagicMock()
mock_author_song.author = mock_author MockedQListWidgetItem.return_value = mock_qlist_widget
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 # WHEN: I display song search results grouped by topic
author_list = self.media_item.generate_footer(service_item, mock_song) media_item.display_results_topic(mock_search_results)
# THEN: I get the following Array returned # THEN: The current list view is cleared, the widget is created, and the relevant attributes set
assert service_item.raw_footer == ['My Song', 'Words: another author', 'Music: my author', media_item.list_view.clear.assert_called_with()
'Translation: translator', '© My copyright'], \ MockedQListWidgetItem.assert_called_once_with('My Topic (My Song)')
'The array should be returned correctly with a song, two authors and copyright' mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
assert author_list == ['another author', 'my author', 'translator'], \ media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
'The author list should be returned correctly with two authors'
def test_build_song_footer_base_ccli(self):
""" def test_display_results_themes(media_item):
Test build songs footer with basic song and a CCLI number """
""" Test displaying song search results sorted by theme with basic song
# GIVEN: A Song and a Service Item and a configured CCLI license """
# 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 = MagicMock()
mock_song_temp = MagicMock()
mock_song.id = 1
mock_song.title = 'My Song' mock_song.title = 'My Song'
mock_song.copyright = 'My copyright' mock_song.sort_key = 'My Song'
mock_song.songbook_entries = [] mock_song.theme_name = 'My Theme'
service_item = ServiceItem(None) mock_song.temporary = False
self.settings.setValue('core/ccli number', '1234') 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 # WHEN: I display song search results sorted by theme
self.media_item.generate_footer(service_item, mock_song) media_item.display_results_themes(mock_search_results)
# THEN: I get the following Array returned # THEN: The current list view is cleared, the widget is created, and the relevant attributes set
assert service_item.raw_footer == ['My Song', '© My copyright', 'CCLI License: 1234'], \ media_item.list_view.clear.assert_called_with()
'The array should be returned correctly with a song, an author, copyright and ccli' 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 def test_display_results_cclinumber(media_item):
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' Test displaying song search results sorted by CCLI number with basic song
"""
def test_build_song_footer_base_songbook(self): # GIVEN: Search results sorted by CCLI number, plus a mocked QtListWidgetItem
""" with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
Test build songs footer with basic song and multiple songbooks patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
""" mock_search_results = []
# 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
mock_song = MagicMock() mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_song.id = 1
mock_song.title = 'My Song' mock_song.title = 'My Song'
mock_song.copyright = 'My copyright' mock_song.sort_key = 'My Song'
mock_song.songbook_entries = [] mock_song.ccli_number = '12345'
service_item = ServiceItem(None) 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 # WHEN: I display song search results sorted by CCLI number
self.media_item.generate_footer(service_item, mock_song) media_item.display_results_cclinumber(mock_search_results)
# THEN: The copyright symbol should be in the footer # THEN: The current list view is cleared, the widget is created, and the relevant attributes set
assert service_item.raw_footer == ['My Song', '© My copyright'] media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('12345 (My Song)')
def test_build_song_footer_copyright_disabled(self): mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
""" media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
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'
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 Test build songs footer with basic song and one author
""" """
# GIVEN: A Song and a Service Item, mocked settings # GIVEN: A Song and a Service Item, mocked settings
media_item.settings.setValue('core/ccli number', "0")
mocked_media_item.settings.value.side_effect = [False, "", "0"] media_item.settings.setValue('songs/footer template', "")
with patch('mako.template.Template.render_unicode') as MockedRenderer: with patch('mako.template.Template.render_unicode') as MockedRenderer:
mock_song = MagicMock() mock_song = MagicMock()
mock_song.title = 'My Song' 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) service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings # 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 # THEN: The mako function was called with the following arguments
args = {'authors_translation': [], 'authors_music_label': 'Music', args = {'authors_translation': [], 'authors_music_label': 'Music',

View File

@ -21,73 +21,70 @@
""" """
This module contains tests for the SongFormat class This module contains tests for the SongFormat class
""" """
from unittest import TestCase
from openlp.plugins.songs.lib.importer import SongFormat 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): def test_get_attributed_no_attributes():
""" """
Test that SongFormat.get(song_format) returns all attributes associated with the given song_format Test that SongFormat.get(song_format) returns all attributes associated with the given song_format
""" """
# GIVEN: A SongFormat # GIVEN: A SongFormat
# WHEN: Retrieving all attributes of a SongFormat # WHEN: Retrieving all attributes of a SongFormat
for song_format in SongFormat.get_format_list(): for song_format in SongFormat.get_format_list():
# THEN: All attributes associated with the SongFormat should be returned # THEN: All attributes associated with the SongFormat should be returned
assert SongFormat.get(song_format) == SongFormat.__attributes__[song_format], \ assert SongFormat.get(song_format) == SongFormat.__attributes__[song_format], \
"The returned attributes don't match the stored ones" "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): def test_get_attributed_single_attribute():
""" """
Test that multiple attributes can be retrieved for a song_format Test that SongFormat.get(song_format, attribute) returns only one -and the correct- attribute
""" """
# GIVEN: A SongFormat # GIVEN: A SongFormat
# WHEN: Retrieving multiple attributes at the same time for song_format in SongFormat.get_format_list():
for song_format in SongFormat.get_format_list(): # WHEN: Retrieving an attribute that overrides the default values
# THEN: Return all attributes that were specified for attribute in SongFormat.get(song_format).keys():
assert len(SongFormat.get(song_format, 'canDisable', 'availability')) == 2, \ # THEN: Return the attribute
"Did not return the correct number of attributes when retrieving multiple attributes at once" 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):
""" def test_get_attributed_multiple_attributes():
Test that get_format_list() returns a list that is ordered """
according to the order specified in SongFormat Test that multiple attributes can be retrieved for a song_format
""" """
# GIVEN: The SongFormat class # GIVEN: A SongFormat
# WHEN: Retrieving all formats # WHEN: Retrieving multiple attributes at the same time
# THEN: The returned list should be sorted according to the ordering defined in SongFormat for song_format in SongFormat.get_format_list():
assert sorted(SongFormat.get_format_list()) == SongFormat.get_format_list(), \ # THEN: Return all attributes that were specified
"The list returned should be sorted according to the ordering in SongFormat" 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. 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 unittest.mock import MagicMock, patch
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.plugins.songs.lib.songstab import SongsTab from openlp.plugins.songs.lib.songstab import SongsTab
from tests.helpers.testmixin import TestMixin
__default_settings__ = { __default_settings__ = {
'songs/footer template': """${title}""", '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): def test_neolatin_notation_on_load(form):
""" """
Delete all the C++ objects at the end so that we don't have a segfault Test the corrent notation is selected on load
""" """
del self.parent # GIVEN: neo-latin notation in the settings
del self.form form.settings.setValue('songs/chord notation', 'neo-latin')
self.destroy_settings() # 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): def test_invalid_notation_on_load(form):
""" """
Test the corrent notation is selected on load Test the invalid notation in settings reverts to english
""" """
# GIVEN: neo-latin notation in the settings # GIVEN: gibberish notation in the settings
self.setting.setValue('songs/chord notation', 'neo-latin') form.settings.setValue('songs/chord notation', 'gibberish')
# WHEN: Load is invoked # WHEN: Load is invoked
self.form.load() form.load()
# THEN: The neo-latin radio button should be checked # THEN: The english radio button should be checked
assert self.form.neolatin_notation_radio_button.isChecked() is True 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): def test_save_check_box_settings(form):
""" """
Test check box options are correctly saved Test check box options are correctly saved
""" """
# GIVEN: A arrangement of enabled/disabled check boxes # GIVEN: A arrangement of enabled/disabled check boxes
self.form.on_search_as_type_check_box_changed(QtCore.Qt.Unchecked) form.on_search_as_type_check_box_changed(QtCore.Qt.Unchecked)
self.form.on_tool_bar_active_check_box_changed(QtCore.Qt.Checked) form.on_tool_bar_active_check_box_changed(QtCore.Qt.Checked)
self.form.on_update_on_edit_check_box_changed(QtCore.Qt.Unchecked) form.on_update_on_edit_check_box_changed(QtCore.Qt.Unchecked)
self.form.on_add_from_service_check_box_changed(QtCore.Qt.Checked) form.on_add_from_service_check_box_changed(QtCore.Qt.Checked)
self.form.on_songbook_slide_check_box_changed(QtCore.Qt.Unchecked) form.on_songbook_slide_check_box_changed(QtCore.Qt.Unchecked)
self.form.on_mainview_chords_check_box_changed(QtCore.Qt.Checked) form.on_mainview_chords_check_box_changed(QtCore.Qt.Checked)
self.form.on_disable_chords_import_check_box_changed(QtCore.Qt.Unchecked) form.on_disable_chords_import_check_box_changed(QtCore.Qt.Unchecked)
# WHEN: Save is invoked # WHEN: Save is invoked
self.form.save() form.save()
# THEN: The correct values should be stored in the settings # THEN: The correct values should be stored in the settings
# song_search is currently unused # song_search is currently unused
assert self.setting.value('songs/display songbar') is True assert form.settings.value('songs/display songbar') is True
assert self.setting.value('songs/update service on edit') is False assert form.settings.value('songs/update service on edit') is False
assert self.setting.value('songs/add song from service') is True assert form.settings.value('songs/add song from service') is True
assert self.setting.value('songs/add songbook slide') is False assert form.settings.value('songs/add songbook slide') is False
assert self.setting.value('songs/mainview chords') is True assert form.settings.value('songs/mainview chords') is True
assert self.setting.value('songs/disable chords import') is False 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): def test_english_notation_button(form):
""" """
Test notation button clicked handler Test notation button clicked handler
""" """
# GIVEN: A normal song form # GIVEN: A normal song form
# WHEN: german notation clicked # WHEN: english notation clicked
self.form.on_german_notation_button_clicked() form.on_english_notation_button_clicked()
# THEN: Chord notation should be correct # THEN: Chord notation should be correct
assert self.form.chord_notation == 'german' 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_german_notation_button(form):
def test_footer_nochange(self, mocked_settings_set_val): """
""" Test notation button clicked handler
Test the footer is not saved when not changed """
""" # GIVEN: A normal song form
# GIVEN: A normal song form # WHEN: german notation clicked
# WHEN: save is invoked form.on_german_notation_button_clicked()
self.form.save() # THEN: Chord notation should be correct
# THEN: footer should not have been saved (one less call than the change test below) assert form.chord_notation == 'german'
assert mocked_settings_set_val.call_count == 8
@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): def test_neolatin_notation_button(form):
""" """
Test the footer is reset when reset clicked Test notation button clicked handler
""" """
# GIVEN: A default footer and different content in the edit box # GIVEN: A normal song form
self.setting.setValue('songs/footer template', 'hello') # WHEN: neolatin notation clicked
self.form.footer_edit_box.setPlainText('A different footer') form.on_neolatin_notation_button_clicked()
# WHEN: reset is invoked # THEN: Chord notation should be correct
self.form.on_footer_reset_button_clicked() assert form.chord_notation == 'neo-latin'
# 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_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): @patch('openlp.core.common.settings.Settings.setValue')
""" def test_footer_nochange(mocked_settings_set_val, form):
Test save after visiting the page triggers post processing. """
""" Test the footer is not saved when not changed
# GIVEN: Form has been visited. """
self.form.tab_visited = True # GIVEN: A normal song form
# WHEN: the save is invoked # WHEN: save is invoked
self.form.save() form.save()
# THEN: the post process should be requested # THEN: footer should not have been saved (one less call than the change test below)
assert 1 == self.form.settings_form.register_post_process.call_count, \ assert mocked_settings_set_val.call_count == 8
'Songs Post processing should have been requested'
@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. Functional tests to test the AppLocation class and related methods.
""" """
from unittest import TestCase
from openlp.core.common import is_not_image_file from openlp.core.common import is_not_image_file
from tests.utils.constants import RESOURCE_PATH 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): # WHEN testing for it
""" result = is_not_image_file(file_name)
Some pre-test setup required.
"""
self.setup_application()
def test_is_not_image_empty(self): # THEN the result is false
""" assert result is True, 'The missing file test should return True'
Test the method handles an empty string
"""
# Given and empty string
file_name = ""
# WHEN testing for it
result = is_not_image_file(file_name)
# THEN the result is false def test_is_not_image_with_image_file():
assert result is True, 'The missing file test should return True' """
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): # WHEN testing for it
""" result = is_not_image_file(file_path)
Test the method handles an image file
"""
# Given and empty string
file_path = RESOURCE_PATH / 'church.jpg'
# WHEN testing for it # THEN the result is false
result = is_not_image_file(file_path) 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): def test_is_not_image_with_none_image_file():
""" """
Test the method handles a non image file Test the method handles a non image file
""" """
# Given and empty string # Given and empty string
file_path = RESOURCE_PATH / 'serviceitem_custom_1.osj' file_path = RESOURCE_PATH / 'serviceitem_custom_1.osj'
# WHEN testing for it # WHEN testing for it
result = is_not_image_file(file_path) result = is_not_image_file(file_path)
# THEN the result is false # THEN the result is false
assert result is True, 'The file is not an image file so the test should return True' 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. Package to test the openlp.core.ui package.
""" """
from unittest import TestCase import pytest
from unittest.mock import MagicMock, patch 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 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): def test_window_title(form):
""" """
Delete all the C++ objects at the end so that we don't have a segfault Test the windowTitle of the FileRenameDialog
""" """
del self.form # GIVEN: A mocked QDialog.exec() method
del self.main_window with patch('PyQt5.QtWidgets.QDialog.exec'):
def test_window_title(self): # WHEN: The form is executed with no args
""" form.exec()
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 # THEN: the window title is set correctly
self.form.exec() assert form.windowTitle() == 'File Rename', 'The window title should be "File Rename"'
# THEN: the window title is set correctly # WHEN: The form is executed with False arg
assert self.form.windowTitle() == 'File Rename', 'The window title should be "File Rename"' form.exec(False)
# WHEN: The form is executed with False arg # THEN: the window title is set correctly
self.form.exec(False) assert form.windowTitle() == 'File Rename', 'The window title should be "File Rename"'
# THEN: the window title is set correctly # WHEN: The form is executed with True arg
assert self.form.windowTitle() == 'File Rename', 'The window title should be "File Rename"' form.exec(True)
# WHEN: The form is executed with True arg # THEN: the window title is set correctly
self.form.exec(True) 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): def test_line_edit_focus(form):
""" """
Regression test for bug1067251 Regression test for bug1067251
Test that the file_name_edit setFocus has called with True when executed 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. # GIVEN: A mocked QDialog.exec() method and mocked file_name_edit.setFocus() method.
with patch('PyQt5.QtWidgets.QDialog.exec'): with patch('PyQt5.QtWidgets.QDialog.exec'):
mocked_set_focus = MagicMock() mocked_set_focus = MagicMock()
self.form.file_name_edit.setFocus = mocked_set_focus form.file_name_edit.setFocus = mocked_set_focus
# WHEN: The form is executed # WHEN: The form is executed
self.form.exec() form.exec()
# THEN: the setFocus method of the file_name_edit has been called with True # THEN: the setFocus method of the file_name_edit has been called with True
mocked_set_focus.assert_called_with() 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. def test_file_name_validation(form):
QtTest.QTest.keyClicks(self.form.file_name_edit, r'I/n\\v?a*l|i<d> \F[i\l]e" :N+a%me') """
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 # WHEN: 'Typing' a string containing invalid file characters.
# out. QtTest.QTest.keyClicks(form.file_name_edit, r'I/n\\v?a*l|i<d> \F[i\l]e" :N+a%me')
assert self.form.file_name_edit.text() == 'Invalid File Name'
# 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. Package to test the openlp.core.ui.firsttimeform package.
""" """
import pytest
from pathlib import Path from pathlib import Path
from unittest import TestCase
from unittest.mock import MagicMock, call, patch from unittest.mock import MagicMock, call, patch
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.ui.firsttimeform import ThemeListWidgetItem from openlp.core.ui.firsttimeform import ThemeListWidgetItem
from openlp.core.ui.icons import UiIcons 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): @pytest.yield_fixture()
def setUp(self): def mocked_set_icon(mock_settings):
self.sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash', move_to_thread_patcher = patch('openlp.core.ui.firsttimeform.DownloadWorker.moveToThread').start()
'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'} set_icon_patcher = patch('openlp.core.ui.firsttimeform.ThemeListWidgetItem.setIcon').start()
Registry.create() q_thread_patcher = patch('openlp.core.ui.firsttimeform.QtCore.QThread').start()
self.registry = Registry() mocked_app = MagicMock()
mocked_app = MagicMock() mocked_app.worker_threads = {}
mocked_app.worker_threads = {} Registry().remove('application')
Registry().register('application', mocked_app) Registry().register('application', mocked_app)
self.setup_application() 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): def test_failed_download(mocked_set_icon):
""" """
Test that icon get set to indicate a failure when `DownloadWorker` emits the download_failed signal Test that icon get set to indicate a failure when `DownloadWorker` emits the download_failed signal
""" """
# GIVEN: An instance of `DownloadWorker` # GIVEN: An instance of `DownloadWorker`
instance = ThemeListWidgetItem('url', self.sample_theme_data, MagicMock()) # noqa Overcome GC issue instance = ThemeListWidgetItem('url', sample_theme_data, MagicMock()) # noqa Overcome GC issue
worker_threads = Registry().get('application').worker_threads worker_threads = Registry().get('application').worker_threads
worker = worker_threads['thumbnail_download_BlueBurst.png']['worker'] worker = worker_threads['thumbnail_download_BlueBurst.png']['worker']
# WHEN: `DownloadWorker` emits the `download_failed` signal # WHEN: `DownloadWorker` emits the `download_failed` signal
worker.download_failed.emit() worker.download_failed.emit()
# THEN: Then the initial loading icon should have been replaced by the exception icon # 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)]) 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 @patch('openlp.core.ui.firsttimeform.build_icon')
worker.download_succeeded.emit(test_path) 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 # WHEN: `DownloadWorker` emits the `download_succeeded` signal
mocked_build_icon.assert_called_once_with(test_path) worker.download_succeeded.emit(test_path)
self.mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(mocked_build_icon())])
# 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. Package to test the openlp.core.ui.mainwindow package.
""" """
from unittest import TestCase, skipIf, skip import pytest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from PyQt5 import QtGui from PyQt5 import QtGui
from openlp.core.state import State from openlp.core.state import State
from openlp.core.common import is_macosx
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib.plugin import PluginStatus from openlp.core.lib.plugin import PluginStatus
from openlp.core.ui.mainwindow import MainWindow 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') @pytest.fixture()
class TestMainWindow(TestCase, TestMixin): 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): def test_restore_current_media_manager_item(main_window):
""" """
Delete all the C++ objects at the end so that we don't have a segfault Regression test for bug #1152509.
""" """
del self.main_window # 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') # WHEN: Call the restore method.
def test_restore_current_media_manager_item(self): main_window.restore_current_media_manager_item()
"""
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. # THEN: The current widget should have been set.
self.main_window.restore_current_media_manager_item() 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): def test_projector_manager_dock_locked(main_window):
""" """
Projector Manager enable UI options - bug #1390702 Projector Manager enable UI options - bug #1390702
""" """
# GIVEN: A mocked projector manager dock item: # GIVEN: A mocked projector manager dock item:
projector_dock = self.main_window.projector_manager_dock projector_dock = main_window.projector_manager_dock
# WHEN: main_window.lock_panel action is triggered # WHEN: main_window.lock_panel action is triggered
self.main_window.lock_panel.triggered.emit(True) main_window.lock_panel.triggered.emit(True)
# THEN: Projector manager dock should have been called with disable UI features # THEN: Projector manager dock should have been called with disable UI features
projector_dock.setFeatures.assert_called_with(0) 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 def test_projector_manager_dock_unlocked(main_window):
self.main_window.lock_panel.triggered.emit(False) """
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 # WHEN: main_window.lock_panel action is triggered
projector_dock.setFeatures.assert_called_with(7) 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)