diff --git a/.gitignore b/.gitignore
index e2cf956..e66dbe5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,7 +8,7 @@
MANIFEST
build/
cover/
-coverage/
+.karma_coverage/
coverage.xml
dist/
docs/_build/
diff --git a/.travis.yml b/.travis.yml
index c1749cf..c6548b0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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"
diff --git a/karma.conf.js b/karma.conf.js
index c64ba37..11ab253 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -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' }
]
}
diff --git a/mopidy_musicbox_webclient/__init__.py b/mopidy_musicbox_webclient/__init__.py
index 1884794..89f4c5d 100644
--- a/mopidy_musicbox_webclient/__init__.py
+++ b/mopidy_musicbox_webclient/__init__.py
@@ -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)
diff --git a/mopidy_musicbox_webclient/static/index.html b/mopidy_musicbox_webclient/static/index.html
index 225ae79..ee58e9e 100644
--- a/mopidy_musicbox_webclient/static/index.html
+++ b/mopidy_musicbox_webclient/static/index.html
@@ -1,30 +1,14 @@
-
@@ -501,7 +485,6 @@
-
diff --git a/mopidy_musicbox_webclient/static/js/custom_scripting.js b/mopidy_musicbox_webclient/static/js/custom_scripting.js
new file mode 100644
index 0000000..2b544b6
--- /dev/null
+++ b/mopidy_musicbox_webclient/static/js/custom_scripting.js
@@ -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
+ })
+})
diff --git a/mopidy_musicbox_webclient/static/js/functionsvars.js b/mopidy_musicbox_webclient/static/js/functionsvars.js
index 525809d..f270607 100644
--- a/mopidy_musicbox_webclient/static/js/functionsvars.js
+++ b/mopidy_musicbox_webclient/static/js/functionsvars.js
@@ -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({
diff --git a/mopidy_musicbox_webclient/static/js/gui.js b/mopidy_musicbox_webclient/static/js/gui.js
index 57fedd5..a1e1be6 100644
--- a/mopidy_musicbox_webclient/static/js/gui.js
+++ b/mopidy_musicbox_webclient/static/js/gui.js
@@ -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')
diff --git a/mopidy_musicbox_webclient/static/js/library.js b/mopidy_musicbox_webclient/static/js/library.js
index 4bf0387..7abd995 100644
--- a/mopidy_musicbox_webclient/static/js/library.js
+++ b/mopidy_musicbox_webclient/static/js/library.js
@@ -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)
diff --git a/mopidy_musicbox_webclient/static/mb.appcache b/mopidy_musicbox_webclient/static/mb.appcache
index a40ec97..c825720 100644
--- a/mopidy_musicbox_webclient/static/mb.appcache
+++ b/mopidy_musicbox_webclient/static/mb.appcache
@@ -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
diff --git a/mopidy_musicbox_webclient/web.py b/mopidy_musicbox_webclient/web.py
index 093f4f6..e562fde 100644
--- a/mopidy_musicbox_webclient/web.py
+++ b/mopidy_musicbox_webclient/web.py
@@ -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
diff --git a/mopidy_musicbox_webclient/webclient.py b/mopidy_musicbox_webclient/webclient.py
new file mode 100644
index 0000000..6db7903
--- /dev/null
+++ b/mopidy_musicbox_webclient/webclient.py
@@ -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)
diff --git a/setup.py b/setup.py
index 17b680e..286077f 100644
--- a/setup.py
+++ b/setup.py
@@ -29,7 +29,7 @@ setup(
],
entry_points={
'mopidy.ext': [
- 'musicbox_webclient = mopidy_musicbox_webclient:MusicBoxExtension',
+ 'musicbox_webclient = mopidy_musicbox_webclient:Extension',
],
},
classifiers=[
diff --git a/tests/test_extension.py b/tests/test_extension.py
index 31c685e..cfaa39d 100644
--- a/tests/test_extension.py
+++ b/tests/test_extension.py
@@ -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):
- config = ext.get_default_config()
+ def test_get_default_config(self):
+ ext = Extension()
- assert '[musicbox_webclient]' in config
- assert 'enabled = true' in config
+ 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
+ schema = ext.get_config_schema()
+ assert 'musicbox' in schema
+ assert 'websocket_host' in schema
+ assert 'websocket_port' in schema
-# TODO Write more tests
+ def test_setup(self):
+ registry = mock.Mock()
+
+ 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)
diff --git a/tests/test_library.js b/tests/test_library.js
index 7895cb2..1d3b19f 100644
--- a/tests/test_library.js
+++ b/tests/test_library.js
@@ -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
diff --git a/tests/test_web.py b/tests/test_web.py
new file mode 100644
index 0000000..a77484c
--- /dev/null
+++ b/tests/test_web.py
@@ -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 '
MusicBox on localhost' 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
diff --git a/tests/test_webclient.py b/tests/test_webclient.py
new file mode 100644
index 0000000..1ac2657
--- /dev/null
+++ b/tests/test_webclient.py
@@ -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()
diff --git a/tox.ini b/tox.ini
index b6a3ba3..73fd03c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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 =