diff --git a/openlp/core/display/html/display.css b/openlp/core/display/html/display.css new file mode 100644 index 000000000..d1af3a902 --- /dev/null +++ b/openlp/core/display/html/display.css @@ -0,0 +1,90 @@ +@keyframes alert-scrolling-text { + 0% { + opacity: 1; + transform: translateX(100%); + } + 99% { + opacity: 1; + } + 100% { + opacity: 0; + transform: translateX(-101%); + } +} + +body { + background: transparent !important; + color: rgb(255, 255, 255) !important; +} + +sup { + vertical-align: super !important; + font-size: smaller !important; +} + +.reveal .slides > section, +.reveal .slides > section > section { + padding: 0; +} + +.reveal > .backgrounds > .present { + visibility: hidden !important; +} + +#global-background { + display: block; + visibility: visible; + z-index: -1; +} + +.alert-container { + position: absolute; + display: flex; + flex-direction: row; + height: 100vh; + width: 100vw; +} + +.hide { + opacity: 0 !important; + transition: opacity 0.5s ease; +} + +.show { + opacity: 1 !important; + transition: opacity 0.5s ease; +} + +.middle { + align-items: center; +} + +.top { + align-items: flex-start; +} + +.bottom { + align-items: flex-end; +} + +#alert-background { + left: 0; + margin: 0; + opacity: 0; + overflow: hidden; + padding: 0.5em 0; + position: absolute; + transition: opacity 0.5s ease; + white-space: nowrap; + width: 100%; + z-index: 11; +} + +#alert-text { + margin: 0 0.5em; + opacity: 0; + overflow: visible; + padding: 0; + transition: opacity 0.5s linear; + z-index: 100; +} diff --git a/openlp/core/display/html/display.html b/openlp/core/display/html/display.html index 154d3855d..4a5870bb0 100644 --- a/openlp/core/display/html/display.html +++ b/openlp/core/display/html/display.html @@ -2,34 +2,16 @@ Display Window - - + + +
+
Testing alerts
+
diff --git a/openlp/core/display/html/display.js b/openlp/core/display/html/display.js index bfaef4185..d65945ae5 100644 --- a/openlp/core/display/html/display.js +++ b/openlp/core/display/html/display.js @@ -52,6 +52,50 @@ var AudioState = { Stopped: "stopped" }; +/** + * Transition state enumeration + */ +var TransitionState = { + EntranceTransition: "entranceTransition", + NoTransition: "noTransition", + ExitTransition: "exitTransition" +}; + +/** + * Animation state enumeration + */ +var AnimationState = { + NoAnimation: "noAnimation", + ScrollingText: "scrollingText", + NonScrollingText: "noScrollingText" +}; + +/** + * Alert location enumeration + */ +var AlertLocation = { + Top: 0, + Middle: 1, + Bottom: 2 +}; + +/** + * Alert state enumeration + */ +var AlertState = { + Displaying: "displaying", + NotDisplaying: "notDisplaying" +} + +/** + * Alert delay enumeration + */ +var AlertDelay = { + FiftyMilliseconds: 50, + OneSecond: 1000, + OnePointFiveSeconds: 1500 +} + /** * Return an array of elements based on the selector query * @param {string} selector - The selector to find elements @@ -117,6 +161,50 @@ function _prepareText(text) { return "

" + _nl2br(text) + "

"; } +/** + * Change a camelCaseString to a camel-case-string + * @private + * @param {string} text + * @returns {string} the Un-camel-case-ified string + */ +function _fromCamelCase(text) { + return text.replace(/([A-Z])/g, function (match, submatch) { + return '-' + submatch.toLowerCase(); + }); +} + +/** + * Create a CSS style + * @private + * @param {string} selector - The selector for this style + * @param {Object} rules - The rules to apply to the style + */ +function _createStyle(selector, rules) { + var id = selector.replace("#", "").replace(" .", "-").replace(".", "-").replace(" ", "_"); + if ($("style#" + id).length != 0) { + var style = $("style#" + id)[0]; + } + else { + var style = document.createElement("style"); + document.getElementsByTagName("head")[0].appendChild(style); + style.type = "text/css"; + style.id = id; + } + var rulesString = selector + " { "; + for (var key in rules) { + var ruleValue = rules[key]; + var ruleKey = _fromCamelCase(key); + rulesString += "" + ruleKey + ": " + ruleValue + ";"; + } + rulesString += " } "; + if (style.styleSheet) { + style.styleSheet.cssText = rulesString; + } + else { + style.appendChild(document.createTextNode(rulesString)); + } +} + /** * An audio player with a play list */ @@ -234,7 +322,12 @@ AudioPlayer.prototype.stop = function () { * The Display object is what we use from OpenLP */ var Display = { + _alerts: [], _slides: {}, + _alertSettings: {}, + _alertState: AlertState.NotDisplaying, + _transitionState: TransitionState.NoTransition, + _animationState: AnimationState.NoAnimation, _revealConfig: { margin: 0.0, minScale: 1.0, @@ -355,21 +448,149 @@ var Display = { Display.reinit(); }, /** - * Display an alert + * Display an alert. If there's an alert already showing, add this one to the queue * @param {string} text - The alert text - * @param {int} location - The location of the text (top, middle or bottom) + * @param {Object} JSON object - The settings for the alert object */ - alert: function (text, location) { - console.debug(" alert text: " + text, ", location: " + location); - /* - * The implementation should show an alert. - * It should be able to handle receiving a new alert before a previous one is "finished", basically queueing it. - */ - return; -}, - + alert: function (text, settings) { + if (text == "") { + return null; + } + if (Display._alertState === AlertState.Displaying) { + console.debug("Adding to queue"); + Display.addAlertToQueue(text, settings); + } + else { + console.debug("Displaying immediately"); + Display.showAlert(text, settings); + } + }, /** - * Add a slides. If the slide exists but the HTML is different, update the slide. + * Show the alert on the screen + * @param {string} text - The alert text + * @param {Object} JSON object - The settings for the alert + */ + showAlert: function (text, settings) { + var alertBackground = $('#alert-background')[0]; + var alertText = $('#alert-text')[0]; + // create styles for the alerts from the settings + _createStyle("#alert-background.settings", { + backgroundColor: settings["backgroundColor"], + fontFamily: "'" + settings["fontFace"] + "'", + fontSize: settings["fontSize"].toString() + "pt", + color: settings["fontColor"] + }); + alertBackground.classList.add("settings"); + alertBackground.classList.replace("hide", "show"); + alertText.innerHTML = text; + Display.setAlertLocation(settings.location); + Display._transitionState = TransitionState.EntranceTransition; + /* Check if the alert is a queued alert */ + if (Display._alertState !== AlertState.Displaying) { + Display._alertState = AlertState.Displaying; + } + alertBackground.addEventListener('transitionend', Display.alertTransitionEndEvent, false); + alertText.addEventListener('animationend', Display.alertAnimationEndEvent, false); + /* Either scroll the alert, or make it disappear at the end of its time */ + if (settings.scroll) { + Display._animationState = AnimationState.ScrollingText; + var animationSettings = "alert-scrolling-text " + settings.timeout + + "s linear 0.6s " + settings.repeat + " normal"; + alertText.style.animation = animationSettings; + } + else { + Display._animationState = AnimationState.NonScrollingText; + alertText.classList.replace("hide", "show"); + setTimeout (function () { + Display._animationState = AnimationState.NoAnimation; + Display.hideAlert(); + }, settings.timeout * AlertDelay.OneSecond); + } + }, + /** + * Hide the alert at the end + */ + hideAlert: function () { + var alertBackground = $('#alert-background')[0]; + var alertText = $('#alert-text')[0]; + Display._transitionState = TransitionState.ExitTransition; + alertText.classList.replace("show", "hide"); + alertBackground.classList.replace("show", "hide"); + alertText.style.animation = ""; + Display._alertState = AlertState.NotDisplaying; + }, + /** + * Add an alert to the alert queue + * @param {string} text - The alert text to be displayed + * @param {Object} setttings - JSON object containing the settings for the alert + */ + addAlertToQueue: function (text, settings) { + Display._alerts.push({text: text, settings: settings}); + }, + /** + * The alertTransitionEndEvent called after a transition has ended + */ + alertTransitionEndEvent: function (e) { + e.stopPropagation(); + console.debug("Transition end event reached: " + Display._transitionState); + if (Display._transitionState === TransitionState.EntranceTransition) { + Display._transitionState = TransitionState.NoTransition; + } + else if (Display._transitionState === TransitionState.ExitTransition) { + Display._transitionState = TransitionState.NoTransition; + Display.hideAlert(); + Display.showNextAlert(); + } + }, + /** + * The alertAnimationEndEvent called after an animation has ended + */ + alertAnimationEndEvent: function (e) { + e.stopPropagation(); + Display.hideAlert(); + }, + /** + * Set the location of the alert + * @param {int} location - Integer number with the location of the alert on screen + */ + setAlertLocation: function (location) { + var alertContainer = $(".alert-container")[0]; + // Remove an existing location classes + alertContainer.classList.remove("top"); + alertContainer.classList.remove("middle"); + alertContainer.classList.remove("bottom"); + // Apply the location class we want + switch (location) { + case AlertLocation.Top: + alertContainer.classList.add("top"); + break; + case AlertLocation.Middle: + alertContainer.classList.add("middle"); + break; + case AlertLocation.Bottom: + default: + alertContainer.classList.add("bottom"); + break; + } + }, + /** + * Display the next alert in the queue + */ + showNextAlert: function () { + console.log("showNextAlert"); + if (Display._alerts.length > 0) { + console.log("Showing next alert"); + var alertObject = Display._alerts.shift(); + Display._alertState = AlertState.DisplayingFromQueue; + Display.showAlert(alertObject.text, alertObject.settings); + } + else { + // For the tests + return null; + } + }, + /** + * Add a slide. If the slide exists but the HTML is different, update the slide. * @param {string} verse - The verse number, e.g. "v1" * @param {string} text - The HTML for the verse, e.g. "line1
line2" * @param {string} footer_text - The HTML for the footer" @@ -770,7 +991,7 @@ var Display = { return videoTypes; }, /** - * Sets the scale of the page - used to make preview widgets scale + * Sets the scale of the page - used to make preview widgets scale */ setScale: function(scale) { document.body.style.zoom = scale+"%"; diff --git a/openlp/core/display/webengine.py b/openlp/core/display/webengine.py index fbec9c915..defc27066 100644 --- a/openlp/core/display/webengine.py +++ b/openlp/core/display/webengine.py @@ -27,6 +27,8 @@ import logging from PyQt5 import QtCore, QtWebEngineWidgets, QtWidgets +from openlp.core.common.applocation import AppLocation + LOG_LEVELS = { QtWebEngineWidgets.QWebEnginePage.InfoMessageLevel: logging.INFO, @@ -46,6 +48,10 @@ class WebEnginePage(QtWebEngineWidgets.QWebEnginePage): """ Override the parent method in order to log the messages in OpenLP """ + # The JS log has the entire file location, which we don't really care about + app_dir = AppLocation.get_directory(AppLocation.AppDir).parent + source_id = source_id.replace('file://{app_dir}/'.format(app_dir=app_dir), '') + # Log the JS messages to the Python logger log.log(LOG_LEVELS[level], '{source_id}:{line_number} {message}'.format(source_id=source_id, line_number=line_number, message=message)) diff --git a/openlp/core/display/window.py b/openlp/core/display/window.py index 0c8c4f3b5..7715a3095 100644 --- a/openlp/core/display/window.py +++ b/openlp/core/display/window.py @@ -399,8 +399,8 @@ class DisplayWindow(QtWidgets.QWidget): self.scale = scale self.run_javascript('Display.setScale({scale});'.format(scale=scale * 100)) - def alert(self, text, location): + def alert(self, text, settings): """ Set an alert """ - self.run_javascript('Display.alert({text}, {location});'.format(text=text, location=location)) + self.run_javascript('Display.alert("{text}", {settings});'.format(text=text, settings=settings)) diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 534c777e7..b9f3267e1 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -126,7 +126,9 @@ __default_settings__ = { 'alerts/location': AlertLocation.Bottom, 'alerts/background color': '#660000', 'alerts/font color': '#ffffff', - 'alerts/timeout': 5 + 'alerts/timeout': 10, + 'alerts/repeat': 1, + 'alerts/scroll': True } diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index 695de6638..3d99bfdaf 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -23,7 +23,9 @@ The :mod:`~openlp.plugins.alerts.lib.alertsmanager` module contains the part of the plugin which manages storing and displaying of alerts. """ -from PyQt5 import QtCore +import json + +from PyQt5 import QtCore, QtGui from openlp.core.common.i18n import translate from openlp.core.common.mixins import LogMixin, RegistryProperties @@ -83,8 +85,23 @@ class AlertsManager(QtCore.QObject, RegistryBase, LogMixin, RegistryProperties): not Settings().value('core/display on monitor')): return text = self.alert_list.pop(0) - alert_tab = self.parent().settings_tab - self.live_controller.displays[0].alert(text, alert_tab.location) + + # Get the rgb color format of the font & background hex colors from settings + rgb_font_color = self.hex_to_rgb(QtGui.QColor(Settings().value('alerts/font color'))) + rgb_background_color = self.hex_to_rgb(QtGui.QColor(Settings().value('alerts/background color'))) + + # Put alert settings together in dict that will be passed to Display in Javascript + alert_settings = { + 'backgroundColor': rgb_background_color, + 'location': Settings().value('alerts/location'), + 'fontFace': Settings().value('alerts/font face'), + 'fontSize': Settings().value('alerts/font size'), + 'fontColor': rgb_font_color, + 'timeout': Settings().value('alerts/timeout'), + 'repeat': Settings().value('alerts/repeat'), + 'scroll': Settings().value('alerts/scroll') + } + self.live_controller.displays[0].alert(text, json.dumps(alert_settings)) def timerEvent(self, event): """ @@ -98,3 +115,13 @@ class AlertsManager(QtCore.QObject, RegistryBase, LogMixin, RegistryProperties): self.killTimer(self.timer_id) self.timer_id = 0 self.generate_alert() + + def hex_to_rgb(self, rgb_values): + """ + Converts rgb color values from QColor to rgb string + + :param rgb_values: + :return: rgb color string + :rtype: string + """ + return "rgb(" + str(rgb_values.red()) + ", " + str(rgb_values.green()) + ", " + str(rgb_values.blue()) + ")" diff --git a/openlp/plugins/alerts/lib/alertstab.py b/openlp/plugins/alerts/lib/alertstab.py index ab0d056b7..a2827be88 100644 --- a/openlp/plugins/alerts/lib/alertstab.py +++ b/openlp/plugins/alerts/lib/alertstab.py @@ -47,35 +47,56 @@ class AlertsTab(SettingsTab): self.font_layout.addRow(self.font_label, self.font_combo_box) self.font_color_label = QtWidgets.QLabel(self.font_group_box) self.font_color_label.setObjectName('font_color_label') - self.color_layout = QtWidgets.QHBoxLayout() - self.color_layout.setObjectName('color_layout') self.font_color_button = ColorButton(self.font_group_box) self.font_color_button.setObjectName('font_color_button') - self.color_layout.addWidget(self.font_color_button) - self.color_layout.addSpacing(20) - self.background_color_label = QtWidgets.QLabel(self.font_group_box) - self.background_color_label.setObjectName('background_color_label') - self.color_layout.addWidget(self.background_color_label) - self.background_color_button = ColorButton(self.font_group_box) - self.background_color_button.setObjectName('background_color_button') - self.color_layout.addWidget(self.background_color_button) - self.font_layout.addRow(self.font_color_label, self.color_layout) + self.font_layout.addRow(self.font_color_label, self.font_color_button) self.font_size_label = QtWidgets.QLabel(self.font_group_box) self.font_size_label.setObjectName('font_size_label') self.font_size_spin_box = QtWidgets.QSpinBox(self.font_group_box) self.font_size_spin_box.setObjectName('font_size_spin_box') self.font_layout.addRow(self.font_size_label, self.font_size_spin_box) - self.timeout_label = QtWidgets.QLabel(self.font_group_box) + self.left_layout.addWidget(self.font_group_box) + # Background Settings + self.background_group_box = QtWidgets.QGroupBox(self.left_column) + self.background_group_box.setObjectName('background_group_box') + self.background_layout = QtWidgets.QFormLayout(self.background_group_box) + self.background_layout.setObjectName('background_settings_layout') + self.background_color_label = QtWidgets.QLabel(self.background_group_box) + self.background_color_label.setObjectName('background_color_label') + self.background_color_button = ColorButton(self.background_group_box) + self.background_color_button.setObjectName('background_color_button') + self.background_layout.addRow(self.background_color_label, self.background_color_button) + self.left_layout.addWidget(self.background_group_box) + # Scroll Settings + self.scroll_group_box = QtWidgets.QGroupBox(self.left_column) + self.scroll_group_box.setObjectName('scroll_group_box') + self.scroll_group_layout = QtWidgets.QFormLayout(self.scroll_group_box) + self.scroll_group_layout.setObjectName('scroll_group_layout') + self.scroll_check_box = QtWidgets.QCheckBox(self.scroll_group_box) + self.scroll_check_box.setObjectName('scroll_check_box') + self.scroll_group_layout.addRow(self.scroll_check_box) + self.repeat_label = QtWidgets.QLabel(self.scroll_group_box) + self.repeat_label.setObjectName('repeat_label') + self.repeat_spin_box = QtWidgets.QSpinBox(self.scroll_group_box) + self.repeat_spin_box.setObjectName('repeat_spin_box') + self.scroll_group_layout.addRow(self.repeat_label, self.repeat_spin_box) + self.left_layout.addWidget(self.scroll_group_box) + # Other Settings + self.settings_group_box = QtWidgets.QGroupBox(self.left_column) + self.settings_group_box.setObjectName('settings_group_box') + self.settings_layout = QtWidgets.QFormLayout(self.settings_group_box) + self.settings_layout.setObjectName('settings_layout') + self.timeout_label = QtWidgets.QLabel(self.settings_group_box) self.timeout_label.setObjectName('timeout_label') - self.timeout_spin_box = QtWidgets.QSpinBox(self.font_group_box) + self.timeout_spin_box = QtWidgets.QSpinBox(self.settings_group_box) self.timeout_spin_box.setMaximum(180) self.timeout_spin_box.setObjectName('timeout_spin_box') - self.font_layout.addRow(self.timeout_label, self.timeout_spin_box) + self.settings_layout.addRow(self.timeout_label, self.timeout_spin_box) self.vertical_label, self.vertical_combo_box = create_valign_selection_widgets(self.font_group_box) self.vertical_label.setObjectName('vertical_label') self.vertical_combo_box.setObjectName('vertical_combo_box') - self.font_layout.addRow(self.vertical_label, self.vertical_combo_box) - self.left_layout.addWidget(self.font_group_box) + self.settings_layout.addRow(self.vertical_label, self.vertical_combo_box) + self.left_layout.addWidget(self.settings_group_box) self.left_layout.addStretch() self.preview_group_box = QtWidgets.QGroupBox(self.right_column) self.preview_group_box.setObjectName('preview_group_box') @@ -92,16 +113,22 @@ class AlertsTab(SettingsTab): self.font_combo_box.activated.connect(self.on_font_combo_box_clicked) self.timeout_spin_box.valueChanged.connect(self.on_timeout_spin_box_changed) self.font_size_spin_box.valueChanged.connect(self.on_font_size_spin_box_changed) + self.repeat_spin_box.valueChanged.connect(self.on_repeat_spin_box_changed) + self.scroll_check_box.toggled.connect(self.scroll_check_box_toggled) def retranslate_ui(self): - self.font_group_box.setTitle(translate('AlertsPlugin.AlertsTab', 'Font')) + self.font_group_box.setTitle(translate('AlertsPlugin.AlertsTab', 'Font Settings')) self.font_label.setText(translate('AlertsPlugin.AlertsTab', 'Font name:')) self.font_color_label.setText(translate('AlertsPlugin.AlertsTab', 'Font color:')) self.background_color_label.setText(UiStrings().BackgroundColorColon) self.font_size_label.setText(translate('AlertsPlugin.AlertsTab', 'Font size:')) self.font_size_spin_box.setSuffix(' {unit}'.format(unit=UiStrings().FontSizePtUnit)) + self.background_group_box.setTitle(translate('AlertsPlugin.AlertsTab', 'Background Settings')) + self.settings_group_box.setTitle(translate('AlertsPlugin.AlertsTab', 'Other Settings')) self.timeout_label.setText(translate('AlertsPlugin.AlertsTab', 'Alert timeout:')) self.timeout_spin_box.setSuffix(' {unit}'.format(unit=UiStrings().Seconds)) + self.repeat_label.setText(translate('AlertsPlugin.AlertsTab', 'Repeat (no. of times):')) + self.scroll_check_box.setText(translate('AlertsPlugin.AlertsTab', 'Enable Scrolling')) self.preview_group_box.setTitle(UiStrings().Preview) self.font_preview.setText(UiStrings().OpenLP) @@ -140,6 +167,24 @@ class AlertsTab(SettingsTab): self.font_size = self.font_size_spin_box.value() self.update_display() + def on_repeat_spin_box_changed(self): + """ + The repeat spin box has changed + """ + self.repeat = self.repeat_spin_box.value() + self.changed = True + + def scroll_check_box_toggled(self): + """ + The scrolling checkbox has been toggled + """ + if self.scroll_check_box.isChecked(): + self.repeat_spin_box.setEnabled(True) + else: + self.repeat_spin_box.setEnabled(False) + self.scroll = self.scroll_check_box.isChecked() + self.changed = True + def load(self): """ Load the settings into the UI. @@ -152,12 +197,17 @@ class AlertsTab(SettingsTab): self.background_color = settings.value('background color') self.font_face = settings.value('font face') self.location = settings.value('location') + self.repeat = settings.value('repeat') + self.scroll = settings.value('scroll') settings.endGroup() self.font_size_spin_box.setValue(self.font_size) self.timeout_spin_box.setValue(self.timeout) self.font_color_button.color = self.font_color self.background_color_button.color = self.background_color + self.repeat_spin_box.setValue(self.repeat) + self.repeat_spin_box.setEnabled(self.scroll) self.vertical_combo_box.setCurrentIndex(self.location) + self.scroll_check_box.setChecked(self.scroll) font = QtGui.QFont() font.setFamily(self.font_face) self.font_combo_box.setCurrentFont(font) @@ -181,6 +231,8 @@ class AlertsTab(SettingsTab): settings.setValue('timeout', self.timeout) self.location = self.vertical_combo_box.currentIndex() settings.setValue('location', self.location) + settings.setValue('repeat', self.repeat) + settings.setValue('scroll', self.scroll_check_box.isChecked()) settings.endGroup() if self.changed: self.settings_form.register_post_process('update_display_css') diff --git a/tests/js/test_display.js b/tests/js/test_display.js index 934bc9ed0..fd051804e 100644 --- a/tests/js/test_display.js +++ b/tests/js/test_display.js @@ -27,6 +27,14 @@ describe("The enumeration object", function () { it("AudioState should exist", function () { expect(AudioState).toBeDefined(); }); + + it("TransitionState should exist", function(){ + expect(TransitionState).toBeDefined(); + }); + + it("AnimationState should exist", function(){ + expect(AnimationState).toBeDefined(); + }); }); describe("The function", function () { @@ -141,6 +149,296 @@ describe("The Display object", function () { Display.goToSlide("v1"); expect(Reveal.slide).toHaveBeenCalledWith(0); }); + + it("should have an alert() method", function () { + expect(Display.alert).toBeDefined(); + }); + +}); + +describe("Display.alert", function () { + var alertContainer, alertBackground, alertText, settings, text; + + beforeEach(function () { + document.body.innerHTML = ""; + alertContainer = _createDiv({"class": "alert-container"}); + alertBackground = _createDiv({"id": "alert-background", "class": "hide"}); + alertText = _createDiv({"id": "alert-text", "class": "hide"}); + settings = { + "location": 1, + "fontFace": "sans-serif", + "fontSize": 40, + "fontColor": "#ffffff", + "backgroundColor": "#660000", + "timeout": 5, + "repeat": 1, + "scroll": true + }; + text = "Display.alert"; + }); + + it("should return null if called without any text", function () { + expect(Display.alert("", settings)).toBeNull(); + }); + + it("should set the correct alert text", function () { + spyOn(Display, "showAlert"); + + Display.alert(text, settings); + + expect(Display.showAlert).toHaveBeenCalled(); + }); + + it("should call the addAlertToQueue method if an alert is displaying", function () { + spyOn(Display, "addAlertToQueue"); + Display._alerts = []; + Display._alertState = AlertState.Displaying; + + Display.alert(text, settings); + + expect(Display.addAlertToQueue).toHaveBeenCalledWith(text, settings); + }); +}); + +describe("Display.showAlert", function () { + var alertContainer, alertBackground, alertText, settings; + + beforeEach(function () { + document.body.innerHTML = ""; + alertContainer = _createDiv({"class": "alert-container"}); + alertBackground = _createDiv({"id": "alert-background", "class": "hide"}); + alertText = _createDiv({"id": "alert-text", "class": "hide"}); + settings = { + "location": 1, + "fontFace": "sans-serif", + "fontSize": 40, + "fontColor": "#ffffff", + "backgroundColor": "#660000", + "timeout": 5, + "repeat": 1, + "scroll": true + }; + }); + + it("should create a stylesheet for the settings", function () { + spyOn(window, "_createStyle"); + Display.showAlert("Test Display.showAlert - stylesheet", settings); + + expect(_createStyle).toHaveBeenCalledWith("#alert-background.settings", { + backgroundColor: settings["backgroundColor"], + fontFamily: "'" + settings["fontFace"] + "'", + fontSize: settings["fontSize"] + 'pt', + color: settings["fontColor"] + }); + }); + + it("should set the alert state to AlertState.Displaying", function () { + Display.showAlert("Test Display.showAlert - state", settings); + + expect(Display._alertState).toEqual(AlertState.Displaying); + }); + + it("should remove the 'hide' classes and add the 'show' classes", function () { + Display.showAlert("Test Display.showAlert - classes", settings); + + expect($("#alert-background")[0].classList.contains("hide")).toEqual(false); + expect($("#alert-background")[0].classList.contains("show")).toEqual(true); + //expect($("#alert-text")[0].classList.contains("hide")).toEqual(false); + //expect($("#alert-text")[0].classList.contains("show")).toEqual(true); + }); +}); + +describe("Display.hideAlert", function () { + var alertContainer, alertBackground, alertText, settings; + + beforeEach(function () { + document.body.innerHTML = ""; + alertContainer = _createDiv({"class": "alert-container"}); + alertBackground = _createDiv({"id": "alert-background", "class": "hide"}); + alertText = _createDiv({"id": "alert-text", "class": "hide"}); + settings = { + "location": 1, + "fontFace": "sans-serif", + "fontSize": 40, + "fontColor": "#ffffff", + "backgroundColor": "#660000", + "timeout": 5, + "repeat": 1, + "scroll": true + }; + }); + + it("should set the alert state to AlertState.NotDisplaying", function () { + Display.showAlert("test", settings); + + Display.hideAlert(); + + expect(Display._alertState).toEqual(AlertState.NotDisplaying); + }); + + it("should hide the alert divs when called", function() { + Display.showAlert("test", settings); + + Display.hideAlert(); + + expect(Display._transitionState).toEqual(TransitionState.ExitTransition); + expect(alertBackground.classList.contains("hide")).toEqual(true); + expect(alertBackground.classList.contains("show")).toEqual(false); + expect(alertText.classList.contains("hide")).toEqual(true); + expect(alertText.classList.contains("show")).toEqual(false); + }); +}); + +describe("Display.setAlertLocation", function() { + var alertContainer, alertBackground, alertText, settings; + + beforeEach(function () { + document.body.innerHTML = ""; + alertContainer = _createDiv({"class": "alert-container"}); + alertBackground = _createDiv({"id": "alert-background", "class": "hide"}); + alertText = _createDiv({"id": "alert-text", "class": "hide"}); + settings = { + "location": 1, + "fontFace": "sans-serif", + "fontSize": 40, + "fontColor": "#ffffff", + "backgroundColor": "#660000", + "timeout": 5, + "repeat": 1, + "scroll": true + }; + }); + + it("should set the correct class when location is top of the page", function () { + Display.setAlertLocation(0); + + expect(alertContainer.className).toEqual("alert-container top"); + }); + + it("should set the correct class when location is middle of the page", function () { + Display.setAlertLocation(1); + + expect(alertContainer.className).toEqual("alert-container middle"); + }); + + it("should set the correct class when location is bottom of the page", function () { + Display.setAlertLocation(2); + + expect(alertContainer.className).toEqual("alert-container bottom"); + }); +}); + +describe("Display.addAlertToQueue", function () { + var alertContainer, alertBackground, alertText, settings; + + beforeEach(function () { + document.body.innerHTML = ""; + alertContainer = _createDiv({"class": "alert-container"}); + alertBackground = _createDiv({"id": "alert-background", "class": "hide"}); + alertText = _createDiv({"id": "alert-text", "class": "hide"}); + settings = { + "location": 1, + "fontFace": "sans-serif", + "fontSize": 40, + "fontColor": "#ffffff", + "backgroundColor": "#660000", + "timeout": 5, + "repeat": 1, + "scroll": true + }; + }); + + it("should add an alert to the queue if one is displaying already", function() { + Display._alerts = []; + Display._alertState = AlertState.Displaying; + var alertObject = {text: "Testing alert queue", settings: settings}; + + Display.addAlertToQueue("Testing alert queue", settings); + + expect(Display._alerts.length).toEqual(1); + expect(Display._alerts[0]).toEqual(alertObject); + }); +}); + +describe("Display.showNextAlert", function () { + var alertContainer, alertBackground, alertText, settings; + + beforeEach(function () { + document.body.innerHTML = ""; + alertContainer = _createDiv({"class": "alert-container"}); + alertBackground = _createDiv({"id": "alert-background", "class": "hide"}); + alertText = _createDiv({"id": "alert-text", "class": "hide"}); + settings = { + "location": 1, + "fontFace": "sans-serif", + "fontSize": 40, + "fontColor": "#ffffff", + "backgroundColor": "#660000", + "timeout": 5, + "repeat": 1, + "scroll": true + }; + }); + + it("should return null if there are no alerts in the queue", function () { + Display._alerts = []; + Display.showNextAlert(); + + expect(Display.showNextAlert()).toBeNull(); + }); + + it("should call the alert function correctly if there is an alert in the queue", function () { + Display._alerts.push({text: "Queued Alert", settings: settings}); + spyOn(Display, "showAlert"); + Display.showNextAlert(); + + expect(Display.showAlert).toHaveBeenCalled(); + expect(Display.showAlert).toHaveBeenCalledWith("Queued Alert", settings); + }); +}); + +describe("Display.alertTransitionEndEvent", function() { + var e = { stopPropagation: function () { } }; + + it("should call event.stopPropagation()", function () { + spyOn(e, "stopPropagation"); + + Display.alertTransitionEndEvent(e); + + expect(e.stopPropagation).toHaveBeenCalled(); + }); + + it("should set the correct state after EntranceTransition", function() { + Display._transitionState = TransitionState.EntranceTransition; + + Display.alertTransitionEndEvent(e); + + expect(Display._transitionState).toEqual(TransitionState.NoTransition); + }); + + it("should set the correct state after ExitTransition, call hideAlert() and showNextAlert()", function() { + spyOn(Display, "hideAlert"); + spyOn(Display, "showNextAlert"); + Display._transitionState = TransitionState.ExitTransition; + + Display.alertTransitionEndEvent(e); + + expect(Display._transitionState).toEqual(TransitionState.NoTransition); + expect(Display.hideAlert).toHaveBeenCalled(); + expect(Display.showNextAlert).toHaveBeenCalled(); + }); +}); + +describe("Display.alertAnimationEndEvent", function () { + var e = { stopPropagation: function () { } }; + + it("should call the hideAlert method", function() { + spyOn(Display, "hideAlert"); + + Display.alertAnimationEndEvent(e); + + expect(Display.hideAlert).toHaveBeenCalled(); + }); }); describe("Display.addTextSlide", function () { @@ -181,7 +479,7 @@ describe("Display.addTextSlide", function () { it("should update an existing slide", function () { var verse = "v1", - text = "Amazing grace, how sweet the sound\nThat saved a wretch like me", + text = "Amazing grace, how sweet the sound\nThat saved a wretch like me", footer = "Public Domain"; Display.addTextSlide(verse, "Amazing grace,\nhow sweet the sound", footer, false); spyOn(Display, "reinit"); @@ -249,6 +547,7 @@ describe("Display.setTextSlides", function () { 'font_main_outline_color': 'red' }; spyOn(Display, "reinit"); + spyOn(Reveal, "slide"); Display.setTextSlides(slides); Display.setTheme(theme);