openlp/tests/openlp_core/ui/test_firsttimeform.py

500 lines
21 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2019-04-13 13:00:22 +00:00
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
2022-02-01 10:10:57 +00:00
# Copyright (c) 2008-2022 OpenLP Developers #
2019-04-13 13:00:22 +00:00
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Package to test the openlp.core.ui.firsttimeform package.
"""
import pytest
from pathlib import Path
from unittest.mock import MagicMock, call, patch, DEFAULT
2021-08-25 21:14:19 +00:00
from PyQt5 import QtCore, QtWidgets, QtTest
2017-12-28 08:22:55 +00:00
from openlp.core.common.registry import Registry
from openlp.core.ui.firsttimeform import FirstTimeForm, ThemeListWidgetItem
from openlp.core.ui.firsttimewizard import RemotePage, ThemeListWidget
2021-04-24 16:55:38 +00:00
from openlp.core.ui.icons import UiIcons
2014-04-15 20:51:55 +00:00
2018-10-02 04:39:42 +00:00
INVALID_CONFIG = """
{
"_comments": "The most recent version should be added to https://openlp.org/files/frw/download_3.0.json",
"_meta": {
}
"""
sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash',
'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'}
@pytest.fixture()
def ftf_app(registry, qapp):
Registry().register('application', qapp)
@pytest.fixture()
def download_env(registry):
download_worker_patcher = patch('openlp.core.ui.firsttimeform.DownloadWorker')
run_thread_patcher = patch('openlp.core.ui.firsttimeform.run_thread')
mocked_download_worker = download_worker_patcher.start()
mocked_run_thread = run_thread_patcher.start()
yield mocked_download_worker, mocked_run_thread
download_worker_patcher.stop()
run_thread_patcher.stop()
2021-04-24 16:55:38 +00:00
@pytest.fixture()
def mocked_set_icon(mock_settings):
move_to_thread_patcher = patch('openlp.core.ui.firsttimeform.DownloadWorker.moveToThread').start()
set_icon_patcher = patch('openlp.core.ui.firsttimeform.ThemeListWidgetItem.setIcon').start()
q_thread_patcher = patch('openlp.core.ui.firsttimeform.QtCore.QThread').start()
mocked_app = MagicMock()
mocked_app.worker_threads = {}
mocked_main_window = MagicMock()
Registry().remove('application')
Registry().register('application', mocked_app)
Registry().register('main_window', mocked_main_window)
yield set_icon_patcher
move_to_thread_patcher.stop()
set_icon_patcher.stop()
q_thread_patcher.stop()
def test_init_sample_data(download_env):
"""
Test that the theme data is loaded correctly in to a ThemeListWidgetItem object when instantiated
"""
# GIVEN: A sample theme dictionary object
# WHEN: Creating an instance of `ThemeListWidgetItem`
mocked_download_worker = download_env[0]
instance = ThemeListWidgetItem('url', sample_theme_data, MagicMock())
# THEN: The data should have been set correctly
assert instance.file_name == 'BlueBurst.otz'
assert instance.sha256 == 'sha_256_hash'
assert instance.text() == 'Blue Burst'
assert instance.toolTip() == 'Blue Burst'
mocked_download_worker.assert_called_once_with('url', 'BlueBurst.png')
def test_init_download_worker(download_env):
"""
Test that the `DownloadWorker` worker is set up correctly and that the thread is started.
"""
# GIVEN: A sample theme dictionary object
mocked_download_worker = download_env[0]
mocked_run_thread = download_env[1]
mocked_ftw = MagicMock(spec=FirstTimeForm)
mocked_ftw.thumbnail_download_threads = []
# WHEN: Creating an instance of `ThemeListWidgetItem`
instance = ThemeListWidgetItem('url', sample_theme_data, mocked_ftw)
# THEN: The `DownloadWorker` should have been set up with the appropriate data
mocked_download_worker.assert_called_once_with('url', 'BlueBurst.png')
mocked_download_worker.download_failed.connect.called_once_with(instance._on_download_failed())
mocked_download_worker.download_succeeded.connect.called_once_with(instance._on_thumbnail_downloaded)
mocked_run_thread.assert_called_once_with(mocked_download_worker(), 'thumbnail_download_BlueBurst.png')
assert mocked_ftw.thumbnail_download_threads == ['thumbnail_download_BlueBurst.png']
def test_firsttimeform_initialise():
"""
Test if we can intialise the FirstTimeForm
"""
# GIVEN: A First Time Wizard and an expected screen object
frw = FirstTimeForm(None)
expected_screens = MagicMock()
# WHEN: The First Time Wizard is initialised
frw.initialize(expected_screens)
# THEN: The screens should be set up, and the default values initialised
assert expected_screens == frw.screens, 'The screens should be correct'
assert frw.has_web_access is True, 'The default value of self.web_access should be True'
assert [] == frw.thumbnail_download_threads, 'The list of threads should be empty'
assert frw.has_run_wizard is False, 'has_run_wizard should be False'
@patch('openlp.core.ui.firsttimeform.QtWidgets.QWizard.exec')
def test_firsttimeform_exec(mocked_qwizard_exec):
# GIVEN: An instance of FirstTimeForm
frw = FirstTimeForm(None)
with patch.object(frw, 'set_defaults') as mocked_set_defaults:
# WHEN: exec is called
frw.exec()
# THEN: The wizard should be reset and the exec methon on the super class should have been called
mocked_set_defaults.assert_called_once()
mocked_qwizard_exec.assert_called_once()
def test_set_defaults(mock_settings):
"""
Test that the default values are set when set_defaults() is run
"""
# GIVEN: An initialised FRW and a whole lot of stuff mocked out
frw = FirstTimeForm(None)
frw.initialize(MagicMock())
mocked_settings = MagicMock()
mocked_settings.value.side_effect = lambda key: {'core/has run wizard': False}[key]
with patch.object(frw, 'restart') as mocked_restart, \
patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \
patch.object(frw, 'theme_combo_box') as mocked_theme_combo_box, \
patch.object(frw, 'songs_check_box') as mocked_songs_check_box, \
patch.object(Registry, 'register_function') as mocked_register_function, \
patch('openlp.core.ui.firsttimeform.gettempdir', return_value='temp') as mocked_gettempdir, \
patch('openlp.core.ui.firsttimeform.create_paths') as mocked_create_paths, \
patch.object(frw.application, 'set_normal_cursor'):
mocked_theme_manager = MagicMock()
Registry().register('theme_manager', mocked_theme_manager)
Registry().remove('settings')
Registry().register('settings', mocked_settings)
# WHEN: The set_defaults() method is run
frw.set_defaults()
# THEN: The default values should have been set
mocked_restart.assert_called_once()
assert 'https://get.openlp.org/ftw/' == frw.web, 'The default URL should be set'
mocked_currentIdChanged.connect.assert_called_once_with(frw.on_current_id_changed)
mocked_register_function.assert_called_once_with('config_screen_changed', frw.screen_selection_widget.load)
mocked_settings.value.assert_has_calls([call('core/has run wizard')])
mocked_gettempdir.assert_called_once()
mocked_create_paths.assert_called_once_with(Path('temp', 'openlp'))
mocked_theme_combo_box.clear.assert_called_once()
mocked_theme_manager.assert_not_called()
mocked_songs_check_box.assert_not_called()
@patch('openlp.core.ui.firsttimeform.QtWidgets.QWizard.accept')
def test_accept_method(mocked_qwizard_accept, registry, *args):
"""
Test the FirstTimeForm.accept method
"""
# GIVEN: An instance of FirstTimeForm
frw = FirstTimeForm(None)
with patch.object(frw, '_set_plugin_status') as mocked_set_plugin_status, \
patch.multiple(frw, songs_check_box=DEFAULT, bible_check_box=DEFAULT, presentation_check_box=DEFAULT,
image_check_box=DEFAULT, media_check_box=DEFAULT, custom_check_box=DEFAULT,
song_usage_check_box=DEFAULT, alert_check_box=DEFAULT) as mocked_check_boxes, \
patch.object(frw, 'screen_selection_widget') as mocked_screen_selection_widget:
# WHEN: Calling accept
frw.accept()
# THEN: The selected plugins should be enabled, the screen selection saved and the super method called
mocked_set_plugin_status.assert_has_calls([
call(mocked_check_boxes['songs_check_box'], 'songs/status'),
call(mocked_check_boxes['bible_check_box'], 'bibles/status'),
call(mocked_check_boxes['presentation_check_box'], 'presentations/status'),
call(mocked_check_boxes['image_check_box'], 'images/status'),
call(mocked_check_boxes['media_check_box'], 'media/status'),
call(mocked_check_boxes['custom_check_box'], 'custom/status'),
call(mocked_check_boxes['song_usage_check_box'], 'songusage/status'),
call(mocked_check_boxes['alert_check_box'], 'alerts/status')])
mocked_screen_selection_widget.save.assert_called_once()
mocked_qwizard_accept.assert_called_once()
def test_accept_method_theme_not_selected(mock_settings):
"""
Test the FirstTimeForm.accept method when there is no default theme selected
"""
# GIVEN: An instance of FirstTimeForm
mock_settings = Registry().get('settings')
frw = FirstTimeForm(None)
with patch.object(frw, '_set_plugin_status'), patch.object(frw, 'screen_selection_widget'), \
patch.object(frw, 'theme_combo_box', **{'currentIndex.return_value': -1}):
# WHEN: Calling accept and the currentIndex method of the theme_combo_box returns -1
frw.accept()
# THEN: OpenLP should not try to save a theme name
mock_settings().setValue.assert_not_called()
@patch('openlp.core.ui.firsttimeform.QtWidgets.QWizard.reject')
@patch('openlp.core.ui.firsttimeform.time')
@patch('openlp.core.ui.firsttimeform.get_thread_worker')
@patch('openlp.core.ui.firsttimeform.is_thread_finished')
def test_reject_method(mocked_is_thread_finished, mocked_get_thread_worker,
mocked_time, mocked_qwizard_reject, ftf_app):
"""
Test that the reject method shuts down the threads correctly
"""
# GIVEN: A FRW, some mocked threads and workers (that isn't quite done) and other mocked stuff
mocked_worker = MagicMock()
mocked_get_thread_worker.return_value = mocked_worker
mocked_is_thread_finished.side_effect = [False, True]
frw = FirstTimeForm(None)
frw.initialize(MagicMock())
frw.thumbnail_download_threads = ['test_thread']
with patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor:
# WHEN: the reject method is called
frw.reject()
# THEN: The right things should be called in the right order
mocked_get_thread_worker.assert_called_once_with('test_thread')
mocked_worker.cancel_download.assert_called_once()
mocked_is_thread_finished.assert_called_with('test_thread')
assert mocked_is_thread_finished.call_count == 2, 'isRunning() should have been called twice'
mocked_time.sleep.assert_called_once_with(0.1)
mocked_set_normal_cursor.assert_called_once()
mocked_qwizard_reject.assert_called_once()
@patch('openlp.core.ui.firsttimeform.ProxyDialog')
def test_on_custom_button_clicked(mocked_proxy_dialog):
"""
Test _on_custom_button when it is called whe the 'internet settings' (CustomButton1) button is not clicked.
"""
# GIVEN: An instance of the FirstTimeForm
frw = FirstTimeForm(None)
# WHEN: Calling _on_custom_button_clicked with a different button to the 'internet settings button.
frw._on_custom_button_clicked(QtWidgets.QWizard.CustomButton2)
# THEN: The ProxyDialog should not be shown.
mocked_proxy_dialog.assert_not_called()
2019-03-08 21:00:16 +00:00
@patch('openlp.core.ui.firsttimeform.ProxyDialog')
def test_on_custom_button_clicked_internet_settings(mocked_proxy_dialog):
"""
Test _on_custom_button when it is called when the 'internet settings' (CustomButton1) button is clicked.
"""
# GIVEN: An instance of the FirstTimeForm
frw = FirstTimeForm(None)
# WHEN: Calling _on_custom_button_clicked with the constant for the 'internet settings' button (CustomButton1)
frw._on_custom_button_clicked(QtWidgets.QWizard.CustomButton1)
# THEN: The ProxyDialog should be shown.
mocked_proxy_dialog.assert_called_with(frw)
mocked_proxy_dialog().retranslate_ui.assert_called_once()
mocked_proxy_dialog().exec.assert_called_once()
@patch('openlp.core.ui.firsttimeform.QtWidgets.QMessageBox')
def test__parse_config_invalid_config(mocked_message_box):
"""
Test `FirstTimeForm._parse_config` when called with invalid data
"""
# GIVEN: An instance of `FirstTimeForm`
first_time_form = FirstTimeForm(None)
# WHEN: Calling _parse_config with a string containing invalid data
result = first_time_form._parse_config(INVALID_CONFIG)
# THEN: _parse_data should return False and the user should have should have been informed.
assert result is False
mocked_message_box.critical.assert_called_once_with(
first_time_form, 'Invalid index file',
'OpenLP was unable to read the resource index file. Please try again later.')
@patch('openlp.core.ui.firsttimeform.get_web_page')
@patch('openlp.core.ui.firsttimeform.QtWidgets.QMessageBox')
def test_network_error(mocked_message_box, mocked_get_web_page, ftf_app):
"""
Test we catch a network error in First Time Wizard - bug 1409627
"""
# GIVEN: Initial setup and mocks
first_time_form = FirstTimeForm(None)
first_time_form.initialize(MagicMock())
mocked_get_web_page.side_effect = ConnectionError('')
mocked_message_box.Ok = 'OK'
# WHEN: the First Time Wizard calls to get the initial configuration
first_time_form._download_index()
# THEN: the critical_error_message_box should have been called
mocked_message_box.critical.assert_called_once_with(
first_time_form, 'Network Error',
'There was a network error attempting to connect to retrieve initial configuration information', 'OK')
def test_accept_method_theme_selected(mock_settings):
"""
Test the FirstTimeForm.accept method when a default theme is selected
"""
# GIVEN: An instance of FirstTimeForm
frw = FirstTimeForm(None)
mock_settings.value.return_value = True
with patch.object(frw, '_set_plugin_status'), \
patch.object(frw, 'screen_selection_widget'), \
patch.object(
frw, 'theme_combo_box', **{'currentIndex.return_value': 0, 'currentText.return_value': 'Test Item'}):
# WHEN: Calling accept and the currentIndex method of the theme_combo_box returns 0
frw.accept()
# THEN: The 'currentItem' in the combobox should have been set as the default theme.
mock_settings.setValue.assert_called_once_with('themes/global theme', 'Test Item')
def test_on_projectors_check_box_checked(mock_settings):
"""
Test that the projector panel is shown when the checkbox in the first time wizard is checked
"""
# GIVEN: A First Time Wizard and a mocked settings object
frw = FirstTimeForm(None)
mock_settings.value.return_value = True
# WHEN: on_projectors_check_box_clicked() is called
frw.on_projectors_check_box_clicked()
# THEN: The visibility of the projects panel should have been set
mock_settings.value.assert_called_once_with('projector/show after wizard')
mock_settings.setValue.assert_called_once_with('projector/show after wizard', False)
def test_on_projectors_check_box_unchecked(mock_settings):
"""
Test that the projector panel is shown when the checkbox in the first time wizard is checked
"""
# GIVEN: A First Time Wizard and a mocked settings object
frw = FirstTimeForm(None)
mock_settings.value.return_value = False
# WHEN: on_projectors_check_box_clicked() is called
frw.on_projectors_check_box_clicked()
# THEN: The visibility of the projects panel should have been set
mock_settings.value.assert_called_once_with('projector/show after wizard')
mock_settings.setValue.assert_called_once_with('projector/show after wizard', True)
def test_remote_page_get_can_download_remote(ftf_app):
"""
Test that the `can_download_remote` property returns the correct value
"""
# GIVEN: A RemotePage object with a mocked out download_checkbox
remote_page = RemotePage(None)
remote_page.download_checkbox = MagicMock(**{"isChecked.return_value": True})
# WHEN: The can_download_remote property is accessed
result = remote_page.can_download_remote
# THEN: The result should be True
assert result is True
def test_remote_page_set_can_download_remote(ftf_app):
"""
Test that the `can_download_remote` property sets the correct value
"""
# GIVEN: A RemotePage object with a mocked out download_checkbox
remote_page = RemotePage(None)
remote_page.download_checkbox = MagicMock()
# WHEN: The can_download_remote property is set
remote_page.can_download_remote = False
# THEN: The result should be True
remote_page.download_checkbox.setChecked.assert_called_once_with(False)
def test_remote_page_set_can_download_remote_not_bool(ftf_app):
"""
Test that the `can_download_remote` property throws an exception when the value is not a boolean
"""
# GIVEN: A RemotePage object with a mocked out download_checkbox
remote_page = RemotePage(None)
remote_page.download_checkbox = MagicMock()
# WHEN: The can_download_remote property is set
# THEN: An exception is thrown
with pytest.raises(TypeError, match='Must be a bool'):
remote_page.can_download_remote = 'not a bool'
def test_theme_list_widget_resize(ftf_app):
"""
Test that the resizeEvent() method in the ThemeListWidget works correctly
"""
# GIVEN: An instance of the ThemeListWidget
widget = ThemeListWidget(None)
# WHEN: resizeEvent() is called
with patch.object(widget, 'viewport') as mocked_viewport, \
patch.object(widget, 'setGridSize') as mocked_setGridSize:
mocked_viewport.return_value.width.return_value = 600
widget.resizeEvent(None)
# THEN: Check that the correct calculations were done
mocked_setGridSize.assert_called_once_with(QtCore.QSize(149, 140))
2021-04-24 16:55:38 +00:00
def test_failed_download(mocked_set_icon):
"""
Test that icon get set to indicate a failure when `DownloadWorker` emits the download_failed signal
"""
# GIVEN: An instance of `DownloadWorker`
instance = ThemeListWidgetItem('url', sample_theme_data, MagicMock()) # noqa Overcome GC issue
worker_threads = Registry().get('application').worker_threads
worker = worker_threads['thumbnail_download_BlueBurst.png']['worker']
# WHEN: `DownloadWorker` emits the `download_failed` signal
worker.download_failed.emit()
# THEN: Then the initial loading icon should have been replaced by the exception icon
mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(UiIcons().exception)])
@patch('openlp.core.ui.firsttimeform.build_icon')
def test_successful_download(mocked_build_icon, mocked_set_icon):
"""
Test that the downloaded thumbnail is set as the icon when `DownloadWorker` emits the `download_succeeded`
signal
"""
# GIVEN: An instance of `DownloadWorker`
instance = ThemeListWidgetItem('url', sample_theme_data, MagicMock()) # noqa Overcome GC issue
worker_threads = Registry().get('application').worker_threads
worker = worker_threads['thumbnail_download_BlueBurst.png']['worker']
test_path = Path('downlaoded', 'file')
# WHEN: `DownloadWorker` emits the `download_succeeded` signal
worker.download_succeeded.emit(test_path)
# THEN: An icon should have been built from the downloaded file and used to replace the loading icon
mocked_build_icon.assert_called_once_with(test_path)
mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(mocked_build_icon())])
2021-08-25 21:14:19 +00:00
@patch.object(FirstTimeForm, 'provide_help')
def test_help(mocked_help, settings):
"""
Test the help button
"""
# GIVEN: A First Time Wizard and a patched help function
frw = FirstTimeForm(None)
# WHEN: The Help button is clicked
QtTest.QTest.mouseClick(frw.button(QtWidgets.QWizard.HelpButton), QtCore.Qt.LeftButton)
# THEN: The Help function should be called
mocked_help.assert_called_once()