From 411a3781fdab270984299e47f136abec8395ff57 Mon Sep 17 00:00:00 2001 From: jcass Date: Mon, 28 Mar 2016 05:46:26 +0200 Subject: [PATCH] Combine .js and .py coverage reports. Remove services that cannot be searched from search dropdown. Refactoring and additional unit tests. --- .gitignore | 2 +- .travis.yml | 19 ++++- karma.conf.js | 3 +- mopidy_musicbox_webclient/__init__.py | 4 +- mopidy_musicbox_webclient/static/index.html | 31 ++------ .../static/js/custom_scripting.js | 8 ++ .../static/js/functionsvars.js | 13 ++++ mopidy_musicbox_webclient/static/js/gui.js | 9 ++- .../static/js/library.js | 5 +- mopidy_musicbox_webclient/static/mb.appcache | 4 +- mopidy_musicbox_webclient/web.py | 44 +++++------ mopidy_musicbox_webclient/webclient.py | 46 +++++++++++ setup.py | 2 +- tests/test_extension.py | 39 +++++++--- tests/test_library.js | 2 +- tests/test_web.py | 67 ++++++++++++++++ tests/test_webclient.py | 78 +++++++++++++++++++ tox.ini | 15 +++- 18 files changed, 312 insertions(+), 79 deletions(-) create mode 100644 mopidy_musicbox_webclient/static/js/custom_scripting.js create mode 100644 mopidy_musicbox_webclient/webclient.py create mode 100644 tests/test_web.py create mode 100644 tests/test_webclient.py 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 @@ - Musicbox + {{ title }} - - - + + + + + @@ -52,7 +36,7 @@ - +
@@ -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 =