Merge pull request #183 from jcass77/enhance/javascript_tests

Add Javascript test and test coverage frameworks.
This commit is contained in:
John Cass 2016-03-16 21:14:31 +02:00
commit 88ff29db4c
10 changed files with 421 additions and 86 deletions

1
.gitignore vendored
View File

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

View File

@ -8,6 +8,7 @@ python:
env:
- TOX_ENV=py27
- TOX_ENV=flake8
- TOX_ENV=test
- TOX_ENV=eslint
- TOX_ENV=csslint
- TOX_ENV=tidy
@ -19,4 +20,6 @@ script:
- "tox -e $TOX_ENV"
after_success:
- "if [ $TOX_ENV == 'py27' ]; then pip install coveralls; coveralls; fi"
# 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"

81
karma.conf.js Normal file
View File

@ -0,0 +1,81 @@
// Karma configuration
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: ['browserify', 'mocha'],
// list of files / patterns to load in the browser
files: [
'mopidy_musicbox_webclient/static/vendors/**/*.js',
// TODO: can remove the next line once JavaScript codebase has been modularized.
'mopidy_musicbox_webclient/static/js/**/*.js',
'tests/**/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: {
'tests/**/test_*.js': [ 'browserify' ],
'mopidy_musicbox_webclient/static/js/**/*.js': ['coverage']
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'coverage'],
// 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_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// 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: true,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
// add additional browserify configuration properties here
// such as transform and/or debug=true to generate source maps
browserify: {
debug: true,
transform: [
'babelify',
['browserify-istanbul', { instrumenter: require('isparta') }]
]
},
coverageReporter: {
// specify a common output directory
dir: 'coverage/',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text'}
]
}
})
}

View File

@ -128,7 +128,7 @@ function setSongInfo (data) {
}
if (data.track.album && data.track.album.name) {
$('#modalalbum').html('<a href="#" onclick="return showAlbum(\'' + data.track.album.uri + '\');">' + data.track.album.name + '</a>')
getCover(data.track.uri, '#infocover, #controlspopupimage', 'extralarge')
coverArt.getCover(data.track.uri, '#infocover, #controlspopupimage', 'extralarge')
} else {
$('#modalalbum').html('')
$('#infocover').attr('src', 'images/default_cover.png')

View File

@ -5,88 +5,106 @@
API_KEY = 'b6d34c3af91d62ab0ae00ab1b6fa8733'
API_SECRET = '2c631802c2285d5d5d1502462fe42a2b'
var fmcache
var lastfm
$(window).load(function () {
// create a Cache object
fmcache = new LastFMCache()
// create a LastFM object
lastfm = new LastFM({
var coverArt = {
fmcache: new LastFMCache(),
lastfm: new LastFM({
apiKey: API_KEY,
apiSecret: API_SECRET,
cache: fmcache
})
})
cache: this.fmcache
}),
function getCover (uri, images, size) {
var defUrl = 'images/default_cover.png'
$(images).attr('src', defUrl)
if (!uri) {
return
}
mopidy.library.getImages({'uris': [uri]}).then(function (imageResults) {
var uri = Object.keys(imageResults)[0]
if (imageResults[uri].length > 0) {
$(images).attr('src', imageResults[uri][0].uri)
} else {
// Also check deprecated 'album.images' in case backend does not
// implement mopidy.library.getImages yet...
getCoverFromAlbum(uri, images, size)
getCover: function (uri, images, size) {
var defUrl = 'images/default_cover.png'
$(images).attr('src', defUrl)
if (!uri) {
return
}
})
}
mopidy.library.getImages({'uris': [uri]}).then(function (imageResults) {
var uri = Object.keys(imageResults)[0]
if (imageResults[uri].length > 0) {
$(images).attr('src', imageResults[uri][0].uri)
} else {
// Also check deprecated 'album.images' in case backend does not
// implement mopidy.library.getImages yet...
coverArt.getCoverFromAlbum(uri, images, size)
}
})
},
// Note that this approach has been deprecated in Mopidy
// TODO: Remove when Mopidy no longer supports getting images
// with 'album.images'.
function getCoverFromAlbum (uri, images, size) {
mopidy.library.lookup({'uris': [uri]}).then(function (resultDict) {
var uri = Object.keys(resultDict)[0]
var track = resultDict[uri][0]
if (track.album && track.album.images && (track.album.images.length > 0)) {
$(images).attr('src', track.album.images[0])
} else {
// Fallback to last.fm
getCoverFromLastFm(track, images, size)
// Note that this approach has been deprecated in Mopidy
// TODO: Remove when Mopidy no longer supports getting images
// with 'album.images'.
getCoverFromAlbum: function (uri, images, size) {
var defUrl = 'images/default_cover.png'
$(images).attr('src', defUrl)
if (!uri) {
return
}
})
}
mopidy.library.lookup({'uris': [uri]}).then(function (resultDict) {
var uri = Object.keys(resultDict)[0]
var track = resultDict[uri][0]
if (track && track.album && track.album.images && track.album.images.length > 0) {
$(images).attr('src', track.album.images[0])
} else if (track && (track.album || track.artist)) {
// Fallback to last.fm
coverArt.getCoverFromLastFm(track, images, size)
} else {
return
}
})
},
function getCoverFromLastFm (track, images, size) {
var defUrl = 'images/default_cover.png'
if (!(track.album || track.artist)) {
return
}
var albumname = track.album.name || ''
var artistname = ''
if (track.album.artists && (track.album.artists.length > 0)) {
// First look for the artist in the album
artistname = track.album.artists[0].name
} else if (track.artists && (track.artists.length > 0)) {
// Fallback to using artists for specific track
artistname = track.artists[0].name
}
getCoverFromLastFm: function (track, images, size) {
var defUrl = 'images/default_cover.png'
$(images).attr('src', defUrl)
if (!track || !(track.album || track.artists)) {
return
}
var albumname = (track.album && track.album.name) ? track.album.name : ''
var artistname = ''
if (track.album && track.album.artists && track.album.artists.length > 0) {
// First look for the artist in the album
artistname = track.album.artists[0].name
} else if (track.artists && (track.artists.length > 0)) {
// Fallback to using artists for specific track
artistname = track.artists[0].name
}
lastfm.album.getInfo({artist: artistname, album: albumname},
{ success: function (data) {
this.lastfm.album.getInfo({artist: artistname, album: albumname}, {success: function (data) {
for (var i = 0; i < data.album.image.length; i++) {
if (data.album.image[i].size === size) {
$(images).attr('src', data.album.image[i]['#text'] || defUrl)
}
}
}, error: function (code, message) {
console.log('Error retrieving album info from last.fm', code, message)
}})
},
getArtistImage: function (artist, images, size) {
var defUrl = 'images/user_24x32.png'
$(images).attr('src', defUrl)
if (!artist || artist.length === 0) {
return
}
}, $(images).attr('src', defUrl))
this.lastfm.artist.getInfo({artist: artist}, {success: function (data) {
for (var i = 0; i < data.artist.image.length; i++) {
if (data.artist.image[i].size === size) {
$(images).attr('src', data.artist.image[i]['#text'] || defUrl)
}
}
}, error: function (code, message) {
console.log('Error retrieving artist info from last.fm', code, message)
}})
}
}
function getArtistImage (nwartist, image, size) {
var defUrl = 'images/user_24x32.png'
lastfm.artist.getInfo({artist: nwartist}, {success: function (data) {
for (var i = 0; i < data.artist.image.length; i++) {
if (data.artist.image[i].size === size) {
$(image).attr('src', data.artist.image[i]['#text'] || defUrl)
}
}
}}, $(images).attr('src', defUrl))
$(document).ready(coverArt.init)
// TODO: Remove this once JavaScript codebase has been completely modularized
// in favour of bundling everything using 'browserify'.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
module.exports = coverArt
}
}

View File

@ -220,7 +220,7 @@ function processCurrentPlaylist (resultArr) {
function processArtistResults (resultArr) {
if (!resultArr || (resultArr.length === 0)) {
$('#h_artistname').text('Artist not found...')
getCover('', '#artistviewimage, #artistpopupimage', 'extralarge')
coverArt.getCover('', '#artistviewimage, #artistpopupimage', 'extralarge')
showLoading(false)
return
}
@ -239,7 +239,7 @@ function processArtistResults (resultArr) {
function processAlbumResults (resultArr) {
if (!resultArr || (resultArr.length === 0)) {
$('#h_albumname').text('Album not found...')
getCover('', '#albumviewcover, #coverpopupimage', 'extralarge')
coverArt.getCover('', '#albumviewcover, #coverpopupimage', 'extralarge')
showLoading(false)
return
}
@ -252,6 +252,6 @@ function processAlbumResults (resultArr) {
$('#h_albumartist').html(artistname)
$('#coverpopupalbumname').html(albumname)
$('#coverpopupartist').html(artistname)
getCover(resultArr[0].uri, '#albumviewcover, #coverpopupimage', 'extralarge')
coverArt.getCover(resultArr[0].uri, '#albumviewcover, #coverpopupimage', 'extralarge')
showLoading(false)
}

View File

@ -1,5 +1,4 @@
var progressTimer
var progressElement = document.getElementById('trackslider')
var positionNode = document.createTextNode('')
var durationNode = document.createTextNode('')
@ -13,8 +12,10 @@ var syncsLeft = MAX_SYNCS
var synced = false
var consecutiveSyncs = 0
document.getElementById('songelapsed').appendChild(positionNode)
document.getElementById('songlength').appendChild(durationNode)
$(document).ready(function () {
$('#songelapsed').append(positionNode)
$('#songlength').append(durationNode)
})
function timerCallback (position, duration, isRunning) {
updateTimers(position, duration, isRunning)

View File

@ -7,8 +7,8 @@
"test": "tests"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"eslint": "eslint mopidy_musicbox_webclient/static/js/**.js",
"test": "karma start karma.conf.js",
"eslint": "eslint mopidy_musicbox_webclient/static/js/**/**.js tests/**/test_*.js",
"csslint": "csslint mopidy_musicbox_webclient/static/css/**.css",
"tidy": "node tidy.js"
},
@ -22,12 +22,32 @@
"url": "https://github.com/pimusicbox/mopidy-musicbox-webclient/issues"
},
"devDependencies": {
"eslint": "latest",
"eslint-config-standard": "latest",
"eslint-plugin-standard": "latest",
"eslint-plugin-promise": "latest",
"csslint": "latest",
"tidy-html5": "latest"
"babelify": "^7.2.0",
"browserify": "^13.0.0",
"browserify-istanbul": "^2.0.0",
"chai": "^3.5.0",
"chai-as-promised": "^5.2.0",
"chai-jquery": "^2.0.0",
"chai-string": "^1.2.0",
"coveralls": "^2.11.8",
"csslint": "^0.10.0",
"eslint": "^2.3.0",
"eslint-config-standard": "^5.1.0",
"eslint-plugin-promise": "^1.1.0",
"eslint-plugin-standard": "^1.3.2",
"install": "^0.5.6",
"isparta": "^4.0.0",
"karma": "^0.13.22",
"karma-browserify": "^5.0.2",
"karma-cli": "^0.1.2",
"karma-coverage": "^0.5.5",
"karma-mocha": "^0.2.2",
"karma-phantomjs-launcher": "^1.0.0",
"mocha": "^2.4.5",
"phantomjs-prebuilt": "^2.1.5",
"sinon": "^1.17.3",
"tidy-html5": "latest",
"watchify": "^3.7.0"
},
"homepage": "https://github.com/pimusicbox/mopidy-musicbox-webclient#readme"
}

201
tests/test_images.js Normal file
View File

@ -0,0 +1,201 @@
var chai = require('chai')
var should = chai.should()
var expect = chai.expect
var assert = chai.assert
chai.use(require('chai-string'))
chai.use(require('chai-jquery'))
var sinon = require('sinon')
var coverArt = require('../mopidy_musicbox_webclient/static/js/images.js')
var images
before(function () {
html =
'<span id="songelapsed" class="pull-left"></span>' +
'<span id="songlength" class="pull-right"></span>'
$(document).ready(function () {
$(document.body).add(html)
})
mopidy = sinon.stub(new Mopidy({callingConvention: 'by-position-or-by-name'}))
images = $('<img id="img_mock">')
})
describe('CoverArt', function () {
describe('#getCover()', function () {
beforeEach(function () {
$(images).removeAttr('src')
})
it('should use default image if no track URI is provided', function () {
coverArt.getCover('', images, '')
$(images).prop('src').should.endWith('images/default_cover.png')
})
it('should get image from Mopidy, if available', function () {
var getImagesResultMock = {'mock:track:uri': [{uri: 'mockImageUri'}]}
var library = { getImages: function () { return $.when(getImagesResultMock) } }
mopidy.library = library
var getImagesSpy = sinon.spy(mopidy.library, 'getImages')
coverArt.getCover('mock:track:uri', images, '')
assert(getImagesSpy.calledOnce)
$(images).prop('src').should.endWith('mockImageUri')
})
it('should fall back to retrieving image from deprecated track.album.images', function () {
var getImagesResultMock = {'mock:track:uri': []}
var lookupResultMock = {'mock:track:uri': [{album: {images: ['mockAlbumImageUri']}}]}
var library = {
getImages: function () { return $.when(getImagesResultMock) },
lookup: function () { return $.when(lookupResultMock) }
}
mopidy.library = library
var getImagesSpy = sinon.spy(mopidy.library, 'getImages')
var getCoverFromAlbumSpy = sinon.spy(coverArt, 'getCoverFromAlbum')
coverArt.getCover('mock:track:uri', images, '')
assert(getImagesSpy.calledOnce)
assert(getCoverFromAlbumSpy.calledOnce)
})
})
describe('#getCoverFromAlbum()', function () {
beforeEach(function () {
$(images).removeAttr('src')
})
it('should use default image if no track URI is provided', function () {
coverArt.getCoverFromAlbum('', images, '')
$(images).prop('src').should.endWith('images/default_cover.png')
})
it('should get image from Mopidy track.album.images, if available', function () {
var lookupResultMock = {'mock:track:uri': [{album: {images: ['mockAlbumImageUri']}}]}
var library = {
lookup: function () { return $.when(lookupResultMock) }
}
mopidy.library = library
var lookupSpy = sinon.spy(mopidy.library, 'lookup')
coverArt.getCoverFromAlbum('mock:track:uri', images, '')
assert(lookupSpy.calledOnce)
$(images).prop('src').should.endWith('mockAlbumImageUri')
})
it('should use default image if track.album or track.artist is not available', function () {
var lookupResultMock = {'mock:track:uri': []}
var library = {
lookup: function () { return $.when(lookupResultMock) }
}
mopidy.library = library
var lookupSpy = sinon.spy(mopidy.library, 'lookup')
coverArt.getCoverFromAlbum('mock:track:uri', images, '')
assert(lookupSpy.calledOnce)
$(images).prop('src').should.endWith('images/default_cover.png')
})
it('should fall back to retrieving image from last.fm if none provided by Mopidy', function () {
var lookupResultMock = {'mock:track:uri': [{album: {images: []}}]}
var library = {
lookup: function () { return $.when(lookupResultMock) }
}
mopidy.library = library
var getCoverFromLastFmSpy = sinon.spy(coverArt, 'getCoverFromLastFm')
coverArt.getCoverFromAlbum('mock:track:uri', images, '')
assert(getCoverFromLastFmSpy.calledOnce)
})
})
describe('#getCoverFromLastFm()', function () {
beforeEach(function () {
$(images).removeAttr('src')
})
it('should use default image if no track is provided', function () {
coverArt.getCoverFromLastFm(undefined, images, '')
$(images).prop('src').should.endWith('images/default_cover.png')
})
it('should fall back to using track artist if album artist is not available', function () {
var track = {artists: [{name: 'artistMock'}]}
var getInfoResultMock = {album: {image: []}}
var getInfoStub = sinon.stub(coverArt.lastfm.album, 'getInfo')
getInfoStub.returns($.when(getInfoResultMock))
coverArt.getCoverFromLastFm(track, images, '')
var args = getInfoStub.args
assert(args[0][0].artist === 'artistMock')
getInfoStub.restore()
})
it('should get album info from last.fm', function () {
var track = {album: {artists: [{name: 'albumMock'}]}}
var getInfoResultMock = {album: {image: [{'#text': 'mockAlbumImageUri', size: 'small'}]}}
var getInfoStub = sinon.stub(coverArt.lastfm.album, 'getInfo')
getInfoStub.yieldsTo('success', getInfoResultMock)
coverArt.getCoverFromLastFm(track, images, 'small')
$(images).prop('src').should.endWith('mockAlbumImageUri')
getInfoStub.restore()
})
it('should log errors', function () {
var track = {album: {artists: [{name: 'albumMock'}]}}
var getInfoStub = sinon.stub(coverArt.lastfm.album, 'getInfo')
getInfoStub.yieldsTo('error', 'code', 'message')
var consoleSpy = sinon.spy(console, 'log')
coverArt.getCoverFromLastFm(track, images, '')
assert(consoleSpy.calledOnce)
getInfoStub.restore()
consoleSpy.restore()
})
})
describe('#getArtistImage()', function () {
beforeEach(function () {
$(images).removeAttr('src')
})
it('should use default image if no artist is provided', function () {
coverArt.getArtistImage('', images, '')
$(images).prop('src').should.endWith('images/user_24x32.png')
})
it('should get artist info from last.fm', function () {
var getInfoResultMock = {artist: {image: [{'#text': 'mockArtistImageUri', size: 'small'}]}}
var getInfoStub = sinon.stub(coverArt.lastfm.artist, 'getInfo')
getInfoStub.yieldsTo('success', getInfoResultMock)
coverArt.getArtistImage('mockArtist', images, 'small')
$(images).prop('src').should.endWith('mockArtistImageUri')
getInfoStub.restore()
})
it('should log errors', function () {
var getInfoStub = sinon.stub(coverArt.lastfm.artist, 'getInfo')
getInfoStub.yieldsTo('error', 'code', 'message')
var consoleSpy = sinon.spy(console, 'log')
coverArt.getArtistImage('mockArtist', images, 'small')
assert(consoleSpy.calledOnce)
getInfoStub.restore()
consoleSpy.restore()
})
})
})

14
tox.ini
View File

@ -1,5 +1,5 @@
[tox]
envlist = py27, flake8, eslint, csslint, tidy
envlist = py27, flake8, test, eslint, csslint, tidy
[testenv]
deps =
@ -20,7 +20,17 @@ deps =
flake8
flake8-import-order
skip_install = true
commands = flake8 {posargs:mopidy_musicbox_webclient}
commands = flake8 {posargs:mopidy_musicbox_webclient tests}
[testenv:test]
whitelist_externals =
/bin/bash
deps =
nodeenv
skip_install = true
commands =
- nodeenv --prebuilt {toxworkdir}/node_env
bash -c '. {toxworkdir}/node_env/bin/activate; npm install; npm test'
[testenv:eslint]
whitelist_externals =