openlp/tests/openlp_core/ui/test_firsttimeform.py

500 lines
21 KiB
Python

# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2022 OpenLP Developers #
# ---------------------------------------------------------------------- #
# 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
from PyQt5 import QtCore, QtWidgets, QtTest
from openlp.core.common.registry import Registry
from openlp.core.ui.firsttimeform import FirstTimeForm, ThemeListWidgetItem
from openlp.core.ui.firsttimewizard import RemotePage, ThemeListWidget
from openlp.core.ui.icons import UiIcons
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()
@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()
@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))
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())])
@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()