mirror of https://gitlab.com/openlp/openlp.git
Merge branch 'master' into new-theme-editor [skip ci]
This commit is contained in:
commit
4dc0af824e
|
@ -13,11 +13,13 @@ environment:
|
|||
CHOCO_VLC_ARG:
|
||||
FORCE_PACKAGING: 0
|
||||
FORCE_PACKAGING_MANUAL: 0
|
||||
PYICU_PACK: PyICU-2.9-cp38-cp38-win_amd64.whl
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
PY_DIR: C:\\Python38
|
||||
CHOCO_VLC_ARG: --forcex86
|
||||
FORCE_PACKAGING: 0
|
||||
FORCE_PACKAGING_MANUAL: 0
|
||||
PYICU_PACK: PyICU-2.9-cp38-cp38-win32.whl
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
FORCE_PACKAGING: 0
|
||||
|
@ -36,9 +38,13 @@ install:
|
|||
# Install Windows only dependencies
|
||||
- cmd: python -m pip install pyodbc pypiwin32
|
||||
- cmd: choco install vlc %CHOCO_VLC_ARG% --no-progress --limit-output
|
||||
# Download and install pyicu for windows (originally from http://www.lfd.uci.edu/~gohlke/pythonlibs/)
|
||||
- cmd: python -m pip install https://get.openlp.org/win-sdk/%PYICU_PACK%
|
||||
# Mac only dependencies
|
||||
- sh: python -m pip install Pyro4 'pyobjc-core<8.2' 'pyobjc-framework-Cocoa<8.2' py-applescript
|
||||
- sh: brew install --cask vlc
|
||||
- sh: brew install pkg-config icu4c
|
||||
- sh: PATH="/usr/local/opt/icu4c/bin:/usr/local/opt/icu4c/sbin:$PATH" PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/icu4c/lib/pkgconfig" python -m pip install pyicu
|
||||
- sh: python -m pip install Pyro4 'pyobjc-core<8.2' 'pyobjc-framework-Cocoa<8.2' py-applescript
|
||||
|
||||
build: off
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
3.0.0
|
||||
3.0.1
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
from openlp.core.api.versions.v2.controller import controller_views
|
||||
from openlp.core.api.versions.v2.core import core
|
||||
from openlp.core.api.versions.v2.service import service_views
|
||||
from openlp.core.api.versions.v2.plugins import plugins
|
||||
from openlp.core.api.versions.v2.plugins import plugins, alert_1_views, alert_2_views
|
||||
|
||||
|
||||
def register_blueprints(app):
|
||||
|
@ -30,3 +30,5 @@ def register_blueprints(app):
|
|||
app.register_blueprint(core, url_prefix='/api/v2/core/')
|
||||
app.register_blueprint(service_views, url_prefix='/api/v2/service/')
|
||||
app.register_blueprint(plugins, url_prefix='/api/v2/plugins/')
|
||||
app.register_blueprint(alert_2_views, url_prefix='/api/v2/plugins/alerts')
|
||||
app.register_blueprint(alert_1_views, url_prefix='/api/alert')
|
||||
|
|
|
@ -24,7 +24,7 @@ import logging
|
|||
import re
|
||||
from flask import abort, request, Blueprint, jsonify
|
||||
|
||||
from openlp.core.api.lib import login_required
|
||||
from openlp.core.api.lib import login_required, extract_request, old_success_response, old_auth
|
||||
from openlp.core.lib.plugin import PluginStatus
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.plugins.songs.lib import transpose_lyrics
|
||||
|
@ -33,6 +33,8 @@ log = logging.getLogger(__name__)
|
|||
|
||||
|
||||
plugins = Blueprint('v2-plugins', __name__)
|
||||
alert_1_views = Blueprint('v1-alert-plugin', __name__)
|
||||
alert_2_views = Blueprint('v2-alert-plugin', __name__)
|
||||
|
||||
|
||||
def search(plugin_name, text):
|
||||
|
@ -172,3 +174,27 @@ def transpose(transpose_value):
|
|||
chord_slides.append({'chords': verse_list[i + 1].strip(), 'verse': verse_list[i]})
|
||||
return jsonify(chord_slides), 200
|
||||
abort(400)
|
||||
|
||||
|
||||
@alert_2_views.route('', methods=['POST'])
|
||||
@login_required
|
||||
def alert():
|
||||
data = request.json
|
||||
if not data:
|
||||
abort(400)
|
||||
alert = data.get('text', '')
|
||||
if alert:
|
||||
if Registry().get('plugin_manager').get_plugin_by_name('alerts').status == PluginStatus.Active:
|
||||
Registry().get('alerts_manager').alerts_text.emit([alert])
|
||||
return '', 204
|
||||
abort(400)
|
||||
|
||||
|
||||
@alert_1_views.route('')
|
||||
@old_auth
|
||||
def old_alert():
|
||||
alert = extract_request(request.args.get('data', ''), 'text')
|
||||
if alert:
|
||||
if Registry().get('plugin_manager').get_plugin_by_name('alerts').status == PluginStatus.Active:
|
||||
Registry().get('alerts_manager').alerts_text.emit([alert])
|
||||
return old_success_response()
|
||||
|
|
|
@ -205,6 +205,8 @@ class WebSocketServer(RegistryBase, RegistryProperties, QtCore.QObject, LogMixin
|
|||
"""
|
||||
Closes the WebSocket server and detach associated signals
|
||||
"""
|
||||
if Registry().get_flag('no_web_server'):
|
||||
return
|
||||
try:
|
||||
poller.poller_changed.disconnect(self.handle_poller_signal)
|
||||
poller.unhook_signals()
|
||||
|
|
|
@ -100,6 +100,25 @@ def trace_error_handler(logger):
|
|||
logger.error(log_string)
|
||||
|
||||
|
||||
def path_to_module(path):
|
||||
"""
|
||||
Convert a path to a module name (i.e openlp.core.common)
|
||||
|
||||
:param pathlib.Path path: The path to convert to a module name.
|
||||
:return: The module name.
|
||||
:rtype: str
|
||||
"""
|
||||
module_path = path.with_suffix('')
|
||||
return 'openlp.' + '.'.join(module_path.parts)
|
||||
|
||||
|
||||
def import_openlp_module(module_name):
|
||||
"""
|
||||
Refactor module import out for testability. In Python 3.11, mock.patch and import_module do not play along nicely.
|
||||
"""
|
||||
importlib.import_module(module_name)
|
||||
|
||||
|
||||
def extension_loader(glob_pattern, excluded_files=None):
|
||||
"""
|
||||
A utility function to find and load OpenLP extensions, such as plugins, presentation and media controllers and
|
||||
|
@ -119,25 +138,13 @@ def extension_loader(glob_pattern, excluded_files=None):
|
|||
log.debug('Attempting to import %s', extension_path)
|
||||
module_name = path_to_module(extension_path)
|
||||
try:
|
||||
importlib.import_module(module_name)
|
||||
import_openlp_module(module_name)
|
||||
except (ImportError, OSError):
|
||||
# On some platforms importing vlc.py might cause OSError exceptions. (e.g. Mac OS X)
|
||||
log.exception('Failed to import {module_name} on path {extension_path}'
|
||||
.format(module_name=module_name, extension_path=extension_path))
|
||||
|
||||
|
||||
def path_to_module(path):
|
||||
"""
|
||||
Convert a path to a module name (i.e openlp.core.common)
|
||||
|
||||
:param pathlib.Path path: The path to convert to a module name.
|
||||
:return: The module name.
|
||||
:rtype: str
|
||||
"""
|
||||
module_path = path.with_suffix('')
|
||||
return 'openlp.' + '.'.join(module_path.parts)
|
||||
|
||||
|
||||
def get_frozen_path(frozen_option, non_frozen_option):
|
||||
"""
|
||||
Return a path based on the system status.
|
||||
|
|
|
@ -112,6 +112,15 @@ class ServiceItemType(IntEnum):
|
|||
Image = 2
|
||||
Command = 3
|
||||
|
||||
@staticmethod
|
||||
def parse(value):
|
||||
if value in [1, '1', 'Text', 'ServiceItemType.Text']:
|
||||
return ServiceItemType.Text
|
||||
elif value in [2, '2', 'Image', 'ServiceItemType.Image']:
|
||||
return ServiceItemType.Image
|
||||
elif value in [3, '3', 'Command', 'ServiceItemType.Command']:
|
||||
return ServiceItemType.Command
|
||||
|
||||
|
||||
@unique
|
||||
class PluginStatus(IntEnum):
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
The :mod:`languages` module provides a list of language names with utility functions.
|
||||
"""
|
||||
import itertools
|
||||
import locale
|
||||
import logging
|
||||
import re
|
||||
from collections import namedtuple
|
||||
|
@ -51,7 +52,8 @@ def translate(context, text, comment=None, qt_translate=QtCore.QCoreApplication.
|
|||
|
||||
|
||||
Language = namedtuple('Language', ['id', 'name', 'code'])
|
||||
COLLATOR = None
|
||||
ICU_COLLATOR = None
|
||||
DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+')
|
||||
LANGUAGES = sorted([
|
||||
Language(1, translate('common.languages', '(Afan) Oromo', 'Language code: om'), 'om'),
|
||||
Language(2, translate('common.languages', 'Abkhazian', 'Language code: ab'), 'ab'),
|
||||
|
@ -503,19 +505,25 @@ def format_time(text, local_time):
|
|||
return re.sub(r'%[a-zA-Z]', match_formatting, text)
|
||||
|
||||
|
||||
def get_locale_key(string, numeric=False):
|
||||
def get_locale_key(string):
|
||||
"""
|
||||
Creates a key for case insensitive, locale aware string sorting.
|
||||
|
||||
:param string: The corresponding string.
|
||||
"""
|
||||
string = string.lower()
|
||||
global COLLATOR
|
||||
if COLLATOR is None:
|
||||
language = LanguageManager.get_language()
|
||||
COLLATOR = QtCore.QCollator(QtCore.QLocale(language))
|
||||
COLLATOR.setNumericMode(numeric)
|
||||
return COLLATOR.sortKey(string)
|
||||
# ICU is the prefered way to handle locale sort key, we fallback to locale.strxfrm which will work in most cases.
|
||||
global ICU_COLLATOR
|
||||
try:
|
||||
if ICU_COLLATOR is None:
|
||||
import icu
|
||||
language = LanguageManager.get_language()
|
||||
icu_locale = icu.Locale(language)
|
||||
ICU_COLLATOR = icu.Collator.createInstance(icu_locale)
|
||||
return ICU_COLLATOR.getSortKey(string)
|
||||
except Exception:
|
||||
log.warning('ICU not found! Fallback to strxfrm')
|
||||
return locale.strxfrm(string).encode()
|
||||
|
||||
|
||||
def get_natural_key(string):
|
||||
|
@ -525,7 +533,13 @@ def get_natural_key(string):
|
|||
:param string: string to be sorted by
|
||||
Returns a list of string compare keys and integers.
|
||||
"""
|
||||
return get_locale_key(string, True)
|
||||
key = DIGITS_OR_NONDIGITS.findall(string)
|
||||
key = [int(part) if part.isdigit() else get_locale_key(part) for part in key]
|
||||
# Python 3 does not support comparison of different types anymore. So make sure, that we do not compare str
|
||||
# and int.
|
||||
if string and string[0].isdigit():
|
||||
return [b''] + key
|
||||
return key
|
||||
|
||||
|
||||
def get_language(name):
|
||||
|
|
|
@ -30,7 +30,7 @@ from openlp.core.common.registry import Registry
|
|||
|
||||
|
||||
DO_NOT_TRACE_EVENTS = ['timerEvent', 'paintEvent', 'drag_enter_event', 'drop_event', 'on_controller_size_changed',
|
||||
'preview_size_changed', 'resizeEvent', 'eventFilter', 'tick']
|
||||
'preview_size_changed', 'resizeEvent', 'eventFilter', 'tick', 'resize', 'update_ui']
|
||||
|
||||
|
||||
class LogMixin(object):
|
||||
|
|
|
@ -291,6 +291,8 @@ class Settings(QtCore.QSettings):
|
|||
'media/vlc arguments': '',
|
||||
'media/live volume': 50,
|
||||
'media/preview volume': 0,
|
||||
'media/live loop': False,
|
||||
'media/preview loop': False,
|
||||
'media/db type': 'sqlite',
|
||||
'media/db username': '',
|
||||
'media/db password': '',
|
||||
|
|
|
@ -474,7 +474,7 @@ var Display = {
|
|||
img.setAttribute("style", "height: 100%; width: 100%");
|
||||
section.appendChild(img);
|
||||
Display._slides['0'] = 0;
|
||||
Display.replaceSlides(parentSection);
|
||||
Display.replaceSlides(section);
|
||||
},
|
||||
/**
|
||||
* Set fullscreen image from base64 data
|
||||
|
@ -932,7 +932,7 @@ var Display = {
|
|||
/*
|
||||
Disabling all transitions (except body) to allow the Webview to attain the
|
||||
transparent state before it gets hidden by Qt.
|
||||
*/
|
||||
*/
|
||||
document.body.classList.add('disable-transitions');
|
||||
document.body.classList.add('is-desktop');
|
||||
Display._slidesContainer.style.opacity = 0;
|
||||
|
|
|
@ -463,7 +463,7 @@ class ServiceItem(RegistryProperties):
|
|||
header = service_item['serviceitem']['header']
|
||||
self.title = header['title']
|
||||
self.name = header['name']
|
||||
self.service_item_type = header['type']
|
||||
self.service_item_type = ServiceItemType.parse(header['type'])
|
||||
self.theme = header['theme']
|
||||
self.add_icon()
|
||||
self.raw_footer = header['footer']
|
||||
|
@ -891,7 +891,7 @@ class ServiceItem(RegistryProperties):
|
|||
data_dict = {
|
||||
'title': self.title,
|
||||
'name': self.name,
|
||||
'type': str(self.service_item_type),
|
||||
'type': self.service_item_type,
|
||||
'theme': self.theme,
|
||||
'footer': self.raw_footer,
|
||||
'audit': self.audit,
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
This is the main window, where all the action happens.
|
||||
"""
|
||||
import shutil
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime, date
|
||||
from pathlib import Path
|
||||
from tempfile import gettempdir
|
||||
|
@ -529,20 +530,32 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||
self.ws_server = WebSocketServer()
|
||||
self.screen_updating_lock = Lock()
|
||||
|
||||
@contextmanager
|
||||
def _show_wait_dialog(self, title, message):
|
||||
"""
|
||||
Show a wait dialog, wait for some tasks to complete, and then close it.
|
||||
"""
|
||||
try:
|
||||
# Display a progress dialog with a message
|
||||
wait_dialog = QtWidgets.QProgressDialog(message, '', 0, 0, self)
|
||||
wait_dialog.setWindowTitle(title)
|
||||
for window_flag in [QtCore.Qt.WindowContextHelpButtonHint]:
|
||||
wait_dialog.setWindowFlag(window_flag, False)
|
||||
wait_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
wait_dialog.setAutoClose(False)
|
||||
wait_dialog.setCancelButton(None)
|
||||
wait_dialog.show()
|
||||
QtWidgets.QApplication.processEvents()
|
||||
yield
|
||||
finally:
|
||||
# Finally close the message window
|
||||
wait_dialog.close()
|
||||
|
||||
def _wait_for_threads(self):
|
||||
"""
|
||||
Wait for the threads
|
||||
"""
|
||||
# Sometimes the threads haven't finished, let's wait for them
|
||||
wait_dialog = QtWidgets.QProgressDialog(translate('OpenLP.MainWindow', 'Waiting for some things to finish...'),
|
||||
'', 0, 0, self)
|
||||
wait_dialog.setWindowTitle(translate('OpenLP.MainWindow', 'Please Wait'))
|
||||
for window_flag in [QtCore.Qt.WindowContextHelpButtonHint]:
|
||||
wait_dialog.setWindowFlag(window_flag, False)
|
||||
wait_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
wait_dialog.setAutoClose(False)
|
||||
wait_dialog.setCancelButton(None)
|
||||
wait_dialog.show()
|
||||
thread_names = list(self.application.worker_threads.keys())
|
||||
for thread_name in thread_names:
|
||||
if thread_name not in self.application.worker_threads.keys():
|
||||
|
@ -569,7 +582,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||
except RuntimeError:
|
||||
# Ignore the RuntimeError that is thrown when Qt has already deleted the C++ thread object
|
||||
pass
|
||||
wait_dialog.close()
|
||||
|
||||
def bootstrap_post_set_up(self):
|
||||
"""
|
||||
|
@ -1085,10 +1097,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||
else:
|
||||
event.accept()
|
||||
if event.isAccepted():
|
||||
# Wait for all the threads to complete
|
||||
self._wait_for_threads()
|
||||
# If we just did a settings import, close without saving changes.
|
||||
self.clean_up(save_settings=not self.settings_imported)
|
||||
with self._show_wait_dialog(translate('OpenLP.MainWindow', 'Please Wait'),
|
||||
translate('OpenLP.MainWindow', 'Waiting for some things to finish...')):
|
||||
# Wait for all the threads to complete
|
||||
self._wait_for_threads()
|
||||
# If we just did a settings import, close without saving changes.
|
||||
self.clean_up(save_settings=not self.settings_imported)
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
if event.type() == QtCore.QEvent.FileOpen:
|
||||
|
|
|
@ -23,6 +23,8 @@ The :mod:`~openlp.core.ui.media` module contains classes and objects for media p
|
|||
"""
|
||||
import logging
|
||||
|
||||
from openlp.core.common.registry import Registry
|
||||
|
||||
log = logging.getLogger(__name__ + '.__init__')
|
||||
|
||||
# Audio and video extensions copied from 'include/vlc_interface.h' from vlc 2.2.0 source
|
||||
|
@ -83,9 +85,7 @@ class ItemMediaInfo(object):
|
|||
This class hold the media related info
|
||||
"""
|
||||
file_info = None
|
||||
volume = 100
|
||||
is_background = False
|
||||
is_looping_playback = False
|
||||
length = 0
|
||||
start_time = 0
|
||||
end_time = 0
|
||||
|
@ -97,6 +97,57 @@ class ItemMediaInfo(object):
|
|||
media_type = MediaType()
|
||||
|
||||
|
||||
def get_volume(controller) -> int:
|
||||
"""
|
||||
The volume needs to be retrieved
|
||||
|
||||
:param controller: the controller in use
|
||||
:return: Are we looping
|
||||
"""
|
||||
if controller.is_live:
|
||||
return Registry().get('settings').value('media/live volume')
|
||||
else:
|
||||
return Registry().get('settings').value('media/preview volume')
|
||||
|
||||
|
||||
def save_volume(controller, volume: int) -> None:
|
||||
"""
|
||||
The volume needs to be saved
|
||||
|
||||
:param controller: the controller in use
|
||||
:param volume: The volume to use and save
|
||||
:return: Are we looping
|
||||
"""
|
||||
if controller.is_live:
|
||||
return Registry().get('settings').setValue('media/live volume', volume)
|
||||
else:
|
||||
return Registry().get('settings').setValue('media/preview volume', volume)
|
||||
|
||||
|
||||
def is_looping_playback(controller) -> bool:
|
||||
"""
|
||||
:param controller: the controller in use
|
||||
:return: Are we looping
|
||||
"""
|
||||
if controller.is_live:
|
||||
return Registry().get('settings').value('media/live loop')
|
||||
else:
|
||||
return Registry().get('settings').value('media/preview loop')
|
||||
|
||||
|
||||
def toggle_looping_playback(controller) -> None:
|
||||
"""
|
||||
|
||||
:param controller: the controller in use
|
||||
:return: None
|
||||
"""
|
||||
if controller.is_live:
|
||||
Registry().get('settings').setValue('media/live loop', not Registry().get('settings').value('media/live loop'))
|
||||
else:
|
||||
Registry().get('settings').setValue('media/preview loop',
|
||||
not Registry().get('settings').value('media/preview loop'))
|
||||
|
||||
|
||||
def parse_optical_path(input_string):
|
||||
"""
|
||||
Split the optical path info.
|
||||
|
|
|
@ -42,7 +42,8 @@ from openlp.core.lib.serviceitem import ItemCapabilities
|
|||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.state import State
|
||||
from openlp.core.ui import DisplayControllerType, HideMode
|
||||
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_stream_path
|
||||
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_stream_path, \
|
||||
get_volume, toggle_looping_playback, is_looping_playback, save_volume
|
||||
from openlp.core.ui.media.remote import register_views
|
||||
from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
|
||||
|
||||
|
@ -236,10 +237,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||
self.media_reset(controller)
|
||||
controller.media_info = ItemMediaInfo()
|
||||
controller.media_info.media_type = MediaType.Video
|
||||
if controller.is_live:
|
||||
controller.media_info.volume = self.settings.value('media/live volume')
|
||||
else:
|
||||
controller.media_info.volume = self.settings.value('media/preview volume')
|
||||
# background will always loop video.
|
||||
if service_item.is_capable(ItemCapabilities.HasBackgroundAudio):
|
||||
controller.media_info.file_info = [file_path for (file_path, file_hash) in service_item.background_audio]
|
||||
|
@ -255,7 +252,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||
elif service_item.is_capable(ItemCapabilities.HasBackgroundVideo):
|
||||
controller.media_info.file_info = [service_item.video_file_name]
|
||||
service_item.media_length = self.media_length(service_item.video_file_name)
|
||||
controller.media_info.is_looping_playback = True
|
||||
controller.media_info.is_background = True
|
||||
else:
|
||||
controller.media_info.file_info = [service_item.get_frame_path()]
|
||||
|
@ -457,13 +453,13 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||
controller.seek_slider.blockSignals(False)
|
||||
controller.volume_slider.blockSignals(False)
|
||||
return False
|
||||
self.media_volume(controller, controller.media_info.volume)
|
||||
self.media_volume(controller, get_volume(controller))
|
||||
if not start_hidden:
|
||||
self._media_set_visibility(controller, True)
|
||||
controller.mediabar.actions['playbackPlay'].setVisible(False)
|
||||
controller.mediabar.actions['playbackPause'].setVisible(True)
|
||||
controller.mediabar.actions['playbackStop'].setDisabled(False)
|
||||
controller.mediabar.actions['playbackLoop'].setChecked(controller.media_info.is_looping_playback)
|
||||
controller.mediabar.actions['playbackLoop'].setChecked(is_looping_playback(controller))
|
||||
controller.mediabar.actions['playbackStop'].setVisible(not controller.media_info.is_background or
|
||||
controller.media_info.media_type is MediaType.Audio)
|
||||
controller.mediabar.actions['playbackLoop'].setVisible((not controller.media_info.is_background and
|
||||
|
@ -501,7 +497,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||
if controller.media_info.is_playing and controller.media_info.length > 0:
|
||||
controller.media_info.timer += TICK_TIME
|
||||
if controller.media_info.timer >= controller.media_info.start_time + controller.media_info.length:
|
||||
if controller.media_info.is_looping_playback:
|
||||
if is_looping_playback(controller):
|
||||
start_again = True
|
||||
else:
|
||||
self.media_stop(controller)
|
||||
|
@ -522,8 +518,11 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||
end_seconds = controller.media_info.end_time // 1000
|
||||
end_minutes = end_seconds // 60
|
||||
end_seconds %= 60
|
||||
controller.position_label.setText(' %02d:%02d / %02d:%02d' %
|
||||
(minutes, seconds, end_minutes, end_seconds))
|
||||
if end_minutes == 0 and end_seconds == 0:
|
||||
controller.position_label.setText('')
|
||||
else:
|
||||
controller.position_label.setText(' %02d:%02d / %02d:%02d' %
|
||||
(minutes, seconds, end_minutes, end_seconds))
|
||||
|
||||
def media_pause_msg(self, msg):
|
||||
"""
|
||||
|
@ -573,8 +572,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||
|
||||
:param controller: The controller that needs to be stopped
|
||||
"""
|
||||
controller.media_info.is_looping_playback = not controller.media_info.is_looping_playback
|
||||
controller.mediabar.actions['playbackLoop'].setChecked(controller.media_info.is_looping_playback)
|
||||
toggle_looping_playback(controller)
|
||||
controller.mediabar.actions['playbackLoop'].setChecked(is_looping_playback(controller))
|
||||
|
||||
def media_stop_msg(self, msg):
|
||||
"""
|
||||
|
@ -638,11 +637,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||
:param volume: The volume to be set
|
||||
"""
|
||||
self.log_debug(f'media_volume {volume}')
|
||||
if controller.is_live:
|
||||
self.settings.setValue('media/live volume', volume)
|
||||
else:
|
||||
self.settings.setValue('media/preview volume', volume)
|
||||
controller.media_info.volume = volume
|
||||
save_volume(controller, volume)
|
||||
self.current_media_players[controller.controller_type].volume(controller, volume)
|
||||
controller.volume_slider.setValue(volume)
|
||||
|
||||
|
@ -688,6 +683,9 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||
else:
|
||||
self._media_set_visibility(controller, False)
|
||||
del self.current_media_players[controller.controller_type]
|
||||
controller.mediabar.actions['playbackPlay'].setVisible(True)
|
||||
controller.mediabar.actions['playbackStop'].setDisabled(True)
|
||||
controller.mediabar.actions['playbackPause'].setVisible(False)
|
||||
|
||||
def media_hide_msg(self, msg):
|
||||
"""
|
||||
|
|
|
@ -32,16 +32,15 @@ from time import sleep
|
|||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.mixins import LogMixin
|
||||
from openlp.core.common.platform import is_linux, is_macosx, is_win
|
||||
from openlp.core.display.screens import ScreenList
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.media import MediaState, MediaType, VlCState
|
||||
from openlp.core.ui.media import MediaState, MediaType, VlCState, get_volume
|
||||
from openlp.core.ui.media.mediaplayer import MediaPlayer
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Audio and video extensions copied from 'include/vlc_interface.h' from vlc 2.2.0 source
|
||||
|
||||
|
||||
STATE_WAIT_TIME = 60
|
||||
|
||||
|
@ -85,7 +84,7 @@ if is_linux() and 'pytest' not in sys.argv[0] and get_vlc():
|
|||
log.exception('Failed to run XInitThreads(), VLC might not work properly!')
|
||||
|
||||
|
||||
class VlcPlayer(MediaPlayer):
|
||||
class VlcPlayer(MediaPlayer, LogMixin):
|
||||
"""
|
||||
A specialised version of the MediaPlayer class, which provides a VLC display.
|
||||
"""
|
||||
|
@ -135,7 +134,7 @@ class VlcPlayer(MediaPlayer):
|
|||
controller.vlc_instance = vlc.Instance(command_line_options)
|
||||
if not controller.vlc_instance:
|
||||
return
|
||||
log.debug(f"VLC version: {vlc.libvlc_get_version()}")
|
||||
self.log_debug(f"VLC version: {vlc.libvlc_get_version()}")
|
||||
# creating an empty vlc media player
|
||||
controller.vlc_media_player = controller.vlc_instance.media_player_new()
|
||||
controller.vlc_widget.resize(controller.size())
|
||||
|
@ -174,7 +173,7 @@ class VlcPlayer(MediaPlayer):
|
|||
"""
|
||||
if not controller.vlc_instance:
|
||||
return False
|
||||
log.debug('load video in VLC Controller')
|
||||
self.log_debug('load video in VLC Controller')
|
||||
path = None
|
||||
if file and not controller.media_info.media_type == MediaType.Stream:
|
||||
path = os.path.normcase(file)
|
||||
|
@ -203,7 +202,7 @@ class VlcPlayer(MediaPlayer):
|
|||
path = '/' + path
|
||||
dvd_location = 'dvd://' + path + '#' + controller.media_info.title_track
|
||||
controller.vlc_media = controller.vlc_instance.media_new_location(dvd_location)
|
||||
log.debug(f"vlc dvd load: {dvd_location}")
|
||||
self.log_debug(f"vlc dvd load: {dvd_location}")
|
||||
controller.vlc_media.add_option(f"start-time={int(controller.media_info.start_time // 1000)}")
|
||||
controller.vlc_media.add_option(f"stop-time={int(controller.media_info.end_time // 1000)}")
|
||||
controller.vlc_media_player.set_media(controller.vlc_media)
|
||||
|
@ -212,10 +211,11 @@ class VlcPlayer(MediaPlayer):
|
|||
self.media_state_wait(controller, VlCState.Playing)
|
||||
if controller.media_info.audio_track > 0:
|
||||
res = controller.vlc_media_player.audio_set_track(controller.media_info.audio_track)
|
||||
log.debug('vlc play, audio_track set: ' + str(controller.media_info.audio_track) + ' ' + str(res))
|
||||
self.log_debug('vlc play, audio_track set: ' + str(controller.media_info.audio_track) + ' ' + str(res))
|
||||
if controller.media_info.subtitle_track > 0:
|
||||
res = controller.vlc_media_player.video_set_spu(controller.media_info.subtitle_track)
|
||||
log.debug('vlc play, subtitle_track set: ' + str(controller.media_info.subtitle_track) + ' ' + str(res))
|
||||
self.log_debug('vlc play, subtitle_track set: ' +
|
||||
str(controller.media_info.subtitle_track) + ' ' + str(res))
|
||||
elif controller.media_info.media_type == MediaType.Stream:
|
||||
controller.vlc_media = controller.vlc_instance.media_new_location(file[0])
|
||||
controller.vlc_media.add_options(file[1])
|
||||
|
@ -229,7 +229,7 @@ class VlcPlayer(MediaPlayer):
|
|||
controller.vlc_media.parse()
|
||||
controller.seek_slider.setMinimum(controller.media_info.start_time)
|
||||
controller.seek_slider.setMaximum(controller.media_info.end_time)
|
||||
self.volume(controller, controller.media_info.volume)
|
||||
self.volume(controller, get_volume(controller))
|
||||
return True
|
||||
|
||||
def media_state_wait(self, controller, media_state):
|
||||
|
@ -271,11 +271,11 @@ class VlcPlayer(MediaPlayer):
|
|||
:param output_display: The display where the media is
|
||||
:return:
|
||||
"""
|
||||
log.debug('vlc play, mediatype: ' + str(controller.media_info.media_type))
|
||||
self.log_debug('vlc play, mediatype: ' + str(controller.media_info.media_type))
|
||||
threading.Thread(target=controller.vlc_media_player.play).start()
|
||||
if not self.media_state_wait(controller, VlCState.Playing):
|
||||
return False
|
||||
self.volume(controller, controller.media_info.volume)
|
||||
self.volume(controller, get_volume(controller))
|
||||
self.set_state(MediaState.Playing, controller)
|
||||
return True
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ import zipfile
|
|||
from contextlib import suppress
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
@ -690,8 +689,8 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
|||
self.log_debug('ServiceManager.save_file - ZIP contents size is %i bytes' % total_size)
|
||||
self.main_window.display_progress_bar(1000)
|
||||
try:
|
||||
with NamedTemporaryFile(dir=str(file_path.parent), prefix='.') as temp_file, \
|
||||
zipfile.ZipFile(temp_file, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
||||
tmp_file_path = str(file_path) + ".saving"
|
||||
with zipfile.ZipFile(tmp_file_path, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
||||
# First we add service contents..
|
||||
zip_file.writestr('service_data.osj', service_content)
|
||||
self.main_window.increment_progress_bar(service_content_size / total_size * 1000)
|
||||
|
@ -701,11 +700,8 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
|||
self.main_window.increment_progress_bar(local_file_item.stat().st_size / total_size * 1000)
|
||||
with suppress(FileNotFoundError):
|
||||
file_path.unlink()
|
||||
# Try to link rather than copy to prevent writing another file
|
||||
try:
|
||||
os.link(temp_file.name, file_path)
|
||||
except OSError:
|
||||
shutil.copyfile(temp_file.name, file_path)
|
||||
# Move rather than copy to prevent writing another file if possible
|
||||
shutil.move(tmp_file_path, file_path)
|
||||
self.settings.setValue('servicemanager/last directory', file_path.parent)
|
||||
except (PermissionError, OSError) as error:
|
||||
self.log_exception('Failed to save service to disk: {name}'.format(name=file_path))
|
||||
|
|
|
@ -971,6 +971,8 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
|||
Registry().execute(
|
||||
'{text}_start'.format(text=self.service_item.name.lower()),
|
||||
[self.service_item, self.is_live, self._current_hide_mode, slide_no])
|
||||
if self.service_item.is_capable(ItemCapabilities.ProvidesOwnTheme):
|
||||
self._set_theme(self.service_item)
|
||||
else:
|
||||
self._set_theme(self.service_item)
|
||||
self.info_label.setText(self.service_item.title)
|
||||
|
|
|
@ -29,7 +29,6 @@ from openlp.core.lib.plugin import Plugin, StringContent
|
|||
from openlp.core.lib.theme import VerticalType
|
||||
from openlp.core.lib.ui import create_action
|
||||
from openlp.core.ui.icons import UiIcons
|
||||
from openlp.plugins.alerts.remote import register_views
|
||||
from openlp.plugins.alerts.forms.alertform import AlertForm
|
||||
from openlp.plugins.alerts.lib.alertsmanager import AlertsManager
|
||||
from openlp.plugins.alerts.lib.alertstab import AlertsTab
|
||||
|
@ -139,7 +138,6 @@ class AlertsPlugin(Plugin):
|
|||
self.tools_alert_item.setVisible(True)
|
||||
action_list = ActionList.get_instance()
|
||||
action_list.add_action(self.tools_alert_item, UiStrings().Tools)
|
||||
register_views()
|
||||
|
||||
def finalise(self):
|
||||
"""
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2023 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/>. #
|
||||
##########################################################################
|
||||
from flask import Blueprint, request, abort
|
||||
|
||||
from openlp.core.api import app
|
||||
from openlp.core.api.lib import login_required, extract_request, old_success_response, old_auth
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.lib.plugin import PluginStatus
|
||||
|
||||
|
||||
v1_views = Blueprint('v1-alert-plugin', __name__)
|
||||
v2_views = Blueprint('v2-alert-plugin', __name__)
|
||||
|
||||
|
||||
@v2_views.route('', methods=['POST'])
|
||||
@login_required
|
||||
def alert():
|
||||
data = request.json
|
||||
if not data:
|
||||
abort(400)
|
||||
alert = data.get('text', '')
|
||||
if alert:
|
||||
if Registry().get('plugin_manager').get_plugin_by_name('alerts').status == PluginStatus.Active:
|
||||
Registry().get('alerts_manager').alerts_text.emit([alert])
|
||||
return '', 204
|
||||
abort(400)
|
||||
|
||||
|
||||
@v1_views.route('')
|
||||
@old_auth
|
||||
def old_alert():
|
||||
alert = extract_request(request.args.get('data', ''), 'text')
|
||||
if alert:
|
||||
if Registry().get('plugin_manager').get_plugin_by_name('alerts').status == PluginStatus.Active:
|
||||
Registry().get('alerts_manager').alerts_text.emit([alert])
|
||||
return old_success_response()
|
||||
|
||||
|
||||
def register_views():
|
||||
app.register_blueprint(v2_views, url_prefix='/api/v2/plugins/alerts')
|
||||
app.register_blueprint(v1_views, url_prefix='/api/alert')
|
|
@ -1,116 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2023 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/>. #
|
||||
##########################################################################
|
||||
import logging
|
||||
|
||||
from flask import abort, request, Blueprint, jsonify
|
||||
|
||||
from openlp.core.api import app
|
||||
from openlp.core.api.lib import login_required, extract_request, old_auth
|
||||
from openlp.core.lib.plugin import PluginStatus
|
||||
from openlp.core.common.registry import Registry
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
v1_media = Blueprint('v1-media-plugin', __name__)
|
||||
v2_media = Blueprint('v2-media-plugin', __name__)
|
||||
|
||||
|
||||
def search(text):
|
||||
plugin = Registry().get('plugin_manager').get_plugin_by_name('media')
|
||||
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
|
||||
results = plugin.media_item.search(text, False)
|
||||
return results
|
||||
return None
|
||||
|
||||
|
||||
def live(id):
|
||||
plugin = Registry().get('plugin_manager').get_plugin_by_name('media')
|
||||
if plugin.status == PluginStatus.Active and plugin.media_item:
|
||||
plugin.media_item.media_go_live.emit([id, True])
|
||||
|
||||
|
||||
def add(id):
|
||||
plugin = Registry().get('plugin_manager').get_plugin_by_name('media')
|
||||
if plugin.status == PluginStatus.Active and plugin.media_item:
|
||||
item_id = plugin.media_item.create_item_from_id(id)
|
||||
plugin.media_item.media_add_to_service.emit([item_id, True])
|
||||
|
||||
|
||||
@v2_media.route('/search')
|
||||
@login_required
|
||||
def search_view():
|
||||
text = request.args.get('text', '')
|
||||
result = search(text)
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@v2_media.route('/add', methods=['POST'])
|
||||
@login_required
|
||||
def add_view():
|
||||
data = request.json
|
||||
if not data:
|
||||
abort(400)
|
||||
id = data.get('id', -1)
|
||||
add(id)
|
||||
return '', 204
|
||||
|
||||
|
||||
@v2_media.route('/live', methods=['POST'])
|
||||
@login_required
|
||||
def live_view():
|
||||
data = request.json
|
||||
if not data:
|
||||
abort(400)
|
||||
id = data.get('id', -1)
|
||||
live(id)
|
||||
return '', 204
|
||||
|
||||
|
||||
# ----------------- DEPRECATED --------------
|
||||
@v1_media.route('/search')
|
||||
@old_auth
|
||||
def old_search():
|
||||
text = extract_request(request.args.get('data', ''), 'text')
|
||||
return jsonify({'results': {'items': search(text)}})
|
||||
|
||||
|
||||
@v1_media.route('/add')
|
||||
@old_auth
|
||||
def old_add():
|
||||
id = extract_request(request.args.get('data', ''), 'id')
|
||||
add(id)
|
||||
return '', 204
|
||||
|
||||
|
||||
@v1_media.route('/live')
|
||||
@old_auth
|
||||
def old_live():
|
||||
id = extract_request(request.args.get('data', ''), 'id')
|
||||
live(id)
|
||||
return '', 204
|
||||
# ---------------- END DEPRECATED ----------------
|
||||
|
||||
|
||||
def register_views():
|
||||
app.register_blueprint(v2_media, url_prefix='/api/v2/plugins/media/')
|
||||
app.register_blueprint(v1_media, url_prefix='/api/media/')
|
|
@ -24,7 +24,7 @@ presentations from a variety of document formats.
|
|||
"""
|
||||
import logging
|
||||
import os
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from openlp.core.common import extension_loader, sha256_file_hash
|
||||
from openlp.core.common.i18n import translate
|
||||
|
@ -84,7 +84,9 @@ class PresentationPlugin(Plugin):
|
|||
has_old_scheme = True
|
||||
# Migrate each file
|
||||
presentation_paths = self.settings.value('presentations/presentations files') or []
|
||||
for path in presentation_paths:
|
||||
for presentation_path in presentation_paths:
|
||||
# Typecast to Path object
|
||||
path = Path(presentation_path)
|
||||
# check to see if the file exists before trying to process it.
|
||||
if not path.exists():
|
||||
continue
|
||||
|
|
|
@ -94,20 +94,18 @@ class WorshipCenterProImport(SongImport):
|
|||
marker_end = verse.find('>')
|
||||
marker = verse[marker_start + 1:marker_end]
|
||||
# Identify the marker type
|
||||
if 'REFRAIN' in marker or 'CHORUS' in marker:
|
||||
if marker in ['REFRAIN', 'CHORUS']:
|
||||
marker_type = 'c'
|
||||
elif 'BRIDGE' in marker:
|
||||
elif marker == 'BRIDGE':
|
||||
marker_type = 'b'
|
||||
elif 'PRECHORUS' in marker:
|
||||
elif marker == 'PRECHORUS':
|
||||
marker_type = 'p'
|
||||
elif 'END' in marker:
|
||||
elif marker == 'END':
|
||||
marker_type = 'e'
|
||||
elif 'INTRO' in marker:
|
||||
elif marker == 'INTRO':
|
||||
marker_type = 'i'
|
||||
elif 'TAG' in marker:
|
||||
elif marker == 'TAG':
|
||||
marker_type = 'o'
|
||||
else:
|
||||
marker_type = 'v'
|
||||
# Strip tags from text
|
||||
verse = re.sub('<[^<]+?>', '', verse)
|
||||
self.add_verse(verse.strip(), marker_type)
|
||||
|
|
|
@ -52,7 +52,7 @@ def report_song_list():
|
|||
report_file_path.with_suffix('.csv')
|
||||
Registry().get('application').set_busy_cursor()
|
||||
try:
|
||||
with report_file_path.open('wt') as export_file:
|
||||
with report_file_path.open('wt', encoding='utf8') as export_file:
|
||||
fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic')
|
||||
writer = csv.DictWriter(export_file, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
|
||||
headers = dict((n, n) for n in fieldnames)
|
||||
|
|
1056
resources/i18n/af.ts
1056
resources/i18n/af.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/bg.ts
1056
resources/i18n/bg.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/cs.ts
1056
resources/i18n/cs.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/da.ts
1056
resources/i18n/da.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/de.ts
1056
resources/i18n/de.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/el.ts
1056
resources/i18n/el.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1133
resources/i18n/es.ts
1133
resources/i18n/es.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/et.ts
1056
resources/i18n/et.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/fi.ts
1056
resources/i18n/fi.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/fr.ts
1056
resources/i18n/fr.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/hu.ts
1056
resources/i18n/hu.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/id.ts
1056
resources/i18n/id.ts
File diff suppressed because it is too large
Load Diff
1452
resources/i18n/it.ts
1452
resources/i18n/it.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/ja.ts
1056
resources/i18n/ja.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/ko.ts
1056
resources/i18n/ko.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/lt.ts
1056
resources/i18n/lt.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/nb.ts
1056
resources/i18n/nb.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/nl.ts
1056
resources/i18n/nl.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/pl.ts
1056
resources/i18n/pl.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/ro.ts
1056
resources/i18n/ro.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/ru.ts
1056
resources/i18n/ru.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/sk.ts
1056
resources/i18n/sk.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/sl.ts
1056
resources/i18n/sl.ts
File diff suppressed because it is too large
Load Diff
1056
resources/i18n/sv.ts
1056
resources/i18n/sv.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -52,6 +52,7 @@ WIN32_MODULES = [
|
|||
'win32com',
|
||||
'win32ui',
|
||||
'pywintypes',
|
||||
'icu',
|
||||
]
|
||||
|
||||
LINUX_MODULES = [
|
||||
|
|
1
setup.py
1
setup.py
|
@ -107,6 +107,7 @@ using a computer and a display/projector.""",
|
|||
'lxml',
|
||||
'Mako',
|
||||
"pillow",
|
||||
'PyICU',
|
||||
'pymediainfo >= 2.2',
|
||||
'pyobjc; platform_system=="Darwin"',
|
||||
'pyobjc-framework-Cocoa; platform_system=="Darwin"',
|
||||
|
|
|
@ -63,7 +63,7 @@ def mocked_qapp():
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def registry():
|
||||
def registry(autouse=True):
|
||||
"""An instance of the Registry"""
|
||||
yield Registry.create()
|
||||
Registry._instances = {}
|
||||
|
@ -78,14 +78,14 @@ def settings(qapp, registry):
|
|||
# Needed on windows to make sure a Settings object is available during the tests
|
||||
sets = Settings()
|
||||
sets.setValue('themes/global theme', 'my_theme')
|
||||
Registry().register('settings', sets)
|
||||
Registry().register('settings_thread', sets)
|
||||
Registry().register('application', qapp)
|
||||
registry.register('settings', sets)
|
||||
registry.register('settings_thread', sets)
|
||||
registry.register('application', qapp)
|
||||
qapp.settings = sets
|
||||
yield sets
|
||||
del sets
|
||||
Registry().remove('settings')
|
||||
Registry().remove('settings_thread')
|
||||
registry.remove('settings')
|
||||
registry.remove('settings_thread')
|
||||
os.close(fd)
|
||||
os.unlink(Settings().fileName())
|
||||
|
||||
|
@ -95,12 +95,12 @@ def mock_settings(qapp, registry):
|
|||
"""A Mock Settings() instance"""
|
||||
# Create and register a mock settings object to work with
|
||||
mk_settings = MagicMock()
|
||||
Registry().register('settings', mk_settings)
|
||||
Registry().register('application', qapp)
|
||||
Registry().register('settings_thread', mk_settings)
|
||||
registry.register('settings', mk_settings)
|
||||
registry.register('application', qapp)
|
||||
registry.register('settings_thread', mk_settings)
|
||||
yield mk_settings
|
||||
Registry().remove('settings')
|
||||
Registry().remove('settings_thread')
|
||||
registry.remove('settings')
|
||||
registry.remove('settings_thread')
|
||||
del mk_settings
|
||||
|
||||
|
||||
|
|
|
@ -346,7 +346,7 @@ describe("Screen Visibility", function () {
|
|||
done();
|
||||
}, TRANSITION_TIMEOUT);
|
||||
});
|
||||
|
||||
|
||||
it("should trigger dispatchEvent when toBlack(eventName) is called with an event parameter", function (done) {
|
||||
var testEventName = 'event_33';
|
||||
displayWatcher.dispatchEvent = function(eventName) {
|
||||
|
@ -959,10 +959,33 @@ describe("Display.setImageSlides", function () {
|
|||
expect(Display._slides["1"]).toEqual(1);
|
||||
expect($(".slides > section > section").length).toEqual(2);
|
||||
expect($(".slides > section > section > img").length).toEqual(2);
|
||||
expect($(".slides > section > section > img")[0].getAttribute("src")).toEqual("file:///openlp1.jpg")
|
||||
expect($(".slides > section > section > img")[0].getAttribute("style")).toEqual("width: 100%; height: 100%; margin: 0; object-fit: contain;")
|
||||
expect($(".slides > section > section > img")[1].getAttribute("src")).toEqual("file:///openlp2.jpg")
|
||||
expect($(".slides > section > section > img")[1].getAttribute("style")).toEqual("width: 100%; height: 100%; margin: 0; object-fit: contain;")
|
||||
expect($(".slides > section > section > img")[0].getAttribute("src")).toEqual("file:///openlp1.jpg");
|
||||
expect($(".slides > section > section > img")[0].getAttribute("style")).toEqual("width: 100%; height: 100%; margin: 0; object-fit: contain;");
|
||||
expect($(".slides > section > section > img")[1].getAttribute("src")).toEqual("file:///openlp2.jpg");
|
||||
expect($(".slides > section > section > img")[1].getAttribute("style")).toEqual("width: 100%; height: 100%; margin: 0; object-fit: contain;");
|
||||
expect(Reveal.sync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.setFullscreenImage", function () {
|
||||
beforeEach(function() {
|
||||
document.body.innerHTML = "";
|
||||
var slides_container = _createDiv({"class": "slides"});
|
||||
var footer_container = _createDiv({"class": "footer"});
|
||||
Display._slidesContainer = slides_container;
|
||||
Display._footerContainer = footer_container;
|
||||
Display._slides = {};
|
||||
});
|
||||
|
||||
it("should set a fullscreen image", function () {
|
||||
var image = "file:///openlp1.jpg";
|
||||
let bg_color = "#000";
|
||||
spyOn(Reveal, "sync");
|
||||
spyOn(Reveal, "slide");
|
||||
|
||||
Display.setFullscreenImage(bg_color, image);
|
||||
|
||||
expect($(".slides > section > img")[0].getAttribute("src")).toEqual("file:///openlp1.jpg");
|
||||
expect(Reveal.sync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -35,15 +35,38 @@ from openlp.core.common.registry import Registry
|
|||
ZERO_URL = '0.0.0.0'
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def mocked_get_installed_version():
|
||||
setup_patcher = patch('openlp.core.api.tab.get_installed_version')
|
||||
mocked_setup_patcher = setup_patcher.start()
|
||||
mocked_setup_patcher.return_value = None
|
||||
yield mocked_setup_patcher
|
||||
setup_patcher.stop()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def api_tab(settings):
|
||||
Registry().set_flag('website_version', '00-00-0000')
|
||||
Registry().set_flag('no_web_server', False)
|
||||
def api_tab_instantiate(mocked_get_installed_version, settings):
|
||||
forms = []
|
||||
parent = QtWidgets.QMainWindow()
|
||||
form = ApiTab(parent)
|
||||
yield form
|
||||
|
||||
def _create_api_tab():
|
||||
nonlocal forms, parent
|
||||
Registry().set_flag('website_version', '00-00-0000')
|
||||
Registry().set_flag('no_web_server', False)
|
||||
form = ApiTab(parent)
|
||||
forms.append(form)
|
||||
return form
|
||||
|
||||
yield _create_api_tab
|
||||
del parent
|
||||
del form
|
||||
for form in forms:
|
||||
del form
|
||||
mocked_get_installed_version.return_value = None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def api_tab(api_tab_instantiate):
|
||||
yield api_tab_instantiate()
|
||||
|
||||
|
||||
def test_get_ip_address_default(api_tab):
|
||||
|
@ -183,9 +206,12 @@ def test_available_version_property_set_none(api_tab):
|
|||
assert api_tab.available_version_value.text() == '(unknown)'
|
||||
|
||||
|
||||
def test_installed_version_property_get_none(api_tab):
|
||||
def test_installed_version_property_get_none(mocked_get_installed_version, api_tab_instantiate, settings):
|
||||
"""Test that the installed version property is None on init"""
|
||||
# GIVEN: An uninitialised API tab
|
||||
mocked_get_installed_version.return_value = None
|
||||
settings.setValue('api/download_version', None)
|
||||
api_tab = api_tab_instantiate()
|
||||
|
||||
# WHEN: the installed version is GET'ed
|
||||
result = api_tab.installed_version
|
||||
|
|
|
@ -41,42 +41,6 @@ def worker(settings):
|
|||
yield worker
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.WebSocketWorker')
|
||||
@patch('openlp.core.api.websockets.run_thread')
|
||||
def test_serverstart(mocked_run_thread, MockWebSocketWorker, registry):
|
||||
"""
|
||||
Test the starting of the WebSockets Server with the disabled flag set off
|
||||
"""
|
||||
# GIVEN: A new WebSocketServer
|
||||
Registry().set_flag('no_web_server', False)
|
||||
server = WebSocketServer()
|
||||
|
||||
# WHEN: I start the server
|
||||
server.start()
|
||||
|
||||
# THEN: the api environment should have been created
|
||||
assert mocked_run_thread.call_count == 1, 'The qthread should have been called once'
|
||||
assert MockWebSocketWorker.call_count == 1, 'The http thread should have been called once'
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.WebSocketWorker')
|
||||
@patch('openlp.core.api.websockets.run_thread')
|
||||
def test_serverstart_not_required(mocked_run_thread, MockWebSocketWorker, registry):
|
||||
"""
|
||||
Test the starting of the WebSockets Server with the disabled flag set on
|
||||
"""
|
||||
# GIVEN: A new WebSocketServer and the server is not required
|
||||
Registry().set_flag('no_web_server', True)
|
||||
server = WebSocketServer()
|
||||
|
||||
# WHEN: I start the server
|
||||
server.start()
|
||||
|
||||
# THEN: the api environment should have not been created
|
||||
assert mocked_run_thread.call_count == 0, 'The qthread should not have been called'
|
||||
assert MockWebSocketWorker.call_count == 0, 'The http thread should not have been called'
|
||||
|
||||
|
||||
def test_poller_get_state(poller, settings):
|
||||
"""
|
||||
Test the get_state function returns the correct JSON
|
||||
|
@ -107,50 +71,6 @@ def test_poller_get_state(poller, settings):
|
|||
assert poll_json['results']['item'] == '23-34-45', 'The item return value should match 23-34-45'
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.serve')
|
||||
@patch('openlp.core.api.websockets.asyncio')
|
||||
@patch('openlp.core.api.websockets.log')
|
||||
def test_worker_start(mocked_log, mocked_asyncio, mocked_serve, worker, settings):
|
||||
"""
|
||||
Test the start function of the worker
|
||||
"""
|
||||
# GIVEN: A mocked serve function and event loop
|
||||
mocked_serve.return_value = 'server_thing'
|
||||
event_loop = MagicMock()
|
||||
mocked_asyncio.new_event_loop.return_value = event_loop
|
||||
# WHEN: The start function is called
|
||||
worker.start()
|
||||
# THEN: No error occurs
|
||||
mocked_serve.assert_called_once()
|
||||
event_loop.run_until_complete.assert_called_once_with('server_thing')
|
||||
event_loop.run_forever.assert_called_once_with()
|
||||
mocked_log.exception.assert_not_called()
|
||||
# Because run_forever is mocked, it doesn't stall the thread so close will be called immediately
|
||||
event_loop.close.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.serve')
|
||||
@patch('openlp.core.api.websockets.asyncio')
|
||||
@patch('openlp.core.api.websockets.log')
|
||||
def test_worker_start_fail(mocked_log, mocked_asyncio, mocked_serve, worker, settings):
|
||||
"""
|
||||
Test the start function of the worker handles a error nicely
|
||||
"""
|
||||
# GIVEN: A mocked serve function and event loop. run_until_complete returns a error
|
||||
mocked_serve.return_value = 'server_thing'
|
||||
event_loop = MagicMock()
|
||||
mocked_asyncio.new_event_loop.return_value = event_loop
|
||||
event_loop.run_until_complete.side_effect = Exception()
|
||||
# WHEN: The start function is called
|
||||
worker.start()
|
||||
# THEN: An exception is logged but is handled and the event_loop is closed
|
||||
mocked_serve.assert_called_once()
|
||||
event_loop.run_until_complete.assert_called_once_with('server_thing')
|
||||
event_loop.run_forever.assert_not_called()
|
||||
mocked_log.exception.assert_called_once()
|
||||
event_loop.close.assert_called_once_with()
|
||||
|
||||
|
||||
def test_poller_event_attach(poller, settings):
|
||||
"""
|
||||
Test the event attach of WebSocketPoller
|
||||
|
@ -206,6 +126,102 @@ def test_poller_get_state_is_never_none(poller):
|
|||
assert state is not None, 'get_state() return should not be None'
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.serve')
|
||||
@patch('openlp.core.api.websockets.asyncio')
|
||||
@patch('openlp.core.api.websockets.log')
|
||||
def test_websocket_worker_start(mocked_log, mocked_asyncio, mocked_serve, worker, settings):
|
||||
"""
|
||||
Test the start function of the worker
|
||||
"""
|
||||
# GIVEN: A mocked serve function and event loop
|
||||
mocked_serve.return_value = 'server_thing'
|
||||
event_loop = MagicMock()
|
||||
mocked_asyncio.new_event_loop.return_value = event_loop
|
||||
# WHEN: The start function is called
|
||||
worker.start()
|
||||
# THEN: No error occurs
|
||||
mocked_serve.assert_called_once()
|
||||
event_loop.run_until_complete.assert_called_once_with('server_thing')
|
||||
event_loop.run_forever.assert_called_once_with()
|
||||
mocked_log.exception.assert_not_called()
|
||||
# Because run_forever is mocked, it doesn't stall the thread so close will be called immediately
|
||||
event_loop.close.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.serve')
|
||||
@patch('openlp.core.api.websockets.asyncio')
|
||||
@patch('openlp.core.api.websockets.log')
|
||||
def test_websocket_worker_start_fail(mocked_log, mocked_asyncio, mocked_serve, worker, settings):
|
||||
"""
|
||||
Test the start function of the worker handles a error nicely
|
||||
"""
|
||||
# GIVEN: A mocked serve function and event loop. run_until_complete returns a error
|
||||
mocked_serve.return_value = 'server_thing'
|
||||
event_loop = MagicMock()
|
||||
mocked_asyncio.new_event_loop.return_value = event_loop
|
||||
event_loop.run_until_complete.side_effect = Exception()
|
||||
# WHEN: The start function is called
|
||||
worker.start()
|
||||
# THEN: An exception is logged but is handled and the event_loop is closed
|
||||
mocked_serve.assert_called_once()
|
||||
event_loop.run_until_complete.assert_called_once_with('server_thing')
|
||||
event_loop.run_forever.assert_not_called()
|
||||
mocked_log.exception.assert_called_once()
|
||||
event_loop.close.assert_called_once_with()
|
||||
|
||||
|
||||
def test_websocket_server_bootstrap_post_set_up(settings):
|
||||
"""
|
||||
Test that the bootstrap_post_set_up() method calls the start method
|
||||
"""
|
||||
# GIVEN: A WebSocketServer with the start() method mocked out
|
||||
Registry().set_flag('no_web_server', False)
|
||||
server = WebSocketServer()
|
||||
server.start = MagicMock()
|
||||
|
||||
# WHEN: bootstrap_post_set_up() is called
|
||||
server.bootstrap_post_set_up()
|
||||
|
||||
# THEN: start() should have been called
|
||||
server.start.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.WebSocketWorker')
|
||||
@patch('openlp.core.api.websockets.run_thread')
|
||||
def test_websocket_server_start(mocked_run_thread, MockWebSocketWorker, registry):
|
||||
"""
|
||||
Test the starting of the WebSockets Server with the disabled flag set off
|
||||
"""
|
||||
# GIVEN: A new WebSocketServer
|
||||
Registry().set_flag('no_web_server', False)
|
||||
server = WebSocketServer()
|
||||
|
||||
# WHEN: I start the server
|
||||
server.start()
|
||||
|
||||
# THEN: the api environment should have been created
|
||||
assert mocked_run_thread.call_count == 1, 'The qthread should have been called once'
|
||||
assert MockWebSocketWorker.call_count == 1, 'The http thread should have been called once'
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.WebSocketWorker')
|
||||
@patch('openlp.core.api.websockets.run_thread')
|
||||
def test_websocket_server_start_not_required(mocked_run_thread, MockWebSocketWorker, registry):
|
||||
"""
|
||||
Test the starting of the WebSockets Server with the disabled flag set on
|
||||
"""
|
||||
# GIVEN: A new WebSocketServer and the server is not required
|
||||
Registry().set_flag('no_web_server', True)
|
||||
server = WebSocketServer()
|
||||
|
||||
# WHEN: I start the server
|
||||
server.start()
|
||||
|
||||
# THEN: the api environment should have not been created
|
||||
assert mocked_run_thread.call_count == 0, 'The qthread should not have been called'
|
||||
assert MockWebSocketWorker.call_count == 0, 'The http thread should not have been called'
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.poller')
|
||||
@patch('openlp.core.api.websockets.run_thread')
|
||||
def test_websocket_server_connects_to_poller(mock_run_thread, mock_poller, settings):
|
||||
|
@ -245,3 +261,65 @@ def test_websocket_worker_register_connections(mock_run_thread, mock_add_state_t
|
|||
|
||||
# THEN: WebSocketWorker state notify function should be called
|
||||
mock_add_state_to_queues.assert_called_once_with(mock_state)
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.poller')
|
||||
@patch('openlp.core.api.websockets.log')
|
||||
def test_websocket_server_try_poller_hook_signals(mocked_log, mock_poller, settings):
|
||||
"""
|
||||
Test if the websocket_server invokes poller.hook_signals
|
||||
"""
|
||||
# GIVEN: A mocked poller signal and a server
|
||||
mock_poller.hook_signals.side_effect = Exception
|
||||
Registry().set_flag('no_web_server', False)
|
||||
server = WebSocketServer()
|
||||
|
||||
# WHEN: WebSocketServer is started
|
||||
server.try_poller_hook_signals()
|
||||
|
||||
# THEN: poller_changed should be connected with WebSocketServer and correct handler
|
||||
mock_poller.hook_signals.assert_called_once_with()
|
||||
mocked_log.error.assert_called_once_with('Failed to hook poller signals!')
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.poller')
|
||||
def test_websocket_server_close(mock_poller, settings):
|
||||
"""
|
||||
Test that the websocket_server close method works correctly
|
||||
"""
|
||||
# GIVEN: A mocked poller signal and a server
|
||||
Registry().set_flag('no_web_server', False)
|
||||
mock_poller.poller_changed = MagicMock()
|
||||
mock_poller.poller_changed.connect = MagicMock()
|
||||
server = WebSocketServer()
|
||||
server.handle_poller_signal = MagicMock()
|
||||
mock_worker = MagicMock()
|
||||
server.worker = mock_worker
|
||||
|
||||
# WHEN: WebSocketServer is started
|
||||
server.close()
|
||||
|
||||
# THEN: poller_changed should be connected with WebSocketServer and correct handler
|
||||
mock_poller.poller_changed.disconnect.assert_called_once_with(server.handle_poller_signal)
|
||||
mock_poller.unhook_signals.assert_called_once_with()
|
||||
mock_worker.stop.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('openlp.core.api.websockets.poller')
|
||||
def test_websocket_server_close_when_disabled(mock_poller, registry, settings):
|
||||
"""
|
||||
Test if the websocket_server close method correctly skips teardown when disabled
|
||||
"""
|
||||
# GIVEN: A mocked poller signal and a server
|
||||
Registry().set_flag('no_web_server', True)
|
||||
mock_poller.poller_changed = MagicMock()
|
||||
mock_poller.poller_changed.connect = MagicMock()
|
||||
server = WebSocketServer()
|
||||
server.handle_poller_signal = MagicMock()
|
||||
|
||||
# WHEN: WebSocketServer is started
|
||||
server.close()
|
||||
|
||||
# THEN: poller_changed should be connected with WebSocketServer and correct handler
|
||||
assert mock_poller.poller_changed.disconnect.call_count == 0
|
||||
assert mock_poller.unhook_signals.call_count == 0
|
||||
|
|
|
@ -141,6 +141,16 @@ def test_get_natural_key():
|
|||
# THEN: We get a properly sorted list
|
||||
assert sorted_list == ['1st item', 'item 3b', 'item 10a'], 'Numbers should be sorted naturally'
|
||||
|
||||
# GIVEN: The language is still English (a language, which sorts digits before letters)
|
||||
mocked_get_language.return_value = 'en'
|
||||
unsorted_list = ['1 songname', '100 songname', '2 songname']
|
||||
|
||||
# WHEN: We sort the list and use get_natural_key() to generate the sorting keys
|
||||
sorted_list = sorted(unsorted_list, key=get_natural_key)
|
||||
|
||||
# THEN: We get a properly sorted list
|
||||
assert sorted_list == ['1 songname', '2 songname', '100 songname'], 'Numbers should be sorted naturally'
|
||||
|
||||
|
||||
def test_check_same_instance():
|
||||
"""
|
||||
|
|
|
@ -77,7 +77,7 @@ def test_extension_loader_files_found():
|
|||
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file2.py'),
|
||||
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file3.py'),
|
||||
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file4.py')]), \
|
||||
patch('openlp.core.common.importlib.import_module') as mocked_import_module:
|
||||
patch('openlp.core.common.import_openlp_module') as mocked_import_module:
|
||||
|
||||
# WHEN: Calling `extension_loader` with a list of files to exclude
|
||||
extension_loader('glob', ['file2.py', 'file3.py'])
|
||||
|
@ -97,7 +97,7 @@ def test_extension_loader_import_error():
|
|||
return_value=Path('/', 'app', 'dir', 'openlp')), \
|
||||
patch.object(Path, 'glob', return_value=[
|
||||
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \
|
||||
patch('openlp.core.common.importlib.import_module', side_effect=ImportError()), \
|
||||
patch('openlp.core.common.import_openlp_module', side_effect=ImportError()), \
|
||||
patch('openlp.core.common.log') as mocked_logger:
|
||||
|
||||
# WHEN: Calling `extension_loader`
|
||||
|
@ -116,7 +116,7 @@ def test_extension_loader_os_error():
|
|||
return_value=Path('/', 'app', 'dir', 'openlp')), \
|
||||
patch.object(Path, 'glob', return_value=[
|
||||
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \
|
||||
patch('openlp.core.common.importlib.import_module', side_effect=OSError()), \
|
||||
patch('openlp.core.common.import_openlp_module', side_effect=OSError()), \
|
||||
patch('openlp.core.common.log') as mocked_logger:
|
||||
|
||||
# WHEN: Calling `extension_loader`
|
||||
|
|
|
@ -307,7 +307,7 @@ def test_set_startup_screen_hide(display_window_env, mock_settings):
|
|||
'Display.setStartupSplashScreen("orange", "");')
|
||||
|
||||
|
||||
def test_after_loaded(display_window_env, mock_settings):
|
||||
def test_after_loaded(display_window_env, mock_settings, registry):
|
||||
"""
|
||||
Test the correct steps are taken when the webview is loaded
|
||||
"""
|
||||
|
@ -335,7 +335,7 @@ def test_after_loaded(display_window_env, mock_settings):
|
|||
display_window.set_startup_screen.assert_called_once()
|
||||
|
||||
|
||||
def test_after_loaded_hide_mouse_not_display(display_window_env, mock_settings):
|
||||
def test_after_loaded_hide_mouse_not_display(display_window_env, mock_settings, registry):
|
||||
"""
|
||||
Test the mouse is showing even if the `hide mouse` setting is set while is_display=false
|
||||
"""
|
||||
|
@ -361,7 +361,7 @@ def test_after_loaded_hide_mouse_not_display(display_window_env, mock_settings):
|
|||
'});')
|
||||
|
||||
|
||||
def test_after_loaded_callback(display_window_env, mock_settings):
|
||||
def test_after_loaded_callback(display_window_env, mock_settings, registry):
|
||||
"""
|
||||
Test if the __ is loaded on after_loaded() method correctly
|
||||
"""
|
||||
|
@ -462,7 +462,7 @@ def test_fix_font_bold_not_windows(mocked_is_win, display_window_env, mock_setti
|
|||
|
||||
|
||||
@patch('openlp.core.display.window.is_win')
|
||||
def test_fix_font_foundry(mocked_is_win, display_window_env, mock_settings):
|
||||
def test_fix_font_foundry(mocked_is_win, display_window_env, mock_settings, registry):
|
||||
"""
|
||||
Test that a font with a foundry name in it has the foundry removed
|
||||
"""
|
||||
|
|
|
@ -104,7 +104,7 @@ def test_get_upgrade_op():
|
|||
MockedOperations.assert_called_with(mocked_context)
|
||||
|
||||
|
||||
def test_delete_database_without_db_file_name():
|
||||
def test_delete_database_without_db_file_name(registry):
|
||||
"""
|
||||
Test that the ``delete_database`` function removes a database file, without the file name parameter
|
||||
"""
|
||||
|
|
|
@ -273,7 +273,7 @@ def test_image_to_byte_base_64():
|
|||
assert 'byte_array base64ified' == result, 'The result should be the return value of the mocked base64 method'
|
||||
|
||||
|
||||
def test_create_thumb_with_size():
|
||||
def test_create_thumb_with_size(registry):
|
||||
"""
|
||||
Test the create_thumb() function with a given size.
|
||||
"""
|
||||
|
@ -308,7 +308,7 @@ def test_create_thumb_with_size():
|
|||
pass
|
||||
|
||||
|
||||
def test_create_thumb_no_size():
|
||||
def test_create_thumb_no_size(registry):
|
||||
"""
|
||||
Test the create_thumb() function with no size specified.
|
||||
"""
|
||||
|
@ -343,7 +343,7 @@ def test_create_thumb_no_size():
|
|||
pass
|
||||
|
||||
|
||||
def test_create_thumb_invalid_size():
|
||||
def test_create_thumb_invalid_size(registry):
|
||||
"""
|
||||
Test the create_thumb() function with invalid size specified.
|
||||
"""
|
||||
|
@ -379,7 +379,7 @@ def test_create_thumb_invalid_size():
|
|||
pass
|
||||
|
||||
|
||||
def test_create_thumb_width_only():
|
||||
def test_create_thumb_width_only(registry):
|
||||
"""
|
||||
Test the create_thumb() function with a size of only width specified.
|
||||
"""
|
||||
|
@ -415,7 +415,7 @@ def test_create_thumb_width_only():
|
|||
pass
|
||||
|
||||
|
||||
def test_create_thumb_height_only():
|
||||
def test_create_thumb_height_only(registry):
|
||||
"""
|
||||
Test the create_thumb() function with a size of only height specified.
|
||||
"""
|
||||
|
@ -451,7 +451,7 @@ def test_create_thumb_height_only():
|
|||
pass
|
||||
|
||||
|
||||
def test_create_thumb_empty_img():
|
||||
def test_create_thumb_empty_img(registry):
|
||||
"""
|
||||
Test the create_thumb() function with a size of only height specified.
|
||||
"""
|
||||
|
@ -502,7 +502,7 @@ def test_create_thumb_empty_img():
|
|||
|
||||
@patch('openlp.core.lib.QtGui.QImageReader')
|
||||
@patch('openlp.core.lib.build_icon')
|
||||
def test_create_thumb_path_fails(mocked_build_icon, MockQImageReader):
|
||||
def test_create_thumb_path_fails(mocked_build_icon, MockQImageReader, registry):
|
||||
"""
|
||||
Test that build_icon() is run against the image_path when the thumbnail fails to be created
|
||||
"""
|
||||
|
@ -537,7 +537,7 @@ def test_check_item_selected_true():
|
|||
assert result is True, 'The result should be True'
|
||||
|
||||
|
||||
def test_check_item_selected_false():
|
||||
def test_check_item_selected_false(registry):
|
||||
"""
|
||||
Test that the check_item_selected() function returns False when there are no selected indexes.
|
||||
"""
|
||||
|
@ -608,7 +608,7 @@ def test_validate_thumb_file_exists_and_older():
|
|||
assert result is False, 'The result should be False'
|
||||
|
||||
|
||||
def test_resize_thumb():
|
||||
def test_resize_thumb(registry):
|
||||
"""
|
||||
Test the resize_thumb() function
|
||||
"""
|
||||
|
@ -630,7 +630,7 @@ def test_resize_thumb():
|
|||
assert image.pixel(0, 0) == wanted_background_rgb, 'The background should be white.'
|
||||
|
||||
|
||||
def test_resize_thumb_ignoring_aspect_ratio():
|
||||
def test_resize_thumb_ignoring_aspect_ratio(registry):
|
||||
"""
|
||||
Test the resize_thumb() function ignoring aspect ratio
|
||||
"""
|
||||
|
@ -652,7 +652,7 @@ def test_resize_thumb_ignoring_aspect_ratio():
|
|||
assert image.pixel(0, 0) == wanted_background_rgb, 'The background should be white.'
|
||||
|
||||
|
||||
def test_resize_thumb_width_aspect_ratio():
|
||||
def test_resize_thumb_width_aspect_ratio(registry):
|
||||
"""
|
||||
Test the resize_thumb() function using the image's width as the reference
|
||||
"""
|
||||
|
@ -670,7 +670,7 @@ def test_resize_thumb_width_aspect_ratio():
|
|||
assert wanted_width == result_size.width(), 'The image should have the requested width.'
|
||||
|
||||
|
||||
def test_resize_thumb_same_aspect_ratio():
|
||||
def test_resize_thumb_same_aspect_ratio(registry):
|
||||
"""
|
||||
Test the resize_thumb() function when the image and the wanted aspect ratio are the same
|
||||
"""
|
||||
|
|
|
@ -862,7 +862,7 @@ def test_to_dict_text_item(mocked_sha256_file_hash, state_media, settings, servi
|
|||
],
|
||||
'theme': None,
|
||||
'title': 'Amazing Grace',
|
||||
'type': 'ServiceItemType.Text',
|
||||
'type': ServiceItemType.Text,
|
||||
'data': {'authors': 'John Newton', 'title': 'amazing grace@'}
|
||||
}
|
||||
assert result == expected_dict
|
||||
|
@ -905,7 +905,7 @@ def test_to_dict_image_item(state_media, settings, service_item_env):
|
|||
],
|
||||
'theme': -1,
|
||||
'title': 'Images',
|
||||
'type': 'ServiceItemType.Image',
|
||||
'type': ServiceItemType.Image,
|
||||
'data': {}
|
||||
}
|
||||
assert result == expected_dict
|
||||
|
@ -961,7 +961,7 @@ def test_to_dict_presentation_item(mocked_image_uri, mocked_get_data_path, state
|
|||
],
|
||||
'theme': None,
|
||||
'title': '',
|
||||
'type': 'ServiceItemType.Command',
|
||||
'type': ServiceItemType.Command,
|
||||
'data': {}
|
||||
}
|
||||
assert result == expected_dict
|
||||
|
|
|
@ -22,14 +22,18 @@
|
|||
Package to test the openlp.core.ui.media package.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from unittest import skipUnless
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from openlp.core.state import State
|
||||
from openlp.core.common.platform import is_linux, is_macosx
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.ui import DisplayControllerType, HideMode
|
||||
from openlp.core.ui.media.mediacontroller import MediaController
|
||||
from openlp.core.ui.media import ItemMediaInfo, MediaState
|
||||
from openlp.core.ui.media import ItemMediaInfo, MediaState, MediaType
|
||||
|
||||
from tests.utils.constants import RESOURCE_PATH
|
||||
|
||||
|
@ -46,6 +50,216 @@ def media_env(registry):
|
|||
yield media_controller
|
||||
|
||||
|
||||
@patch('openlp.core.ui.media.mediacontroller.register_views')
|
||||
def test_setup(mocked_register_views, media_env):
|
||||
"""
|
||||
Test that the setup method is called correctly
|
||||
"""
|
||||
# GIVEN: A media controller, and function list
|
||||
expected_functions_list = ['bootstrap_initialise', 'bootstrap_post_set_up', 'bootstrap_completion',
|
||||
'playbackPlay', 'playbackPause', 'playbackStop', 'playbackLoop', 'seek_slider',
|
||||
'volume_slider', 'media_hide', 'media_blank', 'media_unblank', 'songs_hide',
|
||||
'songs_blank', 'songs_unblank']
|
||||
# WHEN: Setup is called
|
||||
media_env.setup()
|
||||
# THEN: the functions should be defined along with the api blueprint
|
||||
assert list(Registry().functions_list.keys()) == expected_functions_list, \
|
||||
f'The function list should have been {(Registry().functions_list.keys())}'
|
||||
mocked_register_views.called_once(), 'The media blueprint has not been registered'
|
||||
|
||||
|
||||
def test_initialise_good(media_env, state_media):
|
||||
"""
|
||||
Test that the bootstrap initialise method is called correctly
|
||||
"""
|
||||
# GIVEN: a mocked setup
|
||||
with patch.object(media_env.media_controller, u'setup') as mocked_setup:
|
||||
# THEN: The underlying method is called
|
||||
media_env.media_controller.bootstrap_initialise()
|
||||
# THEN: The following should have happened
|
||||
mocked_setup.called_once(), 'The setup function has been called'
|
||||
|
||||
|
||||
def test_initialise_missing_vlc(media_env, state_media):
|
||||
"""
|
||||
Test that the bootstrap initialise method is called correctly with no VLC
|
||||
"""
|
||||
# GIVEN: a mocked setup and no VLC
|
||||
with patch.object(media_env.media_controller, u'setup') as mocked_setup, \
|
||||
patch('openlp.core.ui.media.mediacontroller.get_vlc', return_value=False):
|
||||
# THEN: The underlying method is called
|
||||
media_env.media_controller.bootstrap_initialise()
|
||||
# THEN: The following should have happened
|
||||
mocked_setup.called_once(), 'The setup function has been called'
|
||||
text = State().get_text()
|
||||
if not is_macosx():
|
||||
assert text.find("python3-vlc") > 0, "VLC should not be missing"
|
||||
|
||||
|
||||
@patch('openlp.core.ui.media.mediacontroller.pymediainfo_available', False)
|
||||
def test_initialise_missing_pymedia(media_env, state_media):
|
||||
"""
|
||||
Test that the bootstrap initialise method is called correctly with no pymediainfo
|
||||
"""
|
||||
# GIVEN: a mocked setup and no pymedia
|
||||
with patch.object(media_env.media_controller, u'setup') as mocked_setup:
|
||||
# THEN: The underlying method is called
|
||||
media_env.media_controller.bootstrap_initialise()
|
||||
# THEN: The following should have happened
|
||||
mocked_setup.called_once(), 'The setup function has been called'
|
||||
text = State().get_text()
|
||||
if not is_macosx():
|
||||
assert text.find("python3-pymediainfo") > 0, "PyMedia should not be missing"
|
||||
|
||||
|
||||
@skipUnless(is_linux(), "Linux only")
|
||||
def test_initialise_missing_pymedia_fedora(media_env, state_media):
|
||||
"""
|
||||
Test that the bootstrap initialise method is called correctly with no VLC
|
||||
"""
|
||||
# GIVEN: a mocked setup and no VLC
|
||||
with patch.object(media_env.media_controller, u'setup') as mocked_setup, \
|
||||
patch('openlp.core.ui.media.mediacontroller.get_vlc', return_value=False), \
|
||||
patch('openlp.core.ui.media.mediacontroller.is_linux', return_value=True):
|
||||
# WHEN: The underlying method is called
|
||||
media_env.media_controller.bootstrap_initialise()
|
||||
# THEN: The following should have happened
|
||||
mocked_setup.called_once(), 'The setup function has been called'
|
||||
text = State().get_text()
|
||||
assert text.find("python3-pymediainfo") == -1, "PyMedia should be missing"
|
||||
assert text.find("python3-vlc") > 0, "VLC should not be missing"
|
||||
assert text.find("rpmfusion") > 0, "RPMFusion should provide the modules"
|
||||
|
||||
|
||||
@skipUnless(is_linux(), "Linux only")
|
||||
def test_initialise_missing_pymedia_not_fedora(media_env, state_media):
|
||||
"""
|
||||
Test that the bootstrap initialise method is called correctly with no VLC
|
||||
"""
|
||||
# GIVEN: a mocked setup and no VLC
|
||||
with patch.object(media_env.media_controller, u'setup') as mocked_setup, \
|
||||
patch('openlp.core.ui.media.mediacontroller.get_vlc', return_value=False), \
|
||||
patch('openlp.core.ui.media.mediacontroller.is_linux', return_value=False):
|
||||
# WHEN: The underlying method is called
|
||||
media_env.media_controller.bootstrap_initialise()
|
||||
# THEN: The following should have happened
|
||||
mocked_setup.called_once(), 'The setup function has been called'
|
||||
text = State().get_text()
|
||||
assert text.find("python3-pymediainfo") == -1, "PyMedia should be missing"
|
||||
assert text.find("python3-vlc") > 0, "VLC should not be missing"
|
||||
assert text.find("rpmfusion") == -1, "RPMFusion should not provide the modules"
|
||||
|
||||
|
||||
def test_initialise_missing_pymedia_mac_os(media_env, state_media):
|
||||
"""
|
||||
Test that the bootstrap initialise method is called correctly with no VLC
|
||||
"""
|
||||
# GIVEN: a mocked setup and no VLC
|
||||
with patch.object(media_env.media_controller, u'setup') as mocked_setup, \
|
||||
patch('openlp.core.ui.media.mediacontroller.get_vlc', return_value=False), \
|
||||
patch('openlp.core.ui.media.mediacontroller.is_macosx', return_value=True):
|
||||
# WHEN: The underlying method is called
|
||||
media_env.media_controller.bootstrap_initialise()
|
||||
# THEN: The following should have happened
|
||||
mocked_setup.called_once(), 'The setup function has been called'
|
||||
text = State().get_text()
|
||||
assert text.find("python3-pymediainfo") == -1, "PyMedia should be missing"
|
||||
assert text.find("python3-vlc") == -1, "PyMedia should be missing"
|
||||
assert text.find("videolan") > 0, "VideoLAN should provide the modules"
|
||||
|
||||
|
||||
def test_post_set_up_good(media_env, state_media):
|
||||
"""
|
||||
Test the Bootstrap post set up assuming all functions are good
|
||||
"""
|
||||
# GIVEN: A working environment
|
||||
media_env.vlc_live_media_stop = MagicMock()
|
||||
media_env.vlc_preview_media_stop = MagicMock()
|
||||
media_env.vlc_live_media_tick = MagicMock()
|
||||
media_env.vlc_preview_media_tick = MagicMock()
|
||||
State().add_service("mediacontroller", 0)
|
||||
State().update_pre_conditions("mediacontroller", True)
|
||||
# WHEN: I call the function
|
||||
with patch.object(media_env.media_controller, u'setup_display') as mocked_display:
|
||||
media_env.bootstrap_post_set_up()
|
||||
# THEN: the environment is set up correctly
|
||||
assert mocked_display.call_count == 2, "Should have been called twice"
|
||||
text = State().get_text()
|
||||
assert text.find("No Displays") == -1, "No Displays have been disable"
|
||||
assert mocked_display.has_calls(None, False) # Live Controller
|
||||
assert mocked_display.has_calls(None, True) # Preview Controller
|
||||
|
||||
|
||||
def test_media_state_live(media_env, state_media):
|
||||
"""
|
||||
Test the Bootstrap post set up assuming all functions are good
|
||||
"""
|
||||
# GIVEN: A working environment
|
||||
media_env.vlc_live_media_stop = MagicMock()
|
||||
media_env.vlc_preview_media_stop = MagicMock()
|
||||
media_env.vlc_preview_media_tick = MagicMock()
|
||||
mocked_live_controller = MagicMock()
|
||||
mocked_live_controller.is_live = True
|
||||
mocked_live_controller.media_info.media_type = MediaType.Audio
|
||||
Registry().register('live_controller', mocked_live_controller)
|
||||
media_env.media_controller.vlc_player = MagicMock()
|
||||
media_env.media_controller._display_controllers = MagicMock(return_value=mocked_live_controller)
|
||||
# WHEN: I call the function
|
||||
with patch.object(media_env.media_controller, u'setup_display') as mocked_display:
|
||||
media_env.bootstrap_post_set_up()
|
||||
# THEN: the environment is set up correctly
|
||||
text = State().get_text()
|
||||
assert text.find("No Displays") == -1, "No Displays have been disable"
|
||||
assert mocked_display.has_calls(None, False) # Live Controller
|
||||
assert mocked_display.has_calls(None, True) # Preview Controller
|
||||
|
||||
|
||||
def test_post_set_up_no_controller(media_env, state_media):
|
||||
"""
|
||||
Test the Bootstrap post set up assuming all functions are good
|
||||
"""
|
||||
# GIVEN: A working environment
|
||||
media_env.vlc_live_media_stop = MagicMock()
|
||||
media_env.vlc_preview_media_stop = MagicMock()
|
||||
media_env.vlc_live_media_tick = MagicMock()
|
||||
media_env.vlc_preview_media_tick = MagicMock()
|
||||
State().add_service("mediacontroller", 0)
|
||||
State().update_pre_conditions("mediacontroller", False)
|
||||
# WHEN: I call the function
|
||||
with patch.object(media_env.media_controller, u'setup_display') as mocked_display:
|
||||
media_env.bootstrap_post_set_up()
|
||||
# THEN: the environment is set up correctly
|
||||
assert mocked_display.call_count == 0, "Should have not have been called twice"
|
||||
text = State().get_text()
|
||||
assert text.find("No Displays") == -1, "No Displays have been disable"
|
||||
|
||||
|
||||
def test_post_set_up_controller_exception(media_env, state_media):
|
||||
"""
|
||||
Test the Bootstrap post set up assuming all functions are good
|
||||
"""
|
||||
# GIVEN: A working environment
|
||||
media_env.vlc_live_media_stop = MagicMock()
|
||||
media_env.vlc_preview_media_stop = MagicMock()
|
||||
media_env.vlc_live_media_tick = MagicMock()
|
||||
media_env.vlc_preview_media_tick = MagicMock()
|
||||
State().add_service("mediacontroller", 0)
|
||||
State().update_pre_conditions("mediacontroller", True)
|
||||
State().add_service("media_live", 0)
|
||||
State().update_pre_conditions("media_live", True)
|
||||
# WHEN: I call the function
|
||||
with patch.object(media_env.media_controller, u'setup_display') as mocked_display:
|
||||
mocked_display.side_effect = AttributeError()
|
||||
try:
|
||||
media_env.bootstrap_post_set_up()
|
||||
except AttributeError:
|
||||
pass
|
||||
# THEN: the environment is set up correctly
|
||||
text = State().get_text()
|
||||
assert text.find("Displays") > 0, "Displays have been disable"
|
||||
assert mocked_display.call_count == 2, "Should have been called twice"
|
||||
|
||||
|
||||
def test_resize(media_env):
|
||||
"""
|
||||
Test that the resize method is called correctly
|
||||
|
@ -86,7 +300,6 @@ def test_load_video(media_env, settings):
|
|||
# The video should have autoplayed
|
||||
# The controls should have been made visible
|
||||
media_env.media_controller.media_reset.assert_called_once_with(mocked_slide_controller)
|
||||
assert mocked_slide_controller.media_info.volume == 1
|
||||
media_env.media_controller.media_play.assert_called_once_with(mocked_slide_controller, False)
|
||||
media_env.media_controller.set_controls_visible.assert_called_once_with(mocked_slide_controller, True)
|
||||
|
||||
|
|
|
@ -283,7 +283,7 @@ def test_check_not_available(mocked_get_vlc):
|
|||
|
||||
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
|
||||
@patch('openlp.core.ui.media.vlcplayer.os.path.normcase')
|
||||
def test_load(mocked_normcase, mocked_get_vlc):
|
||||
def test_load(mocked_normcase, mocked_get_vlc, settings):
|
||||
"""
|
||||
Test loading a video into VLC
|
||||
"""
|
||||
|
@ -294,7 +294,6 @@ def test_load(mocked_normcase, mocked_get_vlc):
|
|||
mocked_get_vlc.return_value = mocked_vlc
|
||||
mocked_display = MagicMock()
|
||||
mocked_controller = MagicMock()
|
||||
mocked_controller.media_info.volume = 100
|
||||
mocked_controller.media_info.media_type = MediaType.Video
|
||||
mocked_controller.media_info.file_info.absoluteFilePath.return_value = media_path
|
||||
mocked_vlc_media = MagicMock()
|
||||
|
@ -305,8 +304,7 @@ def test_load(mocked_normcase, mocked_get_vlc):
|
|||
vlc_player = VlcPlayer(None)
|
||||
|
||||
# WHEN: A video is loaded into VLC
|
||||
with patch.object(vlc_player, 'volume') as mocked_volume:
|
||||
result = vlc_player.load(mocked_controller, mocked_display, media_path)
|
||||
result = vlc_player.load(mocked_controller, mocked_display, media_path)
|
||||
|
||||
# THEN: The video should be loaded
|
||||
mocked_normcase.assert_called_with(media_path)
|
||||
|
@ -314,14 +312,13 @@ def test_load(mocked_normcase, mocked_get_vlc):
|
|||
assert mocked_vlc_media == mocked_controller.vlc_media
|
||||
mocked_controller.vlc_media_player.set_media.assert_called_with(mocked_vlc_media)
|
||||
mocked_vlc_media.parse.assert_called_with()
|
||||
mocked_volume.assert_called_with(mocked_controller, 100)
|
||||
assert result is True
|
||||
|
||||
|
||||
@patch('openlp.core.ui.media.vlcplayer.is_win')
|
||||
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
|
||||
@patch('openlp.core.ui.media.vlcplayer.os.path.normcase')
|
||||
def test_load_audio_cd(mocked_normcase, mocked_get_vlc, mocked_is_win):
|
||||
def test_load_audio_cd(mocked_normcase, mocked_get_vlc, mocked_is_win, settings):
|
||||
"""
|
||||
Test loading an audio CD into VLC
|
||||
"""
|
||||
|
@ -333,7 +330,6 @@ def test_load_audio_cd(mocked_normcase, mocked_get_vlc, mocked_is_win):
|
|||
mocked_get_vlc.return_value = mocked_vlc
|
||||
mocked_display = MagicMock()
|
||||
mocked_controller = MagicMock()
|
||||
mocked_controller.media_info.volume = 100
|
||||
mocked_controller.media_info.media_type = MediaType.CD
|
||||
mocked_controller.media_info.title_track = 1
|
||||
mocked_vlc_media = MagicMock()
|
||||
|
@ -351,8 +347,7 @@ def test_load_audio_cd(mocked_normcase, mocked_get_vlc, mocked_is_win):
|
|||
vlc_player = VlcPlayer(None)
|
||||
|
||||
# WHEN: An audio CD is loaded into VLC
|
||||
with patch.object(vlc_player, 'volume') as mocked_volume, \
|
||||
patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
|
||||
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
|
||||
result = vlc_player.load(mocked_controller, mocked_display, media_path)
|
||||
|
||||
# THEN: The video should be loaded
|
||||
|
@ -361,7 +356,6 @@ def test_load_audio_cd(mocked_normcase, mocked_get_vlc, mocked_is_win):
|
|||
assert mocked_vlc_media == mocked_controller.vlc_media
|
||||
mocked_controller.vlc_media_player.set_media.assert_called_with(mocked_vlc_media)
|
||||
mocked_vlc_media.parse.assert_called_with()
|
||||
mocked_volume.assert_called_with(mocked_controller, 100)
|
||||
mocked_media_state_wait.assert_called_with(mocked_controller, ANY)
|
||||
mocked_controller.seek_slider.setMinimum.assert_called_with(20000)
|
||||
mocked_controller.seek_slider.setMaximum.assert_called_with(30000)
|
||||
|
@ -371,7 +365,7 @@ def test_load_audio_cd(mocked_normcase, mocked_get_vlc, mocked_is_win):
|
|||
@patch('openlp.core.ui.media.vlcplayer.is_win')
|
||||
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
|
||||
@patch('openlp.core.ui.media.vlcplayer.os.path.normcase')
|
||||
def test_load_audio_cd_on_windows(mocked_normcase, mocked_get_vlc, mocked_is_win):
|
||||
def test_load_audio_cd_on_windows(mocked_normcase, mocked_get_vlc, mocked_is_win, settings):
|
||||
"""
|
||||
Test loading an audio CD into VLC on Windows
|
||||
"""
|
||||
|
@ -383,7 +377,6 @@ def test_load_audio_cd_on_windows(mocked_normcase, mocked_get_vlc, mocked_is_win
|
|||
mocked_get_vlc.return_value = mocked_vlc
|
||||
mocked_display = MagicMock()
|
||||
mocked_controller = MagicMock()
|
||||
mocked_controller.media_info.volume = 100
|
||||
mocked_controller.media_info.media_type = MediaType.CD
|
||||
mocked_controller.media_info.file_info.absoluteFilePath.return_value = media_path
|
||||
mocked_controller.media_info.title_track = 1
|
||||
|
@ -400,8 +393,7 @@ def test_load_audio_cd_on_windows(mocked_normcase, mocked_get_vlc, mocked_is_win
|
|||
vlc_player = VlcPlayer(None)
|
||||
|
||||
# WHEN: An audio CD is loaded into VLC
|
||||
with patch.object(vlc_player, 'volume') as mocked_volume, \
|
||||
patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
|
||||
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
|
||||
result = vlc_player.load(mocked_controller, mocked_display, media_path)
|
||||
|
||||
# THEN: The video should be loaded
|
||||
|
@ -410,7 +402,6 @@ def test_load_audio_cd_on_windows(mocked_normcase, mocked_get_vlc, mocked_is_win
|
|||
assert mocked_vlc_media == mocked_controller.vlc_media
|
||||
mocked_controller.vlc_media_player.set_media.assert_called_with(mocked_vlc_media)
|
||||
mocked_vlc_media.parse.assert_called_with()
|
||||
mocked_volume.assert_called_with(mocked_controller, 100)
|
||||
mocked_media_state_wait.assert_called_with(mocked_controller, ANY)
|
||||
assert result is True
|
||||
|
||||
|
@ -449,7 +440,7 @@ def test_load_audio_cd_no_tracks(mocked_normcase, mocked_get_vlc, mocked_is_win)
|
|||
vlc_player = VlcPlayer(None)
|
||||
|
||||
# WHEN: An audio CD is loaded into VLC
|
||||
with patch.object(vlc_player, 'volume'), patch.object(vlc_player, 'media_state_wait'):
|
||||
with patch.object(vlc_player, 'media_state_wait'):
|
||||
result = vlc_player.load(mocked_controller, mocked_display, media_path)
|
||||
|
||||
# THEN: The video should be loaded
|
||||
|
@ -465,7 +456,7 @@ def test_load_audio_cd_no_tracks(mocked_normcase, mocked_get_vlc, mocked_is_win)
|
|||
@patch('openlp.core.ui.media.vlcplayer.is_win')
|
||||
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
|
||||
@patch('openlp.core.ui.media.vlcplayer.os.path.normcase')
|
||||
def test_load_dvd(mocked_normcase, mocked_get_vlc, mocked_is_win):
|
||||
def test_load_dvd(mocked_normcase, mocked_get_vlc, mocked_is_win, settings):
|
||||
"""
|
||||
Test loading a DVD into VLC
|
||||
"""
|
||||
|
@ -477,14 +468,12 @@ def test_load_dvd(mocked_normcase, mocked_get_vlc, mocked_is_win):
|
|||
mocked_get_vlc.return_value = mocked_vlc
|
||||
mocked_display = MagicMock()
|
||||
mocked_controller = MagicMock()
|
||||
mocked_controller.media_info.volume = 100
|
||||
mocked_controller.media_info.media_type = MediaType.DVD
|
||||
mocked_controller.media_info.title_track = '2'
|
||||
mocked_controller.media_info.audio_track = 2
|
||||
mocked_controller.media_info.subtitle_track = 4
|
||||
mocked_vlc_media = MagicMock()
|
||||
mocked_media = MagicMock()
|
||||
mocked_controller.media_info.volume = 100
|
||||
mocked_controller.media_info.start_time = 20000
|
||||
mocked_controller.media_info.end_time = 30000
|
||||
mocked_controller.media_info.length = 10000
|
||||
|
@ -493,8 +482,7 @@ def test_load_dvd(mocked_normcase, mocked_get_vlc, mocked_is_win):
|
|||
vlc_player = VlcPlayer(None)
|
||||
|
||||
# WHEN: A DVD clip is loaded into VLC
|
||||
with patch.object(vlc_player, 'volume') as mocked_volume, \
|
||||
patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
|
||||
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
|
||||
result = vlc_player.load(mocked_controller, mocked_display, media_path)
|
||||
|
||||
# THEN: The video should be loaded
|
||||
|
@ -505,7 +493,6 @@ def test_load_dvd(mocked_normcase, mocked_get_vlc, mocked_is_win):
|
|||
mocked_controller.vlc_media_player.audio_set_track.assert_called_with(2)
|
||||
mocked_controller.vlc_media_player.video_set_spu.assert_called_with(4)
|
||||
mocked_vlc_media.parse.assert_called_with()
|
||||
mocked_volume.assert_called_with(mocked_controller, 100)
|
||||
mocked_media_state_wait.assert_called_with(mocked_controller, ANY)
|
||||
mocked_controller.seek_slider.setMinimum.assert_called_with(20000)
|
||||
mocked_controller.seek_slider.setMaximum.assert_called_with(30000)
|
||||
|
@ -606,7 +593,7 @@ def test_resize():
|
|||
|
||||
@patch('openlp.core.ui.media.vlcplayer.threading')
|
||||
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
|
||||
def test_play(mocked_get_vlc, mocked_threading):
|
||||
def test_play(mocked_get_vlc, mocked_threading, settings):
|
||||
"""
|
||||
Test the play() method
|
||||
"""
|
||||
|
@ -618,21 +605,17 @@ def test_play(mocked_get_vlc, mocked_threading):
|
|||
mocked_display = MagicMock()
|
||||
mocked_controller = MagicMock()
|
||||
mocked_media = MagicMock()
|
||||
mocked_controller.media_info.volume = 100
|
||||
mocked_controller.vlc_media_player.get_media.return_value = mocked_media
|
||||
vlc_player = VlcPlayer(None)
|
||||
vlc_player.set_state(MediaState.Paused, mocked_controller)
|
||||
|
||||
# WHEN: play() is called
|
||||
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait, \
|
||||
patch.object(vlc_player, 'volume') as mocked_volume:
|
||||
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
|
||||
mocked_media_state_wait.return_value = True
|
||||
result = vlc_player.play(mocked_controller, mocked_display)
|
||||
|
||||
# THEN: A bunch of things should happen to play the media
|
||||
mocked_thread.start.assert_called_with()
|
||||
mocked_volume.assert_called_with(mocked_controller, 100)
|
||||
|
||||
assert MediaState.Playing == vlc_player.get_live_state()
|
||||
assert result is True, 'The value returned from play() should be True'
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ exceptionform.MIGRATE_VERSION = 'Migrate Test'
|
|||
exceptionform.CHARDET_VERSION = 'CHARDET Test'
|
||||
exceptionform.ENCHANT_VERSION = 'Enchant Test'
|
||||
exceptionform.MAKO_VERSION = 'Mako Test'
|
||||
exceptionform.ICU_VERSION = 'ICU Test'
|
||||
exceptionform.VLC_VERSION = 'VLC Test'
|
||||
|
||||
MAIL_ITEM_TEXT = ('**OpenLP Bug Report**\nVersion: Trunk Test\n\n--- Details of the Exception. ---\n\n'
|
||||
|
|
|
@ -831,3 +831,119 @@ def test_update_recent_files_menu(mocked_create_action, mocked_add_actions, Mock
|
|||
|
||||
# THEN: There should be no errors
|
||||
assert mocked_create_action.call_count == 2
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.QtWidgets.QProgressDialog')
|
||||
def test_show_wait_dialog(MockProcessDialog, main_window_reduced):
|
||||
"""Test that the show wait dialog context manager works correctly"""
|
||||
# GIVEN: A mocked out QProgressDialog and a minimal main window
|
||||
mocked_wait_dialog = MagicMock()
|
||||
MockProcessDialog.return_value = mocked_wait_dialog
|
||||
|
||||
# WHEN: Calling _show_wait_dialog()
|
||||
with main_window_reduced._show_wait_dialog('Test', 'This is a test'):
|
||||
pass
|
||||
|
||||
# THEN: The correct methods should have been called
|
||||
MockProcessDialog.assert_called_once_with('This is a test', '', 0, 0, main_window_reduced)
|
||||
mocked_wait_dialog.setWindowTitle.assert_called_once_with('Test')
|
||||
mocked_wait_dialog.setWindowFlag.assert_called_once_with(QtCore.Qt.WindowContextHelpButtonHint, False)
|
||||
mocked_wait_dialog.setWindowModality.assert_called_once_with(QtCore.Qt.WindowModal)
|
||||
mocked_wait_dialog.setAutoClose.assert_called_once_with(False)
|
||||
mocked_wait_dialog.setCancelButton.assert_called_once_with(None)
|
||||
mocked_wait_dialog.show.assert_called_once_with()
|
||||
mocked_wait_dialog.close.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.QtWidgets.QApplication')
|
||||
def test_wait_for_threads(MockApp, main_window_reduced):
|
||||
"""Test that the wait_for_threads() method correctly stops the threads"""
|
||||
# GIVEN: A mocked application, and a reduced main window
|
||||
mocked_http_thread = MagicMock()
|
||||
mocked_http_thread.isRunning.side_effect = [True, True, False, False]
|
||||
mocked_http_worker = MagicMock()
|
||||
mocked_http_worker.stop = MagicMock()
|
||||
main_window_reduced.application.worker_threads = {
|
||||
'http': {'thread': mocked_http_thread, 'worker': mocked_http_worker}
|
||||
}
|
||||
|
||||
# WHEN: _wait_for_threads() is called
|
||||
main_window_reduced._wait_for_threads()
|
||||
|
||||
# THEN: The correct methods should have been called
|
||||
assert MockApp.processEvents.call_count == 2, 'processEvents() should have been called twice'
|
||||
mocked_http_worker.stop.assert_called_once()
|
||||
assert mocked_http_thread.isRunning.call_count == 4, 'isRunning() should have been called 4 times'
|
||||
mocked_http_thread.wait.assert_called_once_with(100)
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.QtWidgets.QApplication')
|
||||
def test_wait_for_threads_no_threads(MockApp, main_window_reduced):
|
||||
"""Test that the wait_for_threads() method exits early when there are no threads"""
|
||||
# GIVEN: A mocked application, and a reduced main window
|
||||
main_window_reduced.application.worker_threads = {}
|
||||
|
||||
# WHEN: _wait_for_threads() is called
|
||||
main_window_reduced._wait_for_threads()
|
||||
|
||||
# THEN: The correct methods should have been called
|
||||
assert MockApp.processEvents.call_count == 0, 'processEvents() should not have been called'
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.QtWidgets.QApplication')
|
||||
def test_wait_for_threads_disappearing_thread(MockApp, main_window_reduced):
|
||||
"""Test that the wait_for_threads() method correctly ignores threads that resolve themselves"""
|
||||
# GIVEN: A mocked application, and a reduced main window
|
||||
main_window_reduced.application.worker_threads = MagicMock(**{'keys.side_effect': [['http'], []]})
|
||||
|
||||
# WHEN: _wait_for_threads() is called
|
||||
main_window_reduced._wait_for_threads()
|
||||
|
||||
# THEN: The correct methods should have been called
|
||||
assert MockApp.processEvents.call_count == 0, 'processEvents() should not have been called'
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.QtWidgets.QApplication')
|
||||
def test_wait_for_threads_stuck_thread(MockApp, main_window_reduced):
|
||||
"""Test that the wait_for_threads() method correctly stops the threads"""
|
||||
# GIVEN: A mocked application, and a reduced main window
|
||||
mocked_http_thread = MagicMock()
|
||||
mocked_http_thread.isRunning.return_value = True
|
||||
mocked_http_worker = MagicMock()
|
||||
mocked_http_worker.stop = MagicMock()
|
||||
main_window_reduced.application.worker_threads = {
|
||||
'http': {'thread': mocked_http_thread, 'worker': mocked_http_worker}
|
||||
}
|
||||
|
||||
# WHEN: _wait_for_threads() is called
|
||||
main_window_reduced._wait_for_threads()
|
||||
|
||||
# THEN: The correct methods should have been called
|
||||
assert MockApp.processEvents.call_count == 51, 'processEvents() should have been called 51 times'
|
||||
mocked_http_worker.stop.assert_called_once()
|
||||
assert mocked_http_thread.isRunning.call_count == 53, 'isRunning() should have been called 53 times'
|
||||
mocked_http_thread.wait.assert_called_with(100)
|
||||
mocked_http_thread.terminate.assert_called_once()
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.QtWidgets.QApplication')
|
||||
def test_wait_for_threads_runtime_error(MockApp, main_window_reduced):
|
||||
"""Test that the wait_for_threads() method handles a runtime error"""
|
||||
# GIVEN: A mocked application, and a reduced main window
|
||||
mocked_http_thread = MagicMock()
|
||||
mocked_http_thread.isRunning.side_effect = RuntimeError
|
||||
mocked_http_worker = MagicMock()
|
||||
mocked_http_worker.stop = MagicMock()
|
||||
main_window_reduced.application.worker_threads = {
|
||||
'http': {'thread': mocked_http_thread, 'worker': mocked_http_worker}
|
||||
}
|
||||
|
||||
# WHEN: _wait_for_threads() is called
|
||||
main_window_reduced._wait_for_threads()
|
||||
|
||||
# THEN: The correct methods should have been called
|
||||
assert MockApp.processEvents.call_count == 1, 'processEvents() should have been called once'
|
||||
mocked_http_worker.stop.assert_called_once()
|
||||
assert mocked_http_thread.isRunning.call_count == 1, 'isRunning() should have been called once'
|
||||
mocked_http_thread.wait.assert_not_called()
|
||||
mocked_http_thread.terminate.assert_not_called()
|
||||
|
|
|
@ -690,11 +690,8 @@ def test_single_click_timeout_double(mocked_make_live, mocked_make_preview, sett
|
|||
|
||||
@patch('openlp.core.ui.servicemanager.zipfile')
|
||||
@patch('openlp.core.ui.servicemanager.ServiceManager.save_file_as')
|
||||
@patch('openlp.core.ui.servicemanager.os')
|
||||
@patch('openlp.core.ui.servicemanager.shutil')
|
||||
@patch('openlp.core.ui.servicemanager.NamedTemporaryFile')
|
||||
def test_save_file_raises_permission_error(mocked_temp_file, mocked_shutil, mocked_os, mocked_save_file_as,
|
||||
mocked_zipfile, settings):
|
||||
def test_save_file_raises_permission_error(mocked_shutil, mocked_save_file_as, mocked_zipfile, settings):
|
||||
"""
|
||||
Test that when a PermissionError is raised when trying to save a file, it is handled correctly
|
||||
"""
|
||||
|
@ -709,8 +706,7 @@ def test_save_file_raises_permission_error(mocked_temp_file, mocked_shutil, mock
|
|||
service_manager.service_manager_list = MagicMock()
|
||||
mocked_save_file_as.return_value = False
|
||||
mocked_zipfile.ZipFile.return_value = MagicMock()
|
||||
mocked_os.link.side_effect = PermissionError
|
||||
mocked_shutil.copyfile.side_effect = PermissionError
|
||||
mocked_shutil.move.side_effect = PermissionError
|
||||
|
||||
# WHEN: The service is saved and a PermissionError is raised
|
||||
result = service_manager.save_file()
|
||||
|
@ -725,9 +721,7 @@ def test_save_file_raises_permission_error(mocked_temp_file, mocked_shutil, mock
|
|||
@patch('openlp.core.ui.servicemanager.os')
|
||||
@patch('openlp.core.ui.servicemanager.shutil')
|
||||
@patch('openlp.core.ui.servicemanager.len')
|
||||
@patch('openlp.core.ui.servicemanager.NamedTemporaryFile')
|
||||
def test_save_file_large_file(mocked_temp_file, mocked_len, mocked_shutil, mocked_os, mocked_save_file_as,
|
||||
mocked_zipfile, registry):
|
||||
def test_save_file_large_file(mocked_len, mocked_shutil, mocked_os, mocked_save_file_as, mocked_zipfile, registry):
|
||||
"""
|
||||
Test that when a file size size larger than a 32bit signed int is attempted to save, the progress bar
|
||||
should be provided a value that fits in a 32bit int (because it's passed to C++ as a 32bit unsigned int)
|
||||
|
@ -759,39 +753,6 @@ def test_save_file_large_file(mocked_temp_file, mocked_len, mocked_shutil, mocke
|
|||
mocked_save_file_as.assert_not_called()
|
||||
|
||||
|
||||
@patch('openlp.core.ui.servicemanager.zipfile')
|
||||
@patch('openlp.core.ui.servicemanager.ServiceManager.save_file_as')
|
||||
@patch('openlp.core.ui.servicemanager.os')
|
||||
@patch('openlp.core.ui.servicemanager.shutil')
|
||||
@patch('openlp.core.ui.servicemanager.NamedTemporaryFile')
|
||||
def test_save_file_falls_back_to_shutil(mocked_temp_file, mocked_shutil, mocked_os, mocked_save_file_as, mocked_zipfile,
|
||||
registry):
|
||||
"""
|
||||
Test that when a PermissionError is raised when trying to save a file, it is handled correctly
|
||||
"""
|
||||
# GIVEN: A service manager, a service to save
|
||||
mocked_main_window = MagicMock()
|
||||
Registry().register('main_window', mocked_main_window)
|
||||
Registry().register('application', MagicMock())
|
||||
Registry().register('settings', MagicMock())
|
||||
service_manager = ServiceManager(None)
|
||||
service_manager._service_path = MagicMock()
|
||||
service_manager._save_lite = False
|
||||
service_manager.service_items = []
|
||||
service_manager.service_theme = 'Default'
|
||||
service_manager.service_manager_list = MagicMock()
|
||||
mocked_save_file_as.return_value = False
|
||||
mocked_zipfile.ZipFile.return_value = MagicMock()
|
||||
mocked_os.link.side_effect = OSError
|
||||
|
||||
# WHEN: The service is saved and a PermissionError is raised
|
||||
result = service_manager.save_file()
|
||||
|
||||
# THEN: The result is true
|
||||
assert result is True
|
||||
mocked_shutil.copyfile.assert_called_once()
|
||||
|
||||
|
||||
@patch('openlp.core.ui.servicemanager.ServiceManager.regenerate_service_items')
|
||||
def test_theme_change_global(mocked_regenerate_service_items, settings):
|
||||
"""
|
||||
|
|
|
@ -26,10 +26,11 @@ import datetime
|
|||
from unittest.mock import MagicMock, patch, sentinel
|
||||
|
||||
from PyQt5 import QtCore, QtGui
|
||||
from openlp.core.lib.serviceitem import ServiceItem
|
||||
|
||||
from openlp.core.state import State
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.lib import ServiceItemAction
|
||||
from openlp.core.lib import ItemCapabilities, ServiceItemAction
|
||||
from openlp.core.ui import HideMode
|
||||
from openlp.core.ui.slidecontroller import NON_TEXT_MENU, WIDE_MENU, NARROW_MENU, InfoLabel, LiveController, \
|
||||
PreviewController, SlideController
|
||||
|
@ -1141,6 +1142,54 @@ def test_process_item_is_reloading_wont_change_display_hide_mode(mocked_execute,
|
|||
slide_controller.set_hide_mode.assert_not_called()
|
||||
|
||||
|
||||
@patch.object(Registry, 'execute')
|
||||
def test_process_item_provides_own_theme(mocked_execute, registry, state_media):
|
||||
"""
|
||||
Test that media theme is set when media item is flagged with ProvidesOwnTheme
|
||||
"""
|
||||
# GIVEN: A mocked presentation service item that provides it's own theme, a mocked Registry.execute
|
||||
# and a slide controller with many mocks.
|
||||
mocked_pres_item = MagicMock()
|
||||
mocked_pres_item.name = 'mocked_presentation_item'
|
||||
mocked_pres_item.is_command.return_value = True
|
||||
mocked_pres_item.is_media.return_value = False
|
||||
mocked_pres_item.requires_media.return_value = False
|
||||
mocked_pres_item.is_image.return_value = False
|
||||
mocked_pres_item.is_text.return_value = False
|
||||
# Needed to perform the capability checks
|
||||
mocked_pres_item.is_capable = lambda param: ServiceItem.is_capable(mocked_pres_item, param)
|
||||
mocked_pres_item.from_service = False
|
||||
mocked_pres_item.capabilities = [ItemCapabilities.ProvidesOwnTheme]
|
||||
mocked_pres_item.get_frames.return_value = []
|
||||
mocked_settings = MagicMock()
|
||||
mocked_settings.value.return_value = True
|
||||
mocked_main_window = MagicMock()
|
||||
Registry().register('settings', mocked_settings)
|
||||
Registry().register('main_window', mocked_main_window)
|
||||
Registry().register('media_controller', MagicMock())
|
||||
Registry().register('application', MagicMock())
|
||||
slide_controller = SlideController(None)
|
||||
slide_controller.service_item = mocked_pres_item
|
||||
slide_controller.is_live = False
|
||||
slide_controller.preview_widget = MagicMock()
|
||||
slide_controller.preview_display = MagicMock()
|
||||
slide_controller.enable_tool_bar = MagicMock()
|
||||
slide_controller.slide_selected = MagicMock()
|
||||
slide_controller.on_stop_loop = MagicMock()
|
||||
slide_controller.info_label = MagicMock()
|
||||
slide_controller._set_theme = MagicMock()
|
||||
slide_controller.displays = [MagicMock()]
|
||||
slide_controller.split = 0
|
||||
slide_controller.type_prefix = 'test'
|
||||
slide_controller._current_hide_mode = None
|
||||
|
||||
# WHEN: _process_item is called
|
||||
slide_controller._process_item(mocked_pres_item, 0)
|
||||
|
||||
# THEN: _set_theme should be called once
|
||||
slide_controller._set_theme.assert_called_once()
|
||||
|
||||
|
||||
def test_live_stolen_focus_shortcuts(settings):
|
||||
"""
|
||||
Test that all the needed shortcuts are available in scenarios where Live has stolen focus.
|
||||
|
|
|
@ -32,8 +32,7 @@ from openlp.plugins.alerts.alertsplugin import AlertsPlugin
|
|||
def plugin_env(mocked_manager, settings, state, registry):
|
||||
"""An instance of the AlertsPlugin"""
|
||||
mocked_manager.return_value = MagicMock()
|
||||
with patch('openlp.plugins.alerts.alertsplugin.register_views'):
|
||||
return AlertsPlugin(), settings
|
||||
return AlertsPlugin(), settings
|
||||
|
||||
|
||||
def test_plugin_about():
|
||||
|
@ -77,12 +76,10 @@ def test_alerts_initialise(plugin_env):
|
|||
plugin = plugin_env[0]
|
||||
plugin.tools_alert_item = MagicMock()
|
||||
# WHEN: I request the form
|
||||
with patch('openlp.core.common.actions.ActionList') as mocked_actionlist, \
|
||||
patch('openlp.plugins.alerts.alertsplugin.register_views') as mocked_register_views:
|
||||
with patch('openlp.core.common.actions.ActionList') as mocked_actionlist:
|
||||
plugin.initialise()
|
||||
# THEN: the form is loaded
|
||||
mocked_actionlist.instance.add_action.assert_called_once()
|
||||
mocked_register_views.assert_called_once_with()
|
||||
plugin.tools_alert_item.setVisible.assert_called_once_with(True)
|
||||
|
||||
|
||||
|
|
|
@ -21,7 +21,14 @@
|
|||
"""
|
||||
This module contains tests for the plugin class Presentation plugin.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from openlp.plugins.presentations.presentationplugin import PresentationPlugin
|
||||
from openlp.plugins.presentations.lib.presentationtab import PresentationTab
|
||||
|
||||
|
||||
TEST_RESOURCES_PATH = Path(__file__) / '..' / '..' / '..' / 'resources'
|
||||
|
||||
|
||||
def test_plugin_about():
|
||||
|
@ -34,3 +41,33 @@ def test_plugin_about():
|
|||
'programs. The choice of available presentation programs is '
|
||||
'available to the user in a drop down box.'
|
||||
)
|
||||
|
||||
|
||||
def test_creaste_settings_tab(qapp, state, registry, settings):
|
||||
"""Test creating the settings tab"""
|
||||
# GIVEN: A Presentations plugin
|
||||
presentations_plugin = PresentationPlugin()
|
||||
|
||||
# WHEN: create_settings_tab is run
|
||||
presentations_plugin.create_settings_tab(None)
|
||||
|
||||
# THEN: A settings tab should have been created
|
||||
assert isinstance(presentations_plugin.settings_tab, PresentationTab)
|
||||
|
||||
|
||||
@patch('openlp.plugins.presentations.presentationplugin.Manager')
|
||||
def test_initialise(MockedManager, state, registry, mock_settings):
|
||||
"""Test that initialising the plugin works correctly"""
|
||||
# GIVEN: Some initial values needed for intialisation and a presentations plugin
|
||||
mock_settings.setValue.side_effect = [None, [str(TEST_RESOURCES_PATH / 'presentations' / 'test.ppt')]]
|
||||
mocked_main_window = MagicMock()
|
||||
registry.register('main_window', mocked_main_window)
|
||||
presentations_plugin = PresentationPlugin()
|
||||
presentations_plugin.media_item = MagicMock()
|
||||
|
||||
# WHEN: initialise() is called
|
||||
presentations_plugin.initialise()
|
||||
|
||||
# THEN: Nothing should break, and everything should be called
|
||||
mock_settings.setValue.assert_called_with('presentations/thumbnail_scheme', 'sha256file')
|
||||
mock_settings.remove.assert_called_once_with('presentations/presentations files')
|
||||
|
|
|
@ -95,7 +95,9 @@ def test_report_song_list_error_reading(mock_file_dialog, mock_log, registry):
|
|||
Test that report song list sends an exception if the selected file location is not writable
|
||||
"""
|
||||
# GIVEN: A mocked file that returns a os error on open
|
||||
def raise_os_error(x):
|
||||
def raise_os_error(mode, encoding):
|
||||
assert mode == 'wt'
|
||||
assert encoding == 'utf8'
|
||||
raise OSError
|
||||
mock_file = MagicMock()
|
||||
mock_file.open.side_effect = raise_os_error
|
||||
|
|
|
@ -52,10 +52,11 @@ if CAN_RUN_TESTS:
|
|||
"""
|
||||
This class logs changes in the title instance variable
|
||||
"""
|
||||
_title_assignment_list = []
|
||||
_title_assignment_list = None
|
||||
|
||||
def __init__(self, manager):
|
||||
WorshipCenterProImport.__init__(self, manager, file_paths=[])
|
||||
self._title_assignment_list = []
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
|
@ -63,7 +64,10 @@ if CAN_RUN_TESTS:
|
|||
|
||||
@title.setter
|
||||
def title(self, title):
|
||||
self._title_assignment_list.append(title)
|
||||
try:
|
||||
self._title_assignment_list.append(title)
|
||||
except AttributeError:
|
||||
self._title_assignment_list = [title]
|
||||
|
||||
|
||||
RECORDSET_TEST_DATA = [DBTestRecord(1, 'TITLE', 'Amazing Grace'),
|
||||
|
@ -71,21 +75,27 @@ RECORDSET_TEST_DATA = [DBTestRecord(1, 'TITLE', 'Amazing Grace'),
|
|||
DBTestRecord(1, 'CCLISONGID', '12345'),
|
||||
DBTestRecord(1, 'COMMENTS', 'The original version'),
|
||||
DBTestRecord(1, 'COPY', 'Public Domain'),
|
||||
DBTestRecord(1, 'SUBJECT', 'Grace'),
|
||||
DBTestRecord(
|
||||
1, 'LYRICS',
|
||||
'Amazing grace! How&crlf;sweet the sound&crlf;That saved a wretch like me!&crlf;'
|
||||
'<INTRO>Amazing grace! How&crlf;sweet the sound&crlf;That saved a wretch like me!&crlf;'
|
||||
'I once was lost,&crlf;but now am found;&crlf;Was blind, but now I see.&crlf;&crlf;'
|
||||
'\'Twas grace that&crlf;taught my heart to fear,&crlf;And grace my fears relieved;&crlf;'
|
||||
'<PRECHORUS>\'Twas grace that&crlf;taught my heart to fear,&crlf;'
|
||||
'And grace my fears relieved;&crlf;'
|
||||
'How precious did&crlf;that grace appear&crlf;The hour I first believed.&crlf;&crlf;'
|
||||
'Through many dangers,&crlf;toils and snares,&crlf;I have already come;&crlf;'
|
||||
'<CHORUS>Through many dangers,&crlf;toils and snares,&crlf;I have already come;&crlf;'
|
||||
'\'Tis grace hath brought&crlf;me safe thus far,&crlf;'
|
||||
'And grace will lead me home.&crlf;&crlf;The Lord has&crlf;promised good to me,&crlf;'
|
||||
'And grace will lead me home.&crlf;&crlf;'
|
||||
'<REFRAIN>The Lord has&crlf;promised good to me,&crlf;'
|
||||
'His Word my hope secures;&crlf;He will my Shield&crlf;and Portion be,&crlf;'
|
||||
'As long as life endures.&crlf;&crlf;Yea, when this flesh&crlf;and heart shall fail,&crlf;'
|
||||
'As long as life endures.&crlf;&crlf;'
|
||||
'<BRIDGE>Yea, when this flesh&crlf;and heart shall fail,&crlf;'
|
||||
'And mortal life shall cease,&crlf;I shall possess,&crlf;within the veil,&crlf;'
|
||||
'A life of joy and peace.&crlf;&crlf;The earth shall soon&crlf;dissolve like snow,&crlf;'
|
||||
'A life of joy and peace.&crlf;&crlf;'
|
||||
'<TAG>The earth shall soon&crlf;dissolve like snow,&crlf;'
|
||||
'The sun forbear to shine;&crlf;But God, Who called&crlf;me here below,&crlf;'
|
||||
'Shall be forever mine.&crlf;&crlf;When we\'ve been there&crlf;ten thousand years,&crlf;'
|
||||
'Shall be forever mine.&crlf;&crlf;'
|
||||
'<END>When we\'ve been there&crlf;ten thousand years,&crlf;'
|
||||
'Bright shining as the sun,&crlf;We\'ve no less days to&crlf;sing God\'s praise&crlf;'
|
||||
'Than when we\'d first begun.&crlf;&crlf;'),
|
||||
DBTestRecord(2, 'TITLE', 'Beautiful Garden Of Prayer, The'),
|
||||
|
@ -105,32 +115,32 @@ RECORDSET_TEST_DATA = [DBTestRecord(1, 'TITLE', 'Amazing Grace'),
|
|||
SONG_TEST_DATA = [{'title': 'Amazing Grace',
|
||||
'verses': [
|
||||
('Amazing grace! How\nsweet the sound\nThat saved a wretch like me!\nI once was lost,\n'
|
||||
'but now am found;\nWas blind, but now I see.'),
|
||||
'but now am found;\nWas blind, but now I see.', 'i'),
|
||||
('\'Twas grace that\ntaught my heart to fear,\nAnd grace my fears relieved;\nHow precious did\n'
|
||||
'that grace appear\nThe hour I first believed.'),
|
||||
'that grace appear\nThe hour I first believed.', 'p'),
|
||||
('Through many dangers,\ntoils and snares,\nI have already come;\n\'Tis grace hath brought\n'
|
||||
'me safe thus far,\nAnd grace will lead me home.'),
|
||||
'me safe thus far,\nAnd grace will lead me home.', 'c'),
|
||||
('The Lord has\npromised good to me,\nHis Word my hope secures;\n'
|
||||
'He will my Shield\nand Portion be,\nAs long as life endures.'),
|
||||
'He will my Shield\nand Portion be,\nAs long as life endures.', 'c'),
|
||||
('Yea, when this flesh\nand heart shall fail,\nAnd mortal life shall cease,\nI shall possess,\n'
|
||||
'within the veil,\nA life of joy and peace.'),
|
||||
'within the veil,\nA life of joy and peace.', 'b'),
|
||||
('The earth shall soon\ndissolve like snow,\nThe sun forbear to shine;\nBut God, Who called\n'
|
||||
'me here below,\nShall be forever mine.'),
|
||||
'me here below,\nShall be forever mine.', 'o'),
|
||||
('When we\'ve been there\nten thousand years,\nBright shining as the sun,\n'
|
||||
'We\'ve no less days to\nsing God\'s praise\nThan when we\'d first begun.')],
|
||||
'We\'ve no less days to\nsing God\'s praise\nThan when we\'d first begun.', 'e')],
|
||||
'author': 'John Newton',
|
||||
'comments': 'The original version',
|
||||
'copyright': 'Public Domain'},
|
||||
{'title': 'Beautiful Garden Of Prayer, The',
|
||||
'verses': [
|
||||
('There\'s a garden where\nJesus is waiting,\nThere\'s a place that\nis wondrously fair,\n'
|
||||
'For it glows with the\nlight of His presence.\n\'Tis the beautiful\ngarden of prayer.'),
|
||||
'For it glows with the\nlight of His presence.\n\'Tis the beautiful\ngarden of prayer.', 'v'),
|
||||
('Oh, the beautiful garden,\nthe garden of prayer!\nOh, the beautiful\ngarden of prayer!\n'
|
||||
'There my Savior awaits,\nand He opens the gates\nTo the beautiful\ngarden of prayer.'),
|
||||
'There my Savior awaits,\nand He opens the gates\nTo the beautiful\ngarden of prayer.', 'v'),
|
||||
('There\'s a garden where\nJesus is waiting,\nAnd I go with my\nburden and care,\n'
|
||||
'Just to learn from His\nlips words of comfort\nIn the beautiful\ngarden of prayer.'),
|
||||
'Just to learn from His\nlips words of comfort\nIn the beautiful\ngarden of prayer.', 'v'),
|
||||
('There\'s a garden where\nJesus is waiting,\nAnd He bids you to come,\nmeet Him there;\n'
|
||||
'Just to bow and\nreceive a new blessing\nIn the beautiful\ngarden of prayer.')]}]
|
||||
'Just to bow and\nreceive a new blessing\nIn the beautiful\ngarden of prayer.', 'v')]}]
|
||||
|
||||
|
||||
@skipUnless(CAN_RUN_TESTS, 'Not Windows, skipping test')
|
||||
|
@ -233,11 +243,44 @@ class TestWorshipCenterProSongImport(TestCase):
|
|||
verse_calls = song_data['verses']
|
||||
add_verse_call_count += len(verse_calls)
|
||||
for call in verse_calls:
|
||||
mocked_add_verse.assert_any_call(call, 'v')
|
||||
mocked_add_verse.assert_any_call(*call)
|
||||
if 'author' in song_data:
|
||||
mocked_parse_author.assert_any_call(song_data['author'])
|
||||
if 'comments' in song_data:
|
||||
mocked_add_comment.assert_any_call(song_data['comments'])
|
||||
if 'copyright' in song_data:
|
||||
mocked_add_copyright.assert_any_call(song_data['copyright'])
|
||||
if 'subject' in song_data:
|
||||
mocked_add_copyright.assert_any_call(song_data['subject'])
|
||||
assert mocked_add_verse.call_count == add_verse_call_count, 'Incorrect number of calls made to add_verse'
|
||||
|
||||
def test_song_import_stop(self):
|
||||
"""Test that the song importer stops when the flag is set"""
|
||||
with patch('openlp.plugins.songs.lib.importers.worshipcenterpro.SongImport'), \
|
||||
patch('openlp.plugins.songs.lib.importers.worshipcenterpro.pyodbc') as mocked_pyodbc, \
|
||||
patch('openlp.plugins.songs.lib.importers.worshipcenterpro.translate') as mocked_translate:
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
mocked_add_verse = MagicMock()
|
||||
mocked_parse_author = MagicMock()
|
||||
mocked_add_comment = MagicMock()
|
||||
mocked_add_copyright = MagicMock()
|
||||
mocked_finish = MagicMock()
|
||||
mocked_pyodbc.connect().cursor().fetchall.return_value = RECORDSET_TEST_DATA
|
||||
mocked_translate.return_value = 'Translated Text'
|
||||
importer = WorshipCenterProImportLogger(mocked_manager)
|
||||
importer.import_source = 'import_source'
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.add_verse = mocked_add_verse
|
||||
importer.parse_author = mocked_parse_author
|
||||
importer.add_comment = mocked_add_comment
|
||||
importer.add_copyright = mocked_add_copyright
|
||||
importer.stop_import_flag = True
|
||||
importer.finish = mocked_finish
|
||||
|
||||
# WHEN: Calling the do_import method
|
||||
importer.do_import()
|
||||
|
||||
# THEN: No songs should have been imported
|
||||
assert len(importer._title_assignment_list) == 0
|
||||
mocked_finish.assert_not_called()
|
||||
|
|
Loading…
Reference in New Issue