openlp/tests/openlp_plugins/images/test_mediaitem.py

325 lines
15 KiB
Python

# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2024 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/>. #
##########################################################################
"""
This module contains tests for the lib submodule of the Images plugin.
"""
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import ANY, MagicMock, patch
import pytest
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.enum import ImageThemeMode
from openlp.core.common.registry import Registry
from openlp.core.db.manager import DBManager
from openlp.core.lib import build_icon, create_thumb
from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.core.widgets.views import TreeWidgetWithDnD
from openlp.plugins.images.lib.mediaitem import ImageMediaItem
from tests.utils.constants import TEST_RESOURCES_PATH
@pytest.fixture
def media_item(registry: Registry, mock_settings: MagicMock):
"""Local test setup"""
mocked_main_window = MagicMock()
Registry().register('service_list', MagicMock())
Registry().register('main_window', mocked_main_window)
Registry().register('live_controller', MagicMock())
mocked_plugin = MagicMock()
with patch('openlp.plugins.images.lib.mediaitem.FolderLibraryItem._setup'), \
patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.setup_item'):
m_item = ImageMediaItem(None, mocked_plugin)
m_item.settings_section = 'images'
m_item.list_view = MagicMock()
return m_item
def test_setup_item(registry: Registry, media_item: ImageMediaItem):
"""Test that setup_item() does the right thing"""
# GIVEN: An instance of ImageMediaItem with some stuff mocked out
# WHEN: setup_item is called
with patch.object(media_item, 'list_view') as mocked_list_view, \
patch.object(media_item, 'images_go_live') as mocked_images_go_live, \
patch.object(media_item, 'images_add_to_service') as mocked_images_add_to_service:
media_item.setup_item()
# THEN: The correct things should be set and called
mocked_images_go_live.connect.assert_called_once_with(media_item.go_live_remote)
mocked_images_add_to_service.connect.assert_called_once_with(media_item.add_to_service_remote)
assert media_item.quick_preview_allowed is True
assert media_item.has_search is True
mocked_list_view.setSelectionMode.assert_called_once_with(QtWidgets.QAbstractItemView.ExtendedSelection)
assert registry.has_function('live_theme_changed')
assert registry.has_function('slidecontroller_live_started')
mocked_list_view.activateDnD.assert_called_once_with()
@patch('openlp.plugins.images.lib.mediaitem.FolderLibraryItem.retranslate_ui')
def test_retranslate_ui(mocked_retranslate_ui: MagicMock, media_item: ImageMediaItem):
"""Test that the retranslate_ui method sets up the strings in the UI"""
# GIVEN: An insance of ImageMediaItem with some stuff mocked out
media_item.replace_action = MagicMock()
media_item.replace_action_context = MagicMock()
media_item.reset_action = MagicMock()
media_item.reset_action_context = MagicMock()
# WHEN: retranslate_ui() is called
media_item.retranslate_ui()
# THEN: The UI is translated
assert media_item.on_new_prompt == 'Select Image(s)'
# file_formats = get_images_filter()
# self.on_new_file_masks = '{formats};;{files} (*)'.format(formats=file_formats, files=UiStrings().AllFiles)
expected_replace_text = 'Replace Background'
expected_replace_tooltip = 'Replace live background.'
media_item.replace_action.setText.assert_called_once_with(expected_replace_text)
media_item.replace_action.setToolTip.assert_called_once_with(expected_replace_tooltip)
media_item.replace_action_context.setText.assert_called_once_with(expected_replace_text)
media_item.replace_action_context.setToolTip.assert_called_once_with(expected_replace_tooltip)
expected_reset_text = 'Reset Background'
expected_reset_tooltip = 'Reset live background.'
media_item.reset_action.setText.assert_called_once_with(expected_reset_text)
media_item.reset_action.setToolTip.assert_called_once_with(expected_reset_tooltip)
media_item.reset_action_context.setText.assert_called_once_with(expected_reset_text)
media_item.reset_action_context.setToolTip.assert_called_once_with(expected_reset_tooltip)
@patch('openlp.plugins.images.lib.mediaitem.FolderLibraryItem.required_icons')
def test_required_icons(mocked_required_icons: MagicMock, media_item: ImageMediaItem):
"""Test that the required_icons() method sets up the flags for icons needed"""
# GIVEN: An insance of ImageMediaItem
# WHEN: required_icons() is called
media_item.required_icons()
# THEN: The correct flags are set
assert media_item.has_file_icon is True
assert media_item.has_new_icon is False
assert media_item.has_edit_icon is False
assert media_item.add_to_service_item is True
@patch('openlp.plugins.images.lib.mediaitem.create_paths')
@patch('openlp.plugins.images.lib.mediaitem.Item', autospec=True)
def test_initialise(MockItem: MagicMock, mocked_create_paths: MagicMock, media_item: ImageMediaItem):
"""Test that the initialise method does the right things"""
# GIVEN: An instance of ImageMediaItem and some stuff mocked out
media_item.list_view = MagicMock(spec=TreeWidgetWithDnD, default_indentation=4)
media_item.manager = MagicMock(spec=DBManager)
media_item.manager.get_all_objects.return_value = []
media_item.load_list = MagicMock()
# WHEN: initialise() is called
media_item.initialise()
# THEN: The correct methods should have been called
media_item.list_view.clear.assert_called_once_with()
media_item.list_view.setIconSize.assert_called_once_with(QtCore.QSize(88, 50))
media_item.list_view.setIndentation.assert_called_once_with(4)
assert media_item.list_view.allow_internal_dnd is True
assert media_item.service_path.parts[-1] == 'thumbnails'
mocked_create_paths.assert_called_once_with(media_item.service_path)
media_item.manager.get_all_objects.assert_called_once_with(MockItem, order_by_ref=MockItem.file_path)
media_item.load_list.assert_called_once_with([], is_initial_load=True)
def test_on_reset_click(media_item: ImageMediaItem):
"""
Test that on_reset_click() actually resets the background
"""
# GIVEN: A mocked version of reset_action
media_item.reset_action = MagicMock()
media_item.reset_action_context = MagicMock()
# WHEN: on_reset_click is called
media_item.on_reset_click()
# THEN: the reset_action should be set invisible, and the image should be reset
media_item.reset_action.setVisible.assert_called_with(False)
media_item.reset_action_context.setVisible.assert_called_with(False)
media_item.live_controller.reload_theme.assert_called_with()
def test_on_display_changed(media_item):
"""
Test that on_display_changed() hides the reset background button
"""
# GIVEN: A mocked version of reset_action
media_item.reset_action = MagicMock()
media_item.reset_action_context = MagicMock()
# WHEN: on_display_changed is called
media_item.on_display_changed()
# THEN: the reset_action should be set invisible
media_item.reset_action.setVisible.assert_called_with(False)
media_item.reset_action_context.setVisible.assert_called_with(False)
def test_generate_slide_data_default_theme(media_item):
"""
Test that the generated service item provides the corect theme
"""
# GIVEN: A mocked service item and settings
mocked_service_item = MagicMock()
media_item.list_view = MagicMock()
Registry().get('settings').value.side_effect = [ImageThemeMode.Black]
# WHEN: generate_slide_data is called
media_item.generate_slide_data(mocked_service_item)
# THEN: The service item should force the theme, and use the default theme
mocked_service_item.add_capability.assert_any_call(ItemCapabilities.ProvidesOwnTheme)
assert mocked_service_item.theme == -1
def test_generate_slide_data_custom_theme(media_item):
"""
Test that the generated service item provides the corect theme
"""
# GIVEN: A mocked service item and settings
mocked_service_item = MagicMock()
media_item.list_view = MagicMock()
Registry().get('settings').value.side_effect = [ImageThemeMode.CustomTheme, 'theme_name']
# WHEN: generate_slide_data is called
media_item.generate_slide_data(mocked_service_item)
# THEN: The service item should force the theme, and use the theme in the settings
mocked_service_item.add_capability.assert_any_call(ItemCapabilities.ProvidesOwnTheme)
assert mocked_service_item.theme == 'theme_name'
@patch('openlp.plugins.images.lib.mediaitem.check_item_selected')
@patch('openlp.plugins.images.lib.mediaitem.isinstance')
@patch('openlp.plugins.images.lib.mediaitem.Path.exists')
def test_on_replace_click(mocked_exists, mocked_isinstance, mocked_check_item_selected, media_item):
"""
Test that on_replace_click() actually sets the background
"""
# GIVEN: A mocked version of reset_action, and a (faked) existing selected image file
media_item.reset_action = MagicMock()
media_item.reset_action_context = MagicMock()
media_item.list_view = MagicMock()
mocked_check_item_selected.return_value = True
mocked_isinstance.return_value = True
mocked_exists.return_value = True
# WHEN: on_replace_click is called
media_item.on_replace_click()
# THEN: the reset_action should be set visible, and the image should be set
media_item.reset_action.setVisible.assert_called_with(True)
media_item.reset_action_context.setVisible.assert_called_with(True)
media_item.live_controller.set_background_image.assert_called_with(ANY)
def test_generate_thumbnail_path_hash(media_item):
"""
Test that the thumbnail path is correctly generated with the file hash
"""
# GIVEN: A media item and an Image object
mocked_image = MagicMock(file_path=Path('myimage.jpg'), file_hash='c3986a0bd8e1ec9da406df142950c67e7d435410')
media_item.service_path = Path('.')
# WHEN: generate_thumbnail_path() is called
result = media_item.generate_thumbnail_path(mocked_image)
# THEN: The path should be correct
assert result == Path('.') / 'c3986a0bd8e1ec9da406df142950c67e7d435410.jpg'
def test_generate_thumbnail_path_filename(media_item):
"""
Test that the thumbnail path is correctly generated with the file name
"""
# GIVEN: A media item and an Image object
mocked_image = MagicMock(file_path=Path('myimage.jpg'), file_hash=None)
media_item.service_path = Path('.')
# WHEN: generate_thumbnail_path() is called
result = media_item.generate_thumbnail_path(mocked_image)
# THEN: The path should be correct
assert result == Path('.') / 'myimage.jpg'
@patch('openlp.plugins.images.lib.mediaitem.create_thumb')
def test_load_item_file_not_exist(mocked_create_thumb: MagicMock, media_item: ImageMediaItem):
"""Test the load_item method when the file does not exist"""
# GIVEN: A media item and an Item to load
item = MagicMock(file_path=Path('myimage.jpg'), file_hash=None)
# WHEN load_item() is called with the Item
result = media_item.load_item(item)
# THEN: A QTreeWidgetItem with a "delete" icon should be returned
assert isinstance(result, QtWidgets.QTreeWidgetItem)
assert result.text(0) == 'myimage.jpg'
mocked_create_thumb.assert_not_called()
@patch('openlp.plugins.images.lib.mediaitem.validate_thumb')
@patch('openlp.plugins.images.lib.mediaitem.create_thumb', wraps=create_thumb)
@patch('openlp.plugins.images.lib.mediaitem.build_icon', wraps=build_icon)
def test_load_item_valid_thumbnail(mocked_build_icon: MagicMock, mocked_create_thumb: MagicMock,
mocked_validate_thumb: MagicMock, media_item: ImageMediaItem, registry: Registry):
"""Test the load_item method with an existing thumbnail"""
# GIVEN: A media item and an Item to load
media_item.service_path = Path(TEST_RESOURCES_PATH) / 'images'
mocked_validate_thumb.return_value = True
image_path = Path(TEST_RESOURCES_PATH) / 'images' / 'tractor.jpg'
item = MagicMock(file_path=image_path, file_hash=None)
# WHEN load_item() is called with the Item
result = media_item.load_item(item)
# THEN: A QTreeWidgetItem with a "delete" icon should be returned
assert isinstance(result, QtWidgets.QTreeWidgetItem)
assert result.text(0) == 'tractor.jpg'
assert result.toolTip(0) == str(image_path)
mocked_create_thumb.assert_not_called()
mocked_build_icon.assert_called_once_with(image_path)
@patch('openlp.plugins.images.lib.mediaitem.validate_thumb')
@patch('openlp.plugins.images.lib.mediaitem.create_thumb', wraps=create_thumb)
def test_load_item_missing_thumbnail(mocked_create_thumb: MagicMock, mocked_validate_thumb: MagicMock,
media_item: ImageMediaItem, registry: Registry):
"""Test the load_item method with no valid thumbnails"""
# GIVEN: A media item and an Item to load
with TemporaryDirectory() as tmpdir:
media_item.service_path = Path(tmpdir)
mocked_validate_thumb.return_value = False
image_path = Path(TEST_RESOURCES_PATH) / 'images' / 'tractor.jpg'
item = MagicMock(file_path=image_path, file_hash=None)
registry.get('settings').value.return_value = 400
# WHEN load_item() is called with the Item
result = media_item.load_item(item)
# THEN: A QTreeWidgetItem with a "delete" icon should be returned
assert isinstance(result, QtWidgets.QTreeWidgetItem)
assert result.text(0) == 'tractor.jpg'
assert result.toolTip(0) == str(image_path)
mocked_create_thumb.assert_called_once_with(image_path, Path(tmpdir, 'tractor.jpg'), size=QtCore.QSize(-1, 400))