forked from openlp/openlp
Added JavaScript tests in; Refactored some stuff, removed some stuff. Lots börken.
This commit is contained in:
parent
f6a91839fc
commit
640ebf8c3c
@ -44,3 +44,4 @@ coverage
|
||||
tags
|
||||
output
|
||||
htmlcov
|
||||
node_modules
|
||||
|
77
karma.conf.js
Normal file
77
karma.conf.js
Normal file
@ -0,0 +1,77 @@
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: "",
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ["jasmine"],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
"tests/js/polyfill.js",
|
||||
"tests/js/fake_webchannel.js",
|
||||
"openlp/core/display/html/reveal.js",
|
||||
"openlp/core/display/html/display.js",
|
||||
"tests/js/test_*.js"
|
||||
],
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
],
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {
|
||||
// source files, that you wanna generate coverage for
|
||||
// do not include tests or libraries
|
||||
// (these files will be instrumented by Istanbul)
|
||||
"display.js": ["coverage"]
|
||||
},
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: "dots", "progress"
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ["progress", "coverage"],
|
||||
|
||||
// configure the coverateReporter
|
||||
coverageReporter: {
|
||||
type : "html",
|
||||
dir : "htmlcov/"
|
||||
},
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_DEBUG,
|
||||
|
||||
// loggers
|
||||
/* loggers: [
|
||||
{"type": "file", "filename": "karma.log"}
|
||||
],*/
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: true,
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: ["PhantomJS"],
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: false,
|
||||
|
||||
// Concurrency level
|
||||
// how many browser should be started simultaneous
|
||||
concurrency: Infinity,
|
||||
|
||||
client: {
|
||||
captureConsole: true
|
||||
}
|
||||
})
|
||||
}
|
@ -22,5 +22,3 @@
|
||||
"""
|
||||
The Display module.
|
||||
"""
|
||||
from .canvas import MainCanvas, Canvas, DisplayControllerType
|
||||
from .renderer import Renderer
|
356
openlp/core/display/render.py
Normal file
356
openlp/core/display/render.py
Normal file
@ -0,0 +1,356 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`~openlp.display.render` module contains functions for rendering.
|
||||
"""
|
||||
import html
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
|
||||
from openlp.core.lib.formattingtags import FormattingTags
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
SLIM_CHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\'
|
||||
CHORD_LINE_MATCH = re.compile(r'\[(.*?)\]([\u0080-\uFFFF,\w]*)'
|
||||
'([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?')
|
||||
CHORD_TEMPLATE = '<span class="chordline">{chord}</span>'
|
||||
FIRST_CHORD_TEMPLATE = '<span class="chordline firstchordline">{chord}</span>'
|
||||
CHORD_LINE_TEMPLATE = '<span class="chord"><span><strong>{chord}</strong></span></span>{tail}{whitespace}{remainder}'
|
||||
WHITESPACE_TEMPLATE = '<span class="ws">{whitespaces}</span>'
|
||||
|
||||
|
||||
def remove_tags(text, can_remove_chords=False):
|
||||
"""
|
||||
Remove Tags from text for display
|
||||
|
||||
:param text: Text to be cleaned
|
||||
:param can_remove_chords: Can we remove the chords too?
|
||||
"""
|
||||
text = text.replace('<br>', '\n')
|
||||
text = text.replace('{br}', '\n')
|
||||
text = text.replace(' ', ' ')
|
||||
for tag in FormattingTags.get_html_tags():
|
||||
text = text.replace(tag['start tag'], '')
|
||||
text = text.replace(tag['end tag'], '')
|
||||
# Remove ChordPro tags
|
||||
if can_remove_chords:
|
||||
text = re.sub(r'\[.+?\]', r'', text)
|
||||
return text
|
||||
|
||||
|
||||
def has_valid_tags(text):
|
||||
"""
|
||||
The :func:`~openlp.core.display.render.has_valid_tags` function validates the tags within ``text``.
|
||||
|
||||
:param str text: The string with formatting tags in it.
|
||||
:returns bool: Returns True if tags are valid, False if there are parsing problems.
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
def render_chords_in_line(match):
|
||||
"""
|
||||
Render the chords in the line and align them using whitespaces.
|
||||
NOTE: There is equivalent javascript code in chords.js, in the updateSlide function. Make sure to update both!
|
||||
|
||||
:param str match: The line which contains chords
|
||||
:returns str: The line with rendered html-chords
|
||||
"""
|
||||
whitespaces = ''
|
||||
chord_length = 0
|
||||
tail_length = 0
|
||||
# The match could be "[G]sweet the " from a line like "A[D]mazing [D7]grace! How [G]sweet the [D]sound!"
|
||||
# The actual chord, would be "G" in match "[G]sweet the "
|
||||
chord = match.group(1)
|
||||
# The tailing word of the chord, would be "sweet" in match "[G]sweet the "
|
||||
tail = match.group(2)
|
||||
# The remainder of the line, until line end or next chord. Would be " the " in match "[G]sweet the "
|
||||
remainder = match.group(3)
|
||||
# Line end if found, else None
|
||||
end = match.group(4)
|
||||
# Based on char width calculate width of chord
|
||||
for chord_char in chord:
|
||||
if chord_char not in SLIM_CHARS:
|
||||
chord_length += 2
|
||||
else:
|
||||
chord_length += 1
|
||||
# Based on char width calculate width of tail
|
||||
for tail_char in tail:
|
||||
if tail_char not in SLIM_CHARS:
|
||||
tail_length += 2
|
||||
else:
|
||||
tail_length += 1
|
||||
# Based on char width calculate width of remainder
|
||||
for remainder_char in remainder:
|
||||
if remainder_char not in SLIM_CHARS:
|
||||
tail_length += 2
|
||||
else:
|
||||
tail_length += 1
|
||||
# If the chord is wider than the tail+remainder and the line goes on, some padding is needed
|
||||
if chord_length >= tail_length and end is None:
|
||||
# Decide if the padding should be "_" for drawing out words or spaces
|
||||
if tail:
|
||||
if not remainder:
|
||||
for c in range(math.ceil((chord_length - tail_length) / 2) + 2):
|
||||
whitespaces += '_'
|
||||
else:
|
||||
for c in range(chord_length - tail_length + 1):
|
||||
whitespaces += ' '
|
||||
else:
|
||||
if not remainder:
|
||||
for c in range(math.floor((chord_length - tail_length) / 2)):
|
||||
whitespaces += '_'
|
||||
else:
|
||||
for c in range(chord_length - tail_length + 1):
|
||||
whitespaces += ' '
|
||||
else:
|
||||
if not tail and remainder and remainder[0] == ' ':
|
||||
for c in range(chord_length):
|
||||
whitespaces += ' '
|
||||
if whitespaces:
|
||||
if '_' in whitespaces:
|
||||
ws_length = len(whitespaces)
|
||||
if ws_length == 1:
|
||||
whitespaces = '–'
|
||||
else:
|
||||
wsl_mod = ws_length // 2
|
||||
ws_right = ws_left = ' ' * wsl_mod
|
||||
whitespaces = ws_left + '–' + ws_right
|
||||
whitespaces = WHITESPACE_TEMPLATE.format(whitespaces=whitespaces)
|
||||
return CHORD_LINE_TEMPLATE.format(chord=html.escape(chord), tail=html.escape(tail), whitespace=whitespaces,
|
||||
remainder=html.escape(remainder))
|
||||
|
||||
|
||||
def render_chords(text):
|
||||
"""
|
||||
Render ChordPro tags
|
||||
|
||||
:param str text: The text containing the chords
|
||||
:returns str: The text containing the rendered chords
|
||||
"""
|
||||
text_lines = text.split('{br}')
|
||||
rendered_lines = []
|
||||
chords_on_prev_line = False
|
||||
for line in text_lines:
|
||||
# If a ChordPro is detected in the line, replace it with a html-span tag and wrap the line in a span tag.
|
||||
if '[' in line and ']' in line:
|
||||
if chords_on_prev_line:
|
||||
chord_template = CHORD_TEMPLATE
|
||||
else:
|
||||
chord_template = FIRST_CHORD_TEMPLATE
|
||||
chords_on_prev_line = True
|
||||
# Matches a chord, a tail, a remainder and a line end. See expand_and_align_chords_in_line() for more info.
|
||||
new_line = chord_template.format(chord=CHORD_LINE_MATCH.sub(render_chords_in_line, line))
|
||||
rendered_lines.append(new_line)
|
||||
else:
|
||||
chords_on_prev_line = False
|
||||
rendered_lines.append(html.escape(line))
|
||||
return '{br}'.join(rendered_lines)
|
||||
|
||||
|
||||
def compare_chord_lyric_width(chord, lyric):
|
||||
"""
|
||||
Compare the width of chord and lyrics. If chord width is greater than the lyric width the diff is returned.
|
||||
|
||||
:param chord:
|
||||
:param lyric:
|
||||
:return:
|
||||
"""
|
||||
chord_length = 0
|
||||
if chord == ' ':
|
||||
return 0
|
||||
chord = re.sub(r'\{.*?\}', r'', chord)
|
||||
lyric = re.sub(r'\{.*?\}', r'', lyric)
|
||||
for chord_char in chord:
|
||||
if chord_char not in SLIM_CHARS:
|
||||
chord_length += 2
|
||||
else:
|
||||
chord_length += 1
|
||||
lyriclen = 0
|
||||
for lyric_char in lyric:
|
||||
if lyric_char not in SLIM_CHARS:
|
||||
lyriclen += 2
|
||||
else:
|
||||
lyriclen += 1
|
||||
if chord_length > lyriclen:
|
||||
return chord_length - lyriclen
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def find_formatting_tags(text, active_formatting_tags):
|
||||
"""
|
||||
Look for formatting tags in lyrics and adds/removes them to/from the given list. Returns the update list.
|
||||
|
||||
:param text:
|
||||
:param active_formatting_tags:
|
||||
:return:
|
||||
"""
|
||||
if not re.search(r'\{.*?\}', text):
|
||||
return active_formatting_tags
|
||||
word_iterator = iter(text)
|
||||
# Loop through lyrics to find any formatting tags
|
||||
for char in word_iterator:
|
||||
if char == '{':
|
||||
tag = ''
|
||||
char = next(word_iterator)
|
||||
start_tag = True
|
||||
if char == '/':
|
||||
start_tag = False
|
||||
char = next(word_iterator)
|
||||
while char != '}':
|
||||
tag += char
|
||||
char = next(word_iterator)
|
||||
# See if the found tag has an end tag
|
||||
for formatting_tag in FormattingTags.get_html_tags():
|
||||
if formatting_tag['start tag'] == '{' + tag + '}':
|
||||
if formatting_tag['end tag']:
|
||||
if start_tag:
|
||||
# prepend the new tag to the list of active formatting tags
|
||||
active_formatting_tags[:0] = [tag]
|
||||
else:
|
||||
# remove the tag from the list
|
||||
active_formatting_tags.remove(tag)
|
||||
# Break out of the loop matching the found tag against the tag list.
|
||||
break
|
||||
return active_formatting_tags
|
||||
|
||||
|
||||
def render_chords_for_printing(text, line_split):
|
||||
"""
|
||||
Render ChordPro tags for printing
|
||||
|
||||
:param str text: The text containing the chords to be rendered.
|
||||
:param str line_split: The character(s) used to split lines
|
||||
:returns str: The rendered chords
|
||||
"""
|
||||
if not re.search(r'\[.*?\]', text):
|
||||
return text
|
||||
text_lines = text.split(line_split)
|
||||
rendered_text_lines = []
|
||||
for line in text_lines:
|
||||
# If a ChordPro is detected in the line, build html tables.
|
||||
new_line = '<table class="line" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td>'
|
||||
active_formatting_tags = []
|
||||
if re.search(r'\[.*?\]', line):
|
||||
words = line.split(' ')
|
||||
in_chord = False
|
||||
for word in words:
|
||||
chords = []
|
||||
lyrics = []
|
||||
new_line += '<table class="segment" cellpadding="0" cellspacing="0" border="0" align="left">'
|
||||
# If the word contains a chord, we need to handle it.
|
||||
if re.search(r'\[.*?\]', word):
|
||||
chord = ''
|
||||
lyric = ''
|
||||
# Loop over each character of the word
|
||||
for char in word:
|
||||
if char == '[':
|
||||
in_chord = True
|
||||
if lyric != '':
|
||||
if chord == '':
|
||||
chord = ' '
|
||||
chords.append(chord)
|
||||
lyrics.append(lyric)
|
||||
chord = ''
|
||||
lyric = ''
|
||||
elif char == ']' and in_chord:
|
||||
in_chord = False
|
||||
elif in_chord:
|
||||
chord += char
|
||||
else:
|
||||
lyric += char
|
||||
if lyric != '' or chord != '':
|
||||
if chord == '':
|
||||
chord = ' '
|
||||
if lyric == '':
|
||||
lyric = ' '
|
||||
chords.append(chord)
|
||||
lyrics.append(lyric)
|
||||
new_chord_line = '<tr class="chordrow">'
|
||||
new_lyric_line = '</tr><tr>'
|
||||
for i in range(len(lyrics)):
|
||||
spacer = compare_chord_lyric_width(chords[i], lyrics[i])
|
||||
# Handle formatting tags
|
||||
start_formatting_tags = ''
|
||||
if active_formatting_tags:
|
||||
start_formatting_tags = '{' + '}{'.join(active_formatting_tags) + '}'
|
||||
# Update list of active formatting tags
|
||||
active_formatting_tags = find_formatting_tags(lyrics[i], active_formatting_tags)
|
||||
end_formatting_tags = ''
|
||||
if active_formatting_tags:
|
||||
end_formatting_tags = '{/' + '}{/'.join(active_formatting_tags) + '}'
|
||||
new_chord_line += '<td class="chord">%s</td>' % chords[i]
|
||||
# Check if this is the last column, if so skip spacing calc and instead insert a single space
|
||||
if i + 1 == len(lyrics):
|
||||
new_lyric_line += '<td class="lyrics">{starttags}{lyrics} {endtags}</td>'.format(
|
||||
starttags=start_formatting_tags, lyrics=lyrics[i], endtags=end_formatting_tags)
|
||||
else:
|
||||
spacing = ''
|
||||
if spacer > 0:
|
||||
space = ' ' * int(math.ceil(spacer / 2))
|
||||
spacing = '<span class="chordspacing">%s-%s</span>' % (space, space)
|
||||
new_lyric_line += '<td class="lyrics">{starttags}{lyrics}{spacing}{endtags}</td>'.format(
|
||||
starttags=start_formatting_tags, lyrics=lyrics[i], spacing=spacing,
|
||||
endtags=end_formatting_tags)
|
||||
new_line += new_chord_line + new_lyric_line + '</tr>'
|
||||
else:
|
||||
start_formatting_tags = ''
|
||||
if active_formatting_tags:
|
||||
start_formatting_tags = '{' + '}{'.join(active_formatting_tags) + '}'
|
||||
active_formatting_tags = find_formatting_tags(word, active_formatting_tags)
|
||||
end_formatting_tags = ''
|
||||
if active_formatting_tags:
|
||||
end_formatting_tags = '{/' + '}{/'.join(active_formatting_tags) + '}'
|
||||
new_line += '<tr class="chordrow"><td class="chord"> </td></tr><tr><td class="lyrics">' \
|
||||
'{starttags}{lyrics} {endtags}</td></tr>'.format(
|
||||
starttags=start_formatting_tags, lyrics=word, endtags=end_formatting_tags)
|
||||
new_line += '</table>'
|
||||
else:
|
||||
new_line += line
|
||||
new_line += '</td></tr></table>'
|
||||
rendered_text_lines.append(new_line)
|
||||
# the {br} tag used to split lines is not inserted again since the style of the line-tables makes them redundant.
|
||||
return ''.join(rendered_text_lines)
|
||||
|
||||
|
||||
def render_tags(text, can_render_chords=False, is_printing=False):
|
||||
"""
|
||||
The :func:`~openlp.core.display.render.render_tags` function takes a stirng with OpenLP-style tags in it
|
||||
and replaces them with the HTML version.
|
||||
|
||||
:param str text: The string with OpenLP-style tags to be rendered.
|
||||
:param bool can_render_chords: Should the chords be rendererd?
|
||||
:param bool is_printing: Are we going to print this?
|
||||
:returns str: The HTML version of the tags is returned as a string.
|
||||
"""
|
||||
if can_render_chords:
|
||||
if is_printing:
|
||||
text = render_chords_for_printing(text, '{br}')
|
||||
else:
|
||||
text = render_chords(text)
|
||||
for tag in FormattingTags.get_html_tags():
|
||||
text = text.replace(tag['start tag'], tag['start html'])
|
||||
text = text.replace(tag['end tag'], tag['end html'])
|
||||
return text
|
@ -2,9 +2,7 @@ import logging
|
||||
import os
|
||||
import json
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
|
||||
|
||||
from openlp.core.display.webengine import WebEngineView
|
||||
from PyQt5 import QtCore, QtWidgets, QtWebChannel
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -79,6 +77,8 @@ class DisplayWindow(QtWidgets.QWidget):
|
||||
Create the display window
|
||||
"""
|
||||
super(DisplayWindow, self).__init__(parent)
|
||||
# Need to import this inline to get around a QtWebEngine issue
|
||||
from openlp.core.display.webengine import WebEngineView
|
||||
self._is_initialised = False
|
||||
self._fbo = None
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint)
|
||||
@ -114,26 +114,6 @@ class DisplayWindow(QtWidgets.QWidget):
|
||||
"""
|
||||
self.run_javascript('Display.init();')
|
||||
|
||||
def add_script_source(self, fname, source):
|
||||
"""
|
||||
Add a script of source code
|
||||
"""
|
||||
js = QtWebEngineWidgets.QWebEngineScript()
|
||||
js.setSourceCode(source)
|
||||
js.setName(fname)
|
||||
js.setWorldId(QtWebEngineWidgets.QWebEngineScript.MainWorld)
|
||||
self.webview.page().scripts().insert(js)
|
||||
|
||||
def add_script(self, fname):
|
||||
"""
|
||||
Add a script to the page
|
||||
"""
|
||||
js_file = QtCore.QFile(fname)
|
||||
if not js_file.open(QtCore.QIODevice.ReadOnly):
|
||||
log.warning('Could not open %s: %s', fname, js_file.errorString())
|
||||
return
|
||||
self.add_script_source(os.path.basename(fname), str(bytes(js_file.readAll()), 'utf-8'))
|
||||
|
||||
def run_javascript(self, script, is_sync=False):
|
||||
"""
|
||||
Run some Javascript in the WebView
|
||||
|
@ -23,21 +23,16 @@
|
||||
The :mod:`lib` module contains most of the components and libraries that make
|
||||
OpenLP work.
|
||||
"""
|
||||
import html
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import math
|
||||
|
||||
from PyQt5 import QtCore, QtGui, Qt, QtWidgets
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import translate
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
log = logging.getLogger(__name__ + '.__init__')
|
||||
|
||||
SLIMCHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\'
|
||||
|
||||
|
||||
class ServiceItemContext(object):
|
||||
"""
|
||||
@ -287,309 +282,6 @@ def check_item_selected(list_widget, message):
|
||||
return True
|
||||
|
||||
|
||||
def clean_tags(text, remove_chords=False):
|
||||
"""
|
||||
Remove Tags from text for display
|
||||
|
||||
:param text: Text to be cleaned
|
||||
:param remove_chords: Clean ChordPro tags
|
||||
"""
|
||||
text = text.replace('<br>', '\n')
|
||||
text = text.replace('{br}', '\n')
|
||||
text = text.replace(' ', ' ')
|
||||
for tag in FormattingTags.get_html_tags():
|
||||
text = text.replace(tag['start tag'], '')
|
||||
text = text.replace(tag['end tag'], '')
|
||||
# Remove ChordPro tags
|
||||
if remove_chords:
|
||||
text = re.sub(r'\[.+?\]', r'', text)
|
||||
return text
|
||||
|
||||
|
||||
def expand_tags(text, expand_chord_tags=False, for_printing=False):
|
||||
"""
|
||||
Expand tags HTML for display
|
||||
|
||||
:param text: The text to be expanded.
|
||||
"""
|
||||
if expand_chord_tags:
|
||||
if for_printing:
|
||||
text = expand_chords_for_printing(text, '{br}')
|
||||
else:
|
||||
text = expand_chords(text)
|
||||
for tag in FormattingTags.get_html_tags():
|
||||
text = text.replace(tag['start tag'], tag['start html'])
|
||||
text = text.replace(tag['end tag'], tag['end html'])
|
||||
return text
|
||||
|
||||
|
||||
def expand_and_align_chords_in_line(match):
|
||||
"""
|
||||
Expand the chords in the line and align them using whitespaces.
|
||||
NOTE: There is equivalent javascript code in chords.js, in the updateSlide function. Make sure to update both!
|
||||
|
||||
:param match:
|
||||
:return: The line with expanded html-chords
|
||||
"""
|
||||
whitespaces = ''
|
||||
chordlen = 0
|
||||
taillen = 0
|
||||
# The match could be "[G]sweet the " from a line like "A[D]mazing [D7]grace! How [G]sweet the [D]sound!"
|
||||
# The actual chord, would be "G" in match "[G]sweet the "
|
||||
chord = match.group(1)
|
||||
# The tailing word of the chord, would be "sweet" in match "[G]sweet the "
|
||||
tail = match.group(2)
|
||||
# The remainder of the line, until line end or next chord. Would be " the " in match "[G]sweet the "
|
||||
remainder = match.group(3)
|
||||
# Line end if found, else None
|
||||
end = match.group(4)
|
||||
# Based on char width calculate width of chord
|
||||
for chord_char in chord:
|
||||
if chord_char not in SLIMCHARS:
|
||||
chordlen += 2
|
||||
else:
|
||||
chordlen += 1
|
||||
# Based on char width calculate width of tail
|
||||
for tail_char in tail:
|
||||
if tail_char not in SLIMCHARS:
|
||||
taillen += 2
|
||||
else:
|
||||
taillen += 1
|
||||
# Based on char width calculate width of remainder
|
||||
for remainder_char in remainder:
|
||||
if remainder_char not in SLIMCHARS:
|
||||
taillen += 2
|
||||
else:
|
||||
taillen += 1
|
||||
# If the chord is wider than the tail+remainder and the line goes on, some padding is needed
|
||||
if chordlen >= taillen and end is None:
|
||||
# Decide if the padding should be "_" for drawing out words or spaces
|
||||
if tail:
|
||||
if not remainder:
|
||||
for c in range(math.ceil((chordlen - taillen) / 2) + 2):
|
||||
whitespaces += '_'
|
||||
else:
|
||||
for c in range(chordlen - taillen + 1):
|
||||
whitespaces += ' '
|
||||
else:
|
||||
if not remainder:
|
||||
for c in range(math.floor((chordlen - taillen) / 2)):
|
||||
whitespaces += '_'
|
||||
else:
|
||||
for c in range(chordlen - taillen + 1):
|
||||
whitespaces += ' '
|
||||
else:
|
||||
if not tail and remainder and remainder[0] == ' ':
|
||||
for c in range(chordlen):
|
||||
whitespaces += ' '
|
||||
if whitespaces:
|
||||
if '_' in whitespaces:
|
||||
ws_length = len(whitespaces)
|
||||
if ws_length == 1:
|
||||
whitespaces = '–'
|
||||
else:
|
||||
wsl_mod = ws_length // 2
|
||||
ws_right = ws_left = ' ' * wsl_mod
|
||||
whitespaces = ws_left + '–' + ws_right
|
||||
whitespaces = '<span class="ws">' + whitespaces + '</span>'
|
||||
return '<span class="chord"><span><strong>' + html.escape(chord) + '</strong></span></span>' + html.escape(tail) + \
|
||||
whitespaces + html.escape(remainder)
|
||||
|
||||
|
||||
def expand_chords(text):
|
||||
"""
|
||||
Expand ChordPro tags
|
||||
|
||||
:param text:
|
||||
"""
|
||||
text_lines = text.split('{br}')
|
||||
expanded_text_lines = []
|
||||
chords_on_prev_line = False
|
||||
for line in text_lines:
|
||||
# If a ChordPro is detected in the line, replace it with a html-span tag and wrap the line in a span tag.
|
||||
if '[' in line and ']' in line:
|
||||
if chords_on_prev_line:
|
||||
new_line = '<span class="chordline">'
|
||||
else:
|
||||
new_line = '<span class="chordline firstchordline">'
|
||||
chords_on_prev_line = True
|
||||
# Matches a chord, a tail, a remainder and a line end. See expand_and_align_chords_in_line() for more info.
|
||||
new_line += re.sub(r'\[(.*?)\]([\u0080-\uFFFF,\w]*)'
|
||||
'([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?',
|
||||
expand_and_align_chords_in_line, line)
|
||||
new_line += '</span>'
|
||||
expanded_text_lines.append(new_line)
|
||||
else:
|
||||
chords_on_prev_line = False
|
||||
expanded_text_lines.append(html.escape(line))
|
||||
return '{br}'.join(expanded_text_lines)
|
||||
|
||||
|
||||
def compare_chord_lyric(chord, lyric):
|
||||
"""
|
||||
Compare the width of chord and lyrics. If chord width is greater than the lyric width the diff is returned.
|
||||
|
||||
:param chord:
|
||||
:param lyric:
|
||||
:return:
|
||||
"""
|
||||
chordlen = 0
|
||||
if chord == ' ':
|
||||
return 0
|
||||
chord = re.sub(r'\{.*?\}', r'', chord)
|
||||
lyric = re.sub(r'\{.*?\}', r'', lyric)
|
||||
for chord_char in chord:
|
||||
if chord_char not in SLIMCHARS:
|
||||
chordlen += 2
|
||||
else:
|
||||
chordlen += 1
|
||||
lyriclen = 0
|
||||
for lyric_char in lyric:
|
||||
if lyric_char not in SLIMCHARS:
|
||||
lyriclen += 2
|
||||
else:
|
||||
lyriclen += 1
|
||||
if chordlen > lyriclen:
|
||||
return chordlen - lyriclen
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def find_formatting_tags(text, active_formatting_tags):
|
||||
"""
|
||||
Look for formatting tags in lyrics and adds/removes them to/from the given list. Returns the update list.
|
||||
|
||||
:param text:
|
||||
:param active_formatting_tags:
|
||||
:return:
|
||||
"""
|
||||
if not re.search(r'\{.*?\}', text):
|
||||
return active_formatting_tags
|
||||
word_it = iter(text)
|
||||
# Loop through lyrics to find any formatting tags
|
||||
for char in word_it:
|
||||
if char == '{':
|
||||
tag = ''
|
||||
char = next(word_it)
|
||||
start_tag = True
|
||||
if char == '/':
|
||||
start_tag = False
|
||||
char = next(word_it)
|
||||
while char != '}':
|
||||
tag += char
|
||||
char = next(word_it)
|
||||
# See if the found tag has an end tag
|
||||
for formatting_tag in FormattingTags.get_html_tags():
|
||||
if formatting_tag['start tag'] == '{' + tag + '}':
|
||||
if formatting_tag['end tag']:
|
||||
if start_tag:
|
||||
# prepend the new tag to the list of active formatting tags
|
||||
active_formatting_tags[:0] = [tag]
|
||||
else:
|
||||
# remove the tag from the list
|
||||
active_formatting_tags.remove(tag)
|
||||
# Break out of the loop matching the found tag against the tag list.
|
||||
break
|
||||
return active_formatting_tags
|
||||
|
||||
|
||||
def expand_chords_for_printing(text, line_split):
|
||||
"""
|
||||
Expand ChordPro tags
|
||||
|
||||
:param text:
|
||||
:param line_split:
|
||||
"""
|
||||
if not re.search(r'\[.*?\]', text):
|
||||
return text
|
||||
text_lines = text.split(line_split)
|
||||
expanded_text_lines = []
|
||||
for line in text_lines:
|
||||
# If a ChordPro is detected in the line, build html tables.
|
||||
new_line = '<table class="line" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td>'
|
||||
active_formatting_tags = []
|
||||
if re.search(r'\[.*?\]', line):
|
||||
words = line.split(' ')
|
||||
in_chord = False
|
||||
for word in words:
|
||||
chords = []
|
||||
lyrics = []
|
||||
new_line += '<table class="segment" cellpadding="0" cellspacing="0" border="0" align="left">'
|
||||
# If the word contains a chord, we need to handle it.
|
||||
if re.search(r'\[.*?\]', word):
|
||||
chord = ''
|
||||
lyric = ''
|
||||
# Loop over each character of the word
|
||||
for char in word:
|
||||
if char == '[':
|
||||
in_chord = True
|
||||
if lyric != '':
|
||||
if chord == '':
|
||||
chord = ' '
|
||||
chords.append(chord)
|
||||
lyrics.append(lyric)
|
||||
chord = ''
|
||||
lyric = ''
|
||||
elif char == ']' and in_chord:
|
||||
in_chord = False
|
||||
elif in_chord:
|
||||
chord += char
|
||||
else:
|
||||
lyric += char
|
||||
if lyric != '' or chord != '':
|
||||
if chord == '':
|
||||
chord = ' '
|
||||
if lyric == '':
|
||||
lyric = ' '
|
||||
chords.append(chord)
|
||||
lyrics.append(lyric)
|
||||
new_chord_line = '<tr class="chordrow">'
|
||||
new_lyric_line = '</tr><tr>'
|
||||
for i in range(len(lyrics)):
|
||||
spacer = compare_chord_lyric(chords[i], lyrics[i])
|
||||
# Handle formatting tags
|
||||
start_formatting_tags = ''
|
||||
if active_formatting_tags:
|
||||
start_formatting_tags = '{' + '}{'.join(active_formatting_tags) + '}'
|
||||
# Update list of active formatting tags
|
||||
active_formatting_tags = find_formatting_tags(lyrics[i], active_formatting_tags)
|
||||
end_formatting_tags = ''
|
||||
if active_formatting_tags:
|
||||
end_formatting_tags = '{/' + '}{/'.join(active_formatting_tags) + '}'
|
||||
new_chord_line += '<td class="chord">%s</td>' % chords[i]
|
||||
# Check if this is the last column, if so skip spacing calc and instead insert a single space
|
||||
if i + 1 == len(lyrics):
|
||||
new_lyric_line += '<td class="lyrics">{starttags}{lyrics} {endtags}</td>'.format(
|
||||
starttags=start_formatting_tags, lyrics=lyrics[i], endtags=end_formatting_tags)
|
||||
else:
|
||||
spacing = ''
|
||||
if spacer > 0:
|
||||
space = ' ' * int(math.ceil(spacer / 2))
|
||||
spacing = '<span class="chordspacing">%s-%s</span>' % (space, space)
|
||||
new_lyric_line += '<td class="lyrics">{starttags}{lyrics}{spacing}{endtags}</td>'.format(
|
||||
starttags=start_formatting_tags, lyrics=lyrics[i], spacing=spacing,
|
||||
endtags=end_formatting_tags)
|
||||
new_line += new_chord_line + new_lyric_line + '</tr>'
|
||||
else:
|
||||
start_formatting_tags = ''
|
||||
if active_formatting_tags:
|
||||
start_formatting_tags = '{' + '}{'.join(active_formatting_tags) + '}'
|
||||
active_formatting_tags = find_formatting_tags(word, active_formatting_tags)
|
||||
end_formatting_tags = ''
|
||||
if active_formatting_tags:
|
||||
end_formatting_tags = '{/' + '}{/'.join(active_formatting_tags) + '}'
|
||||
new_line += '<tr class="chordrow"><td class="chord"> </td></tr><tr><td class="lyrics">' \
|
||||
'{starttags}{lyrics} {endtags}</td></tr>'.format(
|
||||
starttags=start_formatting_tags, lyrics=word, endtags=end_formatting_tags)
|
||||
new_line += '</table>'
|
||||
else:
|
||||
new_line += line
|
||||
new_line += '</td></tr></table>'
|
||||
expanded_text_lines.append(new_line)
|
||||
# the {br} tag used to split lines is not inserted again since the style of the line-tables makes them redundant.
|
||||
return ''.join(expanded_text_lines)
|
||||
|
||||
|
||||
def create_separated_list(string_list):
|
||||
"""
|
||||
Returns a string that represents a join of a list of strings with a localized separator.
|
||||
|
@ -34,7 +34,8 @@ import ntpath
|
||||
from PyQt5 import QtGui
|
||||
|
||||
from openlp.core.common import RegistryProperties, Settings, translate, AppLocation, md5_hash
|
||||
from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, expand_chords, create_thumb
|
||||
from openlp.core.display.render import remove_tags, render_tags, render_chords
|
||||
from openlp.core.lib import ImageSource, build_icon
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -246,7 +247,7 @@ class ServiceItem(RegistryProperties):
|
||||
self.renderer.set_item_theme(self.theme)
|
||||
self.theme_data, self.main, self.footer = self.renderer.pre_render()
|
||||
if self.service_item_type == ServiceItemType.Text:
|
||||
expand_chord_tags = hasattr(self, 'name') and self.name == 'songs' and Settings().value(
|
||||
can_render_chords = hasattr(self, 'name') and self.name == 'songs' and Settings().value(
|
||||
'songs/enable chords')
|
||||
log.debug('Formatting slides: {title}'.format(title=self.title))
|
||||
# Save rendered pages to this dict. In the case that a slide is used twice we can use the pages saved to
|
||||
@ -261,13 +262,13 @@ class ServiceItem(RegistryProperties):
|
||||
previous_pages[verse_tag] = (slide['raw_slide'], pages)
|
||||
for page in pages:
|
||||
page = page.replace('<br>', '{br}')
|
||||
html_data = expand_tags(page.rstrip(), expand_chord_tags)
|
||||
html_data = render_tags(page.rstrip(), can_render_chords)
|
||||
new_frame = {
|
||||
'title': clean_tags(page),
|
||||
'text': clean_tags(page.rstrip(), expand_chord_tags),
|
||||
'chords_text': expand_chords(clean_tags(page.rstrip(), False)),
|
||||
'title': remove_tags(page),
|
||||
'text': remove_tags(page.rstrip(), can_render_chords),
|
||||
'chords_text': render_chords(remove_tags(page.rstrip(), False)),
|
||||
'html': html_data.replace('&nbsp;', ' '),
|
||||
'printing_html': expand_tags(html.escape(page.rstrip()), expand_chord_tags, True),
|
||||
'printing_html': render_tags(html.escape(page.rstrip()), can_render_chords, is_printing=True),
|
||||
'verseTag': verse_tag,
|
||||
}
|
||||
self._display_frames.append(new_frame)
|
||||
@ -275,7 +276,7 @@ class ServiceItem(RegistryProperties):
|
||||
pass
|
||||
else:
|
||||
log.error('Invalid value renderer: {item}'.format(item=self.service_item_type))
|
||||
self.title = clean_tags(self.title)
|
||||
self.title = remove_tags(self.title)
|
||||
# The footer should never be None, but to be compatible with a few
|
||||
# nightly builds between 1.9.4 and 1.9.5, we have to correct this to
|
||||
# avoid tracebacks.
|
||||
|
@ -101,7 +101,6 @@ from .filerenameform import FileRenameForm
|
||||
from .starttimeform import StartTimeForm
|
||||
from .servicenoteform import ServiceNoteForm
|
||||
from .serviceitemeditform import ServiceItemEditForm
|
||||
from .slidecontroller import SlideController, DisplayController, PreviewController, LiveController
|
||||
from .splashscreen import SplashScreen
|
||||
from .generaltab import GeneralTab
|
||||
from .themestab import ThemesTab
|
||||
|
@ -42,8 +42,9 @@ from openlp.core.common.actions import ActionList, CategoryOrder
|
||||
from openlp.core.common.path import Path, copyfile, path_to_str, str_to_path
|
||||
from openlp.core.lib import PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
|
||||
from openlp.core.lib.ui import create_action
|
||||
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
|
||||
ShortcutListForm, FormattingTagForm, PreviewController
|
||||
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, PluginForm, ShortcutListForm, \
|
||||
FormattingTagForm
|
||||
from openlp.core.ui.slidecontroller import LiveController, PreviewController
|
||||
from openlp.core.ui.firsttimeform import FirstTimeForm
|
||||
from openlp.core.ui.media import MediaController
|
||||
from openlp.core.ui.printserviceform import PrintServiceForm
|
||||
@ -51,7 +52,6 @@ from openlp.core.ui.projector.manager import ProjectorManager
|
||||
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
|
||||
from openlp.core.display import Renderer
|
||||
from openlp.core.ui.style import PROGRESSBAR_STYLE, get_library_stylesheet
|
||||
from openlp.core.version import get_version
|
||||
|
||||
@ -510,7 +510,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
# Set up the path with plugins
|
||||
PluginManager(self)
|
||||
ImageManager()
|
||||
Renderer()
|
||||
# Set up the interface
|
||||
self.setupUi(self)
|
||||
# Define the media Dock Manager
|
||||
|
@ -1,312 +0,0 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`~openlp.core.ui.media.webkit` module contains our WebKit video player
|
||||
"""
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtGui, QtWebEngineWidgets
|
||||
|
||||
from openlp.core.common import Settings
|
||||
from openlp.core.lib import translate
|
||||
from openlp.core.ui.media import MediaState
|
||||
from openlp.core.ui.media.mediaplayer import MediaPlayer
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
VIDEO_CSS = """
|
||||
#videobackboard {
|
||||
z-index:3;
|
||||
background-color: %(bgcolor)s;
|
||||
}
|
||||
#video {
|
||||
background-color: %(bgcolor)s;
|
||||
z-index:4;
|
||||
}
|
||||
"""
|
||||
|
||||
VIDEO_JS = """
|
||||
function show_video(state, path, volume, variable_value){
|
||||
// Sometimes video.currentTime stops slightly short of video.duration and video.ended is intermittent!
|
||||
|
||||
var video = document.getElementById('video');
|
||||
if(volume != null){
|
||||
video.volume = volume;
|
||||
}
|
||||
switch(state){
|
||||
case 'load':
|
||||
video.src = 'file:///' + path;
|
||||
video.load();
|
||||
break;
|
||||
case 'play':
|
||||
video.play();
|
||||
break;
|
||||
case 'pause':
|
||||
video.pause();
|
||||
break;
|
||||
case 'stop':
|
||||
show_video('pause');
|
||||
video.currentTime = 0;
|
||||
break;
|
||||
case 'close':
|
||||
show_video('stop');
|
||||
video.src = '';
|
||||
break;
|
||||
case 'length':
|
||||
return video.duration;
|
||||
case 'current_time':
|
||||
return video.currentTime;
|
||||
case 'seek':
|
||||
video.currentTime = variable_value;
|
||||
break;
|
||||
case 'isEnded':
|
||||
return video.ended;
|
||||
case 'setVisible':
|
||||
video.style.visibility = variable_value;
|
||||
break;
|
||||
case 'setBackBoard':
|
||||
var back = document.getElementById('videobackboard');
|
||||
back.style.visibility = variable_value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
VIDEO_HTML = """
|
||||
<div id="videobackboard" class="size" style="visibility:hidden"></div>
|
||||
<video id="video" class="size" style="visibility:hidden" autobuffer preload></video>
|
||||
"""
|
||||
|
||||
VIDEO_EXT = ['*.3gp', '*.3gpp', '*.3g2', '*.3gpp2', '*.aac', '*.flv', '*.f4a', '*.f4b', '*.f4p', '*.f4v', '*.mov',
|
||||
'*.m4a', '*.m4b', '*.m4p', '*.m4v', '*.mkv', '*.mp4', '*.ogv', '*.webm', '*.mpg', '*.wmv', '*.mpeg',
|
||||
'*.avi', '*.swf']
|
||||
|
||||
AUDIO_EXT = ['*.mp3', '*.ogg']
|
||||
|
||||
|
||||
class WebkitPlayer(MediaPlayer):
|
||||
"""
|
||||
A specialised version of the MediaPlayer class, which provides a QtWebKit
|
||||
display.
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Constructor
|
||||
"""
|
||||
super(WebkitPlayer, self).__init__(parent, 'webkit')
|
||||
self.original_name = 'WebKit'
|
||||
self.display_name = '&WebKit'
|
||||
self.parent = parent
|
||||
self.can_background = True
|
||||
self.audio_extensions_list = AUDIO_EXT
|
||||
self.video_extensions_list = VIDEO_EXT
|
||||
|
||||
def get_media_display_css(self):
|
||||
"""
|
||||
Add css style sheets to htmlbuilder
|
||||
"""
|
||||
background = QtGui.QColor(Settings().value('players/background color')).name()
|
||||
css = VIDEO_CSS % {'bgcolor': background}
|
||||
return css
|
||||
|
||||
def get_media_display_javascript(self):
|
||||
"""
|
||||
Add javascript functions to htmlbuilder
|
||||
"""
|
||||
return VIDEO_JS
|
||||
|
||||
def get_media_display_html(self):
|
||||
"""
|
||||
Add html code to htmlbuilder
|
||||
"""
|
||||
return VIDEO_HTML
|
||||
|
||||
def setup(self, display):
|
||||
"""
|
||||
Set up the player
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
display.web_view.resize(display.size())
|
||||
display.web_view.raise_()
|
||||
self.has_own_widget = False
|
||||
|
||||
def check_available(self):
|
||||
"""
|
||||
Check the availability of the media player.
|
||||
|
||||
:return: boolean. True if available
|
||||
"""
|
||||
web = QtWebEngineWidgets.QWebEnginePage()
|
||||
# This script should return '[object HTMLVideoElement]' if the html5 video is available in webkit. Otherwise it
|
||||
# should return '[object HTMLUnknownElement]'
|
||||
return web.runJavaScript(
|
||||
"Object.prototype.toString.call(document.createElement('video'));") == '[object HTMLVideoElement]'
|
||||
|
||||
def load(self, display):
|
||||
"""
|
||||
Load a video
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
log.debug('load vid in Webkit Controller')
|
||||
controller = display.controller
|
||||
if display.has_audio and not controller.media_info.is_background:
|
||||
volume = controller.media_info.volume
|
||||
vol = float(volume) / float(100)
|
||||
else:
|
||||
vol = 0
|
||||
path = controller.media_info.file_info.absoluteFilePath()
|
||||
display.web_view.setVisible(True)
|
||||
js = 'show_video("load", "{path}", {vol});'.format(path=path.replace('\\', '\\\\'), vol=str(vol))
|
||||
display.frame.runJavaScript(js)
|
||||
return True
|
||||
|
||||
def resize(self, display):
|
||||
"""
|
||||
Resize the player
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
display.web_view.resize(display.size())
|
||||
|
||||
def play(self, display):
|
||||
"""
|
||||
Play a video
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
controller = display.controller
|
||||
display.web_loaded = True
|
||||
start_time = 0
|
||||
if display.controller.is_live:
|
||||
if self.get_live_state() != MediaState.Paused and controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
else:
|
||||
if self.get_preview_state() != MediaState.Paused and controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
self.set_visible(display, True)
|
||||
display.frame.runJavaScript('show_video("play");')
|
||||
if start_time > 0:
|
||||
self.seek(display, controller.media_info.start_time * 1000)
|
||||
self.set_state(MediaState.Playing, display)
|
||||
display.web_view.raise_()
|
||||
return True
|
||||
|
||||
def pause(self, display):
|
||||
"""
|
||||
Pause a video
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
display.frame.runJavaScript('show_video("pause");')
|
||||
self.set_state(MediaState.Paused, display)
|
||||
|
||||
def stop(self, display):
|
||||
"""
|
||||
Stop a video
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
display.frame.runJavaScript('show_video("stop");')
|
||||
self.set_state(MediaState.Stopped, display)
|
||||
|
||||
def volume(self, display, volume):
|
||||
"""
|
||||
Set the volume
|
||||
|
||||
:param display: The display to be updated.
|
||||
:param volume: The volume to be set.
|
||||
"""
|
||||
# 1.0 is the highest value
|
||||
if display.has_audio:
|
||||
vol = float(volume) / float(100)
|
||||
display.frame.runJavaScript('show_video(null, null, %s);' % str(vol))
|
||||
|
||||
def seek(self, display, seek_value):
|
||||
"""
|
||||
Go to a position in the video
|
||||
|
||||
:param display: The display to be updated.
|
||||
:param seek_value: The value to be set.
|
||||
"""
|
||||
seek = float(seek_value) / 1000
|
||||
display.frame.runJavaScript('show_video("seek", null, null, null, "%f");' % seek)
|
||||
|
||||
def reset(self, display):
|
||||
"""
|
||||
Reset the player
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
display.frame.runJavaScript('show_video("close");')
|
||||
self.set_state(MediaState.Off, display)
|
||||
|
||||
def set_visible(self, display, visibility):
|
||||
"""
|
||||
Set the visibility
|
||||
|
||||
:param display: The display to be updated.
|
||||
:param visibility: The visibility to be set.
|
||||
"""
|
||||
if visibility:
|
||||
is_visible = "visible"
|
||||
else:
|
||||
is_visible = "hidden"
|
||||
display.frame.runJavaScript('show_video("setVisible", null, null, null, "%s");' % is_visible)
|
||||
|
||||
def update_ui(self, display):
|
||||
"""
|
||||
Update the UI
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
controller = display.controller
|
||||
if display.frame.runJavaScript('show_video("isEnded");'):
|
||||
self.stop(display)
|
||||
current_time = display.frame.runJavaScript('show_video("current_time");')
|
||||
# check if conversion was ok and value is not 'NaN'
|
||||
if current_time and current_time != float('inf'):
|
||||
current_time = int(current_time * 1000)
|
||||
length = display.frame.runJavaScript('show_video("length");')
|
||||
# check if conversion was ok and value is not 'NaN'
|
||||
if length and length != float('inf'):
|
||||
length = int(length * 1000)
|
||||
if current_time and length:
|
||||
controller.media_info.length = length
|
||||
controller.seek_slider.setMaximum(length)
|
||||
if not controller.seek_slider.isSliderDown():
|
||||
controller.seek_slider.blockSignals(True)
|
||||
controller.seek_slider.setSliderPosition(current_time)
|
||||
controller.seek_slider.blockSignals(False)
|
||||
|
||||
def get_info(self):
|
||||
"""
|
||||
Return some information about this player
|
||||
"""
|
||||
part1 = translate('Media.player', 'Webkit is a media player which runs inside a web browser. This player '
|
||||
'allows text over video to be rendered.')
|
||||
part2 = translate('Media.player', 'Audio')
|
||||
part3 = translate('Media.player', 'Video')
|
||||
return part1 + '<br/> <strong>' + part2 + '</strong><br/>' + str(AUDIO_EXT) + '<br/><strong>' + part3 + \
|
||||
'</strong><br/>' + str(VIDEO_EXT) + '<br/>'
|
@ -39,7 +39,7 @@ from openlp.core.lib.ui import create_action
|
||||
from openlp.core.ui.lib.toolbar import OpenLPToolbar
|
||||
from openlp.core.ui.lib.listpreviewwidget import ListPreviewWidget
|
||||
from openlp.core.ui import HideMode, DisplayControllerType
|
||||
from openlp.core.display import MainCanvas, Canvas
|
||||
from openlp.core.display.window import DisplayWindow
|
||||
|
||||
|
||||
# Threshold which has to be trespassed to toggle.
|
||||
@ -371,7 +371,7 @@ class SlideController(DisplayController, RegistryProperties):
|
||||
self.slide_layout.setSpacing(0)
|
||||
self.slide_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.slide_layout.setObjectName('SlideLayout')
|
||||
self.preview_display = Canvas(self)
|
||||
self.preview_display = DisplayWindow(self)
|
||||
self.slide_layout.insertWidget(0, self.preview_display)
|
||||
self.preview_display.hide()
|
||||
# Actual preview screen
|
||||
@ -581,7 +581,7 @@ class SlideController(DisplayController, RegistryProperties):
|
||||
# rebuild display as screen size changed
|
||||
if self.display:
|
||||
self.display.close()
|
||||
self.display = MainCanvas(self)
|
||||
self.display = DisplayWindow(self)
|
||||
self.display.setup()
|
||||
if self.is_live:
|
||||
self.__add_actions_to_widget(self.display)
|
||||
|
@ -31,8 +31,7 @@ from openlp.core.common import Registry, RegistryProperties, UiStrings, translat
|
||||
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui import ThemeLayoutForm
|
||||
from openlp.core.ui.media.webkitplayer import VIDEO_EXT
|
||||
from .themewizard import Ui_ThemeWizard
|
||||
from openlp.core.ui.themewizard import Ui_ThemeWizard
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -30,8 +30,9 @@ import re
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from openlp.core.common import AppLocation, CONTROL_CHARS, Settings
|
||||
from openlp.core.lib import translate, clean_tags
|
||||
from openlp.plugins.songs.lib.db import Author, MediaFile, Song, Topic
|
||||
from openlp.core.lib import translate
|
||||
from openlp.core.display.render import remove_tags
|
||||
from openlp.plugins.songs.lib.db import Author, MediaFile, Song
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -380,7 +381,7 @@ def clean_song(manager, song):
|
||||
if isinstance(song.lyrics, bytes):
|
||||
song.lyrics = str(song.lyrics, encoding='utf8')
|
||||
verses = SongXML().get_verses(song.lyrics)
|
||||
song.search_lyrics = ' '.join([clean_string(clean_tags(verse[1], True)) for verse in verses])
|
||||
song.search_lyrics = ' '.join([clean_string(remove_tags(verse[1], True)) for verse in verses])
|
||||
# The song does not have any author, add one.
|
||||
if not song.authors_songs:
|
||||
name = SongStrings.AuthorUnknown
|
||||
|
25
package.json
Normal file
25
package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "openlp",
|
||||
"version": "2.5.1",
|
||||
"description": "OpenLP is free church worship presentation software",
|
||||
"repository": "lp:openlp",
|
||||
"directories": {
|
||||
"test": "tests"
|
||||
},
|
||||
"dependencies": {
|
||||
"jasmine-core": "^2.6.4",
|
||||
"karma": "^1.7.0",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-jasmine": "^1.1.0",
|
||||
"karma-phantomjs-launcher": "^1.0.4",
|
||||
"phantomjs-prebuilt": "^2.1.14"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "karma start"
|
||||
},
|
||||
"author": "OpenLP Developers",
|
||||
"license": "GPL-2.0",
|
||||
"devDependencies": {
|
||||
"karma-log-reporter": "0.0.4"
|
||||
}
|
||||
}
|
194
tests/functional/openlp_core/display/test_render.py
Normal file
194
tests/functional/openlp_core/display/test_render.py
Normal file
@ -0,0 +1,194 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
Test the :mod:`~openlp.core.display.render` package.
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
from openlp.core.display.render import remove_tags, render_tags, render_chords, compare_chord_lyric_width, \
|
||||
render_chords_for_printing
|
||||
from openlp.core.lib.formattingtags import FormattingTags
|
||||
|
||||
|
||||
@patch('openlp.core.lib.FormattingTags.get_html_tags')
|
||||
def test_remove_tags(mocked_get_tags):
|
||||
"""
|
||||
Test remove_tags() method.
|
||||
"""
|
||||
# GIVEN: Mocked get_html_tags() method.
|
||||
mocked_get_tags.return_value = [{
|
||||
'desc': 'Black',
|
||||
'start tag': '{b}',
|
||||
'start html': '<span style="-webkit-text-fill-color:black">',
|
||||
'end tag': '{/b}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}]
|
||||
string_to_pass = 'ASDF<br>foo{br}bar {b}black{/b}'
|
||||
expected_string = 'ASDF\nfoo\nbar black'
|
||||
|
||||
# WHEN: Clean the string.
|
||||
result_string = remove_tags(string_to_pass)
|
||||
|
||||
# THEN: The strings should be identical.
|
||||
assert result_string == expected_string, 'The strings should be identical'
|
||||
|
||||
|
||||
@patch('openlp.core.lib.FormattingTags.get_html_tags')
|
||||
def test_render_tags(mocked_get_tags):
|
||||
"""
|
||||
Test the render_tags() method.
|
||||
"""
|
||||
# GIVEN: Mocked get_html_tags() method.
|
||||
mocked_get_tags.return_value = [
|
||||
{
|
||||
'desc': 'Black',
|
||||
'start tag': '{b}',
|
||||
'start html': '<span style="-webkit-text-fill-color:black">',
|
||||
'end tag': '{/b}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
},
|
||||
{
|
||||
'desc': 'Yellow',
|
||||
'start tag': '{y}',
|
||||
'start html': '<span style="-webkit-text-fill-color:yellow">',
|
||||
'end tag': '{/y}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
},
|
||||
{
|
||||
'desc': 'Green',
|
||||
'start tag': '{g}',
|
||||
'start html': '<span style="-webkit-text-fill-color:green">',
|
||||
'end tag': '{/g}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}
|
||||
]
|
||||
string_to_pass = '{b}black{/b}{y}yellow{/y}'
|
||||
expected_string = '<span style="-webkit-text-fill-color:black">black</span>' + \
|
||||
'<span style="-webkit-text-fill-color:yellow">yellow</span>'
|
||||
|
||||
# WHEN: Replace the tags.
|
||||
result_string = render_tags(string_to_pass)
|
||||
|
||||
# THEN: The strings should be identical.
|
||||
assert result_string == expected_string, 'The strings should be identical.'
|
||||
|
||||
|
||||
def test_render_chords():
|
||||
"""
|
||||
Test that the rendering of chords works as expected.
|
||||
"""
|
||||
# GIVEN: A lyrics-line with chords
|
||||
text_with_chords = 'H[C]alleluya.[F] [G]'
|
||||
|
||||
# WHEN: Expanding the chords
|
||||
text_with_rendered_chords = render_chords(text_with_chords)
|
||||
|
||||
# THEN: We should get html that looks like below
|
||||
expected_html = '<span class="chordline firstchordline">H<span class="chord"><span><strong>C</strong></span>' \
|
||||
'</span>alleluya.<span class="chord"><span><strong>F</strong></span></span><span class="ws">' \
|
||||
' </span> <span class="chord"><span><strong>G</strong></span></span></span>'
|
||||
assert text_with_rendered_chords == expected_html, 'The rendered chords should look as expected'
|
||||
|
||||
|
||||
def test_render_chords_with_special_chars(self):
|
||||
"""
|
||||
Test that the rendering of chords works as expected when special chars are involved.
|
||||
"""
|
||||
# GIVEN: A lyrics-line with chords
|
||||
text_with_chords = "I[D]'M NOT MOVED BY WHAT I SEE HALLE[F]LUJA[C]H"
|
||||
|
||||
# WHEN: Expanding the chords
|
||||
text_with_rendered_chords = render_tags(text_with_chords, can_render_chords=True)
|
||||
|
||||
# THEN: We should get html that looks like below
|
||||
expected_html = '<span class="chordline firstchordline">I<span class="chord"><span><strong>D</strong></span>' \
|
||||
'</span>'M NOT MOVED BY WHAT I SEE HALLE<span class="chord"><span><strong>F</strong>' \
|
||||
'</span></span>LUJA<span class="chord"><span><strong>C</strong></span></span>H</span>'
|
||||
assert text_with_rendered_chords == expected_html, 'The rendered chords should look as expected'
|
||||
|
||||
|
||||
def test_compare_chord_lyric_short_chord(self):
|
||||
"""
|
||||
Test that the chord/lyric comparing works.
|
||||
"""
|
||||
# GIVEN: A chord and some lyric
|
||||
chord = 'C'
|
||||
lyrics = 'alleluya'
|
||||
|
||||
# WHEN: Comparing the chord and lyrics
|
||||
ret = compare_chord_lyric_width(chord, lyrics)
|
||||
|
||||
# THEN: The returned value should 0 because the lyric is longer than the chord
|
||||
assert ret == 0, 'The returned value should 0 because the lyric is longer than the chord'
|
||||
|
||||
|
||||
def test_compare_chord_lyric_long_chord(self):
|
||||
"""
|
||||
Test that the chord/lyric comparing works.
|
||||
"""
|
||||
# GIVEN: A chord and some lyric
|
||||
chord = 'Gsus'
|
||||
lyrics = 'me'
|
||||
|
||||
# WHEN: Comparing the chord and lyrics
|
||||
ret = compare_chord_lyric_width(chord, lyrics)
|
||||
|
||||
# THEN: The returned value should 4 because the chord is longer than the lyric
|
||||
assert ret == 4, 'The returned value should 4 because the chord is longer than the lyric'
|
||||
|
||||
|
||||
def test_render_chords_for_printing(self):
|
||||
"""
|
||||
Test that the rendering of chords for printing works as expected.
|
||||
"""
|
||||
# GIVEN: A lyrics-line with chords
|
||||
text_with_chords = '{st}[D]Amazing {r}gr[D7]ace{/r} how [G]sweet the [D]sound [F]{/st}'
|
||||
FormattingTags.load_tags()
|
||||
|
||||
# WHEN: Expanding the chords
|
||||
text_with_rendered_chords = render_chords_for_printing(text_with_chords, '{br}')
|
||||
|
||||
# THEN: We should get html that looks like below
|
||||
expected_html = '<table class="line" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td><table ' \
|
||||
'class="segment" cellpadding="0" cellspacing="0" border="0" align="left"><tr class="chordrow">'\
|
||||
'<td class="chord"> </td><td class="chord">D</td></tr><tr><td class="lyrics">{st}{/st}' \
|
||||
'</td><td class="lyrics">{st}Amazing {/st}</td></tr></table><table class="segment" ' \
|
||||
'cellpadding="0" cellspacing="0" border="0" align="left"><tr class="chordrow">' \
|
||||
'<td class="chord"> </td><td class="chord">D7</td></tr><tr><td class="lyrics">{st}{r}gr' \
|
||||
'{/r}{/st}</td><td class="lyrics">{r}{st}ace{/r} {/st}</td></tr></table><table ' \
|
||||
'class="segment" cellpadding="0" cellspacing="0" border="0" align="left"><tr class="chordrow">'\
|
||||
'<td class="chord"> </td></tr><tr><td class="lyrics">{st} {/st}</td></tr></table>' \
|
||||
'<table class="segment" cellpadding="0" cellspacing="0" border="0" align="left"><tr ' \
|
||||
'class="chordrow"><td class="chord"> </td></tr><tr><td class="lyrics">{st}how {/st}' \
|
||||
'</td></tr></table><table class="segment" cellpadding="0" cellspacing="0" border="0" ' \
|
||||
'align="left"><tr class="chordrow"><td class="chord">G</td></tr><tr><td class="lyrics">{st}' \
|
||||
'sweet {/st}</td></tr></table><table class="segment" cellpadding="0" cellspacing="0" ' \
|
||||
'border="0" align="left"><tr class="chordrow"><td class="chord"> </td></tr><tr><td ' \
|
||||
'class="lyrics">{st}the {/st}</td></tr></table><table class="segment" cellpadding="0" ' \
|
||||
'cellspacing="0" border="0" align="left"><tr class="chordrow"><td class="chord">D</td></tr>' \
|
||||
'<tr><td class="lyrics">{st}sound {/st}</td></tr></table><table class="segment" ' \
|
||||
'cellpadding="0" cellspacing="0" border="0" align="left"><tr class="chordrow"><td ' \
|
||||
'class="chord"> </td></tr><tr><td class="lyrics">{st} {/st}</td></tr></table>' \
|
||||
'<table class="segment" cellpadding="0" cellspacing="0" border="0" align="left"><tr ' \
|
||||
'class="chordrow"><td class="chord">F</td></tr><tr><td class="lyrics">{st}{/st} </td>' \
|
||||
'</tr></table></td></tr></table>'
|
||||
assert text_with_rendered_chords == expected_html, 'The rendered chords should look as expected!'
|
@ -4,11 +4,11 @@ Package to test the openlp.core.lib.htmlbuilder module.
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from PyQt5 import QtCore, QtWebKit
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import Settings
|
||||
from openlp.core.lib.htmlbuilder import build_html, build_background_css, build_lyrics_css, build_lyrics_outline_css, \
|
||||
build_lyrics_format_css, build_footer_css, webkit_version, build_chords_css
|
||||
build_lyrics_format_css, build_footer_css, build_chords_css
|
||||
from openlp.core.lib.theme import HorizontalType, VerticalType
|
||||
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
@ -446,16 +446,6 @@ class Htmbuilder(TestCase, TestMixin):
|
||||
self.assertEqual(FOOTER_CSS_INVALID, css[0], 'The footer strings should be blank.')
|
||||
self.assertEqual(FOOTER_CSS_INVALID, css[1], 'The footer strings should be blank.')
|
||||
|
||||
def test_webkit_version(self):
|
||||
"""
|
||||
Test the webkit_version() function
|
||||
"""
|
||||
# GIVEN: Webkit
|
||||
webkit_ver = float(QtWebKit.qWebKitVersion())
|
||||
# WHEN: Retrieving the webkit version
|
||||
# THEN: Webkit versions should match
|
||||
self.assertEquals(webkit_version(), webkit_ver, "The returned webkit version doesn't match the installed one")
|
||||
|
||||
def test_build_chords_css(self):
|
||||
"""
|
||||
Test the build_chords_css() function
|
||||
|
@ -23,16 +23,14 @@
|
||||
Package to test the openlp.core.lib package.
|
||||
"""
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from PyQt5 import QtCore, QtGui
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.core.lib import FormattingTags, build_icon, check_item_selected, clean_tags, compare_chord_lyric, \
|
||||
create_separated_list, create_thumb, expand_chords, expand_chords_for_printing, expand_tags, find_formatting_tags, \
|
||||
get_text_file_string, image_to_byte, resize_image, str_to_bool, validate_thumb
|
||||
from openlp.core.lib import FormattingTags, build_icon, check_item_selected, create_separated_list, create_thumb, \
|
||||
find_formatting_tags, get_text_file_string, image_to_byte, resize_image, str_to_bool, validate_thumb
|
||||
|
||||
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
|
||||
|
||||
@ -529,67 +527,6 @@ class TestLib(TestCase):
|
||||
MockedQtWidgets.QMessageBox.information.assert_called_with('parent', 'mocked translate', 'message')
|
||||
self.assertFalse(result, 'The result should be False')
|
||||
|
||||
def test_clean_tags(self):
|
||||
"""
|
||||
Test clean_tags() method.
|
||||
"""
|
||||
with patch('openlp.core.lib.FormattingTags.get_html_tags') as mocked_get_tags:
|
||||
# GIVEN: Mocked get_html_tags() method.
|
||||
mocked_get_tags.return_value = [{
|
||||
'desc': 'Black',
|
||||
'start tag': '{b}',
|
||||
'start html': '<span style="-webkit-text-fill-color:black">',
|
||||
'end tag': '{/b}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}]
|
||||
string_to_pass = 'ASDF<br>foo{br}bar {b}black{/b}'
|
||||
wanted_string = 'ASDF\nfoo\nbar black'
|
||||
|
||||
# WHEN: Clean the string.
|
||||
result_string = clean_tags(string_to_pass)
|
||||
|
||||
# THEN: The strings should be identical.
|
||||
self.assertEqual(wanted_string, result_string, 'The strings should be identical')
|
||||
|
||||
def test_expand_tags(self):
|
||||
"""
|
||||
Test the expand_tags() method.
|
||||
"""
|
||||
with patch('openlp.core.lib.FormattingTags.get_html_tags') as mocked_get_tags:
|
||||
# GIVEN: Mocked get_html_tags() method.
|
||||
mocked_get_tags.return_value = [
|
||||
{
|
||||
'desc': 'Black',
|
||||
'start tag': '{b}',
|
||||
'start html': '<span style="-webkit-text-fill-color:black">',
|
||||
'end tag': '{/b}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
},
|
||||
{
|
||||
'desc': 'Yellow',
|
||||
'start tag': '{y}',
|
||||
'start html': '<span style="-webkit-text-fill-color:yellow">',
|
||||
'end tag': '{/y}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
},
|
||||
{
|
||||
'desc': 'Green',
|
||||
'start tag': '{g}',
|
||||
'start html': '<span style="-webkit-text-fill-color:green">',
|
||||
'end tag': '{/g}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}
|
||||
]
|
||||
string_to_pass = '{b}black{/b}{y}yellow{/y}'
|
||||
wanted_string = '<span style="-webkit-text-fill-color:black">black</span>' + \
|
||||
'<span style="-webkit-text-fill-color:yellow">yellow</span>'
|
||||
|
||||
# WHEN: Replace the tags.
|
||||
result_string = expand_tags(string_to_pass)
|
||||
|
||||
# THEN: The strings should be identical.
|
||||
self.assertEqual(wanted_string, result_string, 'The strings should be identical.')
|
||||
|
||||
def test_validate_thumb_file_does_not_exist(self):
|
||||
"""
|
||||
Test the validate_thumb() function when the thumbnail does not exist
|
||||
@ -753,67 +690,6 @@ class TestLib(TestCase):
|
||||
self.assertEqual(string_result, 'Author 1, Author 2 and Author 3', 'The string should be "Author 1, '
|
||||
'Author 2, and Author 3".')
|
||||
|
||||
def test_expand_chords(self):
|
||||
"""
|
||||
Test that the expanding of chords works as expected.
|
||||
"""
|
||||
# GIVEN: A lyrics-line with chords
|
||||
text_with_chords = 'H[C]alleluya.[F] [G]'
|
||||
|
||||
# WHEN: Expanding the chords
|
||||
text_with_expanded_chords = expand_chords(text_with_chords)
|
||||
|
||||
# THEN: We should get html that looks like below
|
||||
expected_html = '<span class="chordline firstchordline">H<span class="chord"><span><strong>C</strong></span>' \
|
||||
'</span>alleluya.<span class="chord"><span><strong>F</strong></span></span><span class="ws">' \
|
||||
' </span> <span class="chord"><span><strong>G</strong></span></span></span>'
|
||||
self.assertEqual(expected_html, text_with_expanded_chords, 'The expanded chords should look as expected!')
|
||||
|
||||
def test_expand_chords2(self):
|
||||
"""
|
||||
Test that the expanding of chords works as expected when special chars are involved.
|
||||
"""
|
||||
import html
|
||||
# GIVEN: A lyrics-line with chords
|
||||
text_with_chords = "I[D]'M NOT MOVED BY WHAT I SEE HALLE[F]LUJA[C]H"
|
||||
|
||||
# WHEN: Expanding the chords
|
||||
text_with_expanded_chords = expand_tags(text_with_chords, True)
|
||||
|
||||
# THEN: We should get html that looks like below
|
||||
expected_html = '<span class="chordline firstchordline">I<span class="chord"><span><strong>D</strong></span>' \
|
||||
'</span>'M NOT MOVED BY WHAT I SEE HALLE<span class="chord"><span><strong>F</strong>' \
|
||||
'</span></span>LUJA<span class="chord"><span><strong>C</strong></span></span>H</span>'
|
||||
self.assertEqual(expected_html, text_with_expanded_chords, 'The expanded chords should look as expected!')
|
||||
|
||||
def test_compare_chord_lyric_short_chord(self):
|
||||
"""
|
||||
Test that the chord/lyric comparing works.
|
||||
"""
|
||||
# GIVEN: A chord and some lyric
|
||||
chord = 'C'
|
||||
lyrics = 'alleluya'
|
||||
|
||||
# WHEN: Comparing the chord and lyrics
|
||||
ret = compare_chord_lyric(chord, lyrics)
|
||||
|
||||
# THEN: The returned value should 0 because the lyric is longer than the chord
|
||||
self.assertEquals(0, ret, 'The returned value should 0 because the lyric is longer than the chord')
|
||||
|
||||
def test_compare_chord_lyric_long_chord(self):
|
||||
"""
|
||||
Test that the chord/lyric comparing works.
|
||||
"""
|
||||
# GIVEN: A chord and some lyric
|
||||
chord = 'Gsus'
|
||||
lyrics = 'me'
|
||||
|
||||
# WHEN: Comparing the chord and lyrics
|
||||
ret = compare_chord_lyric(chord, lyrics)
|
||||
|
||||
# THEN: The returned value should 4 because the chord is longer than the lyric
|
||||
self.assertEquals(4, ret, 'The returned value should 4 because the chord is longer than the lyric')
|
||||
|
||||
def test_find_formatting_tags(self):
|
||||
"""
|
||||
Test that find_formatting_tags works as expected
|
||||
@ -828,40 +704,3 @@ class TestLib(TestCase):
|
||||
|
||||
# THEN: The list of active tags should contain only 'st'
|
||||
self.assertListEqual(['st'], active_tags, 'The list of active tags should contain only "st"')
|
||||
|
||||
def test_expand_chords_for_printing(self):
|
||||
"""
|
||||
Test that the expanding of chords for printing works as expected.
|
||||
"""
|
||||
# GIVEN: A lyrics-line with chords
|
||||
text_with_chords = '{st}[D]Amazing {r}gr[D7]ace{/r} how [G]sweet the [D]sound [F]{/st}'
|
||||
FormattingTags.load_tags()
|
||||
|
||||
# WHEN: Expanding the chords
|
||||
text_with_expanded_chords = expand_chords_for_printing(text_with_chords, '{br}')
|
||||
|
||||
# THEN: We should get html that looks like below
|
||||
expected_html = '<table class="line" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td><table ' \
|
||||
'class="segment" cellpadding="0" cellspacing="0" border="0" align="left"><tr class="chordrow">'\
|
||||
'<td class="chord"> </td><td class="chord">D</td></tr><tr><td class="lyrics">{st}{/st}' \
|
||||
'</td><td class="lyrics">{st}Amazing {/st}</td></tr></table><table class="segment" ' \
|
||||
'cellpadding="0" cellspacing="0" border="0" align="left"><tr class="chordrow">' \
|
||||
'<td class="chord"> </td><td class="chord">D7</td></tr><tr><td class="lyrics">{st}{r}gr' \
|
||||
'{/r}{/st}</td><td class="lyrics">{r}{st}ace{/r} {/st}</td></tr></table><table ' \
|
||||
'class="segment" cellpadding="0" cellspacing="0" border="0" align="left"><tr class="chordrow">'\
|
||||
'<td class="chord"> </td></tr><tr><td class="lyrics">{st} {/st}</td></tr></table>' \
|
||||
'<table class="segment" cellpadding="0" cellspacing="0" border="0" align="left"><tr ' \
|
||||
'class="chordrow"><td class="chord"> </td></tr><tr><td class="lyrics">{st}how {/st}' \
|
||||
'</td></tr></table><table class="segment" cellpadding="0" cellspacing="0" border="0" ' \
|
||||
'align="left"><tr class="chordrow"><td class="chord">G</td></tr><tr><td class="lyrics">{st}' \
|
||||
'sweet {/st}</td></tr></table><table class="segment" cellpadding="0" cellspacing="0" ' \
|
||||
'border="0" align="left"><tr class="chordrow"><td class="chord"> </td></tr><tr><td ' \
|
||||
'class="lyrics">{st}the {/st}</td></tr></table><table class="segment" cellpadding="0" ' \
|
||||
'cellspacing="0" border="0" align="left"><tr class="chordrow"><td class="chord">D</td></tr>' \
|
||||
'<tr><td class="lyrics">{st}sound {/st}</td></tr></table><table class="segment" ' \
|
||||
'cellpadding="0" cellspacing="0" border="0" align="left"><tr class="chordrow"><td ' \
|
||||
'class="chord"> </td></tr><tr><td class="lyrics">{st} {/st}</td></tr></table>' \
|
||||
'<table class="segment" cellpadding="0" cellspacing="0" border="0" align="left"><tr ' \
|
||||
'class="chordrow"><td class="chord">F</td></tr><tr><td class="lyrics">{st}{/st} </td>' \
|
||||
'</tr></table></td></tr></table>'
|
||||
self.assertEqual(expected_html, text_with_expanded_chords, 'The expanded chords should look as expected!')
|
||||
|
@ -1,207 +0,0 @@
|
||||
# -*- 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.ui.renderer package.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import Registry
|
||||
from openlp.core.lib import Renderer, ScreenList, ServiceItem, FormattingTags
|
||||
from openlp.core.lib.renderer import words_split, get_start_tags
|
||||
from openlp.core.lib.theme import Theme
|
||||
|
||||
|
||||
SCREEN = {
|
||||
'primary': False,
|
||||
'number': 1,
|
||||
'size': QtCore.QRect(0, 0, 1024, 768)
|
||||
}
|
||||
|
||||
|
||||
# WARNING: Leave formatting alone - this is how it's returned in renderer.py
|
||||
CSS_TEST_ONE = """<!DOCTYPE html><html><head><script>
|
||||
function show_text(newtext) {
|
||||
var main = document.getElementById('main');
|
||||
main.innerHTML = newtext;
|
||||
// We need to be sure that the page is loaded, that is why we
|
||||
// return the element's height (even though we do not use the
|
||||
// returned value).
|
||||
return main.offsetHeight;
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
*{margin: 0; padding: 0; border: 0;}
|
||||
#main {position: absolute; top: 0px; FORMAT CSS; OUTLINE CSS; }
|
||||
</style></head>
|
||||
<body><div id="main"></div></body></html>'"""
|
||||
|
||||
|
||||
class TestRenderer(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up the components need for all tests
|
||||
"""
|
||||
# Mocked out desktop object
|
||||
self.desktop = MagicMock()
|
||||
self.desktop.primaryScreen.return_value = SCREEN['primary']
|
||||
self.desktop.screenCount.return_value = SCREEN['number']
|
||||
self.desktop.screenGeometry.return_value = SCREEN['size']
|
||||
self.screens = ScreenList.create(self.desktop)
|
||||
Registry.create()
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Delete QApplication.
|
||||
"""
|
||||
del self.screens
|
||||
|
||||
def test_default_screen_layout(self):
|
||||
"""
|
||||
Test the default layout calculations
|
||||
"""
|
||||
# GIVEN: A new renderer instance.
|
||||
renderer = Renderer()
|
||||
# WHEN: given the default screen size has been created.
|
||||
# THEN: The renderer have created a default screen.
|
||||
self.assertEqual(renderer.width, 1024, 'The base renderer should be a live controller')
|
||||
self.assertEqual(renderer.height, 768, 'The base renderer should be a live controller')
|
||||
self.assertEqual(renderer.screen_ratio, 0.75, 'The base renderer should be a live controller')
|
||||
self.assertEqual(renderer.footer_start, 691, 'The base renderer should be a live controller')
|
||||
|
||||
@patch('openlp.core.lib.renderer.FormattingTags.get_html_tags')
|
||||
def test_get_start_tags(self, mocked_get_html_tags):
|
||||
"""
|
||||
Test the get_start_tags() method
|
||||
"""
|
||||
# GIVEN: A new renderer instance. Broken raw_text (missing closing tags).
|
||||
given_raw_text = '{st}{r}Text text text'
|
||||
expected_tuple = ('{st}{r}Text text text{/r}{/st}', '{st}{r}',
|
||||
'<strong><span style="-webkit-text-fill-color:red">')
|
||||
mocked_get_html_tags.return_value = [{'temporary': False, 'end tag': '{/r}', 'desc': 'Red',
|
||||
'start html': '<span style="-webkit-text-fill-color:red">',
|
||||
'end html': '</span>', 'start tag': '{r}', 'protected': True},
|
||||
{'temporary': False, 'end tag': '{/st}', 'desc': 'Bold',
|
||||
'start html': '<strong>', 'end html': '</strong>', 'start tag': '{st}',
|
||||
'protected': True}]
|
||||
|
||||
# WHEN: The renderer converts the start tags
|
||||
result = get_start_tags(given_raw_text)
|
||||
|
||||
# THEN: Check if the correct tuple is returned.
|
||||
self.assertEqual(result, expected_tuple), 'A tuple should be returned containing the text with correct ' \
|
||||
'tags, the opening tags, and the opening html tags.'
|
||||
|
||||
def test_word_split(self):
|
||||
"""
|
||||
Test the word_split() method
|
||||
"""
|
||||
# GIVEN: A line of text
|
||||
given_line = 'beginning asdf \n end asdf'
|
||||
expected_words = ['beginning', 'asdf', 'end', 'asdf']
|
||||
|
||||
# WHEN: Split the line based on word split rules
|
||||
result_words = words_split(given_line)
|
||||
|
||||
# THEN: The word lists should be the same.
|
||||
self.assertListEqual(result_words, expected_words)
|
||||
|
||||
def test_format_slide_logical_split(self):
|
||||
"""
|
||||
Test that a line with text and a logic break does not break the renderer just returns the input
|
||||
"""
|
||||
# GIVEN: A line of with a space text and the logical split
|
||||
renderer = Renderer()
|
||||
renderer.empty_height = 480
|
||||
given_line = 'a\n[---]\nb'
|
||||
expected_words = ['a<br>[---]<br>b']
|
||||
service_item = ServiceItem(None)
|
||||
|
||||
# WHEN: Split the line based on word split rules
|
||||
result_words = renderer.format_slide(given_line, service_item)
|
||||
|
||||
# THEN: The word lists should be the same.
|
||||
self.assertListEqual(result_words, expected_words)
|
||||
|
||||
def test_format_slide_blank_before_split(self):
|
||||
"""
|
||||
Test that a line with blanks before the logical split at handled
|
||||
"""
|
||||
# GIVEN: A line of with a space before the logical split
|
||||
renderer = Renderer()
|
||||
renderer.empty_height = 480
|
||||
given_line = '\n [---]\n'
|
||||
expected_words = ['<br> [---]']
|
||||
service_item = ServiceItem(None)
|
||||
|
||||
# WHEN: Split the line based on word split rules
|
||||
result_words = renderer.format_slide(given_line, service_item)
|
||||
|
||||
# THEN: The blanks have been removed.
|
||||
self.assertListEqual(result_words, expected_words)
|
||||
|
||||
def test_format_slide_blank_after_split(self):
|
||||
"""
|
||||
Test that a line with blanks before the logical split at handled
|
||||
"""
|
||||
# GIVEN: A line of with a space after the logical split
|
||||
renderer = Renderer()
|
||||
renderer.empty_height = 480
|
||||
given_line = '\n[---] \n'
|
||||
expected_words = ['<br>[---] ']
|
||||
service_item = ServiceItem(None)
|
||||
|
||||
# WHEN: Split the line based on word split rules
|
||||
result_words = renderer.format_slide(given_line, service_item)
|
||||
|
||||
# THEN: The blanks have been removed.
|
||||
self.assertListEqual(result_words, expected_words)
|
||||
|
||||
@patch('openlp.core.lib.renderer.QtWebKitWidgets.QWebView')
|
||||
@patch('openlp.core.lib.renderer.build_lyrics_format_css')
|
||||
@patch('openlp.core.lib.renderer.build_lyrics_outline_css')
|
||||
@patch('openlp.core.lib.renderer.build_chords_css')
|
||||
def test_set_text_rectangle(self, mock_build_chords_css, mock_outline_css, mock_lyrics_css, mock_webview):
|
||||
"""
|
||||
Test set_text_rectangle returns a proper html string
|
||||
"""
|
||||
# GIVEN: test object and data
|
||||
mock_lyrics_css.return_value = ' FORMAT CSS; '
|
||||
mock_outline_css.return_value = ' OUTLINE CSS; '
|
||||
mock_build_chords_css.return_value = ' CHORDS CSS; '
|
||||
theme_data = Theme()
|
||||
theme_data.font_main_name = 'Arial'
|
||||
theme_data.font_main_size = 20
|
||||
theme_data.font_main_color = '#FFFFFF'
|
||||
theme_data.font_main_outline_color = '#FFFFFF'
|
||||
main = QtCore.QRect(10, 10, 1280, 900)
|
||||
foot = QtCore.QRect(10, 1000, 1260, 24)
|
||||
renderer = Renderer()
|
||||
|
||||
# WHEN: Calling method
|
||||
renderer._set_text_rectangle(theme_data=theme_data, rect_main=main, rect_footer=foot)
|
||||
|
||||
# THEN: QtWebKitWidgets should be called with the proper string
|
||||
mock_webview.setHtml.called_with(CSS_TEST_ONE, 'Should be the same')
|
@ -56,7 +56,6 @@ class TestMainWindow(TestCase, TestMixin):
|
||||
patch('openlp.core.ui.mainwindow.QtWidgets.QToolBox') as mocked_q_tool_box_class, \
|
||||
patch('openlp.core.ui.mainwindow.QtWidgets.QMainWindow.addDockWidget') as mocked_add_dock_method, \
|
||||
patch('openlp.core.ui.mainwindow.ThemeManager') as mocked_theme_manager, \
|
||||
patch('openlp.core.ui.mainwindow.Renderer') as mocked_renderer, \
|
||||
patch('openlp.core.ui.mainwindow.websockets.WebSocketServer') as mocked_websocketserver, \
|
||||
patch('openlp.core.ui.mainwindow.server.HttpServer') as mocked_httpserver:
|
||||
self.mocked_settings_form = mocked_settings_form
|
||||
@ -67,7 +66,6 @@ class TestMainWindow(TestCase, TestMixin):
|
||||
self.mocked_q_tool_box_class = mocked_q_tool_box_class
|
||||
self.mocked_add_dock_method = mocked_add_dock_method
|
||||
self.mocked_theme_manager = mocked_theme_manager
|
||||
self.mocked_renderer = mocked_renderer
|
||||
self.main_window = MainWindow()
|
||||
|
||||
def tearDown(self):
|
||||
|
@ -28,9 +28,9 @@ from unittest.mock import MagicMock, patch
|
||||
from PyQt5 import QtCore, QtGui
|
||||
|
||||
from openlp.core import Registry
|
||||
from openlp.core.lib import ImageSource, ServiceItemAction
|
||||
from openlp.core.ui import SlideController, LiveController, PreviewController
|
||||
from openlp.core.ui.slidecontroller import InfoLabel, WIDE_MENU, NON_TEXT_MENU
|
||||
from openlp.core.lib import ServiceItemAction
|
||||
from openlp.core.ui.slidecontroller import WIDE_MENU, NON_TEXT_MENU, InfoLabel, SlideController, LiveController, \
|
||||
PreviewController
|
||||
|
||||
|
||||
class TestSlideController(TestCase):
|
||||
|
@ -1,68 +0,0 @@
|
||||
# -*- 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.ui.media.webkitplayer package.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from openlp.core.ui.media.webkitplayer import WebkitPlayer
|
||||
|
||||
|
||||
class TestWebkitPlayer(TestCase):
|
||||
"""
|
||||
Test the functions in the :mod:`webkitplayer` module.
|
||||
"""
|
||||
|
||||
def test_check_available_video_disabled(self):
|
||||
"""
|
||||
Test of webkit video unavailability
|
||||
"""
|
||||
# GIVEN: A WebkitPlayer instance and a mocked QWebPage
|
||||
mocked_qwebpage = MagicMock()
|
||||
mocked_qwebpage.mainFrame().runJavaScript.return_value = '[object HTMLUnknownElement]'
|
||||
with patch('openlp.core.ui.media.webkitplayer.QtWebKitWidgets.QWebPage', **{'return_value': mocked_qwebpage}):
|
||||
webkit_player = WebkitPlayer(None)
|
||||
|
||||
# WHEN: An checking if the player is available
|
||||
available = webkit_player.check_available()
|
||||
|
||||
# THEN: The player should not be available when '[object HTMLUnknownElement]' is returned
|
||||
self.assertEqual(False, available,
|
||||
'The WebkitPlayer should not be available when video feature detection fails')
|
||||
|
||||
def test_check_available_video_enabled(self):
|
||||
"""
|
||||
Test of webkit video availability
|
||||
"""
|
||||
# GIVEN: A WebkitPlayer instance and a mocked QWebPage
|
||||
mocked_qwebpage = MagicMock()
|
||||
mocked_qwebpage.runJavaScript.return_value = '[object HTMLVideoElement]'
|
||||
with patch('openlp.core.ui.media.webkitplayer.QtWebKitWidgets.QWebPage', **{'return_value': mocked_qwebpage}):
|
||||
webkit_player = WebkitPlayer(None)
|
||||
|
||||
# WHEN: An checking if the player is available
|
||||
available = webkit_player.check_available()
|
||||
|
||||
# THEN: The player should be available when '[object HTMLVideoElement]' is returned
|
||||
self.assertEqual(True, available,
|
||||
'The WebkitPlayer should be available when video feature detection passes')
|
@ -57,7 +57,6 @@ class TestMainWindow(TestCase, TestMixin):
|
||||
patch('openlp.core.ui.mainwindow.ServiceManager') as mocked_service_manager, \
|
||||
patch('openlp.core.ui.mainwindow.ThemeManager') as mocked_theme_manager, \
|
||||
patch('openlp.core.ui.mainwindow.ProjectorManager') as mocked_projector_manager, \
|
||||
patch('openlp.core.ui.mainwindow.Renderer') as mocked_renderer, \
|
||||
patch('openlp.core.ui.mainwindow.websockets.WebSocketServer') as mocked_websocketserver, \
|
||||
patch('openlp.core.ui.mainwindow.server.HttpServer') as mocked_httpserver:
|
||||
self.main_window = MainWindow()
|
||||
|
@ -25,16 +25,14 @@
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry
|
||||
from openlp.core.lib import ScreenList, ServiceItem, ItemCapabilities
|
||||
from openlp.core.ui.mainwindow import MainWindow
|
||||
from openlp.core.ui.servicemanager import ServiceManagerList
|
||||
from openlp.core.lib.serviceitem import ServiceItem
|
||||
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class TestServiceManager(TestCase, TestMixin):
|
||||
|
||||
@ -57,7 +55,6 @@ class TestServiceManager(TestCase, TestMixin):
|
||||
patch('openlp.core.ui.mainwindow.QtWidgets.QMainWindow.addDockWidget') as mocked_add_dock_method, \
|
||||
patch('openlp.core.ui.mainwindow.ThemeManager') as mocked_theme_manager, \
|
||||
patch('openlp.core.ui.mainwindow.ProjectorManager') as mocked_projector_manager, \
|
||||
patch('openlp.core.ui.mainwindow.Renderer') as mocked_renderer, \
|
||||
patch('openlp.core.ui.mainwindow.websockets.WebSocketServer') as mocked_websocketserver, \
|
||||
patch('openlp.core.ui.mainwindow.server.HttpServer') as mocked_httpserver:
|
||||
self.main_window = MainWindow()
|
||||
|
5
tests/js/fake_webchannel.js
Normal file
5
tests/js/fake_webchannel.js
Normal file
@ -0,0 +1,5 @@
|
||||
// This is a mock QWebChannel
|
||||
var qt = {webChannelTransport: 1};
|
||||
var QWebChannel = function (transport, callback) {
|
||||
callback({objects: {mediaWatcher: {}}});
|
||||
};
|
84
tests/js/polyfill.js
Normal file
84
tests/js/polyfill.js
Normal file
@ -0,0 +1,84 @@
|
||||
/**
|
||||
* This file contains polyfills, implementing functionality that exists in WebEngine,
|
||||
* but isn't yet supported by PhantomJS.
|
||||
*
|
||||
* These polyfills have been taken from Mozilla Developer Network.
|
||||
*/
|
||||
// Production steps of ECMA-262, Edition 6, 22.1.2.1
|
||||
if (!Array.from) {
|
||||
Array.from = (function () {
|
||||
var toStr = Object.prototype.toString;
|
||||
var isCallable = function (fn) {
|
||||
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
|
||||
};
|
||||
var toInteger = function (value) {
|
||||
var number = Number(value);
|
||||
if (isNaN(number)) { return 0; }
|
||||
if (number === 0 || !isFinite(number)) { return number; }
|
||||
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
|
||||
};
|
||||
var maxSafeInteger = Math.pow(2, 53) - 1;
|
||||
var toLength = function (value) {
|
||||
var len = toInteger(value);
|
||||
return Math.min(Math.max(len, 0), maxSafeInteger);
|
||||
};
|
||||
|
||||
// The length property of the from method is 1.
|
||||
return function from(arrayLike/*, mapFn, thisArg */) {
|
||||
// 1. Let C be the this value.
|
||||
var C = this;
|
||||
|
||||
// 2. Let items be ToObject(arrayLike).
|
||||
var items = Object(arrayLike);
|
||||
|
||||
// 3. ReturnIfAbrupt(items).
|
||||
if (arrayLike == null) {
|
||||
throw new TypeError('Array.from requires an array-like object - not null or undefined');
|
||||
}
|
||||
|
||||
// 4. If mapfn is undefined, then let mapping be false.
|
||||
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
|
||||
var T;
|
||||
if (typeof mapFn !== 'undefined') {
|
||||
// 5. else
|
||||
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
|
||||
if (!isCallable(mapFn)) {
|
||||
throw new TypeError('Array.from: when provided, the second argument must be a function');
|
||||
}
|
||||
|
||||
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
||||
if (arguments.length > 2) {
|
||||
T = arguments[2];
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Let lenValue be Get(items, "length").
|
||||
// 11. Let len be ToLength(lenValue).
|
||||
var len = toLength(items.length);
|
||||
|
||||
// 13. If IsConstructor(C) is true, then
|
||||
// 13. a. Let A be the result of calling the [[Construct]] internal method
|
||||
// of C with an argument list containing the single item len.
|
||||
// 14. a. Else, Let A be ArrayCreate(len).
|
||||
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
|
||||
|
||||
// 16. Let k be 0.
|
||||
var k = 0;
|
||||
// 17. Repeat, while k < len… (also steps a - h)
|
||||
var kValue;
|
||||
while (k < len) {
|
||||
kValue = items[k];
|
||||
if (mapFn) {
|
||||
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
|
||||
} else {
|
||||
A[k] = kValue;
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
// 18. Let putStatus be Put(A, "length", len, true).
|
||||
A.length = len;
|
||||
// 20. Return A.
|
||||
return A;
|
||||
};
|
||||
}());
|
||||
}
|
597
tests/js/test_display.js
Normal file
597
tests/js/test_display.js
Normal file
@ -0,0 +1,597 @@
|
||||
describe("The enumeration object", function () {
|
||||
it("BackgroundType should exist", function () {
|
||||
expect(BackgroundType).toBeDefined();
|
||||
});
|
||||
|
||||
it("GradientType should exist", function () {
|
||||
expect(GradientType).toBeDefined();
|
||||
});
|
||||
|
||||
it("HorizontalAlign should exist", function () {
|
||||
expect(HorizontalAlign).toBeDefined();
|
||||
});
|
||||
|
||||
it("VerticalAlign should exist", function () {
|
||||
expect(VerticalAlign).toBeDefined();
|
||||
});
|
||||
|
||||
it("AudioState should exist", function () {
|
||||
expect(AudioState).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("The function", function () {
|
||||
it("$() should return the right element", function () {
|
||||
var div = document.createElement("div");
|
||||
div.setAttribute("id", "dollar-test");
|
||||
document.body.appendChild(div);
|
||||
expect($("#dollar-test")[0]).toBe(div);
|
||||
});
|
||||
|
||||
it("_buildLinearGradient() should build the correct string", function () {
|
||||
var gradient = _buildLinearGradient("left top", "left bottom", "#000", "#fff");
|
||||
expect(gradient).toBe("-webkit-gradient(linear, left top, left bottom, from(#000), to(#fff)) fixed");
|
||||
});
|
||||
|
||||
it("_buildRadialGradient() should build the correct string", function () {
|
||||
var gradient = _buildRadialGradient(10, "#000", "#fff");
|
||||
expect(gradient).toBe("-webkit-gradient(radial, 10 50%, 100, 10 50%, 10, from(#000), to(#fff)) fixed");
|
||||
});
|
||||
|
||||
it("_getStyle should return the correct style on an element", function () {
|
||||
var div = document.createElement("div");
|
||||
div.style.setProperty("width", "100px");
|
||||
div.setAttribute("id", "style-test");
|
||||
document.body.appendChild(div);
|
||||
expect(_getStyle($("#style-test")[0], "width")).toBe("100px");
|
||||
});
|
||||
|
||||
it("_nl2br should turn UNIX newlines into <br> tags", function () {
|
||||
var text = "Amazing grace, how sweet the sound\nThat saved a wretch like me";
|
||||
expect(_nl2br(text)).toEqual("Amazing grace, how sweet the sound<br>That saved a wretch like me");
|
||||
});
|
||||
|
||||
it("_nl2br should turn Windows newlines into <br> tags", function () {
|
||||
var text = "Amazing grace, how sweet the sound\r\nThat saved a wretch like me";
|
||||
expect(_nl2br(text)).toEqual("Amazing grace, how sweet the sound<br>That saved a wretch like me");
|
||||
});
|
||||
|
||||
it("_prepareText should turn verse text into a paragraph", function () {
|
||||
var text = "Amazing grace, how sweet the sound\nThat saved a wretch like me";
|
||||
expect(_prepareText(text)).toEqual("<p>Amazing grace, how sweet the sound<br>That saved a wretch like me</p>");
|
||||
});
|
||||
});
|
||||
|
||||
describe("The Display object", function () {
|
||||
it("should start with a blank _slides object", function () {
|
||||
expect(Display._slides).toEqual({});
|
||||
});
|
||||
|
||||
it("should have the correct Reveal config", function () {
|
||||
expect(Display._revealConfig).toEqual({
|
||||
margin: 0.0,
|
||||
minScale: 1.0,
|
||||
maxScale: 1.0,
|
||||
controls: false,
|
||||
progress: false,
|
||||
history: false,
|
||||
overview: false,
|
||||
center: false,
|
||||
help: false,
|
||||
transition: "slide",
|
||||
backgroundTransition: "fade",
|
||||
viewDistance: 9999,
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
});
|
||||
});
|
||||
|
||||
it("should have an init() method", function () {
|
||||
expect(Display.init).toBeDefined();
|
||||
});
|
||||
|
||||
it("should initialise Reveal when init is called", function () {
|
||||
spyOn(Reveal, "initialize");
|
||||
Display.init();
|
||||
expect(Reveal.initialize).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should have a reinit() method", function () {
|
||||
expect(Display.reinit).toBeDefined();
|
||||
});
|
||||
|
||||
it("should re-initialise Reveal when reinit is called", function () {
|
||||
spyOn(Reveal, "reinitialize");
|
||||
Display.reinit();
|
||||
expect(Reveal.reinitialize).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
document.body.innerHTML = "";
|
||||
var slidesDiv = document.createElement("div");
|
||||
slidesDiv.setAttribute("class", "slides");
|
||||
slidesDiv.innerHTML = "<section><p></p></section>";
|
||||
document.body.appendChild(slidesDiv);
|
||||
|
||||
Display.clearSlides();
|
||||
expect($(".slides")[0].innerHTML).toEqual("");
|
||||
expect(Display._slides).toEqual({});
|
||||
});
|
||||
|
||||
it("should have a correct goToSlide() method", function () {
|
||||
spyOn(Reveal, "slide");
|
||||
spyOn(Display, "_slides");
|
||||
Display._slides["v1"] = 0;
|
||||
|
||||
Display.goToSlide("v1");
|
||||
expect(Reveal.slide).toHaveBeenCalledWith(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.addTextSlide", function () {
|
||||
beforeEach(function() {
|
||||
document.body.innerHTML = "";
|
||||
var slidesDiv = document.createElement("div");
|
||||
slidesDiv.setAttribute("class", "slides");
|
||||
document.body.appendChild(slidesDiv);
|
||||
Display._slides = {};
|
||||
});
|
||||
|
||||
it("should add a new slide", function () {
|
||||
var verse = "v1", text = "Amazing grace,\nhow sweet the sound";
|
||||
spyOn(Display, "reinit");
|
||||
|
||||
Display.addTextSlide(verse, text);
|
||||
|
||||
expect(Display._slides[verse]).toEqual(0);
|
||||
expect($(".slides > section").length).toEqual(1);
|
||||
expect($(".slides > section")[0].innerHTML).toEqual(_prepareText(text));
|
||||
expect(Display.reinit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should add a new slide without calling reinit()", function () {
|
||||
var verse = "v1", text = "Amazing grace,\nhow sweet the sound";
|
||||
spyOn(Display, "reinit");
|
||||
|
||||
Display.addTextSlide(verse, text, false);
|
||||
|
||||
expect(Display._slides[verse]).toEqual(0);
|
||||
expect($(".slides > section").length).toEqual(1);
|
||||
expect($(".slides > section")[0].innerHTML).toEqual(_prepareText(text));
|
||||
expect(Display.reinit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should update an existing slide", function () {
|
||||
var verse = "v1", text = "Amazing grace, how sweet the sound\nThat saved a wretch like me";
|
||||
Display.addTextSlide(verse, "Amazing grace,\nhow sweet the sound", false);
|
||||
spyOn(Display, "reinit");
|
||||
|
||||
Display.addTextSlide(verse, text, true);
|
||||
|
||||
expect(Display._slides[verse]).toEqual(0);
|
||||
expect($(".slides > section").length).toEqual(1);
|
||||
expect($(".slides > section")[0].innerHTML).toEqual(_prepareText(text));
|
||||
expect(Display.reinit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.setTextSlides", function () {
|
||||
beforeEach(function() {
|
||||
document.body.innerHTML = "";
|
||||
var slidesDiv = document.createElement("div");
|
||||
slidesDiv.setAttribute("class", "slides");
|
||||
document.body.appendChild(slidesDiv);
|
||||
Display._slides = {};
|
||||
});
|
||||
|
||||
it("should add a list of slides", function () {
|
||||
var 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"
|
||||
},
|
||||
{
|
||||
"verse": "v2",
|
||||
"text": "'twas Grace that taught, my heart to fear\nAnd grace, my fears relieved.\n" +
|
||||
"How precious did that grace appear,\nthe hour I first believed."
|
||||
}
|
||||
];
|
||||
spyOn(Display, "clearSlides");
|
||||
spyOn(Display, "reinit");
|
||||
spyOn(Reveal, "slide");
|
||||
|
||||
Display.setTextSlides(slides);
|
||||
|
||||
expect(Display.clearSlides).toHaveBeenCalledTimes(1);
|
||||
expect(Display._slides["v1"]).toEqual(0);
|
||||
expect(Display._slides["v2"]).toEqual(1);
|
||||
expect($(".slides > section").length).toEqual(2);
|
||||
expect(Display.reinit).toHaveBeenCalledTimes(1);
|
||||
expect(Reveal.slide).toHaveBeenCalledWith(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.setImageSlides", function () {
|
||||
beforeEach(function() {
|
||||
document.body.innerHTML = "";
|
||||
var slidesDiv = document.createElement("div");
|
||||
slidesDiv.setAttribute("class", "slides");
|
||||
document.body.appendChild(slidesDiv);
|
||||
var backgroundDiv = document.createElement("div");
|
||||
backgroundDiv.setAttribute("id", "global-background");
|
||||
document.body.appendChild(backgroundDiv);
|
||||
Display._slides = {};
|
||||
});
|
||||
|
||||
it("should add a list of images", function () {
|
||||
var slides = [{"file": "file://openlp1.jpg"}, {"file": "file://openlp2.jpg"}];
|
||||
spyOn(Display, "clearSlides");
|
||||
spyOn(Display, "reinit");
|
||||
spyOn(Reveal, "slide");
|
||||
|
||||
Display.setImageSlides(slides);
|
||||
|
||||
expect(Display.clearSlides).toHaveBeenCalledTimes(1);
|
||||
expect(Display._slides["0"]).toEqual(0);
|
||||
expect(Display._slides["1"]).toEqual(1);
|
||||
expect($(".slides > section").length).toEqual(2);
|
||||
expect($(".slides > section > img").length).toEqual(2);
|
||||
expect($(".slides > section > img")[0].getAttribute("src")).toEqual("file://openlp1.jpg")
|
||||
expect($(".slides > section > img")[1].getAttribute("src")).toEqual("file://openlp2.jpg")
|
||||
expect(Display.reinit).toHaveBeenCalledTimes(1);
|
||||
expect(Reveal.slide).toHaveBeenCalledWith(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.setVideo", function () {
|
||||
beforeEach(function() {
|
||||
document.body.innerHTML = "";
|
||||
var slidesDiv = document.createElement("div");
|
||||
slidesDiv.setAttribute("class", "slides");
|
||||
document.body.appendChild(slidesDiv);
|
||||
var backgroundDiv = document.createElement("div");
|
||||
backgroundDiv.setAttribute("id", "global-background");
|
||||
document.body.appendChild(backgroundDiv);
|
||||
Display._slides = {};
|
||||
});
|
||||
|
||||
it("should add a video to the page", function () {
|
||||
var video = {"file": "file://video.mp4"};
|
||||
spyOn(Display, "clearSlides");
|
||||
spyOn(Display, "reinit");
|
||||
|
||||
Display.setVideo(video);
|
||||
|
||||
expect(Display.clearSlides).toHaveBeenCalledTimes(1);
|
||||
expect($(".slides > section").length).toEqual(1);
|
||||
expect($(".slides > section > video").length).toEqual(1);
|
||||
expect($(".slides > section > video")[0].src).toEqual("file://video.mp4")
|
||||
expect(Display.reinit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.playVideo", function () {
|
||||
var playCalled = false,
|
||||
mockVideo = {
|
||||
play: function () {
|
||||
playCalled = true;
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
spyOn(window, "$").and.returnValue([mockVideo]);
|
||||
});
|
||||
|
||||
it("should play the video when called", function () {
|
||||
Display.playVideo();
|
||||
expect(playCalled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.pauseVideo", function () {
|
||||
var pauseCalled = false,
|
||||
mockVideo = {
|
||||
pause: function () {
|
||||
pauseCalled = true;
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
spyOn(window, "$").and.returnValue([mockVideo]);
|
||||
});
|
||||
|
||||
it("should pause the video when called", function () {
|
||||
Display.pauseVideo();
|
||||
expect(pauseCalled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.stopVideo", function () {
|
||||
var pauseCalled = false,
|
||||
mockVideo = {
|
||||
pause: function () {
|
||||
pauseCalled = true;
|
||||
},
|
||||
currentTime: 10.0
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
spyOn(window, "$").and.returnValue([mockVideo]);
|
||||
});
|
||||
|
||||
it("should play the video when called", function () {
|
||||
Display.stopVideo();
|
||||
expect(pauseCalled).toEqual(true);
|
||||
expect(mockVideo.currentTime).toEqual(0.0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.seekVideo", function () {
|
||||
var mockVideo = {
|
||||
currentTime: 1.0
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
spyOn(window, "$").and.returnValue([mockVideo]);
|
||||
});
|
||||
|
||||
it("should seek to the specified position within the video when called", function () {
|
||||
Display.seekVideo(7.34);
|
||||
expect(mockVideo.currentTime).toEqual(7.34);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.setPlaybackRate", function () {
|
||||
var mockVideo = {
|
||||
playbackRate: 1.0
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
spyOn(window, "$").and.returnValue([mockVideo]);
|
||||
});
|
||||
|
||||
it("should set the playback rate of the video when called", function () {
|
||||
// Let's sound like chipmunks!
|
||||
Display.setPlaybackRate(1.25);
|
||||
expect(mockVideo.playbackRate).toEqual(1.25);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.setVideoVolume", function () {
|
||||
var mockVideo = {
|
||||
volume: 1.0
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
spyOn(window, "$").and.returnValue([mockVideo]);
|
||||
});
|
||||
|
||||
it("should set the correct volume of the video when called", function () {
|
||||
// Make it quiet
|
||||
Display.setVideoVolume(30);
|
||||
expect(mockVideo.volume).toEqual(0.3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Display.toggleVideoMute", function () {
|
||||
var mockVideo = {
|
||||
muted: false
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
spyOn(window, "$").and.returnValue([mockVideo]);
|
||||
});
|
||||
|
||||
it("should mute the video when called", function () {
|
||||
mockVideo.muted = false;
|
||||
Display.toggleVideoMute();
|
||||
expect(mockVideo.muted).toEqual(true);
|
||||
});
|
||||
|
||||
it("should unmute the video when called", function () {
|
||||
mockVideo.muted = true;
|
||||
Display.toggleVideoMute();
|
||||
expect(mockVideo.muted).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AudioPlayer", function () {
|
||||
var audioPlayer, audioElement;
|
||||
|
||||
beforeEach(function () {
|
||||
audioElement = {
|
||||
_eventListeners: {},
|
||||
_playing: false,
|
||||
_paused: false,
|
||||
_stopped: false,
|
||||
src: "",
|
||||
addEventListener: function (eventType, listener) {
|
||||
this._eventListeners[eventType] = this._eventListeners[eventType] || [];
|
||||
this._eventListeners[eventType].push(listener);
|
||||
},
|
||||
play: function () {
|
||||
this._playing = true;
|
||||
this._paused = false;
|
||||
this._stopped = false;
|
||||
},
|
||||
pause: function () {
|
||||
this._playing = false;
|
||||
this._paused = true;
|
||||
this._stopped = false;
|
||||
}
|
||||
};
|
||||
spyOn(document, "createElement").and.returnValue(audioElement);
|
||||
audioPlayer = new AudioPlayer();
|
||||
});
|
||||
|
||||
it("should create an object", function () {
|
||||
expect(audioPlayer).toBeDefined();
|
||||
expect(audioPlayer._audioElement).not.toBeNull();
|
||||
expect(audioPlayer._eventListeners).toEqual({});
|
||||
expect(audioPlayer._playlist).toEqual([]);
|
||||
expect(audioPlayer._currentTrack).toEqual(null);
|
||||
expect(audioPlayer._canRepeat).toEqual(false);
|
||||
expect(audioPlayer._state).toEqual(AudioState.Stopped);
|
||||
});
|
||||
|
||||
it("should call the correct method when _callListener is called", function () {
|
||||
var testCalled = false;
|
||||
function test(event) {
|
||||
testCalled = true;
|
||||
}
|
||||
audioPlayer._eventListeners["test"] = [test];
|
||||
audioPlayer._callListener({"type": "test"});
|
||||
expect(testCalled).toEqual(true);
|
||||
});
|
||||
|
||||
it("should log a warning when _callListener is called for an unknown event", function () {
|
||||
spyOn(console, "warn");
|
||||
audioPlayer._callListener({"type": "test"});
|
||||
expect(console.warn).toHaveBeenCalledWith("Received unknown event \"test\", doing nothing.");
|
||||
});
|
||||
|
||||
it("should add all the correct event listeners", function () {
|
||||
expectedListeners = {
|
||||
"ended": [audioPlayer.onEnded, audioPlayer._callListener],
|
||||
"timeupdate": [audioPlayer._callListener],
|
||||
"volumechange": [audioPlayer._callListener],
|
||||
"durationchange": [audioPlayer._callListener],
|
||||
"loadeddata": [audioPlayer._callListener]
|
||||
};
|
||||
expect(audioElement._eventListeners).toEqual(expectedListeners);
|
||||
});
|
||||
|
||||
it("should add the correct event listener when calling addEventListener", function () {
|
||||
function dummy () {};
|
||||
var expectedListeners = {
|
||||
"test": [dummy]
|
||||
};
|
||||
audioPlayer.addEventListener("test", dummy);
|
||||
expect(audioPlayer._eventListeners).toEqual(expectedListeners);
|
||||
});
|
||||
|
||||
it("should set call nextTrack when the onEnded listener is called", function () {
|
||||
spyOn(audioPlayer, "nextTrack");
|
||||
audioPlayer.onEnded({});
|
||||
expect(audioPlayer.nextTrack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should set the _canRepeat property when calling setCanRepeat", function () {
|
||||
audioPlayer.setCanRepeat(true);
|
||||
expect(audioPlayer._canRepeat).toEqual(true);
|
||||
});
|
||||
|
||||
it("should clear the playlist when clearTracks is called", function () {
|
||||
audioPlayer._playlist = ["one", "two", "three"];
|
||||
audioPlayer.clearTracks();
|
||||
expect(audioPlayer._playlist).toEqual([]);
|
||||
});
|
||||
|
||||
it("should add a track to the playlist when addTrack is called", function () {
|
||||
audioPlayer._playlist = [];
|
||||
audioPlayer.addTrack("one");
|
||||
expect(audioPlayer._playlist).toEqual(["one"]);
|
||||
});
|
||||
|
||||
it("should move to the first track when canRepeat is true and nextTrack is called", function () {
|
||||
spyOn(audioPlayer, "play");
|
||||
audioPlayer.addTrack("one");
|
||||
audioPlayer.addTrack("two");
|
||||
audioPlayer.setCanRepeat(true);
|
||||
audioPlayer._currentTrack = "two";
|
||||
|
||||
audioPlayer.nextTrack();
|
||||
|
||||
expect(audioPlayer.play).toHaveBeenCalledWith("one");
|
||||
});
|
||||
|
||||
it("should move to the next track when nextTrack is called", function () {
|
||||
spyOn(audioPlayer, "play");
|
||||
audioPlayer.addTrack("one");
|
||||
audioPlayer.addTrack("two");
|
||||
audioPlayer._currentTrack = "one";
|
||||
|
||||
audioPlayer.nextTrack();
|
||||
|
||||
expect(audioPlayer.play).toHaveBeenCalledWith("two");
|
||||
});
|
||||
|
||||
it("should stop when canRepeat is false and nextTrack is called on the last track in the list", function () {
|
||||
spyOn(audioPlayer, "play");
|
||||
spyOn(audioPlayer, "stop");
|
||||
audioPlayer.addTrack("one");
|
||||
audioPlayer.addTrack("two");
|
||||
audioPlayer.setCanRepeat(false);
|
||||
audioPlayer._currentTrack = "two";
|
||||
|
||||
audioPlayer.nextTrack();
|
||||
|
||||
expect(audioPlayer.play).not.toHaveBeenCalled();
|
||||
expect(audioPlayer.stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should play the first track when nextTrack is called when no songs are playing", function () {
|
||||
spyOn(audioPlayer, "play");
|
||||
audioPlayer.addTrack("one");
|
||||
audioPlayer.nextTrack();
|
||||
expect(audioPlayer.play).toHaveBeenCalledWith("one");
|
||||
});
|
||||
|
||||
it("should log a warning when nextTrack is called when no songs are in the playlist", function () {
|
||||
spyOn(console, "warn");
|
||||
audioPlayer.nextTrack();
|
||||
expect(console.warn).toHaveBeenCalledWith("No tracks in playlist, doing nothing.");
|
||||
});
|
||||
|
||||
it("should play the track specified when play is called with a filename", function () {
|
||||
audioPlayer.addTrack("one");
|
||||
audioPlayer.play("one");
|
||||
|
||||
expect(audioPlayer._currentTrack).toEqual("one");
|
||||
expect(audioElement._playing).toEqual(true);
|
||||
expect(audioElement.src).toEqual("one");
|
||||
expect(audioPlayer._state).toEqual(AudioState.Playing);
|
||||
});
|
||||
|
||||
it("should continue playing when play is called without a filename and the player is paused", function () {
|
||||
audioPlayer._state = AudioState.Paused;
|
||||
audioPlayer.play();
|
||||
|
||||
expect(audioElement._playing).toEqual(true);
|
||||
expect(audioPlayer._state).toEqual(AudioState.Playing);
|
||||
});
|
||||
|
||||
it("should do nothing when play is called without a filename and the player is not paused", function () {
|
||||
spyOn(console, "warn");
|
||||
audioPlayer._state = AudioState.Playing;
|
||||
audioPlayer.play();
|
||||
|
||||
expect(console.warn).toHaveBeenCalledWith("No track currently paused and no track specified, doing nothing.");
|
||||
});
|
||||
|
||||
it("should pause the current track when pause is called", function () {
|
||||
audioPlayer.pause();
|
||||
expect(audioPlayer._state).toEqual(AudioState.Paused);
|
||||
expect(audioElement._paused).toEqual(true);
|
||||
});
|
||||
|
||||
it("should stop the current track when stop is called", function () {
|
||||
audioPlayer.stop();
|
||||
expect(audioPlayer._state).toEqual(AudioState.Stopped);
|
||||
expect(audioElement._paused).toEqual(true);
|
||||
expect(audioElement.src).toEqual("");
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user