forked from openlp/openlp
Merge branch 'full-service-item' into 'master'
Move JSON rendering into ServiceItem object; Provide entire service item object through API See merge request openlp/openlp!221
This commit is contained in:
commit
3ebd175738
@ -19,16 +19,10 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
##########################################################################
|
##########################################################################
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import urllib.request
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from openlp.core.api.lib import login_required
|
from openlp.core.api.lib import login_required
|
||||||
from openlp.core.common import ThemeLevel
|
from openlp.core.common import ThemeLevel
|
||||||
from openlp.core.common.registry import Registry
|
from openlp.core.common.registry import Registry
|
||||||
from openlp.core.common.applocation import AppLocation
|
|
||||||
from openlp.core.lib import create_thumb
|
|
||||||
from openlp.core.lib.serviceitem import ItemCapabilities
|
|
||||||
|
|
||||||
from flask import jsonify, request, abort, Blueprint
|
from flask import jsonify, request, abort, Blueprint
|
||||||
|
|
||||||
@ -41,44 +35,11 @@ def controller_text_api():
|
|||||||
log.debug('controller-v2-live-item')
|
log.debug('controller-v2-live-item')
|
||||||
live_controller = Registry().get('live_controller')
|
live_controller = Registry().get('live_controller')
|
||||||
current_item = live_controller.service_item
|
current_item = live_controller.service_item
|
||||||
data = []
|
live_item = {}
|
||||||
if current_item:
|
if current_item:
|
||||||
for index, frame in enumerate(current_item.get_frames()):
|
live_item = current_item.to_dict()
|
||||||
item = {}
|
live_item['slides'][live_controller.selected_row]['selected'] = True
|
||||||
item['tag'] = index + 1
|
return jsonify(live_item)
|
||||||
item['selected'] = live_controller.selected_row == index
|
|
||||||
item['title'] = current_item.title
|
|
||||||
if current_item.is_text():
|
|
||||||
if frame['verse']:
|
|
||||||
item['tag'] = str(frame['verse'])
|
|
||||||
item['text'] = frame['text']
|
|
||||||
item['html'] = current_item.rendered_slides[index]['text']
|
|
||||||
item['chords'] = current_item.rendered_slides[index]['chords']
|
|
||||||
elif current_item.is_image() and not frame.get('image', '') and \
|
|
||||||
Registry().get('settings_thread').value('api/thumbnails'):
|
|
||||||
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
|
|
||||||
full_thumbnail_path = AppLocation.get_data_path() / thumbnail_path
|
|
||||||
if not full_thumbnail_path.exists():
|
|
||||||
create_thumb(Path(current_item.get_frame_path(index)), full_thumbnail_path, False)
|
|
||||||
item['img'] = urllib.request.pathname2url(os.path.sep + str(thumbnail_path))
|
|
||||||
item['text'] = str(frame['title'])
|
|
||||||
item['html'] = str(frame['title'])
|
|
||||||
else:
|
|
||||||
# presentations and other things
|
|
||||||
if current_item.is_capable(ItemCapabilities.HasDisplayTitle):
|
|
||||||
item['title'] = str(frame['display_title'])
|
|
||||||
if current_item.is_capable(ItemCapabilities.HasNotes):
|
|
||||||
item['slide_notes'] = str(frame['notes'])
|
|
||||||
if current_item.is_capable(ItemCapabilities.HasThumbnails) and \
|
|
||||||
Registry().get('settings_thread').value('api/thumbnails'):
|
|
||||||
# If the file is under our app directory tree send the portion after the match
|
|
||||||
data_path = str(AppLocation.get_data_path())
|
|
||||||
if frame['image'][0:len(data_path)] == data_path:
|
|
||||||
item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
|
|
||||||
item['text'] = str(frame['title'])
|
|
||||||
item['html'] = str(frame['title'])
|
|
||||||
data.append(item)
|
|
||||||
return jsonify(data)
|
|
||||||
|
|
||||||
|
|
||||||
@controller_views.route('/show', methods=['POST'])
|
@controller_views.route('/show', methods=['POST'])
|
||||||
|
@ -25,6 +25,7 @@ import datetime
|
|||||||
import logging
|
import logging
|
||||||
import ntpath
|
import ntpath
|
||||||
import os
|
import os
|
||||||
|
import urllib.request
|
||||||
import uuid
|
import uuid
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -32,7 +33,6 @@ from shutil import copytree, copy, move
|
|||||||
|
|
||||||
from PyQt5 import QtGui
|
from PyQt5 import QtGui
|
||||||
|
|
||||||
from openlp.core.state import State
|
|
||||||
from openlp.core.common import ThemeLevel, sha256_file_hash
|
from openlp.core.common import ThemeLevel, sha256_file_hash
|
||||||
from openlp.core.common.applocation import AppLocation
|
from openlp.core.common.applocation import AppLocation
|
||||||
from openlp.core.common.enum import ServiceItemType
|
from openlp.core.common.enum import ServiceItemType
|
||||||
@ -41,7 +41,9 @@ from openlp.core.common.mixins import RegistryProperties
|
|||||||
from openlp.core.common.registry import Registry
|
from openlp.core.common.registry import Registry
|
||||||
from openlp.core.display.render import remove_tags, render_tags, render_chords_for_printing
|
from openlp.core.display.render import remove_tags, render_tags, render_chords_for_printing
|
||||||
from openlp.core.lib import ItemCapabilities
|
from openlp.core.lib import ItemCapabilities
|
||||||
|
from openlp.core.lib import create_thumb
|
||||||
from openlp.core.lib.theme import BackgroundType
|
from openlp.core.lib.theme import BackgroundType
|
||||||
|
from openlp.core.state import State
|
||||||
from openlp.core.ui.icons import UiIcons
|
from openlp.core.ui.icons import UiIcons
|
||||||
from openlp.core.ui.media import parse_stream_path
|
from openlp.core.ui.media import parse_stream_path
|
||||||
|
|
||||||
@ -844,3 +846,59 @@ class ServiceItem(RegistryProperties):
|
|||||||
elif self.is_image() and self.slides and 'thumbnail' in self.slides[0]:
|
elif self.is_image() and self.slides and 'thumbnail' in self.slides[0]:
|
||||||
return os.path.dirname(self.slides[0]['thumbnail'])
|
return os.path.dirname(self.slides[0]['thumbnail'])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""
|
||||||
|
Convert the service item into a dictionary
|
||||||
|
"""
|
||||||
|
data_dict = {
|
||||||
|
'title': self.title,
|
||||||
|
'name': self.name,
|
||||||
|
'type': str(self.service_item_type),
|
||||||
|
'theme': self.theme,
|
||||||
|
'footer': self.raw_footer,
|
||||||
|
'audit': self.audit,
|
||||||
|
'notes': self.notes,
|
||||||
|
'fromPlugin': self.from_plugin,
|
||||||
|
'capabilities': self.capabilities,
|
||||||
|
'backgroundAudio': [str(file_path) for file_path in self.background_audio],
|
||||||
|
'isThemeOverwritten': self.theme_overwritten,
|
||||||
|
'slides': []
|
||||||
|
}
|
||||||
|
for index, frame in enumerate(self.get_frames()):
|
||||||
|
item = {
|
||||||
|
'tag': index + 1,
|
||||||
|
'title': self.title,
|
||||||
|
'selected': False
|
||||||
|
}
|
||||||
|
if self.is_text():
|
||||||
|
if frame['verse']:
|
||||||
|
item['tag'] = str(frame['verse'])
|
||||||
|
item['text'] = frame['text']
|
||||||
|
item['html'] = self.rendered_slides[index]['text']
|
||||||
|
item['chords'] = self.rendered_slides[index]['chords']
|
||||||
|
elif self.is_image() and not frame.get('image', '') and \
|
||||||
|
Registry().get('settings_thread').value('api/thumbnails'):
|
||||||
|
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
|
||||||
|
full_thumbnail_path = AppLocation.get_data_path() / thumbnail_path
|
||||||
|
if not full_thumbnail_path.exists():
|
||||||
|
create_thumb(Path(self.get_frame_path(index)), full_thumbnail_path, False)
|
||||||
|
item['img'] = urllib.request.pathname2url(os.path.sep + str(thumbnail_path))
|
||||||
|
item['text'] = str(frame['title'])
|
||||||
|
item['html'] = str(frame['title'])
|
||||||
|
else:
|
||||||
|
# presentations and other things
|
||||||
|
if self.is_capable(ItemCapabilities.HasDisplayTitle):
|
||||||
|
item['title'] = str(frame['display_title'])
|
||||||
|
if self.is_capable(ItemCapabilities.HasNotes):
|
||||||
|
item['slide_notes'] = str(frame['notes'])
|
||||||
|
if self.is_capable(ItemCapabilities.HasThumbnails) and \
|
||||||
|
Registry().get('settings_thread').value('api/thumbnails'):
|
||||||
|
# If the file is under our app directory tree send the portion after the match
|
||||||
|
data_path = str(AppLocation.get_data_path())
|
||||||
|
if frame['image'][0:len(data_path)] == data_path:
|
||||||
|
item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
|
||||||
|
item['text'] = str(frame['title'])
|
||||||
|
item['html'] = str(frame['title'])
|
||||||
|
data_dict['slides'].append(item)
|
||||||
|
return data_dict
|
||||||
|
@ -417,10 +417,10 @@ class ProjectorDB(Manager):
|
|||||||
"""
|
"""
|
||||||
source_dict = {}
|
source_dict = {}
|
||||||
# Apparently, there was a change to the projector object. Test for which object has db id
|
# Apparently, there was a change to the projector object. Test for which object has db id
|
||||||
if hasattr(projector, "id"):
|
if hasattr(projector, 'entry') and hasattr(projector.entry, 'id'):
|
||||||
chk = projector.id
|
|
||||||
elif hasattr(projector.entry, "id"):
|
|
||||||
chk = projector.entry.id
|
chk = projector.entry.id
|
||||||
|
else:
|
||||||
|
chk = projector.id
|
||||||
|
|
||||||
# Get default list first
|
# Get default list first
|
||||||
for key in projector.source_available:
|
for key in projector.source_available:
|
||||||
|
@ -76,10 +76,9 @@ def service_item_env(state):
|
|||||||
|
|
||||||
def test_service_item_basic(settings):
|
def test_service_item_basic(settings):
|
||||||
"""
|
"""
|
||||||
Test the Service Item - basic test
|
Test creating a new Service Item without a plugin
|
||||||
"""
|
"""
|
||||||
# GIVEN: A new service item
|
# GIVEN: A new service item
|
||||||
|
|
||||||
# WHEN: A service item is created (without a plugin)
|
# WHEN: A service item is created (without a plugin)
|
||||||
service_item = ServiceItem(None)
|
service_item = ServiceItem(None)
|
||||||
|
|
||||||
@ -88,6 +87,22 @@ def test_service_item_basic(settings):
|
|||||||
assert service_item.missing_frames() is True, 'There should not be any frames in the service item'
|
assert service_item.missing_frames() is True, 'There should not be any frames in the service item'
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_item_with_plugin(settings):
|
||||||
|
"""
|
||||||
|
Test creating a new Service Item with a plugin
|
||||||
|
"""
|
||||||
|
# GIVEN: A new service item
|
||||||
|
mocked_plugin = MagicMock()
|
||||||
|
mocked_plugin.name = 'songs'
|
||||||
|
# WHEN: A service item is created (with a plugin)
|
||||||
|
service_item = ServiceItem(mocked_plugin)
|
||||||
|
|
||||||
|
# THEN: We should get back a valid service item
|
||||||
|
assert service_item.name == 'songs', 'The service item name should be the same as the plugin name'
|
||||||
|
assert service_item.is_valid is True, 'The new service item should be valid'
|
||||||
|
assert service_item.missing_frames() is True, 'There should not be any frames in the service item'
|
||||||
|
|
||||||
|
|
||||||
def test_service_item_load_custom_from_service(state_media, settings, service_item_env):
|
def test_service_item_load_custom_from_service(state_media, settings, service_item_env):
|
||||||
"""
|
"""
|
||||||
Test the Service Item - adding a custom slide from a saved service
|
Test the Service Item - adding a custom slide from a saved service
|
||||||
@ -522,3 +537,249 @@ def test_service_item_get_theme_data_song_level_global_fallback(settings):
|
|||||||
|
|
||||||
# THEN: theme should be the global theme
|
# THEN: theme should be the global theme
|
||||||
assert theme == mocked_theme_manager.global_theme
|
assert theme == mocked_theme_manager.global_theme
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_capability(settings):
|
||||||
|
# GIVEN: A service item with a capability
|
||||||
|
service_item = ServiceItem(None)
|
||||||
|
service_item.add_capability(ItemCapabilities.CanEdit)
|
||||||
|
assert ItemCapabilities.CanEdit in service_item.capabilities
|
||||||
|
|
||||||
|
# WHEN: A capability is removed
|
||||||
|
service_item.remove_capability(ItemCapabilities.CanEdit)
|
||||||
|
|
||||||
|
# THEN: The capability should no longer be there
|
||||||
|
assert ItemCapabilities.CanEdit not in service_item.capabilities, 'The capability should not be in the list'
|
||||||
|
|
||||||
|
|
||||||
|
def test_to_dict_text_item(state_media, settings, service_item_env):
|
||||||
|
"""
|
||||||
|
Test that the to_dict() method returns the correct data for the service item
|
||||||
|
"""
|
||||||
|
# GIVEN: A ServiceItem with a service loaded from file
|
||||||
|
mocked_plugin = MagicMock()
|
||||||
|
mocked_plugin.name = 'songs'
|
||||||
|
service_item = ServiceItem(mocked_plugin)
|
||||||
|
service_item.add_icon = MagicMock()
|
||||||
|
FormattingTags.load_tags()
|
||||||
|
line = convert_file_service_item(TEST_PATH, 'serviceitem-song-linked-audio.osj')
|
||||||
|
service_item.set_from_service(line, Path('/test/'))
|
||||||
|
|
||||||
|
# WHEN: to_dict() is called
|
||||||
|
result = service_item.to_dict()
|
||||||
|
|
||||||
|
# THEN: The correct dictionary should be returned
|
||||||
|
expected_dict = {
|
||||||
|
|
||||||
|
'audit': ['Amazing Grace', ['John Newton'], '', ''],
|
||||||
|
'backgroundAudio': ['/test/amazing_grace.mp3'],
|
||||||
|
'capabilities': [2, 1, 5, 8, 9, 13, 15],
|
||||||
|
'footer': ['Amazing Grace', 'Written by: John Newton'],
|
||||||
|
'fromPlugin': False,
|
||||||
|
'isThemeOverwritten': False,
|
||||||
|
'name': 'songs',
|
||||||
|
'notes': '',
|
||||||
|
'slides': [
|
||||||
|
{
|
||||||
|
'chords': 'Amazing Grace! how sweet the sound\n'
|
||||||
|
'That saved a wretch like me;\n'
|
||||||
|
'I once was lost, but now am found,\n'
|
||||||
|
'Was blind, but now I see.',
|
||||||
|
'html': 'Amazing Grace! how sweet the sound\n'
|
||||||
|
'That saved a wretch like me;\n'
|
||||||
|
'I once was lost, but now am found,\n'
|
||||||
|
'Was blind, but now I see.',
|
||||||
|
'selected': False,
|
||||||
|
'tag': 'V1',
|
||||||
|
'text': 'Amazing Grace! how sweet the sound\n'
|
||||||
|
'That saved a wretch like me;\n'
|
||||||
|
'I once was lost, but now am found,\n'
|
||||||
|
'Was blind, but now I see.',
|
||||||
|
'title': 'Amazing Grace'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'chords': '’Twas grace that taught my heart to fear,\n'
|
||||||
|
'And grace my fears relieved;\n'
|
||||||
|
'How precious did that grace appear,\n'
|
||||||
|
'The hour I first believed!',
|
||||||
|
'html': '’Twas grace that taught my heart to fear,\n'
|
||||||
|
'And grace my fears relieved;\n'
|
||||||
|
'How precious did that grace appear,\n'
|
||||||
|
'The hour I first believed!',
|
||||||
|
'selected': False,
|
||||||
|
'tag': 'V2',
|
||||||
|
'text': '’Twas grace that taught my heart to fear,\n'
|
||||||
|
'And grace my fears relieved;\n'
|
||||||
|
'How precious did that grace appear,\n'
|
||||||
|
'The hour I first believed!',
|
||||||
|
'title': 'Amazing Grace'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'chords': 'Through many dangers, toils and snares\n'
|
||||||
|
'I have already come;\n'
|
||||||
|
'’Tis grace that brought me safe thus far,\n'
|
||||||
|
'And grace will lead me home.',
|
||||||
|
'html': 'Through many dangers, toils and snares\n'
|
||||||
|
'I have already come;\n'
|
||||||
|
'’Tis grace that brought me safe thus far,\n'
|
||||||
|
'And grace will lead me home.',
|
||||||
|
'selected': False,
|
||||||
|
'tag': 'V3',
|
||||||
|
'text': 'Through many dangers, toils and snares\n'
|
||||||
|
'I have already come;\n'
|
||||||
|
'’Tis grace that brought me safe thus far,\n'
|
||||||
|
'And grace will lead me home.',
|
||||||
|
'title': 'Amazing Grace'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'chords': 'The Lord has promised good to me,\n'
|
||||||
|
'His word my hope secures;\n'
|
||||||
|
'He will my shield and portion be\n'
|
||||||
|
'As long as life endures.',
|
||||||
|
'html': 'The Lord has promised good to me,\n'
|
||||||
|
'His word my hope secures;\n'
|
||||||
|
'He will my shield and portion be\n'
|
||||||
|
'As long as life endures.',
|
||||||
|
'selected': False,
|
||||||
|
'tag': 'V4',
|
||||||
|
'text': 'The Lord has promised good to me,\n'
|
||||||
|
'His word my hope secures;\n'
|
||||||
|
'He will my shield and portion be\n'
|
||||||
|
'As long as life endures.',
|
||||||
|
'title': 'Amazing Grace'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'chords': 'Yes, when this heart and flesh shall fail,\n'
|
||||||
|
'And mortal life shall cease,\n'
|
||||||
|
'I shall possess within the veil\n'
|
||||||
|
'A life of joy and peace.',
|
||||||
|
'html': 'Yes, when this heart and flesh shall fail,\n'
|
||||||
|
'And mortal life shall cease,\n'
|
||||||
|
'I shall possess within the veil\n'
|
||||||
|
'A life of joy and peace.',
|
||||||
|
'selected': False,
|
||||||
|
'tag': 'V5',
|
||||||
|
'text': 'Yes, when this heart and flesh shall fail,\n'
|
||||||
|
'And mortal life shall cease,\n'
|
||||||
|
'I shall possess within the veil\n'
|
||||||
|
'A life of joy and peace.',
|
||||||
|
'title': 'Amazing Grace'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'chords': 'When we’ve been there a thousand years,\n'
|
||||||
|
'Bright shining as the sun,\n'
|
||||||
|
'We’ve no less days to sing God’s praise\n'
|
||||||
|
'Than when we first begun.',
|
||||||
|
'html': 'When we’ve been there a thousand years,\n'
|
||||||
|
'Bright shining as the sun,\n'
|
||||||
|
'We’ve no less days to sing God’s praise\n'
|
||||||
|
'Than when we first begun.',
|
||||||
|
'selected': False,
|
||||||
|
'tag': 'V6',
|
||||||
|
'text': 'When we’ve been there a thousand years,\n'
|
||||||
|
'Bright shining as the sun,\n'
|
||||||
|
'We’ve no less days to sing God’s praise\n'
|
||||||
|
'Than when we first begun.',
|
||||||
|
'title': 'Amazing Grace'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'theme': None,
|
||||||
|
'title': 'Amazing Grace',
|
||||||
|
'type': 'ServiceItemType.Text'
|
||||||
|
}
|
||||||
|
assert result == expected_dict
|
||||||
|
|
||||||
|
|
||||||
|
def test_to_dict_image_item(state_media, settings, service_item_env):
|
||||||
|
"""
|
||||||
|
Test that the to_dict() method returns the correct data for the service item
|
||||||
|
"""
|
||||||
|
# GIVEN: A ServiceItem with a service loaded from file
|
||||||
|
mocked_plugin = MagicMock()
|
||||||
|
mocked_plugin.name = 'image'
|
||||||
|
service_item = ServiceItem(mocked_plugin)
|
||||||
|
service_item.add_icon = MagicMock()
|
||||||
|
FormattingTags.load_tags()
|
||||||
|
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj')
|
||||||
|
|
||||||
|
with patch('openlp.core.lib.serviceitem.sha256_file_hash') as mocked_sha256_file_hash:
|
||||||
|
mocked_sha256_file_hash.return_value = '3a7ccbdb0b5a3db169c4692d7aad0ec8'
|
||||||
|
service_item.set_from_service(line)
|
||||||
|
|
||||||
|
# WHEN: to_dict() is called
|
||||||
|
result = service_item.to_dict()
|
||||||
|
|
||||||
|
# THEN: The correct dictionary should be returned
|
||||||
|
expected_dict = {
|
||||||
|
'audit': '',
|
||||||
|
'backgroundAudio': [],
|
||||||
|
'capabilities': [3, 1, 5, 6],
|
||||||
|
'footer': [],
|
||||||
|
'fromPlugin': False,
|
||||||
|
'isThemeOverwritten': False,
|
||||||
|
'name': 'images',
|
||||||
|
'notes': '',
|
||||||
|
'slides': [
|
||||||
|
{
|
||||||
|
'html': 'image_1.jpg',
|
||||||
|
'img': '/images/thumbnails/image_1.jpg',
|
||||||
|
'selected': False,
|
||||||
|
'tag': 1,
|
||||||
|
'text': 'image_1.jpg',
|
||||||
|
'title': 'Images'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'theme': -1,
|
||||||
|
'title': 'Images',
|
||||||
|
'type': 'ServiceItemType.Image'
|
||||||
|
}
|
||||||
|
assert result == expected_dict
|
||||||
|
|
||||||
|
|
||||||
|
def test_to_dict_presentation_item(state_media, settings, service_item_env):
|
||||||
|
"""
|
||||||
|
Test that the to_dict() method returns the correct data for the service item
|
||||||
|
"""
|
||||||
|
# GIVEN: A ServiceItem with a service loaded from file
|
||||||
|
mocked_plugin = MagicMock()
|
||||||
|
mocked_plugin.name = 'presentations'
|
||||||
|
service_item = ServiceItem(mocked_plugin)
|
||||||
|
presentation_name = 'test.pptx'
|
||||||
|
image = Path('thumbnails/abcd/slide1.png')
|
||||||
|
display_title = 'DisplayTitle'
|
||||||
|
notes = 'Note1\nNote2\n'
|
||||||
|
|
||||||
|
# WHEN: adding presentation to service_item
|
||||||
|
with patch('openlp.core.lib.serviceitem.sha256_file_hash') as mocked_sha256_file_hash,\
|
||||||
|
patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') as mocked_get_section_data_path:
|
||||||
|
mocked_sha256_file_hash.return_value = '4a067fed6834ea2bc4b8819f11636365'
|
||||||
|
mocked_get_section_data_path.return_value = Path('.')
|
||||||
|
service_item.add_from_command(TEST_PATH, presentation_name, image, display_title, notes)
|
||||||
|
|
||||||
|
# WHEN: to_dict() is called
|
||||||
|
result = service_item.to_dict()
|
||||||
|
|
||||||
|
# THEN: The correct dictionary should be returned
|
||||||
|
expected_dict = {
|
||||||
|
'audit': '',
|
||||||
|
'backgroundAudio': [],
|
||||||
|
'capabilities': [],
|
||||||
|
'footer': [],
|
||||||
|
'fromPlugin': False,
|
||||||
|
'isThemeOverwritten': False,
|
||||||
|
'name': 'presentations',
|
||||||
|
'notes': '',
|
||||||
|
'slides': [
|
||||||
|
{
|
||||||
|
'html': 'test.pptx',
|
||||||
|
'selected': False,
|
||||||
|
'tag': 1,
|
||||||
|
'text': 'test.pptx',
|
||||||
|
'title': ''
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'theme': None,
|
||||||
|
'title': '',
|
||||||
|
'type': 'ServiceItemType.Command'
|
||||||
|
}
|
||||||
|
assert result == expected_dict
|
||||||
|
@ -29,6 +29,26 @@ Test the States class.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_settings(state):
|
||||||
|
# GIVEN: Some modules
|
||||||
|
State().modules = {'mock': MagicMock}
|
||||||
|
|
||||||
|
# WHEN: load_settings() is run
|
||||||
|
State().load_settings()
|
||||||
|
|
||||||
|
# THEN: the modules should be empty
|
||||||
|
assert State().modules == {}, 'There should be no modules in the State object'
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_settings(state):
|
||||||
|
# GIVEN: Niks
|
||||||
|
# WHEN: save_settings() is run
|
||||||
|
State().save_settings()
|
||||||
|
|
||||||
|
# THEN: Nothing should happen
|
||||||
|
assert True, 'There should be no exceptions'
|
||||||
|
|
||||||
|
|
||||||
def test_add_service(state):
|
def test_add_service(state):
|
||||||
# GIVEN a new state
|
# GIVEN a new state
|
||||||
# WHEN I add a new service
|
# WHEN I add a new service
|
||||||
@ -128,3 +148,62 @@ def test_basic_preconditions_pass(state, registry):
|
|||||||
assert State().modules['test'].pass_preconditions is True
|
assert State().modules['test'].pass_preconditions is True
|
||||||
assert State().modules['test2'].pass_preconditions is False
|
assert State().modules['test2'].pass_preconditions is False
|
||||||
assert State().modules['test1'].pass_preconditions is True
|
assert State().modules['test1'].pass_preconditions is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_text(state):
|
||||||
|
"""
|
||||||
|
Test that settings the missing text in a module works
|
||||||
|
"""
|
||||||
|
# GIVEN: A state with a module
|
||||||
|
State().modules['test'] = MagicMock()
|
||||||
|
|
||||||
|
# WHEN: missing_text() is called
|
||||||
|
State().missing_text('test', 'Test test')
|
||||||
|
|
||||||
|
# THEN: The text is set
|
||||||
|
assert State().modules['test'].text == 'Test test', 'The text on the module should have been set'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_text(state):
|
||||||
|
"""
|
||||||
|
Test that the get_text() method returns the text of all the states
|
||||||
|
"""
|
||||||
|
# GIVEN: Some states with text
|
||||||
|
State().modules.update({'test1': MagicMock(text='Test 1'), 'test2': MagicMock(text='Test 2')})
|
||||||
|
|
||||||
|
# WHEN: get_text() is called
|
||||||
|
result = State().get_text()
|
||||||
|
|
||||||
|
# THEN: The correct text is returned
|
||||||
|
assert result == 'Test 1\nTest 2\n', 'The full text is returned'
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_preconditions_no_required(state):
|
||||||
|
"""
|
||||||
|
Test that the check_preconditions() method returns the correct attribute when there are no requirements
|
||||||
|
"""
|
||||||
|
# GIVEN: A State with no requires
|
||||||
|
State().modules.update({'test_pre1': MagicMock(requires=None, pass_preconditions=True)})
|
||||||
|
|
||||||
|
# WHEN: check_preconditions() is called
|
||||||
|
result = State().check_preconditions('test_pre1')
|
||||||
|
|
||||||
|
# THEN: The correct result should be returned
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_preconditions_required_module(state):
|
||||||
|
"""
|
||||||
|
Test that the check_preconditions() method returns the correct attribute when there is another required module
|
||||||
|
"""
|
||||||
|
# GIVEN: A State with two modules
|
||||||
|
State().modules.update({
|
||||||
|
'test_pre2': MagicMock(requires='test_pre3', pass_preconditions=True),
|
||||||
|
'test_pre3': MagicMock(requires=None, pass_preconditions=False)
|
||||||
|
})
|
||||||
|
|
||||||
|
# WHEN: check_preconditions() is called
|
||||||
|
result = State().check_preconditions('test_pre2')
|
||||||
|
|
||||||
|
# THEN: The correct result should be returned
|
||||||
|
assert result is False
|
||||||
|
@ -27,7 +27,7 @@ PREREQUISITE: add_record() and get_all() functions validated.
|
|||||||
import pytest
|
import pytest
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from unittest.mock import patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from openlp.core.lib.db import upgrade_db
|
from openlp.core.lib.db import upgrade_db
|
||||||
from openlp.core.projectors import upgrade
|
from openlp.core.projectors import upgrade
|
||||||
@ -321,6 +321,21 @@ def test_get_projector_by_id_none(projector):
|
|||||||
|
|
||||||
|
|
||||||
def test_get_projector_all_none(projector):
|
def test_get_projector_all_none(projector):
|
||||||
|
"""
|
||||||
|
Test get_projector_all() when self.get_all_objects() returns None
|
||||||
|
"""
|
||||||
|
# GIVEN: Mocked out get_all_objects
|
||||||
|
with patch.object(projector, 'get_all_objects') as mocked_get_all_objects:
|
||||||
|
mocked_get_all_objects.return_value = None
|
||||||
|
|
||||||
|
# WHEN: We retrieve the database entries
|
||||||
|
results = projector.get_projector_all()
|
||||||
|
|
||||||
|
# THEN: Verify results is empty
|
||||||
|
assert [] == results, 'Returned results should have returned an empty list'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_projector_all_empty(projector):
|
||||||
"""
|
"""
|
||||||
Test get_projector_all() with no projectors in db
|
Test get_projector_all() with no projectors in db
|
||||||
"""
|
"""
|
||||||
@ -329,7 +344,7 @@ def test_get_projector_all_none(projector):
|
|||||||
# WHEN: We retrieve the database entries
|
# WHEN: We retrieve the database entries
|
||||||
results = projector.get_projector_all()
|
results = projector.get_projector_all()
|
||||||
|
|
||||||
# THEN: Verify results is None
|
# THEN: Verify results is empty
|
||||||
assert [] == results, 'Returned results should have returned an empty list'
|
assert [] == results, 'Returned results should have returned an empty list'
|
||||||
|
|
||||||
|
|
||||||
@ -436,3 +451,111 @@ def test_delete_projector_fail(projector):
|
|||||||
|
|
||||||
# THEN: Results should be False
|
# THEN: Results should be False
|
||||||
assert results is False, 'delete_projector() should have returned False'
|
assert results is False, 'delete_projector() should have returned False'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_source_list_no_sources(projector):
|
||||||
|
"""
|
||||||
|
Test that an empty source list is returned
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked projector
|
||||||
|
mocked_projector = MagicMock(id='1', source_available=[])
|
||||||
|
|
||||||
|
# WHEN: get_source_list is run
|
||||||
|
results = projector.get_source_list(mocked_projector)
|
||||||
|
|
||||||
|
# THEN: The list should be empty
|
||||||
|
assert results == {}, 'The list of sources returned should be empty'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_source_list_source_is_none(projector):
|
||||||
|
"""
|
||||||
|
Test that a default code is returned when a source is not in the database
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked projector
|
||||||
|
mocked_projector = MagicMock(id='1', source_available=['11'])
|
||||||
|
|
||||||
|
with patch.object(projector, 'get_object_filtered') as mocked_get_object_filtered:
|
||||||
|
mocked_get_object_filtered.return_value = None
|
||||||
|
# WHEN: get_source_list is run
|
||||||
|
results = projector.get_source_list(mocked_projector)
|
||||||
|
|
||||||
|
# THEN: The list should contain the one default item
|
||||||
|
assert results == {'11': 'RGB 1'}, 'The list of sources returned should contain "RGB1"'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_source_list_source_has_item(projector):
|
||||||
|
"""
|
||||||
|
Test that a default code is returned when a source is in the database
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked projector
|
||||||
|
mocked_projector = MagicMock(entry=MagicMock(id='1'), source_available=['5Y'])
|
||||||
|
|
||||||
|
with patch.object(projector, 'get_object_filtered') as mocked_get_object_filtered:
|
||||||
|
mocked_get_object_filtered.return_value = MagicMock(text='VGA 1')
|
||||||
|
# WHEN: get_source_list is run
|
||||||
|
results = projector.get_source_list(mocked_projector)
|
||||||
|
|
||||||
|
# THEN: The list should contain the one default item
|
||||||
|
assert results == {'5Y': 'VGA 1'}, 'The list of sources returned should contain "RGB1"'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_source_by_id_none(projector):
|
||||||
|
"""
|
||||||
|
Test that no source in the db returns None
|
||||||
|
"""
|
||||||
|
# GIVEN: A Mocked get_object_filtered method
|
||||||
|
with patch.object(projector, 'get_object_filtered') as mocked_get_object_filtered:
|
||||||
|
mocked_get_object_filtered.return_value = None
|
||||||
|
|
||||||
|
# WHEN: Get the source by ID
|
||||||
|
result = projector.get_source_by_id('source')
|
||||||
|
|
||||||
|
# THEN: The result should be None
|
||||||
|
assert result is None, 'None should be returned by get_source_by_id'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_source_by_id(projector):
|
||||||
|
"""
|
||||||
|
Test that a source in the db returns that source
|
||||||
|
"""
|
||||||
|
# GIVEN: A Mocked get_object_filtered method
|
||||||
|
mocked_entry = MagicMock()
|
||||||
|
with patch.object(projector, 'get_object_filtered') as mocked_get_object_filtered:
|
||||||
|
mocked_get_object_filtered.return_value = mocked_entry
|
||||||
|
|
||||||
|
# WHEN: Get the source by ID
|
||||||
|
result = projector.get_source_by_id('source')
|
||||||
|
|
||||||
|
# THEN: The result should be the mocked entry
|
||||||
|
assert result is mocked_entry, 'The mocked entry should be returned by get_source_by_id'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_source_by_code_none(projector):
|
||||||
|
"""
|
||||||
|
Test that no source in the db returns None
|
||||||
|
"""
|
||||||
|
# GIVEN: A Mocked get_object_filtered method
|
||||||
|
with patch.object(projector, 'get_object_filtered') as mocked_get_object_filtered:
|
||||||
|
mocked_get_object_filtered.return_value = None
|
||||||
|
|
||||||
|
# WHEN: Get the source by code
|
||||||
|
result = projector.get_source_by_code('11', 52)
|
||||||
|
|
||||||
|
# THEN: The result should be None
|
||||||
|
assert result is None, 'None should be returned by get_source_by_code'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_source_by_code(projector):
|
||||||
|
"""
|
||||||
|
Test that a source in the db returns that source
|
||||||
|
"""
|
||||||
|
# GIVEN: A Mocked get_object_filtered method
|
||||||
|
mocked_entry = MagicMock()
|
||||||
|
with patch.object(projector, 'get_object_filtered') as mocked_get_object_filtered:
|
||||||
|
mocked_get_object_filtered.return_value = mocked_entry
|
||||||
|
|
||||||
|
# WHEN: Get the source by code
|
||||||
|
result = projector.get_source_by_code('5Y', 12)
|
||||||
|
|
||||||
|
# THEN: The result should be the mocked entry
|
||||||
|
assert result is mocked_entry, 'The mocked entry should be returned by get_source_by_code'
|
||||||
|
Loading…
Reference in New Issue
Block a user