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,40 +21,25 @@
""" """
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():
Test the functions in the :mod:`db` module. 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): def test_add_author():
"""
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):
""" """
Test adding an author to a song Test adding an author to a song
""" """
@ -74,7 +59,8 @@ class TestDB(TestCase):
assert "Mustermann" == song.authors_songs[0].author.last_name assert "Mustermann" == song.authors_songs[0].author.last_name
assert song.authors_songs[0].author_type is None 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 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 "Mustermann" == song.authors_songs[0].author.last_name
assert AuthorType.Words == song.authors_songs[0].author_type 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 Test removing an author from a song
""" """
@ -110,7 +97,8 @@ class TestDB(TestCase):
# THEN: It should have been removed # THEN: It should have been removed
assert 0 == len(song.authors_songs) 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
""" """
@ -128,7 +116,8 @@ class TestDB(TestCase):
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):
def test_get_author_type_from_translated_text():
""" """
Test getting an 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 # THEN: The type should be correct
assert author_type == AuthorType.Words 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 Test that the display name of an author is correct
""" """
@ -155,7 +145,8 @@ class TestDB(TestCase):
# THEN: It should return only the name # THEN: It should return only the name
assert "John Doe" == display_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) 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 # THEN: It should return the name with the type in brackets
assert "John Doe (Words)" == display_name 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) 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 # THEN: It should return the name with the type in brackets
assert "John Doe (Translation)" == display_name assert "John Doe (Translation)" == display_name
def test_add_songbooks(self):
def test_add_songbooks():
""" """
Test that adding songbooks to a song works correctly Test that adding songbooks to a song works correctly
""" """
@ -200,13 +193,14 @@ class TestDB(TestCase):
# THEN: The song should have two songbook entries # THEN: The song should have two songbook entries
assert len(song.songbook_entries) == 2, 'There should be 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 Test that we can upgrade an old song db to the current schema
""" """
# GIVEN: An old song db # GIVEN: An old song db
old_db_path = os.path.join(TEST_RESOURCES_PATH, "songs", 'songs-1.9.7.sqlite') 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) shutil.copyfile(old_db_path, old_db_tmp_path)
db_url = 'sqlite:///' + 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 # 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' 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 Test that we can upgrade an invalid song db to the current schema
""" """
# GIVEN: A song db with invalid version # GIVEN: A song db with invalid version
invalid_db_path = os.path.join(TEST_RESOURCES_PATH, "songs", 'songs-2.2-invalid.sqlite') 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) shutil.copyfile(invalid_db_path, invalid_db_tmp_path)
db_url = 'sqlite:///' + 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. This module contains tests for the lib submodule of the Songs plugin.
""" """
from unittest import TestCase import pytest
from unittest.mock import MagicMock, PropertyMock, patch 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 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 from openlp.plugins.songs.lib.songcompare import _op_length, _remove_typos, songs_probably_equal
full_lyrics = '''amazing grace how sweet the sound that saved a wretch like me i once was lost but now am
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
found was blind but now i see twas grace that taught my heart to fear and grace my fears relieved how 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 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''' 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 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''' 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 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 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 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''' 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 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 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''' 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 Test the clean_string() function
""" """
@ -68,7 +56,8 @@ class TestLib(TestCase):
# THEN: The string should be cleaned up and lower-cased # THEN: The string should be cleaned up and lower-cased
assert result == 'aint gonna find you there ', 'The string should be cleaned up properly' 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 Test the clean_title() function
""" """
@ -81,13 +70,14 @@ class TestLib(TestCase):
# THEN: The string should be cleaned up # THEN: The string should be cleaned up
assert result == 'This is a dirty string', 'The title should be cleaned up properly: "%s"' % result 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. Test the songs_probably_equal function with twice the same song.
""" """
# GIVEN: Two equal songs. # GIVEN: Two equal songs.
song_tuple1 = (2, self.full_lyrics) song_tuple1 = (2, full_lyrics)
song_tuple2 = (4, self.full_lyrics) song_tuple2 = (4, full_lyrics)
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal((song_tuple1, song_tuple2)) result = songs_probably_equal((song_tuple1, song_tuple2))
@ -95,13 +85,14 @@ class TestLib(TestCase):
# THEN: The result should be a tuple.. # THEN: The result should be a tuple..
assert result == (2, 4), 'The result should be the tuble of song positions' 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. 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. # GIVEN: A song and a short version of the same song.
song_tuple1 = (1, self.full_lyrics) song_tuple1 = (1, full_lyrics)
song_tuple2 = (3, self.short_lyrics) song_tuple2 = (3, short_lyrics)
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal((song_tuple1, song_tuple2)) result = songs_probably_equal((song_tuple1, song_tuple2))
@ -109,13 +100,14 @@ class TestLib(TestCase):
# THEN: The result should be a tuple.. # THEN: The result should be a tuple..
assert result == (1, 3), 'The result should be the tuble of song positions' 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. 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. # GIVEN: A song and the same song with lots of errors.
song_tuple1 = (4, self.full_lyrics) song_tuple1 = (4, full_lyrics)
song_tuple2 = (7, self.error_lyrics) song_tuple2 = (7, error_lyrics)
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal((song_tuple1, song_tuple2)) 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. # THEN: The result should be a tuple of song positions.
assert result == (4, 7), 'The result should be the tuble 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. Test the songs_probably_equal function with two different songs.
""" """
# GIVEN: Two different songs. # GIVEN: Two different songs.
song_tuple1 = (5, self.full_lyrics) song_tuple1 = (5, full_lyrics)
song_tuple2 = (8, self.different_lyrics) song_tuple2 = (8, different_lyrics)
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal((song_tuple1, song_tuple2)) result = songs_probably_equal((song_tuple1, song_tuple2))
@ -137,7 +130,8 @@ class TestLib(TestCase):
# THEN: The result should be None. # THEN: The result should be None.
assert result is None, '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. 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 len(result) == 1, 'The result should contain only one element.'
assert result[0][0] == 'equal', 'The result should contain an equal 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. 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. # THEN: There diff should not have changed.
assert result == diff 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. 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 len(result) == 1, 'The result should contain only one element.'
assert result[0][0] == 'equal', 'The result should contain an equal 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. 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. # THEN: There diff should not have changed.
assert result == diff 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. 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][3] == 0, 'The start indices should be kept.'
assert result[0][4] == 21, 'The stop 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. 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. # THEN: There diff should not have changed.
assert result == diff assert result == diff
def test_op_length(self):
def test_op_length():
""" """
Test the _op_length function. Test the _op_length function.
""" """
@ -235,7 +235,8 @@ class TestLib(TestCase):
# THEN: The maximum length should be returned. # THEN: The maximum length should be returned.
assert result == 10, 'The length should be 10.' 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. 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 # THEN: The stripped text matches thed expected result
assert result == exp_result, 'The result should be %s' % exp_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 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 # THEN: The chord should be transposed up one note
assert new_chord == 'C#', 'The chord should be transposed up.' 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 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 # THEN: The chord should be transposed up one note
assert new_chord == '(C#/E)', 'The chord should be transposed up.' 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 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 # THEN: The chord should be transposed down one note
assert new_chord == 'B', 'The chord should be transposed down.' 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 Test that the transpose_chord() raises exception on invalid chord
""" """
@ -315,13 +320,14 @@ class TestLib(TestCase):
# WHEN: Transposing it 1 down # WHEN: Transposing it 1 down
# THEN: An exception should be raised # THEN: An exception should be raised
with self.assertRaises(ValueError) as err: with pytest.raises(ValueError) as err:
transpose_chord(chord, -1, 'english') 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' '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 Test that the transpose_lyrics() splits verses correctly
""" """
@ -332,7 +338,7 @@ class TestLib(TestCase):
'That saved a wretch like me.\n'\ 'That saved a wretch like me.\n'\
'---[Verse:2]---\n'\ '---[Verse:2]---\n'\
'I once was lost but now I\'m found.' '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 # WHEN: Transposing the lyrics
transpose_lyrics(lyrics, 1) 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') mocked_transpose_verse.assert_any_call('\nI once was lost but now I\'m found.', 1, 'english')
class TestVerseType(TestCase): def test_translated_tag():
"""
This is a test case to test various methods in the VerseType enumeration class.
"""
def test_translated_tag(self):
""" """
Test that the translated_tag() method returns the correct tags Test that the translated_tag() method returns the correct tags
""" """
@ -369,7 +370,8 @@ class TestVerseType(TestCase):
# THEN: The result should be "C" # THEN: The result should be "C"
assert result == 'C', '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 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" # THEN: The result should be "O"
assert result == 'O', 'The result should be "O", but was "%s"' % result 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 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" # THEN: The result should be "B"
assert result == 'B', 'The result should be "B", but was "%s"' % result 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 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" # THEN: The result should be "O"
assert result == 'O', 'The result should be "O", but was "%s"' % result 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 Test that the translated_name() method returns the correct name
""" """
@ -431,7 +436,8 @@ class TestVerseType(TestCase):
# THEN: The result should be "Chorus" # THEN: The result should be "Chorus"
assert result == 'Chorus', '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 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" # THEN: The result should be "Other"
assert result == 'Other', 'The result should be "Other", but was "%s"' % result 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 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" # THEN: The result should be "Bridge"
assert result == 'Bridge', 'The result should be "Bridge", but was "%s"' % result 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 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" # THEN: The result should be "Other"
assert result == 'Other', 'The result should be "Other", but was "%s"' % result 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. Test that the from_tag() method returns the correct VerseType.
""" """
@ -487,7 +496,8 @@ class TestVerseType(TestCase):
# THEN: The result should be VerseType.Verse # THEN: The result should be VerseType.Verse
assert result == VerseType.Verse, 'The result should be VerseType.Verse, but was "%s"' % result 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. 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 # THEN: The result should be VerseType.Other
assert result == VerseType.Other, 'The result should be VerseType.Other, but was "%s"' % result 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. 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 # THEN: The result should be VerseType.Chorus
assert result == VerseType.Chorus, 'The result should be VerseType.Chorus, but was "%s"' % result 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. 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 # THEN: The result should be VerseType.Other
assert result == VerseType.Other, 'The result should be VerseType.Other, but was "%s"' % result 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. 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 # THEN: The result should be VerseType.Other
assert result == VerseType.Other, 'The result should be VerseType.Other, but was "%s"' % result 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. 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 # THEN: The result should be None
assert result is None, 'The result should be None, but was "%s"' % result 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. 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 # THEN: The result should be None
assert result is None, 'The result should be None, but was "%s"' % result 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. 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. 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,44 +111,7 @@ 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.
"""
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):
""" """
Test displaying song search results with basic song Test displaying song search results with basic song
""" """
@ -179,20 +139,21 @@ class TestMediaItem(TestCase, TestMixin):
mock_search_results.append(mock_song_temp) mock_search_results.append(mock_song_temp)
mock_qlist_widget = MagicMock() mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget 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 # 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 # 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()
self.media_item.save_auto_select_id.assert_called_with() media_item.save_auto_select_id.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Song (My Author)') MockedQListWidgetItem.assert_called_once_with('My Song (My Author)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id) 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)
self.media_item.list_view.setCurrentItem.assert_called_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 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 MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by author # 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 # 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)') MockedQListWidgetItem.assert_called_once_with('My Author (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id) 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 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 MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by book # 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 # 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') MockedQListWidgetItem.assert_called_once_with('My Book #1: My Song')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, 1) 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 Test that songbooks are sorted naturally
""" """
@ -260,7 +223,7 @@ class TestMediaItem(TestCase, TestMixin):
('2', 'Thy Book', 'A Song', 8)] ('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(search_results)
# THEN: The songbooks are sorted inplace in the right (natural) order, # THEN: The songbooks are sorted inplace in the right (natural) order,
# grouped first by book, then by number, then by song title # 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', 'A Song', 8),
('2', 'Thy Book', 'Thy Song', 50)] ('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 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 MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by topic # 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 # 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)') MockedQListWidgetItem.assert_called_once_with('My Topic (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id) 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 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 MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results sorted by theme # 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 # 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)') MockedQListWidgetItem.assert_called_once_with('My Theme (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id) 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 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 MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results sorted by CCLI number # 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 # 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)') MockedQListWidgetItem.assert_called_once_with('12345 (My Song)')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id) 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 Test build songs footer with basic song and two authors
""" """
@ -405,7 +372,7 @@ class TestMediaItem(TestCase, TestMixin):
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 = 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 # THEN: I get the following Array returned
assert service_item.raw_footer == ['My Song', 'Words: another author', 'Music: my author', 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'], \ assert author_list == ['another author', 'my author', 'translator'], \
'The author list should be returned correctly with two authors' '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 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.copyright = 'My copyright'
mock_song.songbook_entries = [] mock_song.songbook_entries = []
service_item = ServiceItem(None) 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 # 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 # THEN: I get the following Array returned
assert service_item.raw_footer == ['My Song', '© My copyright', 'CCLI License: 1234'], \ 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' 'The array should be returned correctly with a song, an author, copyright and ccli'
# WHEN: I amend the CCLI value # WHEN: I amend the CCLI value
self.settings.setValue('core/ccli number', '4321') media_item.settings.setValue('core/ccli number', '4321')
self.media_item.generate_footer(service_item, mock_song) media_item.generate_footer(service_item, mock_song)
# THEN: I would get an amended footer string # THEN: I would get an amended footer string
assert service_item.raw_footer == ['My Song', '© My copyright', 'CCLI License: 4321'], \ 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' '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 Test build songs footer with basic song and multiple songbooks
""" """
@ -465,17 +434,18 @@ class TestMediaItem(TestCase, TestMixin):
service_item = ServiceItem(None) service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings # 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 # THEN: The songbook should be in the footer
assert service_item.raw_footer == ['My Song', '© My copyright', 'My songbook #12, Thy songbook #502A'] 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 Test building song footer with displaying the copyright symbol
""" """
# GIVEN: A Song and a Service Item; displaying the copyright symbol is enabled # 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 = MagicMock()
mock_song.title = 'My Song' mock_song.title = 'My Song'
mock_song.copyright = 'My copyright' mock_song.copyright = 'My copyright'
@ -483,12 +453,13 @@ class TestMediaItem(TestCase, TestMixin):
service_item = ServiceItem(None) service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings # 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 # THEN: The copyright symbol should be in the footer
assert service_item.raw_footer == ['My Song', '© My copyright'] 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 Test building song footer without displaying the copyright symbol
""" """
@ -500,12 +471,13 @@ class TestMediaItem(TestCase, TestMixin):
service_item = ServiceItem(None) service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings # 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 # THEN: The copyright symbol should not be in the footer
assert service_item.raw_footer == ['My Song', '© My copyright'] 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 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" authors_str = "Hans Wurst, Max Mustermann, Max Mustermann"
# WHEN: Checking for matching # 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 # THEN: They should match
assert result is True, "Authors 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 # GIVEN: A song and a string with authors
song = MagicMock() song = MagicMock()
song.authors = [] song.authors = []
@ -549,12 +522,13 @@ class TestMediaItem(TestCase, TestMixin):
# WHEN: An author is missing in the string # WHEN: An author is missing in the string
authors_str = "Hans Wurst, Max Mustermann" 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 # THEN: They should not match
assert result is False, "Authors 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 Test results for the remote search api
""" """
@ -564,20 +538,21 @@ class TestMediaItem(TestCase, TestMixin):
mock_song.title = 'My Song' mock_song.title = 'My Song'
mock_song.search_title = 'My Song' mock_song.search_title = 'My Song'
mock_song.alternate_title = 'My alternative' mock_song.alternate_title = 'My alternative'
self.media_item.search_entire = MagicMock() media_item.search_entire = MagicMock()
self.media_item.search_entire.return_value = [mock_song] media_item.search_entire.return_value = [mock_song]
# WHEN: I process a search # 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 # THEN: The correct formatted results are returned
assert search_results == [[123, 'My Song', 'My alternative']] 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.Book')
@patch('openlp.plugins.songs.lib.mediaitem.Song') @patch('openlp.plugins.songs.lib.mediaitem.SongBookEntry')
@patch('openlp.plugins.songs.lib.mediaitem.or_') @patch('openlp.plugins.songs.lib.mediaitem.Song')
def test_entire_song_search(self, mocked_or, MockedSong, MockedSongBookEntry, MockedBook): @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 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 MockedBook.name.like.side_effect = lambda a: a
# WHEN: search_entire_song() is called with the keyword # 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 # THEN: The correct calls were made
MockedSong.search_title.like.assert_called_once_with('%jesus%') 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%') MockedSongBookEntry.entry.like.assert_called_once_with('%jesus%')
MockedBook.name.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%') 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 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,17 +21,10 @@
""" """
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.
"""
def test_get_format_list(self):
""" """
Test that get_format_list() returns all available formats 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__), \ assert len(SongFormat.get_format_list()) == len(SongFormat.__attributes__), \
"The returned SongFormats don't match the stored ones" "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
""" """
@ -52,7 +46,8 @@ class TestSongFormat(TestCase):
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):
def test_get_attributed_single_attribute():
""" """
Test that SongFormat.get(song_format, attribute) returns only one -and the correct- 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], \ assert SongFormat.get(song_format, attribute) == SongFormat.__defaults__[attribute], \
"The returned attribute does not match the default values stored" "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 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, \ assert len(SongFormat.get(song_format, 'canDisable', 'availability')) == 2, \
"Did not return the correct number of attributes when retrieving multiple attributes at once" "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 Test that get_format_list() returns a list that is ordered
according to the order specified in SongFormat 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. 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):
This is a test case to test various methods in the ImageTab.
"""
def setUp(self):
"""
Create the UI
"""
Registry.create()
Registry().register('settings_form', MagicMock()) Registry().register('settings_form', MagicMock())
self.setup_application() Registry().get('settings').extend_default_settings(__default_settings__)
self.build_settings() frm = SongsTab(None, 'Songs', None, None)
Registry().register('settings', self.setting) frm.settings_form.register_post_process = MagicMock()
self.setting.extend_default_settings(__default_settings__) return frm
self.parent = QtWidgets.QMainWindow()
self.form = SongsTab(self.parent, 'Songs', None, None)
self.form.settings_form.register_post_process = MagicMock()
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
del self.parent
del self.form
self.destroy_settings()
def test_german_notation_on_load(self): def test_german_notation_on_load(form):
""" """
Test the corrent notation is selected on load Test the corrent notation is selected on load
""" """
# GIVEN: German notation in the settings # GIVEN: German notation in the settings
self.setting.setValue('songs/chord notation', 'german') form.settings.setValue('songs/chord notation', 'german')
# WHEN: Load is invoked # WHEN: Load is invoked
self.form.load() form.load()
# THEN: The german radio button should be checked # 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 Test the corrent notation is selected on load
""" """
# GIVEN: neo-latin notation in the settings # 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 # WHEN: Load is invoked
self.form.load() form.load()
# THEN: The neo-latin radio button should be checked # 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 Test the invalid notation in settings reverts to english
""" """
# GIVEN: gibberish notation in the settings # GIVEN: gibberish notation in the settings
self.setting.setValue('songs/chord notation', 'gibberish') form.settings.setValue('songs/chord notation', 'gibberish')
# WHEN: Load is invoked # WHEN: Load is invoked
self.form.load() form.load()
# THEN: The english radio button should be checked # 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 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):
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: english notation clicked # WHEN: english notation clicked
self.form.on_english_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 == 'english' assert form.chord_notation == 'english'
def test_german_notation_button(self):
def test_german_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: german notation clicked
self.form.on_german_notation_button_clicked() form.on_german_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 == 'german'
def test_neolatin_notation_button(self):
def test_neolatin_notation_button(form):
""" """
Test notation button clicked handler Test notation button clicked handler
""" """
# GIVEN: A normal song form # GIVEN: A normal song form
# WHEN: neolatin notation clicked # WHEN: neolatin notation clicked
self.form.on_neolatin_notation_button_clicked() form.on_neolatin_notation_button_clicked()
# THEN: Chord notation should be correct # 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 Test the footer is not saved when not changed
""" """
# GIVEN: A normal song form # GIVEN: A normal song form
# WHEN: save is invoked # WHEN: save is invoked
self.form.save() form.save()
# THEN: footer should not have been saved (one less call than the change test below) # THEN: footer should not have been saved (one less call than the change test below)
assert mocked_settings_set_val.call_count == 8 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 Test the footer is saved when changed
""" """
# GIVEN: Footer has 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 # WHEN: save is invoked
self.form.save() form.save()
# THEN: footer should have been saved (one more call to setValue than the nochange test) # THEN: footer should have been saved (one more call to setValue than the nochange test)
assert mocked_settings_set_val.call_count == 9 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 Test the footer is reset when reset clicked
""" """
# GIVEN: A default footer and different content in the edit box # GIVEN: A default footer and different content in the edit box
self.setting.setValue('songs/footer template', 'hello') form.settings.setValue('songs/footer template', 'hello')
self.form.footer_edit_box.setPlainText('A different footer') form.footer_edit_box.setPlainText('A different footer')
# WHEN: reset is invoked # 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 # 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 Test no changes does not trigger post processing
""" """
# GIVEN: No changes on the form. # GIVEN: No changes on the form.
self.initial_color = '#999999'
# WHEN: the save is invoked # WHEN: the save is invoked
self.form.save() form.save()
# THEN: the post process should not be requested # 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' '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. Test save after visiting the page triggers post processing.
""" """
# GIVEN: Form has been visited. # GIVEN: Form has been visited.
self.form.tab_visited = True form.tab_visited = True
# WHEN: the save is invoked # WHEN: the save is invoked
self.form.save() form.save()
# THEN: the post process should be requested # 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' 'Songs Post processing should have been requested'

View File

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

View File

@ -21,36 +21,21 @@
""" """
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
"""
del self.form
del self.main_window
def test_window_title(self):
""" """
Test the windowTitle of the FileRenameDialog Test the windowTitle of the FileRenameDialog
""" """
@ -58,24 +43,25 @@ class TestStartFileRenameForm(TestCase, TestMixin):
with patch('PyQt5.QtWidgets.QDialog.exec'): with patch('PyQt5.QtWidgets.QDialog.exec'):
# WHEN: The form is executed with no args # WHEN: The form is executed with no args
self.form.exec() form.exec()
# THEN: the window title is set correctly # 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 # WHEN: The form is executed with False arg
self.form.exec(False) form.exec(False)
# THEN: the window title is set correctly # 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 # WHEN: The form is executed with True arg
self.form.exec(True) form.exec(True)
# THEN: the window title is set correctly # 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 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
@ -83,23 +69,24 @@ class TestStartFileRenameForm(TestCase, TestMixin):
# 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):
def test_file_name_validation(form):
""" """
Test the file_name_edit validation Test the file_name_edit validation
""" """
# GIVEN: QLineEdit with a validator set with illegal file name characters. # GIVEN: QLineEdit with a validator set with illegal file name characters.
# WHEN: 'Typing' a string containing invalid file 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 # THEN: The text in the QLineEdit should be the same as the input string with the invalid characters filtered
# out. # 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. 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',
class TestThemeListWidgetItem(TestCase, TestMixin):
def setUp(self):
self.sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash',
'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'} '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 = 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']
@ -65,16 +61,17 @@ class TestThemeListWidgetItem(TestCase, TestMixin):
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): @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` Test that the downloaded thumbnail is set as the icon when `DownloadWorker` emits the `download_succeeded`
signal 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']
test_path = Path('downlaoded', 'file') 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 # 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_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. 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):
def setUp(self):
""" """
Create the UI 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) Registry().set_flag('no_web_server', True)
mocked_plugin = MagicMock() mocked_plugin = MagicMock()
mocked_plugin.status = PluginStatus.Active mocked_plugin.status = PluginStatus.Active
mocked_plugin.icon = QtGui.QIcon() mocked_plugin.icon = QtGui.QIcon()
Registry().register('mock_plugin', mocked_plugin) Registry().register('mock_plugin', mocked_plugin)
State().load_settings()
State().add_service("mock", 1, is_plugin=True, status=PluginStatus.Active) State().add_service("mock", 1, is_plugin=True, status=PluginStatus.Active)
# Mock classes and methods used by mainwindow. # Mock classes and methods used by mainwindow.
with patch('openlp.core.ui.mainwindow.SettingsForm'), \ 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.WebSocketServer'), \
patch('openlp.core.ui.mainwindow.start_zeroconf'), \ patch('openlp.core.ui.mainwindow.start_zeroconf'), \
patch('openlp.core.ui.mainwindow.PluginForm'): 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(main_window):
def test_restore_current_media_manager_item(self):
""" """
Regression test for bug #1152509. 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 # 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. # 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. # 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 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):
def test_projector_manager_dock_unlocked(main_window):
""" """
Projector Manager disable UI options - bug #1390702 Projector Manager disable 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(False) main_window.lock_panel.triggered.emit(False)
# THEN: Projector manager dock should have been called with enable UI features # THEN: Projector manager dock should have been called with enable UI features
projector_dock.setFeatures.assert_called_with(7) projector_dock.setFeatures.assert_called_with(7)