Merge branch 'master' into list-view

This commit is contained in:
Mateus Meyer Jiacomelli 2023-01-11 11:58:22 -03:00
commit fba1e54f05
31 changed files with 542 additions and 295 deletions

View File

@ -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

View File

@ -1 +1 @@
3.0.0
3.0.0.dev6+dbea8bf56

View File

@ -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')

View File

@ -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()

View File

@ -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.

View File

@ -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):

View File

@ -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):

View File

@ -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': '',

View File

@ -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,

View File

@ -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.

View File

@ -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)

View File

@ -35,13 +35,11 @@ from openlp.core.common.i18n import translate
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
@ -229,7 +227,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):
@ -275,7 +273,7 @@ class VlcPlayer(MediaPlayer):
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

View File

@ -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)

View File

@ -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):
"""

View File

@ -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')

View File

@ -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/')

View File

@ -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

View File

@ -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)

View File

@ -52,6 +52,7 @@ WIN32_MODULES = [
'win32com',
'win32ui',
'pywintypes',
'icu',
]
LINUX_MODULES = [

View File

@ -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"',

View File

@ -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

View File

@ -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`

View File

@ -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
"""

View File

@ -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

View File

@ -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)

View File

@ -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'

View File

@ -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'

View File

@ -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.

View File

@ -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)

View File

@ -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')

View File

@ -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