From 57dd0897ae89a254382d24ecff579918b6204f3b Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 1 Nov 2019 15:56:34 +0000 Subject: [PATCH] Head --- .gitlab-ci.yml | 5 +- openlp/core/display/html/display.js | 74 ++++++++- openlp/core/display/html/reveal.js | 9 -- openlp/core/display/window.py | 14 +- openlp/core/lib/json/theme.json | 2 + openlp/core/lib/theme.py | 89 ++++++++++- openlp/core/ui/icons.py | 1 + openlp/core/ui/mainwindow.py | 8 +- openlp/core/ui/shortcutlistform.py | 27 ++-- openlp/core/ui/themeform.py | 47 ++++-- openlp/core/ui/thememanager.py | 6 +- openlp/core/ui/themeprogressform.py | 3 + openlp/core/ui/themewizard.py | 37 ++++- openlp/core/widgets/edits.py | 2 +- resources/forms/themewizard.ui | 63 +++++++- scripts/reveal-js.patch | 25 --- .../openlp_core/api/http/test_init.py | 17 +++ .../openlp_core/display/test_window.py | 76 ++++++++++ .../functional/openlp_core/lib/test_theme.py | 76 +++++++++- .../openlp_core/ui/test_slidecontroller.py | 19 ++- .../openlp_core/ui/test_splashscreen.py | 46 ++++++ .../openlp_core/ui/test_themeform.py | 4 +- .../openlp_core/ui/test_thememanager.py | 8 +- tests/js/test_display.js | 115 ++++++++++++-- .../openlp_core/ui/test_themeprogressform.py | 142 ++++++++++++++++++ 25 files changed, 797 insertions(+), 118 deletions(-) delete mode 100644 scripts/reveal-js.patch create mode 100644 tests/functional/openlp_core/display/test_window.py create mode 100644 tests/functional/openlp_core/ui/test_splashscreen.py create mode 100644 tests/openlp_core/ui/test_themeprogressform.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 58d647ebf..73294d97f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,17 +1,16 @@ stages: - - lint - test - deploy lint-python: - stage: lint + stage: test image: openlp/debian script: - sh scripts/generate_resources.sh - flake8 lint-javascript: - stage: lint + stage: test image: openlp/angular script: - yarn install diff --git a/openlp/core/display/html/display.js b/openlp/core/display/html/display.js index 1a55d8507..0eda27cfa 100644 --- a/openlp/core/display/html/display.js +++ b/openlp/core/display/html/display.js @@ -43,6 +43,26 @@ var VerticalAlign = { Bottom: 2 }; +/** + * Transition type enumeration + */ +var TransitionType = { + Fade: 0, + Slide: 1, + Convex: 2, + Concave: 3, + Zoom: 4 +}; + +/** + * Transition speed enumeration + */ +var TransitionSpeed = { + Normal: 0, + Fast: 1, + Slow: 2 +}; + /** * Audio state enumeration */ @@ -329,6 +349,7 @@ var Display = { _alertState: AlertState.NotDisplaying, _transitionState: TransitionState.NoTransition, _animationState: AnimationState.NoAnimation, + _doTransitions: false, _revealConfig: { margin: 0.0, minScale: 1.0, @@ -348,21 +369,25 @@ var Display = { /** * Start up reveal and do any other initialisation */ - init: function () { + init: function (doTransitions=false) { + Display._doTransitions = doTransitions; Reveal.initialize(Display._revealConfig); }, /** * Reinitialise Reveal */ reinit: function () { - Reveal.reinitialize(); + Reveal.sync(); + // Python expects to be on first page after reinit + Reveal.slide(0); }, /** * Set the transition type * @param {string} transitionType - Can be one of "none", "fade", "slide", "convex", "concave", "zoom" + * @param {string} transitionSpeed - Can be one of "default", "fast", "slow" */ - setTransition: function (transitionType) { - Reveal.configure({"transition": transitionType}); + setTransition: function (transitionType, transitionSpeed) { + Reveal.configure({"transition": transitionType, "transitionSpeed": transitionSpeed}); }, /** * Clear the current list of slides @@ -639,7 +664,6 @@ var Display = { Display.addTextSlide(slide.verse, slide.text, slide.footer, false); }); Display.reinit(); - Display.goToSlide(0); }, /** * Create the
that will contain text slides (vertical slides in react) @@ -869,6 +893,44 @@ var Display = { }, setTheme: function (theme) { Display._theme = theme; + // Set slide transitions + var new_transition_type = "none", + new_transition_speed = "default"; + if (!!theme.display_slide_transition && Display._doTransitions) { + switch (theme.display_slide_transition_type) { + case TransitionType.Fade: + new_transition_type = "fade"; + break; + case TransitionType.Slide: + new_transition_type = "slide"; + break; + case TransitionType.Convex: + new_transition_type = "convex"; + break; + case TransitionType.Concave: + new_transition_type = "concave"; + break; + case TransitionType.Zoom: + new_transition_type = "zoom"; + break; + default: + new_transition_type = "fade"; + } + switch (theme.display_slide_transition_speed) { + case TransitionSpeed.Normal: + new_transition_speed = "default"; + break; + case TransitionSpeed.Fast: + new_transition_speed = "fast"; + break; + case TransitionSpeed.Slow: + new_transition_speed = "slow"; + break; + default: + new_transition_speed = "default"; + } + } + Display.setTransition(new_transition_type, new_transition_speed); // Set the background var globalBackground = $("#global-background")[0]; var backgroundStyle = {}; @@ -986,7 +1048,7 @@ var Display = { default: mainStyle["justify-content"] = "center"; } - if (theme.hasOwnProperty('font_main_shadow_size')) { + if (theme.hasOwnProperty('font_main_shadow_size') && !!theme.font_main_shadow) { mainStyle["text-shadow"] = theme.font_main_shadow_color + " " + theme.font_main_shadow_size + "pt " + theme.font_main_shadow_size + "pt"; } diff --git a/openlp/core/display/html/reveal.js b/openlp/core/display/html/reveal.js index 4ca322832..5c026db71 100644 --- a/openlp/core/display/html/reveal.js +++ b/openlp/core/display/html/reveal.js @@ -441,14 +441,6 @@ } - /** - * Restarts up the presentation if the client is capable. - */ - function reinitialize() { - initialized = false; - initialize(config); - } - /** * Inspect the client to see what it's capable of, this * should only happens once per runtime. @@ -5814,7 +5806,6 @@ VERSION: VERSION, initialize: initialize, - reinitialize: reinitialize, configure: configure, sync: sync, diff --git a/openlp/core/display/window.py b/openlp/core/display/window.py index f5a53b57d..52c1a8fc3 100644 --- a/openlp/core/display/window.py +++ b/openlp/core/display/window.py @@ -109,13 +109,17 @@ class DisplayWindow(QtWidgets.QWidget): Create the display window """ super(DisplayWindow, self).__init__(parent) + # Gather all flags for the display window + flags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint + if Settings().value('advanced/x11 bypass wm'): + flags |= QtCore.Qt.X11BypassWindowManagerHint # Need to import this inline to get around a QtWebEngine issue from openlp.core.display.webengine import WebEngineView self._is_initialised = False self._can_show_startup_screen = can_show_startup_screen self._fbo = None self.setWindowTitle(translate('OpenLP.DisplayWindow', 'Display Window')) - self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint) + self.setWindowFlags(flags) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.setAutoFillBackground(True) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) @@ -198,13 +202,13 @@ class DisplayWindow(QtWidgets.QWidget): """ Add stuff after page initialisation """ - self.run_javascript('Display.init();') + js_is_display = str(self.is_display).lower() + self.run_javascript('Display.init({do_transitions});'.format(do_transitions=js_is_display)) self._is_initialised = True - if self._can_show_startup_screen: - self.set_startup_screen() - # Make sure the scale is set if it was attempted set before init if self.scale != 1: self.set_scale(self.scale) + if self._can_show_startup_screen: + self.set_startup_screen() def run_javascript(self, script, is_sync=False): """ diff --git a/openlp/core/lib/json/theme.json b/openlp/core/lib/json/theme.json index b23593c6b..1a6018535 100644 --- a/openlp/core/lib/json/theme.json +++ b/openlp/core/lib/json/theme.json @@ -11,6 +11,8 @@ "display" :{ "horizontal_align": 0, "slide_transition": false, + "slide_transition_type": 0, + "slide_transition_speed": 0, "vertical_align": 0 }, "font": { diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index bc3cbce5c..3c3ec1e24 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -127,6 +127,82 @@ class BackgroundGradientType(object): return BackgroundGradientType.LeftBottom +class TransitionType(object): + """ + Type enumeration for transition types. + """ + Fade = 0 + Slide = 1 + Convex = 2 + Concave = 3 + Zoom = 4 + + @staticmethod + def to_string(transition_type): + """ + Return a string representation of a transition type. + """ + if transition_type == TransitionType.Fade: + return 'fade' + elif transition_type == TransitionType.Slide: + return 'slide' + elif transition_type == TransitionType.Convex: + return 'convex' + elif transition_type == TransitionType.Concave: + return 'concave' + elif transition_type == TransitionType.Zoom: + return 'zoom' + + @staticmethod + def from_string(type_string): + """ + Return a transition type for the given string. + """ + if type_string == 'fade': + return TransitionType.Fade + elif type_string == 'slide': + return TransitionType.Slide + elif type_string == 'convex': + return TransitionType.Convex + elif type_string == 'concave': + return TransitionType.Concave + elif type_string == 'zoom': + return TransitionType.Zoom + + +class TransitionSpeed(object): + """ + Type enumeration for transition types. + """ + Normal = 0 + Fast = 1 + Slow = 2 + + @staticmethod + def to_string(transition_speed): + """ + Return a string representation of a transition type. + """ + if transition_speed == TransitionSpeed.Normal: + return 'normal' + elif transition_speed == TransitionSpeed.Fast: + return 'fast' + elif transition_speed == TransitionSpeed.Slow: + return 'slow' + + @staticmethod + def from_string(type_string): + """ + Return a transition type for the given string. + """ + if type_string == 'normal': + return TransitionSpeed.Normal + if type_string == 'fast': + return TransitionSpeed.Fast + elif type_string == 'slow': + return TransitionSpeed.Slow + + class HorizontalType(object): """ Type enumeration for horizontal alignment. @@ -153,7 +229,7 @@ class VerticalType(object): BOOLEAN_LIST = ['bold', 'italics', 'override', 'outline', 'shadow', 'slide_transition'] INTEGER_LIST = ['size', 'line_adjustment', 'x', 'height', 'y', 'width', 'shadow_size', 'outline_size', - 'horizontal_align', 'vertical_align', 'wrap_style'] + 'horizontal_align', 'vertical_align', 'wrap_style', 'slide_transition_type', 'slide_transition_speed'] class Theme(object): @@ -204,12 +280,21 @@ class Theme(object): Set the header and footer size into the current primary screen. 10 px on each side is removed to allow for a border. """ + self.set_default_header() + self.set_default_footer() + + def set_default_header(self): current_screen_geometry = ScreenList().current.display_geometry + self.font_main_x = 10 self.font_main_y = 0 self.font_main_width = current_screen_geometry.width() - 20 self.font_main_height = current_screen_geometry.height() * 9 / 10 - self.font_footer_width = current_screen_geometry.width() - 20 + + def set_default_footer(self): + current_screen_geometry = ScreenList().current.display_geometry + self.font_footer_x = 10 self.font_footer_y = current_screen_geometry.height() * 9 / 10 + self.font_footer_width = current_screen_geometry.width() - 20 self.font_footer_height = current_screen_geometry.height() / 10 def load_theme(self, theme, theme_path=None): diff --git a/openlp/core/ui/icons.py b/openlp/core/ui/icons.py index bd84dab8f..6f8b3e4bd 100644 --- a/openlp/core/ui/icons.py +++ b/openlp/core/ui/icons.py @@ -62,6 +62,7 @@ class UiIcons(metaclass=Singleton): 'authentication': {'icon': 'fa.exclamation-triangle', 'attr': 'red'}, 'address': {'icon': 'fa.book'}, 'back': {'icon': 'fa.step-backward'}, + 'backspace': {'icon': 'mdi.backspace-outline'}, 'bible': {'icon': 'fa.book'}, 'blank': {'icon': 'fa.times-circle'}, 'blank_theme': {'icon': 'fa.file-image-o'}, diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 81830f287..16ca79072 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -670,6 +670,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert self.application.process_events() plugin.first_time() self.application.process_events() + # Load the themes from files + self.theme_manager_contents.load_first_time_themes() + # Update the theme widget + self.theme_manager_contents.load_themes() temp_path = Path(gettempdir(), 'openlp') shutil.rmtree(temp_path, True) @@ -714,10 +718,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert self.active_plugin.toggle_status(PluginStatus.Inactive) # Set global theme and Registry().execute('theme_update_global') - # Load the themes from files - self.theme_manager_contents.load_first_time_themes() - # Update the theme widget - self.theme_manager_contents.load_themes() # Check if any Bibles downloaded. If there are, they will be processed. Registry().execute('bibles_load_list') self.application.set_normal_cursor() diff --git a/openlp/core/ui/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py index 9b00e459b..168d135ed 100644 --- a/openlp/core/ui/shortcutlistform.py +++ b/openlp/core/ui/shortcutlistform.py @@ -323,16 +323,23 @@ class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryPropert if not toggled: return action = self._current_item_action() - shortcuts = self._action_shortcuts(action) - self.refresh_shortcut_list() - primary_button_text = '' - alternate_button_text = '' - if shortcuts: - primary_button_text = self.get_shortcut_string(shortcuts[0], for_display=True) - if len(shortcuts) == 2: - alternate_button_text = self.get_shortcut_string(shortcuts[1], for_display=True) - self.primary_push_button.setText(primary_button_text) - self.alternate_push_button.setText(alternate_button_text) + if action is None: + QtWidgets.QMessageBox.information(self, translate('OpenLP.ShortcutListForm', 'Select an Action'), + translate('OpenLP.ShortcutListForm', 'Select an action and click one ' + 'of the buttons below to start ' + 'capturing a new primary or alternate shortcut, respectively.')) + + else: + shortcuts = self._action_shortcuts(action) + self.refresh_shortcut_list() + primary_button_text = '' + alternate_button_text = '' + if shortcuts: + primary_button_text = self.get_shortcut_string(shortcuts[0], for_display=True) + if len(shortcuts) == 2: + alternate_button_text = self.get_shortcut_string(shortcuts[1], for_display=True) + self.primary_push_button.setText(primary_button_text) + self.alternate_push_button.setText(alternate_button_text) def save(self): """ diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index 040aad980..c769c64d6 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -98,6 +98,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): self.main_font_combo_box.activated.connect(self.calculate_lines) self.footer_font_combo_box.activated.connect(self.update_theme) self.footer_size_spin_box.valueChanged.connect(self.update_theme) + self.transitions_check_box.stateChanged.connect(self.on_transitions_check_box_state_changed) def set_defaults(self): """ @@ -145,6 +146,8 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): self.background_page.registerField('horizontal', self.horizontal_combo_box) self.background_page.registerField('vertical', self.vertical_combo_box) self.background_page.registerField('slide_transition', self.transitions_check_box) + self.background_page.registerField('slide_transition_type', self.transition_combo_box) + self.background_page.registerField('slide_transition_speed', self.transition_speed_combo_box) self.background_page.registerField('name', self.theme_name_edit) def calculate_lines(self): @@ -251,10 +254,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): Change state as Shadow check box changed """ if self.update_theme_allowed: - if state == QtCore.Qt.Checked: - self.theme.font_main_shadow = True - else: - self.theme.font_main_shadow = False + self.theme.font_main_shadow = state == QtCore.Qt.Checked self.shadow_color_button.setEnabled(self.theme.font_main_shadow) self.shadow_size_spin_box.setEnabled(self.theme.font_main_shadow) self.calculate_lines() @@ -275,6 +275,16 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): if self.update_theme_allowed: self.theme.font_footer_override = (value != QtCore.Qt.Checked) + def on_transitions_check_box_state_changed(self, state): + """ + Change state as Transitions check box is changed + """ + if self.update_theme_allowed: + self.theme.display_slide_transition = state == QtCore.Qt.Checked + self.transition_combo_box.setEnabled(self.theme.display_slide_transition) + self.transition_speed_combo_box.setEnabled(self.theme.display_slide_transition) + self.calculate_lines() + def exec(self, edit=False): """ Run the wizard. @@ -395,6 +405,8 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): self.setField('horizontal', self.theme.display_horizontal_align) self.setField('vertical', self.theme.display_vertical_align) self.setField('slide_transition', self.theme.display_slide_transition) + self.setField('slide_transition_type', self.theme.display_slide_transition_type) + self.setField('slide_transition_speed', self.theme.display_slide_transition_speed) def set_preview_page_values(self): """ @@ -525,19 +537,28 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): # footer page self.theme.font_footer_name = self.footer_font_combo_box.currentFont().family() self.theme.font_footer_size = self.field('footer_size_spin_box') - # position page - self.theme.font_main_x = self.field('main_position_x') - self.theme.font_main_y = self.field('main_position_y') - self.theme.font_main_height = self.field('main_position_height') - self.theme.font_main_width = self.field('main_position_width') - self.theme.font_footer_x = self.field('footer_position_x') - self.theme.font_footer_y = self.field('footer_position_y') - self.theme.font_footer_height = self.field('footer_position_height') - self.theme.font_footer_width = self.field('footer_position_width') + # position page (main) + if self.theme.font_main_override: + self.theme.font_main_x = self.field('main_position_x') + self.theme.font_main_y = self.field('main_position_y') + self.theme.font_main_height = self.field('main_position_height') + self.theme.font_main_width = self.field('main_position_width') + else: + self.theme.set_default_header() + # position page (footer) + if self.theme.font_footer_override: + self.theme.font_footer_x = self.field('footer_position_x') + self.theme.font_footer_y = self.field('footer_position_y') + self.theme.font_footer_height = self.field('footer_position_height') + self.theme.font_footer_width = self.field('footer_position_width') + else: + self.theme.set_default_footer() # position page self.theme.display_horizontal_align = self.horizontal_combo_box.currentIndex() self.theme.display_vertical_align = self.vertical_combo_box.currentIndex() self.theme.display_slide_transition = self.field('slide_transition') + self.theme.display_slide_transition_type = self.field('slide_transition_type') + self.theme.display_slide_transition_speed = self.field('slide_transition_speed') def accept(self): """ diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 2a447b7a9..8539f1eab 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -149,16 +149,15 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R process the bootstrap initialise setup request """ self.setup_ui(self) - self.progress_form = ThemeProgressForm(self) self.global_theme = Settings().value(self.settings_section + '/global theme') self.build_theme_path() - self.load_first_time_themes() self.upgrade_themes() # TODO: Can be removed when upgrade path from OpenLP 2.4 no longer needed def bootstrap_post_set_up(self): """ process the bootstrap post setup request """ + self.progress_form = ThemeProgressForm(self) self.theme_form = ThemeForm(self) self.theme_form.path = self.theme_path self.file_rename_form = FileRenameForm() @@ -481,7 +480,8 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R self.save_theme(theme) Settings().setValue(self.settings_section + '/global theme', theme.theme_name) new_themes = [theme.theme_name] - self.update_preview_images(new_themes) + if new_themes: + self.update_preview_images(new_themes) self.application.set_normal_cursor() def load_themes(self): diff --git a/openlp/core/ui/themeprogressform.py b/openlp/core/ui/themeprogressform.py index 4d61357a3..c636dd834 100644 --- a/openlp/core/ui/themeprogressform.py +++ b/openlp/core/ui/themeprogressform.py @@ -36,6 +36,8 @@ class ThemeProgressForm(QtWidgets.QDialog, UiThemeProgressDialog, RegistryProper super().__init__(parent) self.setup_ui(self) self._theme_list = [] + + def show(self): self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(0) self.progress_bar.setValue(0) @@ -45,6 +47,7 @@ class ThemeProgressForm(QtWidgets.QDialog, UiThemeProgressDialog, RegistryProper except ZeroDivisionError: self.ratio = 16 / 9 self.theme_preview_layout.aspect_ratio = self.ratio + return super().show() def get_preview(self, theme_name, theme_data): self.label.setText(theme_name) diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py index 4fb8f3883..5738a70c5 100644 --- a/openlp/core/ui/themewizard.py +++ b/openlp/core/ui/themewizard.py @@ -25,7 +25,13 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import is_macosx from openlp.core.common.i18n import UiStrings, translate -from openlp.core.lib.theme import BackgroundGradientType, BackgroundType, HorizontalType +from openlp.core.lib.theme import ( + BackgroundGradientType, + BackgroundType, + HorizontalType, + TransitionType, + TransitionSpeed +) from openlp.core.lib.ui import add_welcome_page, create_valign_selection_widgets from openlp.core.ui.icons import UiIcons from openlp.core.widgets.buttons import ColorButton @@ -271,12 +277,22 @@ class Ui_ThemeWizard(object): self.vertical_label.setObjectName('vertical_label') self.vertical_combo_box.setObjectName('vertical_combo_box') self.alignment_layout.addRow(self.vertical_label, self.vertical_combo_box) - self.transitions_label = QtWidgets.QLabel(self.alignment_page) - self.transitions_label.setObjectName('transitions_label') self.transitions_check_box = QtWidgets.QCheckBox(self.alignment_page) self.transitions_check_box.setObjectName('transitions_check_box') - self.alignment_layout.addRow(self.transitions_label, self.transitions_check_box) - self.alignment_layout.setItem(3, QtWidgets.QFormLayout.LabelRole, self.spacer) + self.transition_layout = QtWidgets.QHBoxLayout() + self.transition_layout.setObjectName("transition_layout") + self.transition_combo_box = QtWidgets.QComboBox(self.alignment_page) + self.transition_combo_box.setObjectName("transition_combo_box") + self.transition_combo_box.addItems(['', '', '', '', '']) + self.transition_layout.addWidget(self.transition_combo_box) + self.transition_speed_label = QtWidgets.QLabel(self.alignment_page) + self.transition_speed_label.setObjectName("transition_speed_label") + self.transition_layout.addWidget(self.transition_speed_label) + self.transition_speed_combo_box = QtWidgets.QComboBox(self.alignment_page) + self.transition_speed_combo_box.setObjectName("transition_speed_combo_box") + self.transition_speed_combo_box.addItems(['', '', '']) + self.transition_layout.addWidget(self.transition_speed_combo_box) + self.alignment_layout.addRow(self.transitions_check_box, self.transition_layout) theme_wizard.addPage(self.alignment_page) # Area Position Page self.area_position_page = QtWidgets.QWizardPage() @@ -460,7 +476,16 @@ class Ui_ThemeWizard(object): self.horizontal_combo_box.setItemText(HorizontalType.Right, translate('OpenLP.ThemeWizard', 'Right')) self.horizontal_combo_box.setItemText(HorizontalType.Center, translate('OpenLP.ThemeWizard', 'Center')) self.horizontal_combo_box.setItemText(HorizontalType.Justify, translate('OpenLP.ThemeWizard', 'Justify')) - self.transitions_label.setText(translate('OpenLP.ThemeWizard', 'Transitions:')) + self.transitions_check_box.setText(translate('OpenLP.ThemeWizard', 'Transitions:')) + self.transition_combo_box.setItemText(TransitionType.Fade, translate('OpenLP.ThemeWizard', 'Fade')) + self.transition_combo_box.setItemText(TransitionType.Slide, translate('OpenLP.ThemeWizard', 'Slide')) + self.transition_combo_box.setItemText(TransitionType.Concave, translate('OpenLP.ThemeWizard', 'Concave')) + self.transition_combo_box.setItemText(TransitionType.Convex, translate('OpenLP.ThemeWizard', 'Convex')) + self.transition_combo_box.setItemText(TransitionType.Zoom, translate('OpenLP.ThemeWizard', 'Zoom')) + self.transition_speed_label.setText(translate('OpenLP.ThemeWizard', 'Speed:')) + self.transition_speed_combo_box.setItemText(TransitionSpeed.Normal, translate('OpenLP.ThemeWizard', 'Normal')) + self.transition_speed_combo_box.setItemText(TransitionSpeed.Fast, translate('OpenLP.ThemeWizard', 'Fast')) + self.transition_speed_combo_box.setItemText(TransitionSpeed.Slow, translate('OpenLP.ThemeWizard', 'Slow')) self.area_position_page.setTitle(translate('OpenLP.ThemeWizard', 'Output Area Locations')) self.area_position_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Allows you to change and move the' ' Main and Footer areas.')) diff --git a/openlp/core/widgets/edits.py b/openlp/core/widgets/edits.py index 2efae2e1d..022ab6d53 100644 --- a/openlp/core/widgets/edits.py +++ b/openlp/core/widgets/edits.py @@ -64,7 +64,7 @@ class SearchEdit(QtWidgets.QLineEdit): self.settings_section = settings_section self._current_search_type = -1 self.clear_button = QtWidgets.QToolButton(self) - self.clear_button.setIcon(UiIcons().shortcuts) + self.clear_button.setIcon(UiIcons().backspace) self.clear_button.setCursor(QtCore.Qt.ArrowCursor) self.clear_button.setStyleSheet('QToolButton { border: none; padding: 0px; }') self.clear_button.resize(18, 18) diff --git a/resources/forms/themewizard.ui b/resources/forms/themewizard.ui index 9e1f8f9e8..41faeadca 100644 --- a/resources/forms/themewizard.ui +++ b/resources/forms/themewizard.ui @@ -765,13 +765,72 @@ p, li { white-space: pre-wrap; } - + - Transitions + Transitions: + + + + + + + Fade + + + + + Slide + + + + + Convex + + + + + Concave + + + + + Zoom + + + + + + + + Speed: + + + + + + + + Normal + + + + + Fast + + + + + Slow + + + + + + diff --git a/scripts/reveal-js.patch b/scripts/reveal-js.patch deleted file mode 100644 index 70306926a..000000000 --- a/scripts/reveal-js.patch +++ /dev/null @@ -1,25 +0,0 @@ ---- reveal.js.orig 2018-08-01 10:37:51.000000000 +0200 -+++ reveal.js 2019-02-11 21:25:39.396198927 +0100 -@@ -383,6 +383,14 @@ - } - - /** -+ * Restarts up the presentation if the client is capable. -+ */ -+ function reinitialize() { -+ initialized = false; -+ initialize(config); -+ } -+ -+ /** - * Inspect the client to see what it's capable of, this - * should only happens once per runtime. - */ -@@ -5372,6 +5380,7 @@ - VERSION: VERSION, - - initialize: initialize, -+ reinitialize: reinitialize, - configure: configure, - - sync: sync, diff --git a/tests/functional/openlp_core/api/http/test_init.py b/tests/functional/openlp_core/api/http/test_init.py index 770599a30..166ec6ef4 100644 --- a/tests/functional/openlp_core/api/http/test_init.py +++ b/tests/functional/openlp_core/api/http/test_init.py @@ -144,6 +144,23 @@ class TestInit(TestCase, TestMixin): # THEN: the result will be as expected - try again assert str(value) == 'called' + def test_requires_auth_missing_credentials(self): + """ + Test the requires_auth wrapper with enabled security and authorization taken place and and error + :return: + """ + # GIVEN: An enabled security and a known user + Settings().setValue('api/authentication enabled', True) + Settings().setValue('api/user id', 'superfly') + Settings().setValue('api/password', 'lamas') + + # WHEN: I call the function with no password + wrapped_function = requires_auth(func) + value = wrapped_function(0) + + # THEN: the result will be as expected (unauthorized) + assert str(value) == str(authenticate()) + def func(field=None): return 'called' diff --git a/tests/functional/openlp_core/display/test_window.py b/tests/functional/openlp_core/display/test_window.py new file mode 100644 index 000000000..df1b8aded --- /dev/null +++ b/tests/functional/openlp_core/display/test_window.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2019 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 . # +########################################################################## +""" +Package to test the openlp.core.display.window package. +""" +import sys + +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from PyQt5 import QtCore + +# Mock QtWebEngineWidgets +sys.modules['PyQt5.QtWebEngineWidgets'] = MagicMock() + +from openlp.core.display.window import DisplayWindow +from tests.helpers.testmixin import TestMixin + + +@patch('PyQt5.QtWidgets.QVBoxLayout') +@patch('openlp.core.display.webengine.WebEngineView') +@patch('openlp.core.display.window.Settings') +class TestDisplayWindow(TestCase, TestMixin): + """ + A test suite to test the functions in DisplayWindow + """ + + def test_x11_override_on(self, MockSettings, mocked_webengine, mocked_addWidget): + """ + Test that the x11 override option bit is set + """ + # GIVEN: x11 bypass is on + mocked_settings = MagicMock() + mocked_settings.value.return_value = True + MockSettings.return_value = mocked_settings + + # WHEN: A DisplayWindow is generated + display_window = DisplayWindow() + + # THEN: The x11 override flag should be set + x11_bit = display_window.windowFlags() & QtCore.Qt.X11BypassWindowManagerHint + assert x11_bit == QtCore.Qt.X11BypassWindowManagerHint + + def test_x11_override_off(self, MockSettings, mocked_webengine, mocked_addWidget): + """ + Test that the x11 override option bit is not set when setting if off + """ + # GIVEN: x11 bypass is off + mocked_settings = MagicMock() + mocked_settings.value.return_value = False + MockSettings.return_value = mocked_settings + + # WHEN: A DisplayWindow is generated + display_window = DisplayWindow() + + # THEN: The x11 override flag should not be set + x11_bit = display_window.windowFlags() & QtCore.Qt.X11BypassWindowManagerHint + assert x11_bit != QtCore.Qt.X11BypassWindowManagerHint diff --git a/tests/functional/openlp_core/lib/test_theme.py b/tests/functional/openlp_core/lib/test_theme.py index 48b3e5367..b738abaa0 100644 --- a/tests/functional/openlp_core/lib/test_theme.py +++ b/tests/functional/openlp_core/lib/test_theme.py @@ -23,6 +23,7 @@ Package to test the openlp.core.lib.theme package. """ from pathlib import Path from unittest import TestCase +from unittest.mock import MagicMock, patch from openlp.core.lib.theme import BackgroundType, Theme @@ -175,10 +176,83 @@ class TestTheme(TestCase): lt.load_theme(save_theme_json) self.check_theme(lt) + @patch('openlp.core.display.screens.ScreenList.current') + def test_set_default_footer(self, mock_geometry): + """ + Test the set_default_footer function sets the footer back to default + (reletive to the screen) + """ + # GIVEN: A screen geometry object and a Theme footer with a strange area + mock_geometry.display_geometry = MagicMock() + mock_geometry.display_geometry.height.return_value = 600 + mock_geometry.display_geometry.width.return_value = 400 + theme = Theme() + theme.font_main_x = 20 + theme.font_footer_x = 207 + theme.font_footer_y = 25 + theme.font_footer_width = 4253 + theme.font_footer_height = 5423 + + # WHEN: set_default_footer is called + theme.set_default_footer() + + # THEN: footer should be set, header should not have changed + assert theme.font_main_x == 20, 'header should not have been changed' + assert theme.font_footer_x == 10, 'x pos should be reset to default of 10' + assert theme.font_footer_y == 540, 'y pos should be reset to (screen_size_height * 9 / 10)' + assert theme.font_footer_width == 380, 'width should have been reset to (screen_size_width - 20)' + assert theme.font_footer_height == 60, 'height should have been reset to (screen_size_height / 10)' + + @patch('openlp.core.display.screens.ScreenList.current') + def test_set_default_header(self, mock_geometry): + """ + Test the set_default_header function sets the header back to default + (reletive to the screen) + """ + # GIVEN: A screen geometry object and a Theme header with a strange area + mock_geometry.display_geometry = MagicMock() + mock_geometry.display_geometry.height.return_value = 600 + mock_geometry.display_geometry.width.return_value = 400 + theme = Theme() + theme.font_footer_x = 200 + theme.font_main_x = 687 + theme.font_main_y = 546 + theme.font_main_width = 345 + theme.font_main_height = 653 + + # WHEN: set_default_header is called + theme.set_default_header() + + # THEN: footer should be set, header should not have changed + assert theme.font_footer_x == 200, 'footer should not have been changed' + assert theme.font_main_x == 10, 'x pos should be reset to default of 10' + assert theme.font_main_y == 0, 'y pos should be reset to 0' + assert theme.font_main_width == 380, 'width should have been reset to (screen_size_width - 20)' + assert theme.font_main_height == 540, 'height should have been reset to (screen_size_height * 9 / 10)' + + @patch('openlp.core.display.screens.ScreenList.current') + def test_set_default_header_footer(self, mock_geometry): + """ + Test the set_default_header_footer function sets the header and footer back to default + (reletive to the screen) + """ + # GIVEN: A screen geometry object and a Theme header with a strange area + mock_geometry.display_geometry = MagicMock() + theme = Theme() + theme.font_footer_x = 200 + theme.font_main_x = 687 + + # WHEN: set_default_header is called + theme.set_default_header_footer() + + # THEN: footer should be set, header should not have changed + assert theme.font_footer_x == 10, 'footer x pos should be reset to default of 10' + assert theme.font_main_x == 10, 'header x pos should be reset to default of 10' + def check_theme(self, theme): assert '#000000' == theme.background_border_color, 'background_border_color should be "#000000"' assert 'solid' == theme.background_type, 'background_type should be "solid"' assert 0 == theme.display_vertical_align, 'display_vertical_align should be 0' assert theme.font_footer_bold is False, 'font_footer_bold should be False' assert 'Arial' == theme.font_main_name, 'font_main_name should be "Arial"' - assert 49 == len(theme.__dict__), 'The theme should have 49 attributes' + assert 51 == len(theme.__dict__), 'The theme should have 51 attributes' diff --git a/tests/functional/openlp_core/ui/test_slidecontroller.py b/tests/functional/openlp_core/ui/test_slidecontroller.py index e09ba70f1..2f26b95cd 100644 --- a/tests/functional/openlp_core/ui/test_slidecontroller.py +++ b/tests/functional/openlp_core/ui/test_slidecontroller.py @@ -941,10 +941,8 @@ class TestInfoLabel(TestCase): """ Test the paintEvent method when text fits the label """ - font = QtGui.QFont() - metrics = QtGui.QFontMetrics(font) - with patch('openlp.core.ui.slidecontroller.QtWidgets.QLabel'), \ + patch('openlp.core.ui.slidecontroller.QtGui.QFontMetrics') as MockFontMetrics, \ patch('openlp.core.ui.slidecontroller.QtGui.QPainter') as mocked_qpainter: # GIVEN: An instance of InfoLabel, with mocked text return, width and rect methods @@ -957,9 +955,11 @@ class TestInfoLabel(TestCase): info_label.rect = mocked_rect info_label.text = mocked_text info_label.width = mocked_width + mocked_font_metrics = MagicMock() + mocked_font_metrics.elidedText.return_value = test_string + MockFontMetrics.return_value = mocked_font_metrics # WHEN: The instance is wider than its text, and the paintEvent method is called - info_label.width.return_value = metrics.boundingRect(test_string).width() + 10 info_label.paintEvent(MagicMock()) # THEN: The text should be drawn centered with the complete test_string @@ -969,15 +969,14 @@ class TestInfoLabel(TestCase): """ Test the paintEvent method when text fits the label """ - font = QtGui.QFont() - metrics = QtGui.QFontMetrics(font) - with patch('openlp.core.ui.slidecontroller.QtWidgets.QLabel'), \ + patch('openlp.core.ui.slidecontroller.QtGui.QFontMetrics') as MockFontMetrics, \ patch('openlp.core.ui.slidecontroller.QtGui.QPainter') as mocked_qpainter: # GIVEN: An instance of InfoLabel, with mocked text return, width and rect methods info_label = InfoLabel() test_string = 'Label Text' + elided_test_string = test_string[0:5] + '...' mocked_rect = MagicMock() mocked_text = MagicMock() mocked_width = MagicMock() @@ -985,14 +984,14 @@ class TestInfoLabel(TestCase): info_label.rect = mocked_rect info_label.text = mocked_text info_label.width = mocked_width + mocked_font_metrics = MagicMock() + mocked_font_metrics.elidedText.return_value = elided_test_string + MockFontMetrics.return_value = mocked_font_metrics # WHEN: The instance is narrower than its text, and the paintEvent method is called - label_width = metrics.boundingRect(test_string).width() - 10 - info_label.width.return_value = label_width info_label.paintEvent(MagicMock()) # THEN: The text should be drawn aligned left with an elided test_string - elided_test_string = metrics.elidedText(test_string, QtCore.Qt.ElideRight, label_width) mocked_qpainter().drawText.assert_called_once_with(mocked_rect(), QtCore.Qt.AlignLeft, elided_test_string) @patch('builtins.super') diff --git a/tests/functional/openlp_core/ui/test_splashscreen.py b/tests/functional/openlp_core/ui/test_splashscreen.py new file mode 100644 index 000000000..e9d8bcca5 --- /dev/null +++ b/tests/functional/openlp_core/ui/test_splashscreen.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2019 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 . # +########################################################################## +""" +Package to test the openlp.core.ui package. +""" +from PyQt5 import QtCore + +from openlp.core.ui.splashscreen import SplashScreen + + +class TestSplashScreen(): + """ + This will test the SplachScreen.py file + """ + def test_SplashScreen(self): + """ + Test that the SpashScreen is created correctly + """ + # GIVEN: the SplashScreen class + # WHEN: An object is created + + ss = SplashScreen() + # THEN: Nothing should go wrong and the instance should have the correct values + assert ss.objectName() == 'splashScreen', 'The ObjectName should have be ' \ + 'splashScreen' + assert ss.frameSize() == QtCore.QSize(370, 370), 'The frameSize should be (370, 370)' + assert ss.contextMenuPolicy() == QtCore.Qt.PreventContextMenu, 'The ContextMenuPolicy ' \ + 'should have been QtCore.Qt.PreventContextMenu or 4' diff --git a/tests/functional/openlp_core/ui/test_themeform.py b/tests/functional/openlp_core/ui/test_themeform.py index c05b9a2b7..bbb205f0a 100644 --- a/tests/functional/openlp_core/ui/test_themeform.py +++ b/tests/functional/openlp_core/ui/test_themeform.py @@ -28,9 +28,9 @@ from unittest.mock import MagicMock, patch from openlp.core.ui.themeform import ThemeForm -class TestThemeManager(TestCase): +class TestThemeForm(TestCase): """ - Test the functions in the ThemeManager Class + Test the functions in the ThemeForm Class """ def setUp(self): with patch('openlp.core.ui.themeform.ThemeForm._setup'): diff --git a/tests/interfaces/openlp_core/ui/test_thememanager.py b/tests/interfaces/openlp_core/ui/test_thememanager.py index a5b5a88e5..df47d1d81 100644 --- a/tests/interfaces/openlp_core/ui/test_thememanager.py +++ b/tests/interfaces/openlp_core/ui/test_thememanager.py @@ -58,19 +58,16 @@ class TestThemeManager(TestCase, TestMixin): # GIVEN: A new a call to initialise self.theme_manager.setup_ui = MagicMock() self.theme_manager.build_theme_path = MagicMock() - self.theme_manager.load_first_time_themes = MagicMock() self.theme_manager.upgrade_themes = MagicMock() Settings().setValue('themes/global theme', 'my_theme') # WHEN: the initialisation is run - with patch('openlp.core.ui.thememanager.ThemeProgressForm'): - self.theme_manager.bootstrap_initialise() + self.theme_manager.bootstrap_initialise() # THEN: self.theme_manager.setup_ui.assert_called_once_with(self.theme_manager) assert self.theme_manager.global_theme == 'my_theme' self.theme_manager.build_theme_path.assert_called_once_with() - self.theme_manager.load_first_time_themes.assert_called_once_with() self.theme_manager.upgrade_themes.assert_called_once_with() @patch('openlp.core.ui.thememanager.create_paths') @@ -117,7 +114,8 @@ class TestThemeManager(TestCase, TestMixin): self.theme_manager.theme_path = MagicMock() # WHEN: - self.theme_manager.bootstrap_post_set_up() + with patch('openlp.core.ui.thememanager.ThemeProgressForm'): + self.theme_manager.bootstrap_post_set_up() # THEN: assert 1 == self.theme_manager.load_themes.call_count, "load_themes should have been called once" diff --git a/tests/js/test_display.js b/tests/js/test_display.js index a8b50dfa1..857e78507 100644 --- a/tests/js/test_display.js +++ b/tests/js/test_display.js @@ -113,22 +113,18 @@ describe("The Display object", function () { expect(Display.reinit).toBeDefined(); }); - it("should re-initialise Reveal when reinit is called", function () { - spyOn(Reveal, "reinitialize"); + it("should sync Reveal and set to first slide when reinit is called", function () { + spyOn(Reveal, "sync"); + spyOn(Reveal, "slide"); Display.reinit(); - expect(Reveal.reinitialize).toHaveBeenCalled(); + expect(Reveal.sync).toHaveBeenCalled(); + expect(Reveal.slide).toHaveBeenCalledWith(0); }); it("should have a setTransition() method", function () { expect(Display.setTransition).toBeDefined(); }); - it("should have a correctly functioning setTransition() method", function () { - spyOn(Reveal, "configure"); - Display.setTransition("fade"); - expect(Reveal.configure).toHaveBeenCalledWith({"transition": "fade"}); - }); - it("should have a correctly functioning clearSlides() method", function () { expect(Display.clearSlides).toBeDefined(); @@ -156,6 +152,55 @@ describe("The Display object", function () { }); +describe("Transitions", function () { + beforeEach(function() { + document.body.innerHTML = ""; + _createDiv({"class": "slides"}); + _createDiv({"class": "footer"}); + _createDiv({"id": "global-background"}); + Display._slides = {}; + }); + afterEach(function() { + // Reset theme + Display._theme = null; + }); + + it("should have a correctly functioning setTransition() method", function () { + spyOn(Reveal, "configure"); + Display.setTransition("fade", "slow"); + expect(Reveal.configure).toHaveBeenCalledWith({"transition": "fade", "transitionSpeed": "slow"}); + }); + + it("should have enabled transitions when _doTransitions is true and setTheme is run", function () { + spyOn(Display, "setTransition"); + Display._doTransitions = true; + var theme = { + "display_slide_transition": true, + "display_slide_transition_type": TransitionType.Slide, + "display_slide_transition_speed": TransitionSpeed.Fast + } + + Display.setTheme(theme); + + expect(Display.setTransition).toHaveBeenCalledWith("slide", "fast"); + }); + + it("should have not enabled transitions when init() with no transitions and setTheme is run", function () { + spyOn(Display, "setTransition"); + Display._doTransitions = false; + var theme = { + "display_slide_transition": true, + "display_slide_transition_type": TransitionType.Slide, + "display_slide_transition_speed": TransitionSpeed.Fast, + } + + Display.setTheme(theme); + + expect(Display.setTransition).toHaveBeenCalledWith("none", "default"); + }); + +}); + describe("Display.alert", function () { var alertContainer, alertBackground, alertText, settings, text; @@ -519,7 +564,6 @@ describe("Display.setTextSlides", function () { ]; spyOn(Display, "clearSlides"); spyOn(Display, "reinit"); - spyOn(Reveal, "slide"); Display.setTextSlides(slides); @@ -528,7 +572,6 @@ describe("Display.setTextSlides", function () { expect(Display._slides["v2"]).toEqual(1); expect($(".slides > section > section").length).toEqual(2); expect(Display.reinit).toHaveBeenCalledTimes(1); - expect(Reveal.slide).toHaveBeenCalledWith(0, 0); }); it("should correctly set outline width", function () { @@ -583,6 +626,56 @@ describe("Display.setTextSlides", function () { expect(slidesDiv.style['justify-content']).toEqual('center'); }) + it("should enable shadows", function () { + const slides = [ + { + "verse": "v1", + "text": "Amazing grace, how sweet the sound\nThat saved a wretch like me\n" + + "I once was lost, but now I'm found\nWas blind but now I see", + "footer": "Public Domain" + } + ]; + // + const theme = { + 'font_main_shadow': true, + 'font_main_shadow_color': "#000", + 'font_main_shadow_size': 5 + }; + spyOn(Display, "reinit"); + spyOn(Reveal, "slide"); + + Display.setTheme(theme); + Display.setTextSlides(slides); + + const slidesDiv = $(".text-slides")[0]; + expect(slidesDiv.style['text-shadow']).not.toEqual(''); + }) + + it("should not enable shadows", function () { + const slides = [ + { + "verse": "v1", + "text": "Amazing grace, how sweet the sound\nThat saved a wretch like me\n" + + "I once was lost, but now I'm found\nWas blind but now I see", + "footer": "Public Domain" + } + ]; + // + const theme = { + 'font_main_shadow': false, + 'font_main_shadow_color': "#000", + 'font_main_shadow_size': 5 + }; + spyOn(Display, "reinit"); + spyOn(Reveal, "slide"); + + Display.setTheme(theme); + Display.setTextSlides(slides); + + const slidesDiv = $(".text-slides")[0]; + expect(slidesDiv.style['text-shadow']).toEqual(''); + }) + it("should correctly set slide size position to theme size when adding a text slide", function () { const slides = [ { diff --git a/tests/openlp_core/ui/test_themeprogressform.py b/tests/openlp_core/ui/test_themeprogressform.py new file mode 100644 index 000000000..dfb91f496 --- /dev/null +++ b/tests/openlp_core/ui/test_themeprogressform.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2019 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 . # +########################################################################## +""" +Package to test the openlp.core.ui.themeform package. +""" +# from pathlib import Path +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from openlp.core.ui.themeprogressform import ThemeProgressForm +from tests.helpers.testmixin import TestMixin + + +class TestThemeProgressForm(TestCase, TestMixin): + """ + Test the functions in the ThemeProgressForm class + """ + def setUp(self): + self.setup_application() + + def tearDown(self): + del self.app + + def _get_theme_progress_form(self): + """Common code used to create the ThemeProgressForm object""" + with patch('openlp.core.ui.themeprogressdialog.ThemePreviewRenderer'), \ + patch('openlp.core.ui.themeprogressdialog.UiThemeProgressDialog.setup_ui'): + form = ThemeProgressForm() + return form + + def test_init(self): + """Test that the ThemeProgressForm is created without problems""" + # GIVEN: ThemeProgressForm class + # WHEN: An object is instatiated + # THEN: There is no problem + self._get_theme_progress_form() + + @patch('openlp.core.ui.themeprogressform.ScreenList') + @patch('openlp.core.ui.themeprogressform.QtWidgets.QDialog.show') + def test_show(self, mocked_show, MockScreenList): + """Test that the ThemeProgressForm is created without problems""" + # GIVEN: ThemeProgressForm object + form = self._get_theme_progress_form() + mocked_screen_list = MagicMock() + mocked_screen_list.current.display_geometry.width.return_value = 1920 + mocked_screen_list.current.display_geometry.height.return_value = 1080 + MockScreenList.return_value = mocked_screen_list + form.progress_bar = MagicMock() + form.theme_preview_layout = MagicMock() + + # WHEN: The show() method is called + form.show() + + # THEN: The correct display ratio is calculated and the form is shown + expected_ratio = 16 / 9 + form.progress_bar.setMinimum.assert_called_once_with(0) + form.progress_bar.setMaximum.assert_called_once_with(0) + form.progress_bar.setValue.assert_called_once_with(0) + assert form.ratio == expected_ratio + assert form.theme_preview_layout.aspect_ratio == expected_ratio + mocked_show.assert_called_once() + + @patch('openlp.core.ui.themeprogressform.ScreenList') + @patch('openlp.core.ui.themeprogressform.QtWidgets.QDialog.show') + def test_show_divide_by_zero(self, mocked_show, MockScreenList): + """Test that the ThemeProgressForm is created without problems even if there's a divide by zero exception""" + # GIVEN: ThemeProgressForm object + form = self._get_theme_progress_form() + mocked_screen_list = MagicMock() + mocked_screen_list.current.display_geometry.width.return_value = 1920 + mocked_screen_list.current.display_geometry.height.return_value = 0 + MockScreenList.return_value = mocked_screen_list + form.progress_bar = MagicMock() + form.theme_preview_layout = MagicMock() + + # WHEN: The show() method is called + form.show() + + # THEN: The correct display ratio is calculated and the form is shown + expected_ratio = 16 / 9 + form.progress_bar.setMinimum.assert_called_once_with(0) + form.progress_bar.setMaximum.assert_called_once_with(0) + form.progress_bar.setValue.assert_called_once_with(0) + assert form.ratio == expected_ratio + assert form.theme_preview_layout.aspect_ratio == expected_ratio + mocked_show.assert_called_once() + + def test_get_preview(self): + """Test that the get_preview() method returns a preview image""" + # GIVEN: ThemeProgressForm object + test_theme_name = 'Test Theme' + test_theme_data = {'name': test_theme_name} + form = self._get_theme_progress_form() + form.progress_bar = MagicMock(**{'value.return_value': 0}) + form.label = MagicMock() + form.theme_display = MagicMock(**{'width.return_value': 192, 'generate_preview.return_value': 'preview'}) + form.renderer.width = MagicMock(return_value=1920) + + # WHEN: get_preview() is called + preview = form.get_preview(test_theme_name, test_theme_data) + + # THEN: All the correct methods should be called and the correct results should be returned + form.label.setText.assert_called_once_with(test_theme_name) + form.progress_bar.value.assert_called_once() + form.progress_bar.setValue.assert_called_once_with(1) + form.theme_display.width.assert_called_once() + form.renderer.width.assert_called_once() + form.theme_display.set_scale.assert_called_once_with(0.1) + form.theme_display.generate_preview.assert_called_once_with(test_theme_data, generate_screenshot=True) + assert preview == 'preview' + + def test_theme_list(self): + # GIVEN: ThemeProgressForm object and theme list + test_theme_list = ['Theme 1', 'Theme 2'] + form = self._get_theme_progress_form() + form.progress_bar = MagicMock() + + # WHEN: theme_list is set and get'ed + form.theme_list = test_theme_list + theme_list = form.theme_list + + # THEN: The theme list should be correct + form.progress_bar.setMaximum.assert_called_once_with(2) + assert theme_list == test_theme_list