Definitely ready for merging, unless, of course you guys find some more issues!

Part 2

Changed the pathedit widget over to using pathlib
Added a 'patched' file dialog
Added a few utility methods

lp:~phill-ridout/openlp/pathlib2 (revision 2763)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2125/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/2033/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1938/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis...

bzr-revno: 2758
This commit is contained in:
Phill 2017-08-11 21:31:09 +01:00 committed by Tim Bentley
commit 081c5fe7a0
22 changed files with 658 additions and 225 deletions

View File

@ -19,40 +19,43 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 # # with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
"""
Provide a work around for a bug in QFileDialog <https://bugs.launchpad.net/openlp/+bug/1209515>
"""
import logging
import os
from urllib import parse
from PyQt5 import QtWidgets from pathlib import Path
from openlp.core.common import UiStrings
log = logging.getLogger(__name__)
class FileDialog(QtWidgets.QFileDialog): def path_to_str(path):
""" """
Subclass QFileDialog to work round a bug A utility function to convert a Path object or NoneType to a string equivalent.
:param path: The value to convert to a string
:type: pathlib.Path or None
:return: An empty string if :param:`path` is None, else a string representation of the :param:`path`
:rtype: str
""" """
@staticmethod if not isinstance(path, Path) and path is not None:
def getOpenFileNames(parent, *args, **kwargs): raise TypeError('parameter \'path\' must be of type Path or NoneType')
""" if path is None:
Reimplement getOpenFileNames to fix the way it returns some file names that url encoded when selecting multiple return ''
files else:
""" return str(path)
files, filter_used = QtWidgets.QFileDialog.getOpenFileNames(parent, *args, **kwargs)
file_list = []
for file in files: def str_to_path(string):
if not os.path.exists(file): """
log.info('File not found. Attempting to unquote.') A utility function to convert a str object to a Path or NoneType.
file = parse.unquote(file)
if not os.path.exists(file): This function is of particular use because initating a Path object with an empty string causes the Path object to
log.error('File {text} not found.'.format(text=file)) point to the current working directory.
QtWidgets.QMessageBox.information(parent, UiStrings().FileNotFound,
UiStrings().FileNotFoundMessage.format(name=file)) :param string: The string to convert
continue :type string: str
file_list.append(file)
return file_list :return: None if :param:`string` is empty, or a Path object representation of :param:`string`
:rtype: pathlib.Path or None
"""
if not isinstance(string, str):
raise TypeError('parameter \'string\' must be of type str')
if string == '':
return None
return Path(string)

View File

@ -608,8 +608,42 @@ def create_separated_list(string_list):
return list_to_string return list_to_string
def replace_params(args, kwargs, params):
"""
Apply a transformation function to the specified args or kwargs
:param args: Positional arguments
:type args: (,)
:param kwargs: Key Word arguments
:type kwargs: dict
:param params: A tuple of tuples with the position and the key word to replace.
:type params: ((int, str, path_to_str),)
:return: The modified positional and keyword arguments
:rtype: (tuple, dict)
Usage:
Take a method with the following signature, and assume we which to apply the str function to arg2:
def method(arg1=None, arg2=None, arg3=None)
As arg2 can be specified postitionally as the second argument (1 with a zero index) or as a keyword, the we
would call this function as follows:
replace_params(args, kwargs, ((1, 'arg2', str),))
"""
args = list(args)
for position, key_word, transform in params:
if len(args) > position:
args[position] = transform(args[position])
elif key_word in kwargs:
kwargs[key_word] = transform(kwargs[key_word])
return tuple(args), kwargs
from .exceptions import ValidationError from .exceptions import ValidationError
from .filedialog import FileDialog
from .screen import ScreenList from .screen import ScreenList
from .formattingtags import FormattingTags from .formattingtags import FormattingTags
from .plugin import PluginStatus, StringContent, Plugin from .plugin import PluginStatus, StringContent, Plugin

View File

@ -26,12 +26,14 @@ import logging
import os import os
import re import re
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate
from openlp.core.lib import FileDialog, ServiceItem, StringContent, ServiceItemContext from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib import ServiceItem, StringContent, ServiceItemContext
from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.searchedit import SearchEdit
from openlp.core.lib.ui import create_widget_action, critical_error_message_box from openlp.core.lib.ui import create_widget_action, critical_error_message_box
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
from openlp.core.ui.lib.toolbar import OpenLPToolbar from openlp.core.ui.lib.toolbar import OpenLPToolbar
@ -309,13 +311,14 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
""" """
Add a file to the list widget to make it available for showing Add a file to the list widget to make it available for showing
""" """
files = FileDialog.getOpenFileNames(self, self.on_new_prompt, file_paths, selected_filter = FileDialog.getOpenFileNames(
Settings().value(self.settings_section + '/last directory'), self, self.on_new_prompt,
self.on_new_file_masks) str_to_path(Settings().value(self.settings_section + '/last directory')),
log.info('New files(s) {files}'.format(files=files)) self.on_new_file_masks)
if files: log.info('New files(s) {file_paths}'.format(file_paths=file_paths))
if file_paths:
self.application.set_busy_cursor() self.application.set_busy_cursor()
self.validate_and_load(files) self.validate_and_load([path_to_str(path) for path in file_paths])
self.application.set_normal_cursor() self.application.set_normal_cursor()
def load_file(self, data): def load_file(self, data):

View File

@ -30,6 +30,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import AppLocation, Settings, SlideLimits, UiStrings, translate from openlp.core.common import AppLocation, Settings, SlideLimits, UiStrings, translate
from openlp.core.common.languagemanager import format_time from openlp.core.common.languagemanager import format_time
from openlp.core.common.path import path_to_str
from openlp.core.lib import SettingsTab, build_icon from openlp.core.lib import SettingsTab, build_icon
from openlp.core.ui.lib import PathEdit, PathType from openlp.core.ui.lib import PathEdit, PathType
@ -156,7 +157,7 @@ class AdvancedTab(SettingsTab):
self.data_directory_new_label = QtWidgets.QLabel(self.data_directory_group_box) self.data_directory_new_label = QtWidgets.QLabel(self.data_directory_group_box)
self.data_directory_new_label.setObjectName('data_directory_current_label') self.data_directory_new_label.setObjectName('data_directory_current_label')
self.data_directory_path_edit = PathEdit(self.data_directory_group_box, path_type=PathType.Directories, self.data_directory_path_edit = PathEdit(self.data_directory_group_box, path_type=PathType.Directories,
default_path=str(AppLocation.get_directory(AppLocation.DataDir))) default_path=AppLocation.get_directory(AppLocation.DataDir))
self.data_directory_layout.addRow(self.data_directory_new_label, self.data_directory_path_edit) self.data_directory_layout.addRow(self.data_directory_new_label, self.data_directory_path_edit)
self.new_data_directory_has_files_label = QtWidgets.QLabel(self.data_directory_group_box) self.new_data_directory_has_files_label = QtWidgets.QLabel(self.data_directory_group_box)
self.new_data_directory_has_files_label.setObjectName('new_data_directory_has_files_label') self.new_data_directory_has_files_label.setObjectName('new_data_directory_has_files_label')
@ -373,7 +374,7 @@ class AdvancedTab(SettingsTab):
self.new_data_directory_has_files_label.hide() self.new_data_directory_has_files_label.hide()
self.data_directory_cancel_button.hide() self.data_directory_cancel_button.hide()
# Since data location can be changed, make sure the path is present. # Since data location can be changed, make sure the path is present.
self.data_directory_path_edit.path = str(AppLocation.get_data_path()) self.data_directory_path_edit.path = AppLocation.get_data_path()
# Don't allow data directory move if running portable. # Don't allow data directory move if running portable.
if settings.value('advanced/is portable'): if settings.value('advanced/is portable'):
self.data_directory_group_box.hide() self.data_directory_group_box.hide()
@ -497,12 +498,12 @@ class AdvancedTab(SettingsTab):
'closed.').format(path=new_data_path), 'closed.').format(path=new_data_path),
defaultButton=QtWidgets.QMessageBox.No) defaultButton=QtWidgets.QMessageBox.No)
if answer != QtWidgets.QMessageBox.Yes: if answer != QtWidgets.QMessageBox.Yes:
self.data_directory_path_edit.path = str(AppLocation.get_data_path()) self.data_directory_path_edit.path = AppLocation.get_data_path()
return return
# Check if data already exists here. # Check if data already exists here.
self.check_data_overwrite(new_data_path) self.check_data_overwrite(path_to_str(new_data_path))
# Save the new location. # Save the new location.
self.main_window.set_new_data_path(new_data_path) self.main_window.set_new_data_path(path_to_str(new_data_path))
self.data_directory_cancel_button.show() self.data_directory_cancel_button.show()
def on_data_directory_copy_check_box_toggled(self): def on_data_directory_copy_check_box_toggled(self):
@ -550,7 +551,7 @@ class AdvancedTab(SettingsTab):
""" """
Cancel the data directory location change Cancel the data directory location change
""" """
self.data_directory_path_edit.path = str(AppLocation.get_data_path()) self.data_directory_path_edit.path = AppLocation.get_data_path()
self.data_directory_copy_check_box.setChecked(False) self.data_directory_copy_check_box.setChecked(False)
self.main_window.set_new_data_path(None) self.main_window.set_new_data_path(None)
self.main_window.set_copy_data(False) self.main_window.set_copy_data(False)

View File

@ -23,10 +23,12 @@
The general tab of the configuration dialog. The general tab of the configuration dialog.
""" """
import logging import logging
from pathlib import Path
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, Settings, UiStrings, translate, get_images_filter from openlp.core.common import Registry, Settings, UiStrings, translate, get_images_filter
from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib import SettingsTab, ScreenList from openlp.core.lib import SettingsTab, ScreenList
from openlp.core.ui.lib import ColorButton, PathEdit from openlp.core.ui.lib import ColorButton, PathEdit
@ -172,7 +174,8 @@ class GeneralTab(SettingsTab):
self.logo_layout.setObjectName('logo_layout') self.logo_layout.setObjectName('logo_layout')
self.logo_file_label = QtWidgets.QLabel(self.logo_group_box) self.logo_file_label = QtWidgets.QLabel(self.logo_group_box)
self.logo_file_label.setObjectName('logo_file_label') self.logo_file_label.setObjectName('logo_file_label')
self.logo_file_path_edit = PathEdit(self.logo_group_box, default_path=':/graphics/openlp-splash-screen.png') self.logo_file_path_edit = PathEdit(self.logo_group_box,
default_path=Path(':/graphics/openlp-splash-screen.png'))
self.logo_layout.addRow(self.logo_file_label, self.logo_file_path_edit) self.logo_layout.addRow(self.logo_file_label, self.logo_file_path_edit)
self.logo_color_label = QtWidgets.QLabel(self.logo_group_box) self.logo_color_label = QtWidgets.QLabel(self.logo_group_box)
self.logo_color_label.setObjectName('logo_color_label') self.logo_color_label.setObjectName('logo_color_label')
@ -266,7 +269,7 @@ class GeneralTab(SettingsTab):
self.audio_group_box.setTitle(translate('OpenLP.GeneralTab', 'Background Audio')) self.audio_group_box.setTitle(translate('OpenLP.GeneralTab', 'Background Audio'))
self.start_paused_check_box.setText(translate('OpenLP.GeneralTab', 'Start background audio paused')) self.start_paused_check_box.setText(translate('OpenLP.GeneralTab', 'Start background audio paused'))
self.repeat_list_check_box.setText(translate('OpenLP.GeneralTab', 'Repeat track list')) self.repeat_list_check_box.setText(translate('OpenLP.GeneralTab', 'Repeat track list'))
self.logo_file_path_edit.dialog_caption = dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File') self.logo_file_path_edit.dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File')
self.logo_file_path_edit.filters = '{text};;{names} (*)'.format( self.logo_file_path_edit.filters = '{text};;{names} (*)'.format(
text=get_images_filter(), names=UiStrings().AllFiles) text=get_images_filter(), names=UiStrings().AllFiles)
@ -291,7 +294,7 @@ class GeneralTab(SettingsTab):
self.auto_open_check_box.setChecked(settings.value('auto open')) self.auto_open_check_box.setChecked(settings.value('auto open'))
self.show_splash_check_box.setChecked(settings.value('show splash')) self.show_splash_check_box.setChecked(settings.value('show splash'))
self.logo_background_color = settings.value('logo background color') self.logo_background_color = settings.value('logo background color')
self.logo_file_path_edit.path = settings.value('logo file') self.logo_file_path_edit.path = str_to_path(settings.value('logo file'))
self.logo_hide_on_startup_check_box.setChecked(settings.value('logo hide on startup')) self.logo_hide_on_startup_check_box.setChecked(settings.value('logo hide on startup'))
self.logo_color_button.color = self.logo_background_color self.logo_color_button.color = self.logo_background_color
self.check_for_updates_check_box.setChecked(settings.value('update check')) self.check_for_updates_check_box.setChecked(settings.value('update check'))
@ -325,7 +328,7 @@ class GeneralTab(SettingsTab):
settings.setValue('auto open', self.auto_open_check_box.isChecked()) settings.setValue('auto open', self.auto_open_check_box.isChecked())
settings.setValue('show splash', self.show_splash_check_box.isChecked()) settings.setValue('show splash', self.show_splash_check_box.isChecked())
settings.setValue('logo background color', self.logo_background_color) settings.setValue('logo background color', self.logo_background_color)
settings.setValue('logo file', self.logo_file_path_edit.path) settings.setValue('logo file', path_to_str(self.logo_file_path_edit.path))
settings.setValue('logo hide on startup', self.logo_hide_on_startup_check_box.isChecked()) settings.setValue('logo hide on startup', self.logo_hide_on_startup_check_box.isChecked())
settings.setValue('update check', self.check_for_updates_check_box.isChecked()) settings.setValue('update check', self.check_for_updates_check_box.isChecked())
settings.setValue('save prompt', self.save_check_service_check_box.isChecked()) settings.setValue('save prompt', self.save_check_service_check_box.isChecked())

113
openlp/core/ui/lib/filedialog.py Executable file
View File

@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 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; version 2 of the License. #
# #
# 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, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
""" Patch the QFileDialog so it accepts and returns Path objects"""
from pathlib import Path
from PyQt5 import QtWidgets
from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib import replace_params
class FileDialog(QtWidgets.QFileDialog):
@classmethod
def getExistingDirectory(cls, *args, **kwargs):
"""
Wraps `getExistingDirectory` so that it can be called with, and return Path objects
:type parent: QtWidgets.QWidget or None
:type caption: str
:type directory: pathlib.Path
:type options: QtWidgets.QFileDialog.Options
:rtype: tuple[Path, str]
"""
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
return_value = super().getExistingDirectory(*args, **kwargs)
# getExistingDirectory returns a str that represents the path. The string is empty if the user cancels the
# dialog.
return str_to_path(return_value)
@classmethod
def getOpenFileName(cls, *args, **kwargs):
"""
Wraps `getOpenFileName` so that it can be called with, and return Path objects
:type parent: QtWidgets.QWidget or None
:type caption: str
:type directory: pathlib.Path
:type filter: str
:type initialFilter: str
:type options: QtWidgets.QFileDialog.Options
:rtype: tuple[Path, str]
"""
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
file_name, selected_filter = super().getOpenFileName(*args, **kwargs)
# getOpenFileName returns a tuple. The first item is a str that represents the path. The string is empty if
# the user cancels the dialog.
return str_to_path(file_name), selected_filter
@classmethod
def getOpenFileNames(cls, *args, **kwargs):
"""
Wraps `getOpenFileNames` so that it can be called with, and return Path objects
:type parent: QtWidgets.QWidget or None
:type caption: str
:type directory: pathlib.Path
:type filter: str
:type initialFilter: str
:type options: QtWidgets.QFileDialog.Options
:rtype: tuple[list[Path], str]
"""
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
file_names, selected_filter = super().getOpenFileNames(*args, **kwargs)
# getSaveFileName returns a tuple. The first item is a list of str's that represents the path. The list is
# empty if the user cancels the dialog.
paths = [str_to_path(path) for path in file_names]
return paths, selected_filter
@classmethod
def getSaveFileName(cls, *args, **kwargs):
"""
Wraps `getSaveFileName` so that it can be called with, and return Path objects
:type parent: QtWidgets.QWidget or None
:type caption: str
:type directory: pathlib.Path
:type filter: str
:type initialFilter: str
:type options: QtWidgets.QFileDialog.Options
:rtype: tuple[Path or None, str]
"""
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
file_name, selected_filter = super().getSaveFileName(*args, **kwargs)
# getSaveFileName returns a tuple. The first item represents the path as a str. The string is empty if the user
# cancels the dialog.
return str_to_path(file_name), selected_filter

56
openlp/core/ui/lib/pathedit.py Executable file → Normal file
View File

@ -20,12 +20,14 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
from enum import Enum from enum import Enum
import os.path from pathlib import Path
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from openlp.core.common import UiStrings, translate from openlp.core.common import UiStrings, translate
from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib import build_icon from openlp.core.lib import build_icon
from openlp.core.ui.lib.filedialog import FileDialog
class PathType(Enum): class PathType(Enum):
@ -38,11 +40,11 @@ class PathEdit(QtWidgets.QWidget):
The :class:`~openlp.core.ui.lib.pathedit.PathEdit` class subclasses QWidget to create a custom widget for use when The :class:`~openlp.core.ui.lib.pathedit.PathEdit` class subclasses QWidget to create a custom widget for use when
a file or directory needs to be selected. a file or directory needs to be selected.
""" """
pathChanged = QtCore.pyqtSignal(str) pathChanged = QtCore.pyqtSignal(Path)
def __init__(self, parent=None, path_type=PathType.Files, default_path=None, dialog_caption=None, show_revert=True): def __init__(self, parent=None, path_type=PathType.Files, default_path=None, dialog_caption=None, show_revert=True):
""" """
Initalise the PathEdit widget Initialise the PathEdit widget
:param parent: The parent of the widget. This is just passed to the super method. :param parent: The parent of the widget. This is just passed to the super method.
:type parent: QWidget or None :type parent: QWidget or None
@ -51,9 +53,9 @@ class PathEdit(QtWidgets.QWidget):
:type dialog_caption: str :type dialog_caption: str
:param default_path: The default path. This is set as the path when the revert button is clicked :param default_path: The default path. This is set as the path when the revert button is clicked
:type default_path: str :type default_path: pathlib.Path
:param show_revert: Used to determin if the 'revert button' should be visible. :param show_revert: Used to determine if the 'revert button' should be visible.
:type show_revert: bool :type show_revert: bool
:return: None :return: None
@ -79,7 +81,6 @@ class PathEdit(QtWidgets.QWidget):
widget_layout = QtWidgets.QHBoxLayout() widget_layout = QtWidgets.QHBoxLayout()
widget_layout.setContentsMargins(0, 0, 0, 0) widget_layout.setContentsMargins(0, 0, 0, 0)
self.line_edit = QtWidgets.QLineEdit(self) self.line_edit = QtWidgets.QLineEdit(self)
self.line_edit.setText(self._path)
widget_layout.addWidget(self.line_edit) widget_layout.addWidget(self.line_edit)
self.browse_button = QtWidgets.QToolButton(self) self.browse_button = QtWidgets.QToolButton(self)
self.browse_button.setIcon(build_icon(':/general/general_open.png')) self.browse_button.setIcon(build_icon(':/general/general_open.png'))
@ -101,7 +102,7 @@ class PathEdit(QtWidgets.QWidget):
A property getter method to return the selected path. A property getter method to return the selected path.
:return: The selected path :return: The selected path
:rtype: str :rtype: pathlib.Path
""" """
return self._path return self._path
@ -111,11 +112,15 @@ class PathEdit(QtWidgets.QWidget):
A Property setter method to set the selected path A Property setter method to set the selected path
:param path: The path to set the widget to :param path: The path to set the widget to
:type path: str :type path: pathlib.Path
:return: None
:rtype: None
""" """
self._path = path self._path = path
self.line_edit.setText(path) text = path_to_str(path)
self.line_edit.setToolTip(path) self.line_edit.setText(text)
self.line_edit.setToolTip(text)
@property @property
def path_type(self): def path_type(self):
@ -124,7 +129,7 @@ class PathEdit(QtWidgets.QWidget):
selecting a file or directory. selecting a file or directory.
:return: The type selected :return: The type selected
:rtype: Enum of PathEdit :rtype: PathType
""" """
return self._path_type return self._path_type
@ -133,8 +138,11 @@ class PathEdit(QtWidgets.QWidget):
""" """
A Property setter method to set the path type A Property setter method to set the path type
:param path: The type of path to select :param path_type: The type of path to select
:type path: Enum of PathEdit :type path_type: PathType
:return: None
:rtype: None
""" """
self._path_type = path_type self._path_type = path_type
self.update_button_tool_tips() self.update_button_tool_tips()
@ -142,7 +150,9 @@ class PathEdit(QtWidgets.QWidget):
def update_button_tool_tips(self): def update_button_tool_tips(self):
""" """
Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised
:return: None :return: None
:rtype: None
""" """
if self._path_type == PathType.Directories: if self._path_type == PathType.Directories:
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.')) self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.'))
@ -156,21 +166,21 @@ class PathEdit(QtWidgets.QWidget):
A handler to handle a click on the browse button. A handler to handle a click on the browse button.
Show the QFileDialog and process the input from the user Show the QFileDialog and process the input from the user
:return: None :return: None
:rtype: None
""" """
caption = self.dialog_caption caption = self.dialog_caption
path = '' path = None
if self._path_type == PathType.Directories: if self._path_type == PathType.Directories:
if not caption: if not caption:
caption = translate('OpenLP.PathEdit', 'Select Directory') caption = translate('OpenLP.PathEdit', 'Select Directory')
path = QtWidgets.QFileDialog.getExistingDirectory(self, caption, path = FileDialog.getExistingDirectory(self, caption, self._path, FileDialog.ShowDirsOnly)
self._path, QtWidgets.QFileDialog.ShowDirsOnly)
elif self._path_type == PathType.Files: elif self._path_type == PathType.Files:
if not caption: if not caption:
caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File') caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File')
path, filter_used = QtWidgets.QFileDialog.getOpenFileName(self, caption, self._path, self.filters) path, filter_used = FileDialog.getOpenFileName(self, caption, self._path, self.filters)
if path: if path:
path = os.path.normpath(path)
self.on_new_path(path) self.on_new_path(path)
def on_revert_button_clicked(self): def on_revert_button_clicked(self):
@ -178,16 +188,21 @@ class PathEdit(QtWidgets.QWidget):
A handler to handle a click on the revert button. A handler to handle a click on the revert button.
Set the new path to the value of the default_path instance variable. Set the new path to the value of the default_path instance variable.
:return: None :return: None
:rtype: None
""" """
self.on_new_path(self.default_path) self.on_new_path(self.default_path)
def on_line_edit_editing_finished(self): def on_line_edit_editing_finished(self):
""" """
A handler to handle when the line edit has finished being edited. A handler to handle when the line edit has finished being edited.
:return: None :return: None
:rtype: None
""" """
self.on_new_path(self.line_edit.text()) path = str_to_path(self.line_edit.text())
self.on_new_path(path)
def on_new_path(self, path): def on_new_path(self, path):
""" """
@ -196,9 +211,10 @@ class PathEdit(QtWidgets.QWidget):
Emits the pathChanged Signal Emits the pathChanged Signal
:param path: The new path :param path: The new path
:type path: str :type path: pathlib.Path
:return: None :return: None
:rtype: None
""" """
if self._path != path: if self._path != path:
self.path = path self.path = path

View File

@ -28,6 +28,7 @@ import os
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, RegistryProperties, UiStrings, translate, get_images_filter, is_not_image_file from openlp.core.common import Registry, RegistryProperties, UiStrings, translate, get_images_filter, is_not_image_file
from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui import ThemeLayoutForm from openlp.core.ui import ThemeLayoutForm
@ -316,11 +317,11 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
self.setField('background_type', 1) self.setField('background_type', 1)
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image): elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image):
self.image_color_button.color = self.theme.background_border_color self.image_color_button.color = self.theme.background_border_color
self.image_path_edit.path = self.theme.background_filename self.image_path_edit.path = str_to_path(self.theme.background_filename)
self.setField('background_type', 2) self.setField('background_type', 2)
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video): elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
self.video_color_button.color = self.theme.background_border_color self.video_color_button.color = self.theme.background_border_color
self.video_path_edit.path = self.theme.background_filename self.video_path_edit.path = str_to_path(self.theme.background_filename)
self.setField('background_type', 4) self.setField('background_type', 4)
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent): elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
self.setField('background_type', 3) self.setField('background_type', 3)
@ -448,18 +449,18 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
""" """
self.theme.background_end_color = color self.theme.background_end_color = color
def on_image_path_edit_path_changed(self, filename): def on_image_path_edit_path_changed(self, file_path):
""" """
Background Image button pushed. Background Image button pushed.
""" """
self.theme.background_filename = filename self.theme.background_filename = path_to_str(file_path)
self.set_background_page_values() self.set_background_page_values()
def on_video_path_edit_path_changed(self, filename): def on_video_path_edit_path_changed(self, file_path):
""" """
Background video button pushed. Background video button pushed.
""" """
self.theme.background_filename = filename self.theme.background_filename = path_to_str(file_path)
self.set_background_page_values() self.set_background_page_values()
def on_main_color_changed(self, color): def on_main_color_changed(self, color):

View File

@ -22,7 +22,6 @@
""" """
The Theme Manager manages adding, deleteing and modifying of themes. The Theme Manager manages adding, deleteing and modifying of themes.
""" """
import json
import os import os
import zipfile import zipfile
import shutil import shutil
@ -32,12 +31,14 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \ from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
UiStrings, check_directory_exists, translate, is_win, get_filesystem_encoding, delete_file UiStrings, check_directory_exists, translate, is_win, get_filesystem_encoding, delete_file
from openlp.core.lib import FileDialog, ImageSource, ValidationError, get_text_file_string, build_icon, \ from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \
check_item_selected, create_thumb, validate_thumb check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.theme import Theme, BackgroundType from openlp.core.lib.theme import Theme, BackgroundType
from openlp.core.lib.ui import critical_error_message_box, create_widget_action from openlp.core.lib.ui import critical_error_message_box, create_widget_action
from openlp.core.ui import FileRenameForm, ThemeForm from openlp.core.ui import FileRenameForm, ThemeForm
from openlp.core.ui.lib import OpenLPToolbar from openlp.core.ui.lib import OpenLPToolbar
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.common.languagemanager import get_locale_key from openlp.core.common.languagemanager import get_locale_key
@ -424,15 +425,17 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
those files. This process will only load version 2 themes. those files. This process will only load version 2 themes.
:param field: :param field:
""" """
files = FileDialog.getOpenFileNames(self, file_paths, selected_filter = FileDialog.getOpenFileNames(
translate('OpenLP.ThemeManager', 'Select Theme Import File'), self,
Settings().value(self.settings_section + '/last directory import'), translate('OpenLP.ThemeManager', 'Select Theme Import File'),
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)')) str_to_path(Settings().value(self.settings_section + '/last directory import')),
self.log_info('New Themes {name}'.format(name=str(files))) translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
if not files: self.log_info('New Themes {file_paths}'.format(file_paths=file_paths))
if not file_paths:
return return
self.application.set_busy_cursor() self.application.set_busy_cursor()
for file_name in files: for file_path in file_paths:
file_name = path_to_str(file_path)
Settings().setValue(self.settings_section + '/last directory import', str(file_name)) Settings().setValue(self.settings_section + '/last directory import', str(file_name))
self.unzip_theme(file_name, self.path) self.unzip_theme(file_name, self.path)
self.load_themes() self.load_themes()

View File

@ -22,6 +22,8 @@
""" """
The Create/Edit theme wizard The Create/Edit theme wizard
""" """
from pathlib import Path
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import UiStrings, translate, is_macosx from openlp.core.common import UiStrings, translate, is_macosx

View File

@ -255,7 +255,7 @@ class BGExtract(RegistryProperties):
chapter=chapter, chapter=chapter,
version=version) version=version)
soup = get_soup_for_bible_ref( soup = get_soup_for_bible_ref(
'http://biblegateway.com/passage/?{url}'.format(url=url_params), 'http://www.biblegateway.com/passage/?{url}'.format(url=url_params),
pre_parse_regex=r'<meta name.*?/>', pre_parse_substitute='') pre_parse_regex=r'<meta name.*?/>', pre_parse_substitute='')
if not soup: if not soup:
return None return None
@ -284,7 +284,7 @@ class BGExtract(RegistryProperties):
""" """
log.debug('BGExtract.get_books_from_http("{version}")'.format(version=version)) log.debug('BGExtract.get_books_from_http("{version}")'.format(version=version))
url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '{version}'.format(version=version)}) url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '{version}'.format(version=version)})
reference_url = 'http://biblegateway.com/versions/?{url}#books'.format(url=url_params) reference_url = 'http://www.biblegateway.com/versions/?{url}#books'.format(url=url_params)
page = get_web_page(reference_url) page = get_web_page(reference_url)
if not page: if not page:
send_error_message('download') send_error_message('download')

View File

@ -20,10 +20,11 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
from PyQt5 import QtGui, QtWidgets from PyQt5 import QtWidgets
from openlp.core.common import Settings, UiStrings, translate from openlp.core.common import Settings, UiStrings, translate
from openlp.core.lib import SettingsTab, build_icon from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib import SettingsTab
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib import PathEdit from openlp.core.ui.lib import PathEdit
from openlp.plugins.presentations.lib.pdfcontroller import PdfController from openlp.plugins.presentations.lib.pdfcontroller import PdfController
@ -156,7 +157,7 @@ class PresentationTab(SettingsTab):
self.program_path_edit.setEnabled(enable_pdf_program) self.program_path_edit.setEnabled(enable_pdf_program)
pdf_program = Settings().value(self.settings_section + '/pdf_program') pdf_program = Settings().value(self.settings_section + '/pdf_program')
if pdf_program: if pdf_program:
self.program_path_edit.path = pdf_program self.program_path_edit.path = str_to_path(pdf_program)
def save(self): def save(self):
""" """
@ -192,7 +193,7 @@ class PresentationTab(SettingsTab):
Settings().setValue(setting_key, self.ppt_window_check_box.checkState()) Settings().setValue(setting_key, self.ppt_window_check_box.checkState())
changed = True changed = True
# Save pdf-settings # Save pdf-settings
pdf_program = self.program_path_edit.path pdf_program = path_to_str(self.program_path_edit.path)
enable_pdf_program = self.pdf_program_check_box.checkState() enable_pdf_program = self.pdf_program_check_box.checkState()
# If the given program is blank disable using the program # If the given program is blank disable using the program
if pdf_program == '': if pdf_program == '':
@ -219,12 +220,13 @@ class PresentationTab(SettingsTab):
checkbox.setEnabled(controller.is_available()) checkbox.setEnabled(controller.is_available())
self.set_controller_text(checkbox, controller) self.set_controller_text(checkbox, controller)
def on_program_path_edit_path_changed(self, filename): def on_program_path_edit_path_changed(self, new_path):
""" """
Select the mudraw or ghostscript binary that should be used. Select the mudraw or ghostscript binary that should be used.
""" """
if filename: new_path = path_to_str(new_path)
if not PdfController.process_check_binary(filename): if new_path:
if not PdfController.process_check_binary(new_path):
critical_error_message_box(UiStrings().Error, critical_error_message_box(UiStrings().Error,
translate('PresentationPlugin.PresentationTab', translate('PresentationPlugin.PresentationTab',
'The program is not ghostscript or mudraw which is required.')) 'The program is not ghostscript or mudraw which is required.'))

View File

@ -28,12 +28,15 @@ import logging
import re import re
import os import os
import shutil import shutil
from pathlib import Path
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStrings, check_directory_exists, translate from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStrings, check_directory_exists, translate
from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list from openlp.core.common.path import path_to_str
from openlp.core.lib import PluginStatus, MediaType, create_separated_list
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.common.languagemanager import get_natural_key from openlp.core.common.languagemanager import get_natural_key
from openlp.plugins.songs.lib import VerseType, clean_song from openlp.plugins.songs.lib import VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
@ -925,9 +928,10 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
Loads file(s) from the filesystem. Loads file(s) from the filesystem.
""" """
filters = '{text} (*)'.format(text=UiStrings().AllFiles) filters = '{text} (*)'.format(text=UiStrings().AllFiles)
file_names = FileDialog.getOpenFileNames(self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), '', file_paths, selected_filter = FileDialog.getOpenFileNames(
filters) self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), Path(), filters)
for filename in file_names: for file_path in file_paths:
filename = path_to_str(file_path)
item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1]) item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1])
item.setData(QtCore.Qt.UserRole, filename) item.setData(QtCore.Qt.UserRole, filename)
self.audio_list_widget.addItem(item) self.audio_list_widget.addItem(item)

View File

@ -29,8 +29,9 @@ import os
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from openlp.core.common import RegistryProperties, Settings, UiStrings, translate from openlp.core.common import RegistryProperties, Settings, UiStrings, translate
from openlp.core.lib import FileDialog from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
@ -237,10 +238,11 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
if filters: if filters:
filters += ';;' filters += ';;'
filters += '{text} (*)'.format(text=UiStrings().AllFiles) filters += '{text} (*)'.format(text=UiStrings().AllFiles)
file_names = FileDialog.getOpenFileNames( file_paths, selected_filter = FileDialog.getOpenFileNames(
self, title, self, title,
Settings().value(self.plugin.settings_section + '/last directory import'), filters) str_to_path(Settings().value(self.plugin.settings_section + '/last directory import')), filters)
if file_names: if file_paths:
file_names = [path_to_str(file_path) for file_path in file_paths]
listbox.addItems(file_names) listbox.addItems(file_names)
Settings().setValue(self.plugin.settings_section + '/last directory import', Settings().setValue(self.plugin.settings_section + '/last directory import',
os.path.split(str(file_names[0]))[0]) os.path.split(str(file_names[0]))[0])

View File

@ -27,6 +27,7 @@ from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import and_ from sqlalchemy.sql import and_
from openlp.core.common import RegistryProperties, Settings, check_directory_exists, translate from openlp.core.common import RegistryProperties, Settings, check_directory_exists, translate
from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.songusage.lib.db import SongUsageItem from openlp.plugins.songusage.lib.db import SongUsageItem
from .songusagedetaildialog import Ui_SongUsageDetailDialog from .songusagedetaildialog import Ui_SongUsageDetailDialog
@ -55,20 +56,21 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
""" """
self.from_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/from date')) self.from_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/from date'))
self.to_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/to date')) self.to_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/to date'))
self.report_path_edit.path = Settings().value(self.plugin.settings_section + '/last directory export') self.report_path_edit.path = str_to_path(
Settings().value(self.plugin.settings_section + '/last directory export'))
def on_report_path_edit_path_changed(self, file_path): def on_report_path_edit_path_changed(self, file_path):
""" """
Triggered when the Directory selection button is clicked Triggered when the Directory selection button is clicked
""" """
Settings().setValue(self.plugin.settings_section + '/last directory export', file_path) Settings().setValue(self.plugin.settings_section + '/last directory export', path_to_str(file_path))
def accept(self): def accept(self):
""" """
Ok was triggered so lets save the data and run the report Ok was triggered so lets save the data and run the report
""" """
log.debug('accept') log.debug('accept')
path = self.report_path_edit.path path = path_to_str(self.report_path_edit.path)
if not path: if not path:
self.main_window.error_message( self.main_window.error_message(
translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'), translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'),

View File

@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 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; version 2 of the License. #
# #
# 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, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.core.common.path package.
"""
import os
from pathlib import Path
from unittest import TestCase
from openlp.core.common.path import path_to_str, str_to_path
class TestPath(TestCase):
"""
Tests for the :mod:`openlp.core.common.path` module
"""
def test_path_to_str_type_error(self):
"""
Test that `path_to_str` raises a type error when called with an invalid type
"""
# GIVEN: The `path_to_str` function
# WHEN: Calling `path_to_str` with an invalid Type
# THEN: A TypeError should have been raised
with self.assertRaises(TypeError):
path_to_str(str())
def test_path_to_str_none(self):
"""
Test that `path_to_str` correctly converts the path parameter when passed with None
"""
# GIVEN: The `path_to_str` function
# WHEN: Calling the `path_to_str` function with None
result = path_to_str(None)
# THEN: `path_to_str` should return an empty string
self.assertEqual(result, '')
def test_path_to_str_path_object(self):
"""
Test that `path_to_str` correctly converts the path parameter when passed a Path object
"""
# GIVEN: The `path_to_str` function
# WHEN: Calling the `path_to_str` function with a Path object
result = path_to_str(Path('test/path'))
# THEN: `path_to_str` should return a string representation of the Path object
self.assertEqual(result, os.path.join('test', 'path'))
def test_str_to_path_type_error(self):
"""
Test that `str_to_path` raises a type error when called with an invalid type
"""
# GIVEN: The `str_to_path` function
# WHEN: Calling `str_to_path` with an invalid Type
# THEN: A TypeError should have been raised
with self.assertRaises(TypeError):
str_to_path(Path())
def test_str_to_path_empty_str(self):
"""
Test that `str_to_path` correctly converts the string parameter when passed with and empty string
"""
# GIVEN: The `str_to_path` function
# WHEN: Calling the `str_to_path` function with None
result = str_to_path('')
# THEN: `path_to_str` should return None
self.assertEqual(result, None)

View File

@ -20,12 +20,10 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
""" """
Package to test the openlp.core.lib.filedialog package. Package to test the openlp.core.ui.lib.filedialog package.
""" """
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, call, patch from unittest.mock import MagicMock, patch
from openlp.core.lib.filedialog import FileDialog
class TestFileDialog(TestCase): class TestFileDialog(TestCase):
@ -33,9 +31,9 @@ class TestFileDialog(TestCase):
Test the functions in the :mod:`filedialog` module. Test the functions in the :mod:`filedialog` module.
""" """
def setUp(self): def setUp(self):
self.os_patcher = patch('openlp.core.lib.filedialog.os') self.os_patcher = patch('openlp.core.ui.lib.filedialog.os')
self.qt_gui_patcher = patch('openlp.core.lib.filedialog.QtWidgets') self.qt_gui_patcher = patch('openlp.core.ui.lib.filedialog.QtWidgets')
self.ui_strings_patcher = patch('openlp.core.lib.filedialog.UiStrings') self.ui_strings_patcher = patch('openlp.core.ui.lib.filedialog.UiStrings')
self.mocked_os = self.os_patcher.start() self.mocked_os = self.os_patcher.start()
self.mocked_qt_gui = self.qt_gui_patcher.start() self.mocked_qt_gui = self.qt_gui_patcher.start()
self.mocked_ui_strings = self.ui_strings_patcher.start() self.mocked_ui_strings = self.ui_strings_patcher.start()
@ -45,52 +43,3 @@ class TestFileDialog(TestCase):
self.os_patcher.stop() self.os_patcher.stop()
self.qt_gui_patcher.stop() self.qt_gui_patcher.stop()
self.ui_strings_patcher.stop() self.ui_strings_patcher.stop()
def test_get_open_file_names_canceled(self):
"""
Test that FileDialog.getOpenFileNames() returns and empty QStringList when QFileDialog is canceled
(returns an empty QStringList)
"""
self.mocked_os.reset_mock()
# GIVEN: An empty QStringList as a return value from QFileDialog.getOpenFileNames
self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = ([], [])
# WHEN: FileDialog.getOpenFileNames is called
result = FileDialog.getOpenFileNames(self.mocked_parent)
# THEN: The returned value should be an empty QStringList and os.path.exists should not have been called
assert not self.mocked_os.path.exists.called
self.assertEqual(result, [],
'FileDialog.getOpenFileNames should return and empty list when QFileDialog.getOpenFileNames '
'is canceled')
def test_returned_file_list(self):
"""
Test that FileDialog.getOpenFileNames handles a list of files properly when QFileList.getOpenFileNames
returns a good file name, a url encoded file name and a non-existing file
"""
self.mocked_os.rest_mock()
self.mocked_qt_gui.reset_mock()
# GIVEN: A List of known values as a return value from QFileDialog.getOpenFileNames and a list of valid file
# names.
self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = ([
'/Valid File', '/url%20encoded%20file%20%231', '/non-existing'], [])
self.mocked_os.path.exists.side_effect = lambda file_name: file_name in [
'/Valid File', '/url encoded file #1']
self.mocked_ui_strings().FileNotFound = 'File Not Found'
self.mocked_ui_strings().FileNotFoundMessage = 'File {name} not found.\nPlease try selecting it individually.'
# WHEN: FileDialog.getOpenFileNames is called
result = FileDialog.getOpenFileNames(self.mocked_parent)
# THEN: os.path.exists should have been called with known args. QmessageBox.information should have been
# called. The returned result should correlate with the input.
call_list = [call('/Valid File'), call('/url%20encoded%20file%20%231'), call('/url encoded file #1'),
call('/non-existing'), call('/non-existing')]
self.mocked_os.path.exists.assert_has_calls(call_list)
self.mocked_qt_gui.QMessageBox.information.assert_called_with(
self.mocked_parent, 'File Not Found',
'File /non-existing not found.\nPlease try selecting it individually.')
self.assertEqual(result, ['/Valid File', '/url encoded file #1'], 'The returned file list is incorrect')

View File

@ -29,10 +29,9 @@ from unittest.mock import MagicMock, patch
from PyQt5 import QtCore, QtGui from PyQt5 import QtCore, QtGui
from openlp.core.lib import FormattingTags, expand_chords_for_printing from openlp.core.lib import FormattingTags, build_icon, check_item_selected, clean_tags, compare_chord_lyric, \
from openlp.core.lib import build_icon, check_item_selected, clean_tags, create_thumb, create_separated_list, \ create_separated_list, create_thumb, expand_chords, expand_chords_for_printing, expand_tags, find_formatting_tags, \
expand_tags, get_text_file_string, image_to_byte, resize_image, str_to_bool, validate_thumb, expand_chords, \ get_text_file_string, image_to_byte, replace_params, resize_image, str_to_bool, validate_thumb
compare_chord_lyric, find_formatting_tags
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources')) TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
@ -652,6 +651,38 @@ class TestLib(TestCase):
mocked_os.stat.assert_any_call(thumb_path) mocked_os.stat.assert_any_call(thumb_path)
assert result is False, 'The result should be False' assert result is False, 'The result should be False'
def test_replace_params_no_params(self):
"""
Test replace_params when called with and empty tuple instead of parameters to replace
"""
# GIVEN: Some test data
test_args = (1, 2)
test_kwargs = {'arg3': 3, 'arg4': 4}
test_params = tuple()
# WHEN: Calling replace_params
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
# THEN: The positional and keyword args should not have changed
self.assertEqual(test_args, result_args)
self.assertEqual(test_kwargs, result_kwargs)
def test_replace_params_params(self):
"""
Test replace_params when given a positional and a keyword argument to change
"""
# GIVEN: Some test data
test_args = (1, 2)
test_kwargs = {'arg3': 3, 'arg4': 4}
test_params = ((1, 'arg2', str), (2, 'arg3', str))
# WHEN: Calling replace_params
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
# THEN: The positional and keyword args should have have changed
self.assertEqual(result_args, (1, '2'))
self.assertEqual(result_kwargs, {'arg3': '3', 'arg4': 4})
def test_resize_thumb(self): def test_resize_thumb(self):
""" """
Test the resize_thumb() function Test the resize_thumb() function

View File

@ -22,6 +22,7 @@
""" """
Package to test the openlp.core.ui.themeform package. Package to test the openlp.core.ui.themeform package.
""" """
from pathlib import Path
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@ -45,7 +46,7 @@ class TestThemeManager(TestCase):
self.instance.theme = MagicMock() self.instance.theme = MagicMock()
# WHEN: `on_image_path_edit_path_changed` is clicked # WHEN: `on_image_path_edit_path_changed` is clicked
self.instance.on_image_path_edit_path_changed('/new/pat.h') self.instance.on_image_path_edit_path_changed(Path('/', 'new', 'pat.h'))
# THEN: The theme background file should be set and `set_background_page_values` should have been called # THEN: The theme background file should be set and `set_background_page_values` should have been called
self.assertEqual(self.instance.theme.background_filename, '/new/pat.h') self.assertEqual(self.instance.theme.background_filename, '/new/pat.h')

View File

@ -0,0 +1,188 @@
import os
from unittest import TestCase
from unittest.mock import patch
from pathlib import Path
from PyQt5 import QtWidgets
from openlp.core.ui.lib.filedialog import FileDialog
class TestFileDialogPatches(TestCase):
"""
Tests for the :mod:`openlp.core.ui.lib.filedialogpatches` module
"""
def test_file_dialog(self):
"""
Test that the :class:`FileDialog` instantiates correctly
"""
# GIVEN: The FileDialog class
# WHEN: Creating an instance
instance = FileDialog()
# THEN: The instance should be an instance of QFileDialog
self.assertIsInstance(instance, QtWidgets.QFileDialog)
def test_get_existing_directory_user_abort(self):
"""
Test that `getExistingDirectory` handles the case when the user cancels the dialog
"""
# GIVEN: FileDialog with a mocked QDialog.getExistingDirectory method
# WHEN: Calling FileDialog.getExistingDirectory and the user cancels the dialog returns a empty string
with patch('PyQt5.QtWidgets.QFileDialog.getExistingDirectory', return_value=''):
result = FileDialog.getExistingDirectory()
# THEN: The result should be None
self.assertEqual(result, None)
def test_get_existing_directory_user_accepts(self):
"""
Test that `getExistingDirectory` handles the case when the user accepts the dialog
"""
# GIVEN: FileDialog with a mocked QDialog.getExistingDirectory method
# WHEN: Calling FileDialog.getExistingDirectory, the user chooses a file and accepts the dialog (it returns a
# string pointing to the directory)
with patch('PyQt5.QtWidgets.QFileDialog.getExistingDirectory', return_value=os.path.join('test', 'dir')):
result = FileDialog.getExistingDirectory()
# THEN: getExistingDirectory() should return a Path object pointing to the chosen file
self.assertEqual(result, Path('test', 'dir'))
def test_get_existing_directory_param_order(self):
"""
Test that `getExistingDirectory` passes the parameters to `QFileDialog.getExistingDirectory` in the correct
order
"""
# GIVEN: FileDialog
with patch('openlp.core.ui.lib.filedialog.QtWidgets.QFileDialog.getExistingDirectory', return_value='') \
as mocked_get_existing_directory:
# WHEN: Calling the getExistingDirectory method with all parameters set
FileDialog.getExistingDirectory('Parent', 'Caption', Path('test', 'dir'), 'Options')
# THEN: The `QFileDialog.getExistingDirectory` should have been called with the parameters in the correct
# order
mocked_get_existing_directory.assert_called_once_with('Parent', 'Caption', os.path.join('test', 'dir'),
'Options')
def test_get_open_file_name_user_abort(self):
"""
Test that `getOpenFileName` handles the case when the user cancels the dialog
"""
# GIVEN: FileDialog with a mocked QDialog.getOpenFileName method
# WHEN: Calling FileDialog.getOpenFileName and the user cancels the dialog (it returns a tuple with the first
# value set as an empty string)
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')):
result = FileDialog.getOpenFileName()
# THEN: First value should be None
self.assertEqual(result[0], None)
def test_get_open_file_name_user_accepts(self):
"""
Test that `getOpenFileName` handles the case when the user accepts the dialog
"""
# GIVEN: FileDialog with a mocked QDialog.getOpenFileName method
# WHEN: Calling FileDialog.getOpenFileName, the user chooses a file and accepts the dialog (it returns a
# tuple with the first value set as an string pointing to the file)
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileName',
return_value=(os.path.join('test', 'chosen.file'), '')):
result = FileDialog.getOpenFileName()
# THEN: getOpenFileName() should return a tuple with the first value set to a Path object pointing to the
# chosen file
self.assertEqual(result[0], Path('test', 'chosen.file'))
def test_get_open_file_name_selected_filter(self):
"""
Test that `getOpenFileName` does not modify the selectedFilter as returned by `QFileDialog.getOpenFileName`
"""
# GIVEN: FileDialog with a mocked QDialog.get_save_file_name method
# WHEN: Calling FileDialog.getOpenFileName, and `QFileDialog.getOpenFileName` returns a known `selectedFilter`
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileName', return_value=('', 'selected filter')):
result = FileDialog.getOpenFileName()
# THEN: getOpenFileName() should return a tuple with the second value set to a the selected filter
self.assertEqual(result[1], 'selected filter')
def test_get_open_file_names_user_abort(self):
"""
Test that `getOpenFileNames` handles the case when the user cancels the dialog
"""
# GIVEN: FileDialog with a mocked QDialog.getOpenFileNames method
# WHEN: Calling FileDialog.getOpenFileNames and the user cancels the dialog (it returns a tuple with the first
# value set as an empty list)
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileNames', return_value=([], '')):
result = FileDialog.getOpenFileNames()
# THEN: First value should be an empty list
self.assertEqual(result[0], [])
def test_get_open_file_names_user_accepts(self):
"""
Test that `getOpenFileNames` handles the case when the user accepts the dialog
"""
# GIVEN: FileDialog with a mocked QDialog.getOpenFileNames method
# WHEN: Calling FileDialog.getOpenFileNames, the user chooses some files and accepts the dialog (it returns a
# tuple with the first value set as a list of strings pointing to the file)
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileNames',
return_value=([os.path.join('test', 'chosen.file1'), os.path.join('test', 'chosen.file2')], '')):
result = FileDialog.getOpenFileNames()
# THEN: getOpenFileNames() should return a tuple with the first value set to a list of Path objects pointing
# to the chosen file
self.assertEqual(result[0], [Path('test', 'chosen.file1'), Path('test', 'chosen.file2')])
def test_get_open_file_names_selected_filter(self):
"""
Test that `getOpenFileNames` does not modify the selectedFilter as returned by `QFileDialog.getOpenFileNames`
"""
# GIVEN: FileDialog with a mocked QDialog.getOpenFileNames method
# WHEN: Calling FileDialog.getOpenFileNames, and `QFileDialog.getOpenFileNames` returns a known
# `selectedFilter`
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileNames', return_value=([], 'selected filter')):
result = FileDialog.getOpenFileNames()
# THEN: getOpenFileNames() should return a tuple with the second value set to a the selected filter
self.assertEqual(result[1], 'selected filter')
def test_get_save_file_name_user_abort(self):
"""
Test that `getSaveFileName` handles the case when the user cancels the dialog
"""
# GIVEN: FileDialog with a mocked QDialog.get_save_file_name method
# WHEN: Calling FileDialog.getSaveFileName and the user cancels the dialog (it returns a tuple with the first
# value set as an empty string)
with patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName', return_value=('', '')):
result = FileDialog.getSaveFileName()
# THEN: First value should be None
self.assertEqual(result[0], None)
def test_get_save_file_name_user_accepts(self):
"""
Test that `getSaveFileName` handles the case when the user accepts the dialog
"""
# GIVEN: FileDialog with a mocked QDialog.getSaveFileName method
# WHEN: Calling FileDialog.getSaveFileName, the user chooses a file and accepts the dialog (it returns a
# tuple with the first value set as an string pointing to the file)
with patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName',
return_value=(os.path.join('test', 'chosen.file'), '')):
result = FileDialog.getSaveFileName()
# THEN: getSaveFileName() should return a tuple with the first value set to a Path object pointing to the
# chosen file
self.assertEqual(result[0], Path('test', 'chosen.file'))
def test_get_save_file_name_selected_filter(self):
"""
Test that `getSaveFileName` does not modify the selectedFilter as returned by `QFileDialog.getSaveFileName`
"""
# GIVEN: FileDialog with a mocked QDialog.get_save_file_name method
# WHEN: Calling FileDialog.getSaveFileName, and `QFileDialog.getSaveFileName` returns a known `selectedFilter`
with patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName', return_value=('', 'selected filter')):
result = FileDialog.getSaveFileName()
# THEN: getSaveFileName() should return a tuple with the second value set to a the selected filter
self.assertEqual(result[1], 'selected filter')

View File

@ -22,12 +22,13 @@
""" """
This module contains tests for the openlp.core.ui.lib.pathedit module This module contains tests for the openlp.core.ui.lib.pathedit module
""" """
import os
from pathlib import Path
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, PropertyMock, patch
from PyQt5 import QtWidgets
from openlp.core.ui.lib import PathEdit, PathType from openlp.core.ui.lib import PathEdit, PathType
from unittest.mock import MagicMock, PropertyMock, patch from openlp.core.ui.lib.filedialog import FileDialog
class TestPathEdit(TestCase): class TestPathEdit(TestCase):
@ -43,11 +44,11 @@ class TestPathEdit(TestCase):
Test the `path` property getter. Test the `path` property getter.
""" """
# GIVEN: An instance of PathEdit with the `_path` instance variable set # GIVEN: An instance of PathEdit with the `_path` instance variable set
self.widget._path = 'getter/test/pat.h' self.widget._path = Path('getter', 'test', 'pat.h')
# WHEN: Reading the `path` property # WHEN: Reading the `path` property
# THEN: The value that we set should be returned # THEN: The value that we set should be returned
self.assertEqual(self.widget.path, 'getter/test/pat.h') self.assertEqual(self.widget.path, Path('getter', 'test', 'pat.h'))
def test_path_setter(self): def test_path_setter(self):
""" """
@ -57,13 +58,13 @@ class TestPathEdit(TestCase):
self.widget.line_edit = MagicMock() self.widget.line_edit = MagicMock()
# WHEN: Writing to the `path` property # WHEN: Writing to the `path` property
self.widget.path = 'setter/test/pat.h' self.widget.path = Path('setter', 'test', 'pat.h')
# THEN: The `_path` instance variable should be set with the test data. The `line_edit` text and tooltip # THEN: The `_path` instance variable should be set with the test data. The `line_edit` text and tooltip
# should have also been set. # should have also been set.
self.assertEqual(self.widget._path, 'setter/test/pat.h') self.assertEqual(self.widget._path, Path('setter', 'test', 'pat.h'))
self.widget.line_edit.setToolTip.assert_called_once_with('setter/test/pat.h') self.widget.line_edit.setToolTip.assert_called_once_with(os.path.join('setter', 'test', 'pat.h'))
self.widget.line_edit.setText.assert_called_once_with('setter/test/pat.h') self.widget.line_edit.setText.assert_called_once_with(os.path.join('setter', 'test', 'pat.h'))
def test_path_type_getter(self): def test_path_type_getter(self):
""" """
@ -125,22 +126,20 @@ class TestPathEdit(TestCase):
""" """
# GIVEN: An instance of PathEdit with the `path_type` set to `Directories` and a mocked # GIVEN: An instance of PathEdit with the `path_type` set to `Directories` and a mocked
# QFileDialog.getExistingDirectory # QFileDialog.getExistingDirectory
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory', return_value='') as \ with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory', return_value=None) as \
mocked_get_existing_directory, \ mocked_get_existing_directory, \
patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName') as \ patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName') as mocked_get_open_file_name:
mocked_get_open_file_name, \
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
self.widget._path_type = PathType.Directories self.widget._path_type = PathType.Directories
self.widget._path = 'test/path/' self.widget._path = Path('test', 'path')
# WHEN: Calling on_browse_button_clicked # WHEN: Calling on_browse_button_clicked
self.widget.on_browse_button_clicked() self.widget.on_browse_button_clicked()
# THEN: The FileDialog.getExistingDirectory should have been called with the default caption # THEN: The FileDialog.getExistingDirectory should have been called with the default caption
mocked_get_existing_directory.assert_called_once_with(self.widget, 'Select Directory', 'test/path/', mocked_get_existing_directory.assert_called_once_with(self.widget, 'Select Directory',
QtWidgets.QFileDialog.ShowDirsOnly) Path('test', 'path'),
FileDialog.ShowDirsOnly)
self.assertFalse(mocked_get_open_file_name.called) self.assertFalse(mocked_get_open_file_name.called)
self.assertFalse(mocked_normpath.called)
def test_on_browse_button_clicked_directory_custom_caption(self): def test_on_browse_button_clicked_directory_custom_caption(self):
""" """
@ -149,45 +148,40 @@ class TestPathEdit(TestCase):
""" """
# GIVEN: An instance of PathEdit with the `path_type` set to `Directories` and a mocked # GIVEN: An instance of PathEdit with the `path_type` set to `Directories` and a mocked
# QFileDialog.getExistingDirectory with `default_caption` set. # QFileDialog.getExistingDirectory with `default_caption` set.
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory', return_value='') as \ with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory', return_value=None) as \
mocked_get_existing_directory, \ mocked_get_existing_directory, \
patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName') as \ patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName') as mocked_get_open_file_name:
mocked_get_open_file_name, \
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
self.widget._path_type = PathType.Directories self.widget._path_type = PathType.Directories
self.widget._path = 'test/path/' self.widget._path = Path('test', 'path')
self.widget.dialog_caption = 'Directory Caption' self.widget.dialog_caption = 'Directory Caption'
# WHEN: Calling on_browse_button_clicked # WHEN: Calling on_browse_button_clicked
self.widget.on_browse_button_clicked() self.widget.on_browse_button_clicked()
# THEN: The FileDialog.getExistingDirectory should have been called with the custom caption # THEN: The FileDialog.getExistingDirectory should have been called with the custom caption
mocked_get_existing_directory.assert_called_once_with(self.widget, 'Directory Caption', 'test/path/', mocked_get_existing_directory.assert_called_once_with(self.widget, 'Directory Caption',
QtWidgets.QFileDialog.ShowDirsOnly) Path('test', 'path'),
FileDialog.ShowDirsOnly)
self.assertFalse(mocked_get_open_file_name.called) self.assertFalse(mocked_get_open_file_name.called)
self.assertFalse(mocked_normpath.called)
def test_on_browse_button_clicked_file(self): def test_on_browse_button_clicked_file(self):
""" """
Test the `browse_button` `clicked` handler on_browse_button_clicked when the `path_type` is set to Files. Test the `browse_button` `clicked` handler on_browse_button_clicked when the `path_type` is set to Files.
""" """
# GIVEN: An instance of PathEdit with the `path_type` set to `Files` and a mocked QFileDialog.getOpenFileName # GIVEN: An instance of PathEdit with the `path_type` set to `Files` and a mocked QFileDialog.getOpenFileName
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory') as \ with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory') as mocked_get_existing_directory, \
mocked_get_existing_directory, \ patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \
patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')) as \ mocked_get_open_file_name:
mocked_get_open_file_name, \
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
self.widget._path_type = PathType.Files self.widget._path_type = PathType.Files
self.widget._path = 'test/pat.h' self.widget._path = Path('test', 'pat.h')
# WHEN: Calling on_browse_button_clicked # WHEN: Calling on_browse_button_clicked
self.widget.on_browse_button_clicked() self.widget.on_browse_button_clicked()
# THEN: The FileDialog.getOpenFileName should have been called with the default caption # THEN: The FileDialog.getOpenFileName should have been called with the default caption
mocked_get_open_file_name.assert_called_once_with(self.widget, 'Select File', 'test/pat.h', mocked_get_open_file_name.assert_called_once_with(self.widget, 'Select File', Path('test', 'pat.h'),
self.widget.filters) self.widget.filters)
self.assertFalse(mocked_get_existing_directory.called) self.assertFalse(mocked_get_existing_directory.called)
self.assertFalse(mocked_normpath.called)
def test_on_browse_button_clicked_file_custom_caption(self): def test_on_browse_button_clicked_file_custom_caption(self):
""" """
@ -196,23 +190,20 @@ class TestPathEdit(TestCase):
""" """
# GIVEN: An instance of PathEdit with the `path_type` set to `Files` and a mocked QFileDialog.getOpenFileName # GIVEN: An instance of PathEdit with the `path_type` set to `Files` and a mocked QFileDialog.getOpenFileName
# with `default_caption` set. # with `default_caption` set.
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory') as \ with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory') as mocked_get_existing_directory, \
mocked_get_existing_directory, \ patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \
patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')) as \ mocked_get_open_file_name:
mocked_get_open_file_name, \
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
self.widget._path_type = PathType.Files self.widget._path_type = PathType.Files
self.widget._path = 'test/pat.h' self.widget._path = Path('test', 'pat.h')
self.widget.dialog_caption = 'File Caption' self.widget.dialog_caption = 'File Caption'
# WHEN: Calling on_browse_button_clicked # WHEN: Calling on_browse_button_clicked
self.widget.on_browse_button_clicked() self.widget.on_browse_button_clicked()
# THEN: The FileDialog.getOpenFileName should have been called with the custom caption # THEN: The FileDialog.getOpenFileName should have been called with the custom caption
mocked_get_open_file_name.assert_called_once_with(self.widget, 'File Caption', 'test/pat.h', mocked_get_open_file_name.assert_called_once_with(self.widget, 'File Caption', Path('test', 'pat.h'),
self.widget.filters) self.widget.filters)
self.assertFalse(mocked_get_existing_directory.called) self.assertFalse(mocked_get_existing_directory.called)
self.assertFalse(mocked_normpath.called)
def test_on_browse_button_clicked_user_cancels(self): def test_on_browse_button_clicked_user_cancels(self):
""" """
@ -221,16 +212,14 @@ class TestPathEdit(TestCase):
""" """
# GIVEN: An instance of PathEdit with a mocked QFileDialog.getOpenFileName which returns an empty str for the # GIVEN: An instance of PathEdit with a mocked QFileDialog.getOpenFileName which returns an empty str for the
# file path. # file path.
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')) as \ with patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \
mocked_get_open_file_name, \ mocked_get_open_file_name:
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
# WHEN: Calling on_browse_button_clicked # WHEN: Calling on_browse_button_clicked
self.widget.on_browse_button_clicked() self.widget.on_browse_button_clicked()
# THEN: normpath should not have been called # THEN: normpath should not have been called
self.assertTrue(mocked_get_open_file_name.called) self.assertTrue(mocked_get_open_file_name.called)
self.assertFalse(mocked_normpath.called)
def test_on_browse_button_clicked_user_accepts(self): def test_on_browse_button_clicked_user_accepts(self):
""" """
@ -239,9 +228,8 @@ class TestPathEdit(TestCase):
""" """
# GIVEN: An instance of PathEdit with a mocked QFileDialog.getOpenFileName which returns a str for the file # GIVEN: An instance of PathEdit with a mocked QFileDialog.getOpenFileName which returns a str for the file
# path. # path.
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName', with patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName',
return_value=('/test/pat.h', '')) as mocked_get_open_file_name, \ return_value=(Path('test', 'pat.h'), '')) as mocked_get_open_file_name, \
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath, \
patch.object(self.widget, 'on_new_path'): patch.object(self.widget, 'on_new_path'):
# WHEN: Calling on_browse_button_clicked # WHEN: Calling on_browse_button_clicked
@ -249,7 +237,6 @@ class TestPathEdit(TestCase):
# THEN: normpath and `on_new_path` should have been called # THEN: normpath and `on_new_path` should have been called
self.assertTrue(mocked_get_open_file_name.called) self.assertTrue(mocked_get_open_file_name.called)
mocked_normpath.assert_called_once_with('/test/pat.h')
self.assertTrue(self.widget.on_new_path.called) self.assertTrue(self.widget.on_new_path.called)
def test_on_revert_button_clicked(self): def test_on_revert_button_clicked(self):
@ -258,13 +245,13 @@ class TestPathEdit(TestCase):
""" """
# GIVEN: An instance of PathEdit with a mocked `on_new_path`, and the `default_path` set. # GIVEN: An instance of PathEdit with a mocked `on_new_path`, and the `default_path` set.
with patch.object(self.widget, 'on_new_path') as mocked_on_new_path: with patch.object(self.widget, 'on_new_path') as mocked_on_new_path:
self.widget.default_path = '/default/pat.h' self.widget.default_path = Path('default', 'pat.h')
# WHEN: Calling `on_revert_button_clicked` # WHEN: Calling `on_revert_button_clicked`
self.widget.on_revert_button_clicked() self.widget.on_revert_button_clicked()
# THEN: on_new_path should have been called with the default path # THEN: on_new_path should have been called with the default path
mocked_on_new_path.assert_called_once_with('/default/pat.h') mocked_on_new_path.assert_called_once_with(Path('default', 'pat.h'))
def test_on_line_edit_editing_finished(self): def test_on_line_edit_editing_finished(self):
""" """
@ -272,13 +259,13 @@ class TestPathEdit(TestCase):
""" """
# GIVEN: An instance of PathEdit with a mocked `line_edit` and `on_new_path`. # GIVEN: An instance of PathEdit with a mocked `line_edit` and `on_new_path`.
with patch.object(self.widget, 'on_new_path') as mocked_on_new_path: with patch.object(self.widget, 'on_new_path') as mocked_on_new_path:
self.widget.line_edit = MagicMock(**{'text.return_value': '/test/pat.h'}) self.widget.line_edit = MagicMock(**{'text.return_value': 'test/pat.h'})
# WHEN: Calling `on_line_edit_editing_finished` # WHEN: Calling `on_line_edit_editing_finished`
self.widget.on_line_edit_editing_finished() self.widget.on_line_edit_editing_finished()
# THEN: on_new_path should have been called with the path enetered in `line_edit` # THEN: on_new_path should have been called with the path enetered in `line_edit`
mocked_on_new_path.assert_called_once_with('/test/pat.h') mocked_on_new_path.assert_called_once_with(Path('test', 'pat.h'))
def test_on_new_path_no_change(self): def test_on_new_path_no_change(self):
""" """
@ -286,11 +273,11 @@ class TestPathEdit(TestCase):
""" """
# GIVEN: An instance of PathEdit with a test path and mocked `pathChanged` signal # GIVEN: An instance of PathEdit with a test path and mocked `pathChanged` signal
with patch('openlp.core.ui.lib.pathedit.PathEdit.path', new_callable=PropertyMock): with patch('openlp.core.ui.lib.pathedit.PathEdit.path', new_callable=PropertyMock):
self.widget._path = '/old/test/pat.h' self.widget._path = Path('/old', 'test', 'pat.h')
self.widget.pathChanged = MagicMock() self.widget.pathChanged = MagicMock()
# WHEN: Calling `on_new_path` with the same path as the existing path # WHEN: Calling `on_new_path` with the same path as the existing path
self.widget.on_new_path('/old/test/pat.h') self.widget.on_new_path(Path('/old', 'test', 'pat.h'))
# THEN: The `pathChanged` signal should not be emitted # THEN: The `pathChanged` signal should not be emitted
self.assertFalse(self.widget.pathChanged.emit.called) self.assertFalse(self.widget.pathChanged.emit.called)
@ -301,11 +288,11 @@ class TestPathEdit(TestCase):
""" """
# GIVEN: An instance of PathEdit with a test path and mocked `pathChanged` signal # GIVEN: An instance of PathEdit with a test path and mocked `pathChanged` signal
with patch('openlp.core.ui.lib.pathedit.PathEdit.path', new_callable=PropertyMock): with patch('openlp.core.ui.lib.pathedit.PathEdit.path', new_callable=PropertyMock):
self.widget._path = '/old/test/pat.h' self.widget._path = Path('/old', 'test', 'pat.h')
self.widget.pathChanged = MagicMock() self.widget.pathChanged = MagicMock()
# WHEN: Calling `on_new_path` with the a new path # WHEN: Calling `on_new_path` with the a new path
self.widget.on_new_path('/new/test/pat.h') self.widget.on_new_path(Path('/new', 'test', 'pat.h'))
# THEN: The `pathChanged` signal should be emitted # THEN: The `pathChanged` signal should be emitted
self.widget.pathChanged.emit.assert_called_once_with('/new/test/pat.h') self.widget.pathChanged.emit.assert_called_once_with(Path('/new', 'test', 'pat.h'))