forked from openlp/openlp
500 lines
21 KiB
Python
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()
|