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.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,40 +21,25 @@
"""
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):
"""
Test the functions in the :mod:`db` module.
"""
@pytest.yield_fixture()
def tmp_folder():
t_folder = mkdtemp()
yield t_folder
shutil.rmtree(t_folder, ignore_errors=True)
def setUp(self):
"""
Setup for tests
"""
self.tmp_folder = mkdtemp()
Registry.create()
Registry().register('settings', Settings())
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)
def test_add_author(self):
def test_add_author():
"""
Test adding an author to a song
"""
@ -74,7 +59,8 @@ class TestDB(TestCase):
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):
def test_add_author_with_type():
"""
Test adding an author with a type specified to a song
"""
@ -94,7 +80,8 @@ class TestDB(TestCase):
assert "Mustermann" == song.authors_songs[0].author.last_name
assert AuthorType.Words == song.authors_songs[0].author_type
def test_remove_author(self):
def test_remove_author():
"""
Test removing an author from a song
"""
@ -110,7 +97,8 @@ class TestDB(TestCase):
# 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
"""
@ -128,7 +116,8 @@ class TestDB(TestCase):
assert 1 == len(song.authors_songs)
assert song.authors_songs[0].author_type is None
def test_get_author_type_from_translated_text(self):
def test_get_author_type_from_translated_text():
"""
Test getting an author type from translated text
"""
@ -141,7 +130,8 @@ class TestDB(TestCase):
# THEN: The type should be correct
assert author_type == AuthorType.Words
def test_author_get_display_name(self):
def test_author_get_display_name():
"""
Test that the display name of an author is correct
"""
@ -155,7 +145,8 @@ class TestDB(TestCase):
# THEN: It should return only the name
assert "John Doe" == display_name
def test_author_get_display_name_with_type_words(self):
def test_author_get_display_name_with_type_words():
"""
Test that the display name of an author with a type is correct (Words)
"""
@ -169,7 +160,8 @@ class TestDB(TestCase):
# 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_translation():
"""
Test that the display name of an author with a type is correct (Translation)
"""
@ -183,7 +175,8 @@ class TestDB(TestCase):
# THEN: It should return the name with the type in brackets
assert "John Doe (Translation)" == display_name
def test_add_songbooks(self):
def test_add_songbooks():
"""
Test that adding songbooks to a song works correctly
"""
@ -200,13 +193,14 @@ class TestDB(TestCase):
# THEN: The song should have two songbook entries
assert len(song.songbook_entries) == 2, 'There should be two Songbook entries.'
def test_upgrade_old_song_db(self):
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(self.tmp_folder, '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
@ -216,13 +210,14 @@ class TestDB(TestCase):
# 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(self):
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(self.tmp_folder, '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

View File

@ -21,41 +21,29 @@
"""
This module contains tests for the lib submodule of the Songs plugin.
"""
from unittest import TestCase
from unittest.mock import MagicMock, PropertyMock, patch
import pytest
from unittest.mock import PropertyMock, patch
from openlp.core.common.registry import Registry
from openlp.plugins.songs.lib import VerseType, clean_string, clean_title, strip_rtf, transpose_chord, transpose_lyrics
from openlp.plugins.songs.lib.songcompare import _op_length, _remove_typos, songs_probably_equal
class TestLib(TestCase):
"""
Test the functions in the :mod:`lib` module.
"""
def setUp(self):
"""
Mock up two songs and provide a set of lyrics for the songs_probably_equal tests.
"""
self.full_lyrics = '''amazing grace how sweet the sound that saved a wretch like me i once was lost but now am
full_lyrics = '''amazing grace how sweet the sound that saved a wretch like me i once was lost but now am
found was blind but now i see twas grace that taught my heart to fear and grace my fears relieved how
precious did that grace appear the hour i first believed through many dangers toils and snares i have
already come tis grace that brought me safe thus far and grace will lead me home'''
self.short_lyrics = '''twas grace that taught my heart to fear and grace my fears relieved how precious did
that grace appear the hour i first believed'''
self.error_lyrics = '''amazing how sweet the trumpet that saved a wrench like me i once was losst but now am
short_lyrics = '''twas grace that taught my heart to fear and grace my fears relieved how precious did
that grace appear the hour i first believed'''
error_lyrics = '''amazing how sweet the trumpet that saved a wrench like me i once was losst but now am
found waf blind but now i see it was grace that taught my heart to fear and grace my fears relieved how
precious did that grace appppppppear the hour i first believedxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx snares i have
already come to this grace that brought me safe so far and grace will lead me home'''
self.different_lyrics = '''on a hill far away stood an old rugged cross the emblem of suffering and shame and
different_lyrics = '''on a hill far away stood an old rugged cross the emblem of suffering and shame and
i love that old cross where the dearest and best for a world of lost sinners was slain so ill cherish the
old rugged cross till my trophies at last i lay down i will cling to the old rugged cross and exchange it
some day for a crown'''
Registry.create()
Registry().register('settings', MagicMock())
self.settings = Registry().get('settings')
def test_clean_string(self):
def test_clean_string():
"""
Test the clean_string() function
"""
@ -68,7 +56,8 @@ class TestLib(TestCase):
# THEN: The string should be cleaned up and lower-cased
assert result == 'aint gonna find you there ', 'The string should be cleaned up properly'
def test_clean_title(self):
def test_clean_title():
"""
Test the clean_title() function
"""
@ -81,13 +70,14 @@ class TestLib(TestCase):
# THEN: The string should be cleaned up
assert result == 'This is a dirty string', 'The title should be cleaned up properly: "%s"' % result
def test_songs_probably_equal_same_song(self):
def test_songs_probably_equal_same_song():
"""
Test the songs_probably_equal function with twice the same song.
"""
# GIVEN: Two equal songs.
song_tuple1 = (2, self.full_lyrics)
song_tuple2 = (4, self.full_lyrics)
song_tuple1 = (2, full_lyrics)
song_tuple2 = (4, full_lyrics)
# WHEN: We compare those songs for equality.
result = songs_probably_equal((song_tuple1, song_tuple2))
@ -95,13 +85,14 @@ class TestLib(TestCase):
# THEN: The result should be a tuple..
assert result == (2, 4), 'The result should be the tuble of song positions'
def test_songs_probably_equal_short_song(self):
def test_songs_probably_equal_short_song():
"""
Test the songs_probably_equal function with a song and a shorter version of the same song.
"""
# GIVEN: A song and a short version of the same song.
song_tuple1 = (1, self.full_lyrics)
song_tuple2 = (3, self.short_lyrics)
song_tuple1 = (1, full_lyrics)
song_tuple2 = (3, short_lyrics)
# WHEN: We compare those songs for equality.
result = songs_probably_equal((song_tuple1, song_tuple2))
@ -109,13 +100,14 @@ class TestLib(TestCase):
# THEN: The result should be a tuple..
assert result == (1, 3), 'The result should be the tuble of song positions'
def test_songs_probably_equal_error_song(self):
def test_songs_probably_equal_error_song():
"""
Test the songs_probably_equal function with a song and a very erroneous version of the same song.
"""
# GIVEN: A song and the same song with lots of errors.
song_tuple1 = (4, self.full_lyrics)
song_tuple2 = (7, self.error_lyrics)
song_tuple1 = (4, full_lyrics)
song_tuple2 = (7, error_lyrics)
# WHEN: We compare those songs for equality.
result = songs_probably_equal((song_tuple1, song_tuple2))
@ -123,13 +115,14 @@ class TestLib(TestCase):
# THEN: The result should be a tuple of song positions.
assert result == (4, 7), 'The result should be the tuble of song positions'
def test_songs_probably_equal_different_song(self):
def test_songs_probably_equal_different_song():
"""
Test the songs_probably_equal function with two different songs.
"""
# GIVEN: Two different songs.
song_tuple1 = (5, self.full_lyrics)
song_tuple2 = (8, self.different_lyrics)
song_tuple1 = (5, full_lyrics)
song_tuple2 = (8, different_lyrics)
# WHEN: We compare those songs for equality.
result = songs_probably_equal((song_tuple1, song_tuple2))
@ -137,7 +130,8 @@ class TestLib(TestCase):
# THEN: The result should be None.
assert result is None, 'The result should be None'
def test_remove_typos_beginning(self):
def test_remove_typos_beginning():
"""
Test the _remove_typos function with a typo at the beginning.
"""
@ -151,7 +145,8 @@ class TestLib(TestCase):
assert len(result) == 1, 'The result should contain only one element.'
assert result[0][0] == 'equal', 'The result should contain an equal element.'
def test_remove_typos_beginning_negated(self):
def test_remove_typos_beginning_negated():
"""
Test the _remove_typos function with a large difference at the beginning.
"""
@ -164,7 +159,8 @@ class TestLib(TestCase):
# THEN: There diff should not have changed.
assert result == diff
def test_remove_typos_end(self):
def test_remove_typos_end():
"""
Test the _remove_typos function with a typo at the end.
"""
@ -178,7 +174,8 @@ class TestLib(TestCase):
assert len(result) == 1, 'The result should contain only one element.'
assert result[0][0] == 'equal', 'The result should contain an equal element.'
def test_remove_typos_end_negated(self):
def test_remove_typos_end_negated():
"""
Test the _remove_typos function with a large difference at the end.
"""
@ -191,7 +188,8 @@ class TestLib(TestCase):
# THEN: There diff should not have changed.
assert result == diff
def test_remove_typos_middle(self):
def test_remove_typos_middle():
"""
Test the _remove_typos function with a typo in the middle.
"""
@ -209,7 +207,8 @@ class TestLib(TestCase):
assert result[0][3] == 0, 'The start indices should be kept.'
assert result[0][4] == 21, 'The stop indices should be kept.'
def test_remove_typos_middle_negated(self):
def test_remove_typos_middle_negated():
"""
Test the _remove_typos function with a large difference in the middle.
"""
@ -222,7 +221,8 @@ class TestLib(TestCase):
# THEN: There diff should not have changed.
assert result == diff
def test_op_length(self):
def test_op_length():
"""
Test the _op_length function.
"""
@ -235,7 +235,8 @@ class TestLib(TestCase):
# THEN: The maximum length should be returned.
assert result == 10, 'The length should be 10.'
def test_strip_rtf_charsets(self):
def test_strip_rtf_charsets():
"""
Test that the strip_rtf() method properly decodes the supported charsets.
"""
@ -267,7 +268,8 @@ class TestLib(TestCase):
# THEN: The stripped text matches thed expected result
assert result == exp_result, 'The result should be %s' % exp_result
def test_transpose_chord_up(self):
def test_transpose_chord_up():
"""
Test that the transpose_chord() method works when transposing up
"""
@ -280,7 +282,8 @@ class TestLib(TestCase):
# THEN: The chord should be transposed up one note
assert new_chord == 'C#', 'The chord should be transposed up.'
def test_transpose_chord_up_adv(self):
def test_transpose_chord_up_adv():
"""
Test that the transpose_chord() method works when transposing up an advanced chord
"""
@ -293,7 +296,8 @@ class TestLib(TestCase):
# THEN: The chord should be transposed up one note
assert new_chord == '(C#/E)', 'The chord should be transposed up.'
def test_transpose_chord_down(self):
def test_transpose_chord_down():
"""
Test that the transpose_chord() method works when transposing down
"""
@ -306,7 +310,8 @@ class TestLib(TestCase):
# THEN: The chord should be transposed down one note
assert new_chord == 'B', 'The chord should be transposed down.'
def test_transpose_chord_error(self):
def test_transpose_chord_error():
"""
Test that the transpose_chord() raises exception on invalid chord
"""
@ -315,13 +320,14 @@ class TestLib(TestCase):
# WHEN: Transposing it 1 down
# THEN: An exception should be raised
with self.assertRaises(ValueError) as err:
with pytest.raises(ValueError) as err:
transpose_chord(chord, -1, 'english')
assert err.exception.args[0] == '\'T\' is not in list', \
assert err.value != ValueError('\'T\' is not in list'), \
'ValueError exception should have been thrown for invalid chord'
@patch('openlp.plugins.songs.lib.transpose_verse')
def test_transpose_lyrics(self, mocked_transpose_verse):
@patch('openlp.plugins.songs.lib.transpose_verse')
def test_transpose_lyrics(mocked_transpose_verse, mock_settings):
"""
Test that the transpose_lyrics() splits verses correctly
"""
@ -332,7 +338,7 @@ class TestLib(TestCase):
'That saved a wretch like me.\n'\
'---[Verse:2]---\n'\
'I once was lost but now I\'m found.'
self.settings.value.return_value = 'english'
mock_settings.value.return_value = 'english'
# WHEN: Transposing the lyrics
transpose_lyrics(lyrics, 1)
@ -344,12 +350,7 @@ class TestLib(TestCase):
mocked_transpose_verse.assert_any_call('\nI once was lost but now I\'m found.', 1, 'english')
class TestVerseType(TestCase):
"""
This is a test case to test various methods in the VerseType enumeration class.
"""
def test_translated_tag(self):
def test_translated_tag():
"""
Test that the translated_tag() method returns the correct tags
"""
@ -369,7 +370,8 @@ class TestVerseType(TestCase):
# THEN: The result should be "C"
assert result == 'C', 'The result should be "C"'
def test_translated_invalid_tag(self):
def test_translated_invalid_tag():
"""
Test that the translated_tag() method returns the default tag when passed an invalid tag
"""
@ -383,7 +385,8 @@ class TestVerseType(TestCase):
# THEN: The result should be "O"
assert result == 'O', 'The result should be "O", but was "%s"' % result
def test_translated_invalid_tag_with_specified_default(self):
def test_translated_invalid_tag_with_specified_default():
"""
Test that the translated_tag() method returns the specified default tag when passed an invalid tag
"""
@ -397,7 +400,8 @@ class TestVerseType(TestCase):
# THEN: The result should be "B"
assert result == 'B', 'The result should be "B", but was "%s"' % result
def test_translated_invalid_tag_with_invalid_default(self):
def test_translated_invalid_tag_with_invalid_default():
"""
Test that the translated_tag() method returns a sane default tag when passed an invalid default
"""
@ -411,7 +415,8 @@ class TestVerseType(TestCase):
# THEN: The result should be "O"
assert result == 'O', 'The result should be "O", but was "%s"' % result
def test_translated_name(self):
def test_translated_name():
"""
Test that the translated_name() method returns the correct name
"""
@ -431,7 +436,8 @@ class TestVerseType(TestCase):
# THEN: The result should be "Chorus"
assert result == 'Chorus', 'The result should be "Chorus"'
def test_translated_invalid_name(self):
def test_translated_invalid_name():
"""
Test that the translated_name() method returns the default name when passed an invalid tag
"""
@ -445,7 +451,8 @@ class TestVerseType(TestCase):
# THEN: The result should be "Other"
assert result == 'Other', 'The result should be "Other", but was "%s"' % result
def test_translated_invalid_name_with_specified_default(self):
def test_translated_invalid_name_with_specified_default():
"""
Test that the translated_name() method returns the specified default name when passed an invalid tag
"""
@ -459,7 +466,8 @@ class TestVerseType(TestCase):
# THEN: The result should be "Bridge"
assert result == 'Bridge', 'The result should be "Bridge", but was "%s"' % result
def test_translated_invalid_name_with_invalid_default(self):
def test_translated_invalid_name_with_invalid_default():
"""
Test that the translated_name() method returns the specified default tag when passed an invalid tag
"""
@ -473,7 +481,8 @@ class TestVerseType(TestCase):
# THEN: The result should be "Other"
assert result == 'Other', 'The result should be "Other", but was "%s"' % result
def test_from_tag(self):
def test_from_tag():
"""
Test that the from_tag() method returns the correct VerseType.
"""
@ -487,7 +496,8 @@ class TestVerseType(TestCase):
# THEN: The result should be VerseType.Verse
assert result == VerseType.Verse, 'The result should be VerseType.Verse, but was "%s"' % result
def test_from_tag_with_invalid_tag(self):
def test_from_tag_with_invalid_tag():
"""
Test that the from_tag() method returns the default VerseType when it is passed an invalid tag.
"""
@ -501,7 +511,8 @@ class TestVerseType(TestCase):
# THEN: The result should be VerseType.Other
assert result == VerseType.Other, 'The result should be VerseType.Other, but was "%s"' % result
def test_from_tag_with_specified_default(self):
def test_from_tag_with_specified_default():
"""
Test that the from_tag() method returns the specified default when passed an invalid tag.
"""
@ -515,7 +526,8 @@ class TestVerseType(TestCase):
# THEN: The result should be VerseType.Chorus
assert result == VerseType.Chorus, 'The result should be VerseType.Chorus, but was "%s"' % result
def test_from_tag_with_invalid_intdefault(self):
def test_from_tag_with_invalid_intdefault():
"""
Test that the from_tag() method returns a sane default when passed an invalid tag and an invalid int default.
"""
@ -529,7 +541,8 @@ class TestVerseType(TestCase):
# THEN: The result should be VerseType.Other
assert result == VerseType.Other, 'The result should be VerseType.Other, but was "%s"' % result
def test_from_tag_with_invalid_default(self):
def test_from_tag_with_invalid_default():
"""
Test that the from_tag() method returns a sane default when passed an invalid tag and an invalid default.
"""
@ -543,7 +556,8 @@ class TestVerseType(TestCase):
# THEN: The result should be VerseType.Other
assert result == VerseType.Other, 'The result should be VerseType.Other, but was "%s"' % result
def test_from_tag_with_none_default(self):
def test_from_tag_with_none_default():
"""
Test that the from_tag() method returns a sane default when passed an invalid tag and None as default.
"""
@ -557,8 +571,9 @@ class TestVerseType(TestCase):
# THEN: The result should be None
assert result is None, 'The result should be None, but was "%s"' % result
@patch('openlp.plugins.songs.lib.VerseType.translated_tags', new_callable=PropertyMock, return_value=['x'])
def test_from_loose_input_with_invalid_input(self, mocked_translated_tags):
@patch('openlp.plugins.songs.lib.VerseType.translated_tags', new_callable=PropertyMock, return_value=['x'])
def test_from_loose_input_with_invalid_input(mocked_translated_tags):
"""
Test that the from_loose_input() method returns a sane default when passed an invalid tag and None as default.
"""
@ -569,8 +584,9 @@ class TestVerseType(TestCase):
# THEN: The result should be None
assert result is None, 'The result should be None, but was "%s"' % result
@patch('openlp.plugins.songs.lib.VerseType.translated_tags', new_callable=PropertyMock, return_value=['x'])
def test_from_loose_input_with_valid_input(self, mocked_translated_tags):
@patch('openlp.plugins.songs.lib.VerseType.translated_tags', new_callable=PropertyMock, return_value=['x'])
def test_from_loose_input_with_valid_input(mocked_translated_tags):
"""
Test that the from_loose_input() method returns valid output on valid input.
"""

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,44 +111,7 @@ def mocked_media_item(mock_settings):
yield media_item
class TestMediaItem(TestCase, TestMixin):
"""
Test the functions in the :mod:`lib` module.
"""
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'))
def tearDown(self):
"""
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):
def test_display_results_song(media_item):
"""
Test displaying song search results with basic song
"""
@ -179,20 +139,21 @@ class TestMediaItem(TestCase, TestMixin):
mock_search_results.append(mock_song_temp)
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
self.media_item.auto_select_id = 1
media_item.auto_select_id = 1
# WHEN: I display song search results
self.media_item.display_results_song(mock_search_results)
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()
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)
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)
media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
media_item.list_view.setCurrentItem.assert_called_with(mock_qlist_widget)
def test_display_results_author(self):
def test_display_results_author(media_item):
"""
Test displaying song search results grouped by author with basic song
"""
@ -220,15 +181,16 @@ class TestMediaItem(TestCase, TestMixin):
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by author
self.media_item.display_results_author(mock_search_results)
media_item.display_results_author(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()
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)
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
"""
@ -240,15 +202,16 @@ class TestMediaItem(TestCase, TestMixin):
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by book
self.media_item.display_results_book(mock_search_results)
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()
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)
media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def test_songbook_natural_sorting(self):
def test_songbook_natural_sorting(media_item):
"""
Test that songbooks are sorted naturally
"""
@ -260,7 +223,7 @@ class TestMediaItem(TestCase, TestMixin):
('2', 'Thy Book', 'A Song', 8)]
# WHEN: I display song search results grouped by book
self.media_item.display_results_book(search_results)
media_item.display_results_book(search_results)
# THEN: The songbooks are sorted inplace in the right (natural) order,
# grouped first by book, then by number, then by song title
@ -270,7 +233,8 @@ class TestMediaItem(TestCase, TestMixin):
('2', 'Thy Book', 'A Song', 8),
('2', 'Thy Book', 'Thy Song', 50)]
def test_display_results_topic(self):
def test_display_results_topic(media_item):
"""
Test displaying song search results grouped by topic with basic song
"""
@ -298,15 +262,16 @@ class TestMediaItem(TestCase, TestMixin):
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by topic
self.media_item.display_results_topic(mock_search_results)
media_item.display_results_topic(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()
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)
media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def test_display_results_themes(self):
def test_display_results_themes(media_item):
"""
Test displaying song search results sorted by theme with basic song
"""
@ -332,15 +297,16 @@ class TestMediaItem(TestCase, TestMixin):
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results sorted by theme
self.media_item.display_results_themes(mock_search_results)
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()
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)
media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def test_display_results_cclinumber(self):
def test_display_results_cclinumber(media_item):
"""
Test displaying song search results sorted by CCLI number with basic song
"""
@ -366,15 +332,16 @@ class TestMediaItem(TestCase, TestMixin):
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)
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()
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)
media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def test_build_song_footer_two_authors(self):
def test_build_song_footer_two_authors(media_item):
"""
Test build songs footer with basic song and two authors
"""
@ -405,7 +372,7 @@ class TestMediaItem(TestCase, TestMixin):
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
author_list = self.media_item.generate_footer(service_item, mock_song)
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',
@ -414,7 +381,8 @@ class TestMediaItem(TestCase, TestMixin):
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(self):
def test_build_song_footer_base_ccli(media_item):
"""
Test build songs footer with basic song and a CCLI number
"""
@ -424,24 +392,25 @@ class TestMediaItem(TestCase, TestMixin):
mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None)
self.settings.setValue('core/ccli number', '1234')
media_item.settings.setValue('core/ccli number', '1234')
# WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, mock_song)
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
self.settings.setValue('core/ccli number', '4321')
self.media_item.generate_footer(service_item, mock_song)
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(self):
def test_build_song_footer_base_songbook(media_item):
"""
Test build songs footer with basic song and multiple songbooks
"""
@ -465,17 +434,18 @@ class TestMediaItem(TestCase, TestMixin):
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, song)
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):
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
self.media_item.display_copyright_symbol = True
media_item.display_copyright_symbol = True
mock_song = MagicMock()
mock_song.title = 'My Song'
mock_song.copyright = 'My copyright'
@ -483,12 +453,13 @@ class TestMediaItem(TestCase, TestMixin):
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, mock_song)
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(self):
def test_build_song_footer_copyright_disabled(media_item):
"""
Test building song footer without displaying the copyright symbol
"""
@ -500,12 +471,13 @@ class TestMediaItem(TestCase, TestMixin):
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, mock_song)
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):
def test_authors_match(media_item):
"""
Test the author matching when importing a song from a service
"""
@ -526,12 +498,13 @@ class TestMediaItem(TestCase, TestMixin):
authors_str = "Hans Wurst, Max Mustermann, Max Mustermann"
# WHEN: Checking for matching
result = self.media_item._authors_match(song, authors_str)
result = media_item._authors_match(song, authors_str)
# THEN: They should match
assert result is True, "Authors should match"
def test_authors_dont_match(self):
def test_authors_dont_match(media_item):
# GIVEN: A song and a string with authors
song = MagicMock()
song.authors = []
@ -549,12 +522,13 @@ class TestMediaItem(TestCase, TestMixin):
# WHEN: An author is missing in the string
authors_str = "Hans Wurst, Max Mustermann"
result = self.media_item._authors_match(song, authors_str)
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(self):
def test_build_remote_search(media_item):
"""
Test results for the remote search api
"""
@ -564,20 +538,21 @@ class TestMediaItem(TestCase, TestMixin):
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]
media_item.search_entire = MagicMock()
media_item.search_entire.return_value = [mock_song]
# WHEN: I process a search
search_results = self.media_item.search('My Song', False)
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(self, mocked_or, MockedSong, MockedSongBookEntry, MockedBook):
@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
"""
@ -591,7 +566,7 @@ class TestMediaItem(TestCase, TestMixin):
MockedBook.name.like.side_effect = lambda a: a
# WHEN: search_entire_song() is called with the keyword
self.media_item.search_entire(keyword)
media_item.search_entire(keyword)
# THEN: The correct calls were made
MockedSong.search_title.like.assert_called_once_with('%jesus%')
@ -600,19 +575,18 @@ class TestMediaItem(TestCase, TestMixin):
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)
media_item.plugin.manager.session.query.assert_called_once_with(MockedSong)
assert self.mocked_plugin.manager.session.query.mock_calls[4][0] == '().join().join().filter().all'
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(mocked_media_item):
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,17 +21,10 @@
"""
This module contains tests for the SongFormat class
"""
from unittest import TestCase
from openlp.plugins.songs.lib.importer import SongFormat
class TestSongFormat(TestCase):
"""
Test the functions in the :class:`SongFormat` class.
"""
def test_get_format_list(self):
def test_get_format_list():
"""
Test that get_format_list() returns all available formats
"""
@ -41,7 +34,8 @@ class TestSongFormat(TestCase):
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
"""
@ -52,7 +46,8 @@ class TestSongFormat(TestCase):
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):
def test_get_attributed_single_attribute():
"""
Test that SongFormat.get(song_format, attribute) returns only one -and the correct- attribute
"""
@ -70,7 +65,8 @@ class TestSongFormat(TestCase):
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_multiple_attributes():
"""
Test that multiple attributes can be retrieved for a song_format
"""
@ -81,7 +77,8 @@ class TestSongFormat(TestCase):
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(self):
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

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):
"""
This is a test case to test various methods in the ImageTab.
"""
def setUp(self):
"""
Create the UI
"""
Registry.create()
@pytest.fixture()
def form(settings):
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()
Registry().get('settings').extend_default_settings(__default_settings__)
frm = SongsTab(None, 'Songs', None, None)
frm.settings_form.register_post_process = MagicMock()
return frm
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_german_notation_on_load(self):
def test_german_notation_on_load(form):
"""
Test the corrent notation is selected on load
"""
# GIVEN: German notation in the settings
self.setting.setValue('songs/chord notation', 'german')
form.settings.setValue('songs/chord notation', 'german')
# WHEN: Load is invoked
self.form.load()
form.load()
# THEN: The german radio button should be checked
assert self.form.german_notation_radio_button.isChecked() is True
assert form.german_notation_radio_button.isChecked() is True
def test_neolatin_notation_on_load(self):
def test_neolatin_notation_on_load(form):
"""
Test the corrent notation is selected on load
"""
# GIVEN: neo-latin notation in the settings
self.setting.setValue('songs/chord notation', 'neo-latin')
form.settings.setValue('songs/chord notation', 'neo-latin')
# WHEN: Load is invoked
self.form.load()
form.load()
# THEN: The neo-latin radio button should be checked
assert self.form.neolatin_notation_radio_button.isChecked() is True
assert form.neolatin_notation_radio_button.isChecked() is True
def test_invalid_notation_on_load(self):
def test_invalid_notation_on_load(form):
"""
Test the invalid notation in settings reverts to english
"""
# GIVEN: gibberish notation in the settings
self.setting.setValue('songs/chord notation', 'gibberish')
form.settings.setValue('songs/chord notation', 'gibberish')
# WHEN: Load is invoked
self.form.load()
form.load()
# THEN: The english radio button should be checked
assert self.form.english_notation_radio_button.isChecked() is True
assert 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
"""
# 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)
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
self.form.save()
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
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):
def test_english_notation_button(form):
"""
Test notation button clicked handler
"""
# GIVEN: A normal song form
# WHEN: english notation clicked
self.form.on_english_notation_button_clicked()
form.on_english_notation_button_clicked()
# THEN: Chord notation should be correct
assert self.form.chord_notation == 'english'
assert form.chord_notation == 'english'
def test_german_notation_button(self):
def test_german_notation_button(form):
"""
Test notation button clicked handler
"""
# GIVEN: A normal song form
# WHEN: german notation clicked
self.form.on_german_notation_button_clicked()
form.on_german_notation_button_clicked()
# THEN: Chord notation should be correct
assert self.form.chord_notation == 'german'
assert form.chord_notation == 'german'
def test_neolatin_notation_button(self):
def test_neolatin_notation_button(form):
"""
Test notation button clicked handler
"""
# GIVEN: A normal song form
# WHEN: neolatin notation clicked
self.form.on_neolatin_notation_button_clicked()
form.on_neolatin_notation_button_clicked()
# THEN: Chord notation should be correct
assert self.form.chord_notation == 'neo-latin'
assert form.chord_notation == 'neo-latin'
@patch('openlp.core.common.settings.Settings.setValue')
def test_footer_nochange(self, mocked_settings_set_val):
@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
self.form.save()
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(self, mocked_settings_set_val):
@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
self.form.footer_edit_box.setPlainText('A new footer')
form.footer_edit_box.setPlainText('A new footer')
# WHEN: save is invoked
self.form.save()
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'
assert form.footer_edit_box.toPlainText() == 'A new footer'
def test_footer_reset(self):
def test_footer_reset(form):
"""
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')
form.settings.setValue('songs/footer template', 'hello')
form.footer_edit_box.setPlainText('A different footer')
# WHEN: reset is invoked
self.form.on_footer_reset_button_clicked()
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')
assert form.footer_edit_box.toPlainText() == form.settings.get_default_value('songs/footer template')
def test_save_tab_nochange(self):
def test_save_tab_nochange(form):
"""
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()
form.save()
# THEN: the post process should not be requested
assert 0 == self.form.settings_form.register_post_process.call_count, \
assert 0 == form.settings_form.register_post_process.call_count, \
'Songs Post processing should not have been requested'
def test_save_tab_change(self):
def test_save_tab_change(form):
"""
Test save after visiting the page triggers post processing.
"""
# GIVEN: Form has been visited.
self.form.tab_visited = True
form.tab_visited = True
# WHEN: the save is invoked
self.form.save()
form.save()
# THEN: the post process should be requested
assert 1 == self.form.settings_form.register_post_process.call_count, \
assert 1 == form.settings_form.register_post_process.call_count, \
'Songs Post processing should have been requested'

View File

@ -21,25 +21,12 @@
"""
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):
"""
A test suite to test out various methods around the Utils functions.
"""
def setUp(self):
"""
Some pre-test setup required.
"""
self.setup_application()
def test_is_not_image_empty(self):
def test_is_not_image_empty():
"""
Test the method handles an empty string
"""
@ -52,7 +39,8 @@ class TestUtils(TestCase, TestMixin):
# THEN the result is false
assert result is True, 'The missing file test should return True'
def test_is_not_image_with_image_file(self):
def test_is_not_image_with_image_file():
"""
Test the method handles an image file
"""
@ -65,7 +53,8 @@ class TestUtils(TestCase, TestMixin):
# 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
"""

View File

@ -21,36 +21,21 @@
"""
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(self):
def test_window_title(form):
"""
Test the windowTitle of the FileRenameDialog
"""
@ -58,24 +43,25 @@ class TestStartFileRenameForm(TestCase, TestMixin):
with patch('PyQt5.QtWidgets.QDialog.exec'):
# WHEN: The form is executed with no args
self.form.exec()
form.exec()
# THEN: the window title is set correctly
assert self.form.windowTitle() == 'File Rename', 'The window title should be "File Rename"'
assert form.windowTitle() == 'File Rename', 'The window title should be "File Rename"'
# WHEN: The form is executed with False arg
self.form.exec(False)
form.exec(False)
# THEN: the window title is set correctly
assert self.form.windowTitle() == 'File Rename', 'The window title should be "File Rename"'
assert form.windowTitle() == 'File Rename', 'The window title should be "File Rename"'
# WHEN: The form is executed with True arg
self.form.exec(True)
form.exec(True)
# THEN: the window title is set correctly
assert self.form.windowTitle() == 'File Copy', 'The window title should be "File Copy"'
assert 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
Test that the file_name_edit setFocus has called with True when executed
@ -83,23 +69,24 @@ class TestStartFileRenameForm(TestCase, TestMixin):
# 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
form.file_name_edit.setFocus = mocked_set_focus
# WHEN: The form is executed
self.form.exec()
form.exec()
# 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):
def test_file_name_validation(form):
"""
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')
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 self.form.file_name_edit.text() == 'Invalid File Name'
assert form.file_name_edit.text() == 'Invalid File Name'

View File

@ -21,43 +21,39 @@
"""
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
class TestThemeListWidgetItem(TestCase, TestMixin):
def setUp(self):
self.sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash',
sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash',
'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'}
Registry.create()
self.registry = Registry()
@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)
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
"""
# 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 = worker_threads['thumbnail_download_BlueBurst.png']['worker']
@ -65,16 +61,17 @@ class TestThemeListWidgetItem(TestCase, TestMixin):
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)])
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):
@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', 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 = worker_threads['thumbnail_download_BlueBurst.png']['worker']
test_path = Path('downlaoded', 'file')
@ -84,4 +81,4 @@ class TestThemeListWidgetItem(TestCase, TestMixin):
# 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())])
mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(mocked_build_icon())])

View File

@ -21,42 +21,27 @@
"""
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):
def setUp(self):
@pytest.fixture()
def main_window(settings, state):
"""
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'), \
@ -70,52 +55,47 @@ class TestMainWindow(TestCase, TestMixin):
patch('openlp.core.ui.mainwindow.WebSocketServer'), \
patch('openlp.core.ui.mainwindow.start_zeroconf'), \
patch('openlp.core.ui.mainwindow.PluginForm'):
self.main_window = MainWindow()
return MainWindow()
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
del self.main_window
@skip('Fix when migrate to PyTest')
def test_restore_current_media_manager_item(self):
def test_restore_current_media_manager_item(main_window):
"""
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]
main_window.settings.setValue('advanced/save current plugin', True)
main_window.settings.setValue('advanced/current media plugin', 2)
# WHEN: Call the restore method.
self.main_window.restore_current_media_manager_item()
main_window.restore_current_media_manager_item()
# THEN: The current widget should have been set.
self.main_window.media_tool_box.setCurrentIndex.assert_called_with(2)
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
"""
# 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
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
projector_dock.setFeatures.assert_called_with(0)
def test_projector_manager_dock_unlocked(self):
def test_projector_manager_dock_unlocked(main_window):
"""
Projector Manager disable UI options - bug #1390702
"""
# 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
self.main_window.lock_panel.triggered.emit(False)
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)