Combine .js and .py coverage reports.

Remove services that cannot be searched from search dropdown.

Refactoring and additional unit tests.
This commit is contained in:
jcass 2016-03-28 05:46:26 +02:00
parent 2629464c18
commit 411a3781fd
18 changed files with 312 additions and 79 deletions

2
.gitignore vendored
View File

@ -8,7 +8,7 @@
MANIFEST
build/
cover/
coverage/
.karma_coverage/
coverage.xml
dist/
docs/_build/

View File

@ -1,10 +1,18 @@
sudo: false
sudo: required
dist: trusty
language: python
python:
- "2.7_with_system_site_packages"
addons:
apt:
sources:
- mopidy-stable
packages:
- mopidy
env:
- TOX_ENV=py27
- TOX_ENV=flake8
@ -13,6 +21,11 @@ env:
- TOX_ENV=csslint
- TOX_ENV=tidy
before_install:
- "sudo sed -i '/127.0.1.1/d' /etc/hosts" # Workaround https://github.com/tornadoweb/tornado/issues/1573
- "sudo apt-get update -qq"
- "sudo apt-get install -y gstreamer0.10-plugins-good python-gst0.10"
install:
- "pip install tox"
@ -20,6 +33,4 @@ script:
- "tox -e $TOX_ENV"
after_success:
# TODO: find a way to combine .py and .js coverage reports.
# - "if [ $TOX_ENV == 'py27' ]; then pip install coveralls; coveralls; fi"
- "if [ $TOX_ENV == 'test' ]; then cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js; fi"
- "if [ $TOX_ENV == 'test' ]; then pip install coveralls; coveralls --merge=./karma_coverage/coverage-final.json; fi"

View File

@ -71,9 +71,10 @@ module.exports = function (config) {
coverageReporter: {
// specify a common output directory
dir: 'coverage/',
dir: '.karma_coverage/',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'json', subdir: '.' },
{ type: 'text' }
]
}

View File

@ -7,7 +7,7 @@ from mopidy import config, ext
__version__ = '2.2.0'
class MusicBoxExtension(ext.Extension):
class Extension(ext.Extension):
dist_name = 'Mopidy-MusicBox-Webclient'
ext_name = 'musicbox_webclient'
@ -18,7 +18,7 @@ class MusicBoxExtension(ext.Extension):
return config.read(conf_file)
def get_config_schema(self):
schema = super(MusicBoxExtension, self).get_config_schema()
schema = super(Extension, self).get_config_schema()
schema['musicbox'] = config.Boolean(optional=True)
schema['websocket_host'] = config.Hostname(optional=True)
schema['websocket_port'] = config.Port(optional=True)

View File

@ -1,30 +1,14 @@
<!DOCTYPE html>
<html manifest="mb.appcache">
<head>
<title>Musicbox</title>
<title>{{ title }}</title>
<meta charset="utf-8">
<script type="text/javascript" src="vendors/jquery/jquery-1.12.0.min.js"></script>
<link rel="stylesheet" type="text/css" href="vendors/jquery_mobile_flat_ui_theme/jquery.mobile.flatui.min.css"/>
<script>
//configuration
var isMusicBox = '{{musicbox}}' == 'True'; // Remove MusicBox only content (e.g. settings, system pages)
var websocketUrl = ('{{useWebsocketUrl}}' == 'True') ? '{{websocket_url}}' : ''
var hasAlarmClock = '{{alarmclock}}' == 'True'; // Add Alarm Clock icons
$(document).bind("mobileinit", function () {
$.extend($.mobile, {
ajaxEnabled: false,
hashListeningEnabled: false
// linkBindingEnabled: false
// buttonMarkup.hoverDelay: 100,
// buttonMarkup.corners: false
});
});
/* window.addEventListener('load', function () {
new FastClick(document.body);
}, false);
*/
</script>
<script type="text/javascript" src="vendors/jquery/jquery-1.12.0.min.js"></script>
<script type="text/javascript" src="vendors/jquery_cookie/jquery.cookie.js"></script>
<script type="text/javascript" src="js/custom_scripting.js"></script>
<link rel="stylesheet" type="text/css" href="vendors/jquery_mobile_flat_ui_theme/jquery.mobile.flatui.min.css"/>
<link rel="icon" type="image/gif" href="images/icons/musicbox32.gif" />
<link rel="apple-touch-icon" href="images/icons/musicbox57.png" />
<link rel="apple-touch-icon" sizes="72x72" href="images/icons/musicbox72.png" />
@ -52,7 +36,7 @@
</head>
<body>
<body data-websocket-url="{{websocketUrl}}" data-is-musicbox="{{isMusicBox}}" data-has-alarmclock="{{hasAlarmClock}}">
<div data-role="page" id="page" class="ui-responsive-panel" data-theme="c">
<div data-role="panel" id="panel" data-position="left" data-theme="a" data-display="reveal" data-position-fixed="true">
@ -501,7 +485,6 @@
</div>
<!-- /page one -->
<script type="text/javascript" src="vendors/mopidy/mopidy.min.js"></script>
<script type="text/javascript" src="vendors/jquery_cookie/jquery.cookie.js"></script>
<script type="text/javascript" src="vendors/media_progress_timer/timer.js"></script>
<script type="text/javascript" src="js/progress_timer.js"></script>
<script type="text/javascript" src="js/controls.js"></script>

View File

@ -0,0 +1,8 @@
// jQuery Mobile configuration options
// see: http://api.jquerymobile.com/1.3/global-config/
$(document).bind('mobileinit', function () {
$.extend($.mobile, {
ajaxEnabled: false,
hashListeningEnabled: false
})
})

View File

@ -110,6 +110,19 @@ var uriHumanList = [
['subsonic', 'Subsonic']
]
// List of Mopidy URI schemes that should not be searched directly.
// Also blacklists 'yt' in favour of using the other 'youtube' supported scheme.
var searchBlacklist = [
'file',
'http',
'https',
'mms',
'rtmp',
'rtmps',
'rtsp',
'yt'
]
function scrollToTop () {
var divtop = 0
$('body,html').animate({

View File

@ -475,6 +475,7 @@ $(document).ready(function (event) {
$(window).hashchange()
// Connect to server
var websocketUrl = $(document.body).data('websocket-url')
if (websocketUrl) {
try {
mopidy = new Mopidy({
@ -539,16 +540,16 @@ $(document).ready(function (event) {
}
})
// remove buttons only for MusicBox
if (!isMusicBox) {
// Remove MusicBox only content (e.g. settings, system pages)
if (!$(document.body).data('is-musicbox')) {
$('#navSettings').hide()
$('#navshutdown').hide()
$('#homesettings').hide()
$('#homeshutdown').hide()
}
// remove Alarm Clock if it is not present
if (!hasAlarmClock) {
// Remove Alarm Clock icons if it is not present
if (!$(document.body).data('has-alarmclock')) {
$('#navAlarmClock').hide()
$('#homeAlarmClock').hide()
$('#homeAlarmClock').nextAll().find('.ui-block-a, .ui-block-b').toggleClass('ui-block-a').toggleClass('ui-block-b')

View File

@ -322,8 +322,11 @@ var library = {
searchScheme = 'all'
}
$('#selectSearchService').empty()
$('#selectSearchService').append(new Option('All backends', 'all'))
$('#selectSearchService').append(new Option('All services', 'all'))
mopidy.getUriSchemes().then(function (schemesArray) {
schemesArray = schemesArray.filter(function (el) {
return searchBlacklist.indexOf(el) < 0
})
for (var i = 0; i < schemesArray.length; i++) {
backendName = getMediaHuman(schemesArray[i])
backendName = backendName.charAt(0).toUpperCase() + backendName.slice(1)

View File

@ -1,6 +1,6 @@
CACHE MANIFEST
# 2016-03-19:v1
# 2016-03-28:v1
NETWORK:
*
@ -19,8 +19,8 @@ images/icons/play_alt_12x12.png
images/icons/play_alt_16x16.png
images/loader.gif
images/user_24x32.png
index.html
js/controls.js
js/custom_scripting.js
js/functionsvars.js
js/gui.js
js/images.js

View File

@ -1,10 +1,13 @@
from __future__ import unicode_literals
from __future__ import absolute_import, division, print_function, unicode_literals
import json
import logging
import string
import tornado.web
from . import MusicBoxExtension
import mopidy_musicbox_webclient.webclient as mmw
logger = logging.getLogger(__name__)
@ -21,40 +24,31 @@ class StaticHandler(tornado.web.StaticFileHandler):
@classmethod
def get_version(cls, settings, path):
return MusicBoxExtension.version
return mmw.Extension.version
class IndexHandler(tornado.web.RequestHandler):
def initialize(self, config, path):
ext_config = config[MusicBoxExtension.ext_name]
host, port = ext_config['websocket_host'], ext_config['websocket_port']
ws_url = ''
if host or port:
if not host:
host = self.request.host.partition(':')[0]
logger.warning('Musicbox websocket_host not specified, '
'using %s', host)
elif not port:
port = config['http']['port']
logger.warning('Musicbox websocket_port not specified, '
'using %s', port)
protocol = 'ws'
if self.request.protocol == 'https':
protocol = 'wss'
ws_url = "%s://%s:%d/mopidy/ws" % (protocol, host, port)
webclient = mmw.Webclient(config)
self.__dict = {
'version': MusicBoxExtension.version,
'musicbox': ext_config.get('musicbox', False),
'useWebsocketUrl': ws_url != '',
'websocket_url': ws_url,
'alarmclock': config.get('alarmclock', {}).get('enabled', False),
'isMusicBox': json.dumps(webclient.is_music_box()),
'websocketUrl': webclient.get_websocket_url(self.request),
'hasAlarmClock': json.dumps(webclient.has_alarm_clock()),
}
self.__path = path
self.__title = string.Template('MusicBox on $hostname')
def get(self, path):
return self.render('index.html', **self.__dict)
return self.render(path, title=self.get_title(), **self.__dict)
def get_title(self):
hostname, sep, port = self.request.host.rpartition(':')
if not sep or not port.isdigit():
hostname, port = self.request.host, '80'
return self.__title.safe_substitute(hostname=hostname, port=port)
def get_template_path(self):
return self.__path

View File

@ -0,0 +1,46 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
from mopidy_musicbox_webclient import Extension
logger = logging.getLogger(__name__)
class Webclient(object):
def __init__(self, config):
self.config = config
@property
def ext_config(self):
return self.config.get(Extension.ext_name, {})
@classmethod
def get_version(cls):
return Extension.version
def get_websocket_url(self, request):
host, port = self.ext_config['websocket_host'], self.ext_config['websocket_port']
ws_url = ''
if host or port:
if not host:
host = request.host.partition(':')[0]
logger.warning('Musicbox websocket_host not specified, '
'using %s', host)
elif not port:
port = self.config['http']['port']
logger.warning('Musicbox websocket_port not specified, '
'using %s', port)
protocol = 'ws'
if request.protocol == 'https':
protocol = 'wss'
ws_url = "%s://%s:%d/mopidy/ws" % (protocol, host, port)
return ws_url
def has_alarm_clock(self):
return self.ext_config.get('alarmclock', {}).get('enabled', False)
def is_music_box(self):
return self.ext_config.get('musicbox', False)

View File

@ -29,7 +29,7 @@ setup(
],
entry_points={
'mopidy.ext': [
'musicbox_webclient = mopidy_musicbox_webclient:MusicBoxExtension',
'musicbox_webclient = mopidy_musicbox_webclient:Extension',
],
},
classifiers=[

View File

@ -1,22 +1,37 @@
from __future__ import unicode_literals
from __future__ import absolute_import, division, print_function, unicode_literals
from mopidy_musicbox_webclient import MusicBoxExtension
import unittest
import mock
from mopidy_musicbox_webclient import Extension
def test_get_default_config():
ext = MusicBoxExtension()
class ExtensionTests(unittest.TestCase):
def test_get_default_config(self):
ext = Extension()
config = ext.get_default_config()
assert '[musicbox_webclient]' in config
assert 'enabled = true' in config
assert 'websocket_host =' in config
assert 'websocket_port =' in config
def test_get_config_schema():
ext = MusicBoxExtension()
def test_get_config_schema(self):
ext = Extension()
schema = ext.get_config_schema()
assert 'musicbox' in schema
assert 'websocket_host' in schema
assert 'websocket_port' in schema
def test_setup(self):
registry = mock.Mock()
# TODO Write more tests
ext = Extension()
ext.setup(registry)
calls = [mock.call('http:app', {'name': ext.ext_name, 'factory': ext.factory})]
registry.add.assert_has_calls(calls, any_order=True)

View File

@ -7,7 +7,7 @@ chai.use(require('chai-jquery'))
var sinon = require('sinon')
var coverArt = require('../mopidy_musicbox_webclient/static/js/library.js')
var library = require('../mopidy_musicbox_webclient/static/js/library.js')
var selectID = '#selectSearchService'
var schemesArray

67
tests/test_web.py Normal file
View File

@ -0,0 +1,67 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import mock
import mopidy.config as config
import tornado.testing
import tornado.web
import tornado.websocket
from mopidy_musicbox_webclient import Extension
from mopidy_musicbox_webclient.web import StaticHandler
class BaseTest(tornado.testing.AsyncHTTPTestCase):
def get_app(self):
extension = Extension()
self.config = config.Proxy({'musicbox_webclient': {
'enabled': True,
'musicbox': True,
'websocket_host': '',
'websocket_port': '',
}
})
return tornado.web.Application(extension.factory(self.config, mock.Mock()))
class StaticFileHandlerTest(BaseTest):
def test_static_handler(self):
response = self.fetch('/vendors/mopidy/mopidy.js', method='GET')
assert response.code == 200
def test_get_version(self):
assert StaticHandler.get_version(None, None) == Extension.version
class RedirectHandlerTest(BaseTest):
def test_redirect_handler(self):
response = self.fetch('/', method='GET', follow_redirects=False)
assert response.code == 301
response.headers['Location'].endswith('index.html')
class IndexHandlerTest(BaseTest):
def test_index_handler(self):
response = self.fetch('/index.html', method='GET')
assert response.code == 200
def test_get_title(self):
response = self.fetch('/index.html', method='GET')
body = tornado.escape.to_unicode(response.body)
assert '<title>MusicBox on localhost</title>' in body
def test_initialize_sets_dictionary_objects(self):
response = self.fetch('/index.html', method='GET')
body = tornado.escape.to_unicode(response.body)
assert 'data-is-musicbox="true"' in body
assert 'data-has-alarmclock="false"' in body
assert 'data-websocket-url=""' in body

78
tests/test_webclient.py Normal file
View File

@ -0,0 +1,78 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import unittest
import mock
import mopidy.config as mopidy_config
from mopidy_musicbox_webclient import Extension
from mopidy_musicbox_webclient.webclient import Webclient
class WebclientTests(unittest.TestCase):
def setUp(self):
config = mopidy_config.Proxy(
{
'musicbox_webclient': {
'enabled': True,
'musicbox': False,
'websocket_host': 'host_mock',
'websocket_port': 999,
}
})
self.ext = Extension()
self.mmw = Webclient(config)
def test_get_version(self):
assert self.mmw.get_version() == self.ext.version
def test_get_websocket_url_uses_config_file(self):
assert self.mmw.get_websocket_url(mock.Mock()) == 'ws://host_mock:999/mopidy/ws'
def test_get_websocket_url_uses_request_host(self):
config = mopidy_config.Proxy(
{
'musicbox_webclient': {
'enabled': True,
'musicbox': False,
'websocket_host': '',
'websocket_port': 999,
}
})
request_mock = mock.Mock(spec='tornado.HTTPServerRequest')
request_mock.host = '127.0.0.1'
request_mock.protocol = 'https'
self.mmw.config = config
assert self.mmw.get_websocket_url(request_mock) == 'wss://127.0.0.1:999/mopidy/ws'
def test_get_websocket_url_uses_http_port(self):
config = mopidy_config.Proxy(
{
'http': {
'port': 999
},
'musicbox_webclient': {
'enabled': True,
'musicbox': False,
'websocket_host': '127.0.0.1',
'websocket_port': '',
}
})
request_mock = mock.Mock(spec='tornado.HTTPServerRequest')
request_mock.host = '127.0.0.1'
request_mock.protocol = 'https'
self.mmw.config = config
assert self.mmw.get_websocket_url(request_mock) == 'wss://127.0.0.1:999/mopidy/ws'
def test_has_alarmclock(self):
assert not self.mmw.has_alarm_clock()
def test_is_musicbox(self):
assert not self.mmw.is_music_box()

15
tox.ini
View File

@ -2,12 +2,18 @@
envlist = py27, flake8, test, eslint, csslint, tidy
[testenv]
sitepackages = true
whitelist_externals =
py.test
deps =
mock
mopidy
pytest
pytest-capturelog
pytest-cov
pytest-xdist
responses
install_command = pip install --allow-unverified=mopidy --pre {opts} {packages}
commands =
py.test \
--basetemp={envtmpdir} \
@ -16,13 +22,17 @@ commands =
{posargs:tests/}
[testenv:flake8]
sitepackages = false
deps =
flake8
flake8-import-order
pep8-naming
skip_install = true
commands = flake8 {posargs:mopidy_musicbox_webclient tests}
commands = flake8 --show-source --statistics --max-line-length 120 {posargs:mopidy_musicbox_webclient tests}
[testenv:test]
envdir = py27
sitepackages = false
whitelist_externals =
/bin/bash
deps =
@ -33,6 +43,7 @@ commands =
bash -c '. {toxworkdir}/node_env/bin/activate; npm install; npm test'
[testenv:eslint]
sitepackages = false
whitelist_externals =
/bin/bash
deps =
@ -43,6 +54,7 @@ commands =
bash -c '. {toxworkdir}/node_env/bin/activate; npm install; npm run eslint'
[testenv:csslint]
sitepackages = false
whitelist_externals =
/bin/bash
deps =
@ -53,6 +65,7 @@ commands =
bash -c '. {toxworkdir}/node_env/bin/activate; npm install; npm run csslint'
[testenv:tidy]
sitepackages = false
whitelist_externals =
/bin/bash
deps =