Merge branch 'feature/testing' into develop
This commit is contained in:
3
babel.config.js
Executable file
3
babel.config.js
Executable file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ['@babel/preset-env', '@babel/preset-react'],
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -98,7 +98,7 @@
|
||||
|
||||
// Release details
|
||||
// These are automatically injected to built HTML
|
||||
var build = "1552983069";
|
||||
var build = "1553731760";
|
||||
var version = "3.33.0";
|
||||
|
||||
// Construct the script tag
|
||||
|
||||
14
package.json
14
package.json
@ -16,7 +16,10 @@
|
||||
"react-router": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.4.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"babel-core": "^6",
|
||||
"babel-jest": "^24.5.0",
|
||||
"babel-loader": "^7.1.5",
|
||||
"babel-preset-es2015": "^6",
|
||||
"babel-preset-react": "^6",
|
||||
@ -24,10 +27,13 @@
|
||||
"babel-preset-stage-2": "^6",
|
||||
"copy-dir": "^0.3.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"enzyme": "^3.9.0",
|
||||
"enzyme-adapter-react-16": "^1.11.2",
|
||||
"expose-loader": "0.7.3",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^0.11.2",
|
||||
"fs-copy-file": "2.1.2",
|
||||
"jest": "^24.5.0",
|
||||
"js-sha256": "^0.9.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"react": "^16.8.1",
|
||||
@ -38,6 +44,7 @@
|
||||
"react-redux": "^6",
|
||||
"react-router-dom": "^4",
|
||||
"react-sortablejs": "^1.5.0",
|
||||
"react-test-renderer": "^16.8.4",
|
||||
"redux": "^4",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"sass-loader": "^6",
|
||||
@ -48,9 +55,16 @@
|
||||
"webpack-strip": "0.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "node build_tools/build.js",
|
||||
"dev": "node build_tools/build.js && NODE_ENV=development webpack --watch",
|
||||
"prod": "node build_tools/build.js && NODE_ENV=development webpack && NODE_ENV=production webpack --watch",
|
||||
"release": "node build_tools/build.js && NODE_ENV=development webpack && NODE_ENV=production webpack && python setup.py sdist upload -r pypi && build_tools/release.sh"
|
||||
},
|
||||
"jest": {
|
||||
"verbose": true,
|
||||
"setupFilesAfterEnv": [
|
||||
"./tests/setup.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ import * as lastfmActions from './services/lastfm/actions';
|
||||
import * as geniusActions from './services/genius/actions';
|
||||
import * as snapcastActions from './services/snapcast/actions';
|
||||
|
||||
class App extends React.Component{
|
||||
export class App extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
|
||||
@ -13,7 +13,7 @@ export default class Dater extends React.Component{
|
||||
* @param milliseconds int
|
||||
* @return string (HH:mm:ss)
|
||||
**/
|
||||
durationTime(milliseconds = false){
|
||||
durationTime(milliseconds = null){
|
||||
if (!milliseconds ) return null
|
||||
|
||||
var string = '';
|
||||
@ -47,7 +47,7 @@ export default class Dater extends React.Component{
|
||||
* @param milliseconds int
|
||||
* @return string (eg 2+ hours)
|
||||
**/
|
||||
durationSentence(milliseconds = false){
|
||||
durationSentence(milliseconds = null){
|
||||
if (!milliseconds ) return null
|
||||
|
||||
var string = '';
|
||||
@ -118,8 +118,13 @@ export default class Dater extends React.Component{
|
||||
render(){
|
||||
if (!this.props.data){
|
||||
return null;
|
||||
} else {
|
||||
return <span className="dater">{ this.calculate() }</span>;
|
||||
}
|
||||
|
||||
var calculation = this.calculate();
|
||||
if (!calculation){
|
||||
return null;
|
||||
}
|
||||
|
||||
return <span className="dater">{calculation}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ export default class GridItem extends React.Component{
|
||||
case 'playlist':
|
||||
if (item.tracks_total){
|
||||
return (
|
||||
<span>
|
||||
<span className="grid__item__secondary__content">
|
||||
{item.tracks_total} tracks
|
||||
</span>
|
||||
);
|
||||
@ -66,8 +66,8 @@ export default class GridItem extends React.Component{
|
||||
|
||||
case 'artist':
|
||||
return (
|
||||
<span>
|
||||
{item.followers !== undefined ? item.followers.toLocaleString()+' followers' : null}
|
||||
<span className="grid__item__secondary__content">
|
||||
{item.followers !== undefined ? item.followers.toLocaleString()+' followers ' : null}
|
||||
{item.albums_uris !== undefined ? item.albums_uris.length+' albums' : null}
|
||||
</span>
|
||||
)
|
||||
@ -75,7 +75,7 @@ export default class GridItem extends React.Component{
|
||||
|
||||
case 'album':
|
||||
return (
|
||||
<span>
|
||||
<span className="grid__item__secondary__content">
|
||||
{item.artists !== undefined ? <ArtistSentence nolinks artists={item.artists} /> : null}
|
||||
</span>
|
||||
)
|
||||
@ -83,7 +83,7 @@ export default class GridItem extends React.Component{
|
||||
|
||||
default:
|
||||
return (
|
||||
<span>
|
||||
<span className="grid__item__secondary__content">
|
||||
{ item.artists !== undefined ? <ArtistSentence nolinks artists={ item.artists } /> : null }
|
||||
{ item.followers !== undefined ? item.followers.toLocaleString()+' followers' : null }
|
||||
</span>
|
||||
|
||||
@ -62,6 +62,10 @@ class CustomLink extends React.Component{
|
||||
className += this.props.className;
|
||||
}
|
||||
|
||||
if (!this.props.to){
|
||||
return <span className={className}>{this.props.children}</span>;
|
||||
}
|
||||
|
||||
// We have an active detector method
|
||||
// This is used almost solely by the Sidebar navigation
|
||||
if (this.props.history !== undefined){
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
|
||||
import createBrowserHistory from 'history/createBrowserHistory'
|
||||
|
||||
const customHistory = createBrowserHistory();
|
||||
export default customHistory;
|
||||
@ -951,6 +951,49 @@ export let uriSource = function(uri){
|
||||
var exploded = uri.split(':');
|
||||
return exploded[0]
|
||||
}
|
||||
/**
|
||||
* Identify what kind of asset a URI is (playlist, album, etc)
|
||||
*
|
||||
* @param uri = string
|
||||
* @return string
|
||||
**/
|
||||
export let uriType = function(uri){
|
||||
if (!uri) return null;
|
||||
|
||||
var exploded = uri.split(':')
|
||||
|
||||
if (exploded[0] == 'm3u'){
|
||||
return 'playlist'
|
||||
}
|
||||
|
||||
if (exploded[0] == 'iris'){
|
||||
switch (exploded[1]){
|
||||
case 'search':
|
||||
case 'discover':
|
||||
case 'browse':
|
||||
return exploded[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (exploded[1]){
|
||||
case 'track':
|
||||
case 'artist':
|
||||
case 'album':
|
||||
case 'playlist':
|
||||
case 'genre':
|
||||
return exploded[1]
|
||||
break
|
||||
|
||||
case 'user':
|
||||
if (exploded.length > 3 && exploded[3] == 'playlist'){
|
||||
return 'playlist'
|
||||
}
|
||||
return exploded[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export let sourceIcon = function(uri,source = null){
|
||||
if (uri) source = uriSource(uri)
|
||||
@ -1062,49 +1105,6 @@ export let getFromUri = function(element, uri = ""){
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify what kind of asset a URI is (playlist, album, etc)
|
||||
*
|
||||
* @param uri = string
|
||||
* @return string
|
||||
**/
|
||||
export let uriType = function(uri){
|
||||
if (!uri) return null;
|
||||
|
||||
var exploded = uri.split(':')
|
||||
|
||||
if (exploded[0] == 'm3u'){
|
||||
return 'playlist'
|
||||
}
|
||||
|
||||
if (exploded[0] == 'iris'){
|
||||
switch (exploded[1]){
|
||||
case 'search':
|
||||
case 'discover':
|
||||
case 'browse':
|
||||
return exploded[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (exploded[1]){
|
||||
case 'track':
|
||||
case 'artist':
|
||||
case 'album':
|
||||
case 'playlist':
|
||||
case 'genre':
|
||||
return exploded[1]
|
||||
break
|
||||
|
||||
case 'user':
|
||||
if (exploded.length > 3 && exploded[3] == 'playlist'){
|
||||
return 'playlist'
|
||||
}
|
||||
return exploded[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a link to an asset. Using the URI type we can ascertain where we need
|
||||
@ -1139,7 +1139,11 @@ export let buildLink = function (uri){
|
||||
export let arrayOf = function(property, items){
|
||||
let array = [];
|
||||
for (var item of items){
|
||||
array.push(item[property]);
|
||||
|
||||
// Make sure the property is defined
|
||||
if (item[property] !== undefined && item[property] != null){
|
||||
array.push(item[property]);
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
@ -5,11 +5,11 @@ import { Provider } from 'react-redux';
|
||||
import { createStore } from 'redux';
|
||||
import { BrowserRouter, Route, IndexRoute } from "react-router-dom";
|
||||
|
||||
import store from './bootstrap.js';
|
||||
require('../scss/app.scss');
|
||||
|
||||
import store from './store';
|
||||
import App from './App';
|
||||
|
||||
require('../scss/app.scss');
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<BrowserRouter basename="/iris">
|
||||
|
||||
@ -596,15 +596,19 @@ const MopidyMiddleware = (function(){
|
||||
**/
|
||||
|
||||
case 'MOPIDY_PLAY_PLAYLIST':
|
||||
|
||||
var playlist = store.getState().core.playlists[action.uri];
|
||||
|
||||
// We have the playlist loaded already, and we've got at least 1 track to start playing
|
||||
if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0){
|
||||
|
||||
// Spotify-provied playlists need to be handled by the Spotify service
|
||||
// We only need to load them if we haven't already got all the tracks
|
||||
if (playlist.provider == 'spotify' && playlist.tracks_total != playlist.tracks_uris.length){
|
||||
// We've got all of the tracks, so just play those; no further action required
|
||||
if (playlist.tracks_total == playlist.tracks_uris.length){
|
||||
store.dispatch(mopidyActions.playURIs(playlist.tracks_uris, action.uri));
|
||||
break;
|
||||
}
|
||||
|
||||
// Spotify-provided playlists need to be handled by the Spotify service
|
||||
if (playlist.provider == 'spotify'){
|
||||
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'play'));
|
||||
break;
|
||||
}
|
||||
@ -620,7 +624,7 @@ const MopidyMiddleware = (function(){
|
||||
request(socket, store, 'playlists.lookup', {uri: action.uri})
|
||||
.then(
|
||||
response => {
|
||||
if (response.tracks === undefined){
|
||||
if (!response || response.tracks === undefined || !response.tracks){
|
||||
store.dispatch(uiActions.createNotification({content: 'Failed to load playlist tracks', type: 'bad'}));
|
||||
} else {
|
||||
var tracks_uris = helpers.arrayOf('uri',response.tracks);
|
||||
@ -646,9 +650,18 @@ const MopidyMiddleware = (function(){
|
||||
// We have the playlist loaded already, and we've got at least 1 track to start playing
|
||||
if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0){
|
||||
|
||||
// Spotify-provied playlists need to be handled by the Spotify service
|
||||
// We only need to load them if we haven't already got all the tracks
|
||||
if (playlist.provider == 'spotify' && playlist.tracks_total != playlist.tracks_uris.length){
|
||||
// We've got all of the tracks, so just play those; no further action required
|
||||
if (playlist.tracks_total == playlist.tracks_uris.length){
|
||||
var tracks_uris = Object.assign([], playlist.tracks_uris);
|
||||
if (action.shuffle){
|
||||
tracks_uris = helpers.shuffle(tracks_uris);
|
||||
}
|
||||
store.dispatch(mopidyActions.enqueueURIs(tracks_uris, action.uri, action.play_next, action.at_position, action.offset))
|
||||
break;
|
||||
}
|
||||
|
||||
// Spotify-provided playlists need to be handled by the Spotify service
|
||||
if (playlist.provider == 'spotify'){
|
||||
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'enqueue', action.play_next));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1671,8 +1671,10 @@ export function getAllPlaylistTracksProcessor(data){
|
||||
// Add on our new batch of loaded tracks
|
||||
var uris = []
|
||||
var new_uris = []
|
||||
for (var i = 0; i < response.items.length; i++){
|
||||
new_uris.push(response.items[i].track.uri)
|
||||
for (var item of response.items){
|
||||
if (item.track){
|
||||
new_uris.push(item.track.uri);
|
||||
}
|
||||
}
|
||||
if (data.uris){
|
||||
uris = [...data.uris, ...new_uris];
|
||||
|
||||
@ -25,7 +25,7 @@ import googleMiddleware from './services/google/middleware';
|
||||
import snapcastMiddleware from './services/snapcast/middleware';
|
||||
import localstorageMiddleware from './services/localstorage/middleware';
|
||||
|
||||
var initialState = {
|
||||
export const state = {
|
||||
core: {
|
||||
outputs: [],
|
||||
queue: [],
|
||||
@ -99,17 +99,15 @@ var initialState = {
|
||||
};
|
||||
|
||||
// load all our stored values from LocalStorage
|
||||
initialState.core = Object.assign({}, initialState.core, helpers.getStorage('core'));
|
||||
initialState.ui = Object.assign({}, initialState.ui, helpers.getStorage('ui'));
|
||||
initialState.mopidy = Object.assign({}, initialState.mopidy, helpers.getStorage('mopidy'));
|
||||
initialState.pusher = Object.assign({}, initialState.pusher, helpers.getStorage('pusher'));
|
||||
initialState.spotify = Object.assign({}, initialState.spotify, helpers.getStorage('spotify'));
|
||||
initialState.lastfm = Object.assign({}, initialState.lastfm, helpers.getStorage('lastfm'));
|
||||
initialState.genius = Object.assign({}, initialState.genius, helpers.getStorage('genius'));
|
||||
initialState.google = Object.assign({}, initialState.google, helpers.getStorage('google'));
|
||||
initialState.snapcast = Object.assign({}, initialState.snapcast, helpers.getStorage('snapcast'));
|
||||
|
||||
console.log('Bootstrapping', initialState);
|
||||
state.core = Object.assign({}, state.core, helpers.getStorage('core'));
|
||||
state.ui = Object.assign({}, state.ui, helpers.getStorage('ui'));
|
||||
state.mopidy = Object.assign({}, state.mopidy, helpers.getStorage('mopidy'));
|
||||
state.pusher = Object.assign({}, state.pusher, helpers.getStorage('pusher'));
|
||||
state.spotify = Object.assign({}, state.spotify, helpers.getStorage('spotify'));
|
||||
state.lastfm = Object.assign({}, state.lastfm, helpers.getStorage('lastfm'));
|
||||
state.genius = Object.assign({}, state.genius, helpers.getStorage('genius'));
|
||||
state.google = Object.assign({}, state.google, helpers.getStorage('google'));
|
||||
state.snapcast = Object.assign({}, state.snapcast, helpers.getStorage('snapcast'));
|
||||
|
||||
var reducers = combineReducers({
|
||||
core,
|
||||
@ -125,7 +123,7 @@ var reducers = combineReducers({
|
||||
|
||||
export default createStore(
|
||||
reducers,
|
||||
initialState,
|
||||
state,
|
||||
applyMiddleware(
|
||||
thunk,
|
||||
localstorageMiddleware,
|
||||
@ -24,7 +24,7 @@ import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as spotifyActions from '../services/spotify/actions';
|
||||
import * as lastfmActions from '../services/lastfm/actions';
|
||||
|
||||
class Album extends React.Component{
|
||||
export class Album extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
|
||||
45
tests/App.test.js
Executable file
45
tests/App.test.js
Executable file
@ -0,0 +1,45 @@
|
||||
|
||||
import React from 'react';;
|
||||
import ReactDOM from 'react-dom';;
|
||||
import { Provider } from 'react-redux';
|
||||
import { createStore } from 'redux';
|
||||
import { BrowserRouter, Route, IndexRoute } from "react-router-dom";
|
||||
|
||||
// Testing-specific
|
||||
import renderer from 'react-test-renderer';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
|
||||
// Test subjects
|
||||
import store from '../src/js/store';
|
||||
import * as coreActions from '../src/js/services/core/actions';
|
||||
import * as uiActions from '../src/js/services/ui/actions';
|
||||
import * as pusherActions from '../src/js/services/pusher/actions';
|
||||
import * as mopidyActions from '../src/js/services/mopidy/actions';
|
||||
import * as spotifyActions from '../src/js/services/spotify/actions';
|
||||
import * as lastfmActions from '../src/js/services/lastfm/actions';
|
||||
import * as geniusActions from '../src/js/services/genius/actions';
|
||||
import * as snapcastActions from '../src/js/services/snapcast/actions';
|
||||
import { App } from '../src/js/App';
|
||||
|
||||
describe('<App />', () => {
|
||||
|
||||
it('should render', () => {
|
||||
const dom = shallow(
|
||||
<App
|
||||
history={[]}
|
||||
location={{}}
|
||||
uiActions={uiActions}
|
||||
coreActions={coreActions}
|
||||
pusherActions={pusherActions}
|
||||
mopidyActions={mopidyActions}
|
||||
spotifyActions={spotifyActions}
|
||||
lastfmActions={lastfmActions}
|
||||
geniusActions={geniusActions}
|
||||
snapcastActions={snapcastActions}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
88
tests/components/Dater.test.js
Executable file
88
tests/components/Dater.test.js
Executable file
@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
// Testing-specific
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
|
||||
// Test subjects
|
||||
import Dater from '../../src/js/components/Dater';
|
||||
|
||||
describe('<Dater />', () => {
|
||||
|
||||
it('should render null when given invalid props', () => {
|
||||
|
||||
// Invalid type
|
||||
const dom_1 = shallow(<Dater type="invalid-type" data={100000} />);
|
||||
expect(dom_1.type()).toEqual(null);
|
||||
|
||||
// Missing type
|
||||
const dom_2 = shallow(<Dater data={100000} />);
|
||||
expect(dom_2.type()).toEqual(null);
|
||||
|
||||
// Invalid data
|
||||
var data = {
|
||||
invalid_object: "value"
|
||||
};
|
||||
const dom_3 = shallow(<Dater type="total-time" data={data} />);
|
||||
expect(dom_3.type()).toEqual(null);
|
||||
|
||||
// Missing data
|
||||
const dom_4 = shallow(<Dater type="ago" />);
|
||||
expect(dom_4.type()).toEqual(null);
|
||||
});
|
||||
|
||||
it('should handle milliseconds', () => {
|
||||
const dom = shallow(<Dater type="length" data={30000} />);
|
||||
expect(dom.text()).toEqual('0:30');
|
||||
});
|
||||
|
||||
it('should handle date (yyyy-mm-dd)', () => {
|
||||
const dom = shallow(<Dater type="date" data="2015-10-23" />);
|
||||
expect(dom.text()).toEqual("23/10/2015");
|
||||
});
|
||||
|
||||
it('should handle date (mm/dd/yyyy)', () => {
|
||||
const dom = shallow(<Dater type="date" data="10/23/2015" />);
|
||||
expect(dom.text()).toEqual("23/10/2015");
|
||||
});
|
||||
|
||||
it('should handle ago (days)', () => {
|
||||
var date = new Date();
|
||||
date.setDate( date.getDate() - 3 );
|
||||
|
||||
const dom = shallow(<Dater type="ago" data={date} />);
|
||||
expect(dom.text()).toEqual('3 days');
|
||||
});
|
||||
|
||||
it('should handle ago (hours)', () => {
|
||||
var date = new Date();
|
||||
date.setTime( date.getTime() - 3 * 3600000 );
|
||||
|
||||
const dom = shallow(<Dater type="ago" data={date} />);
|
||||
expect(dom.text()).toEqual('3 hours');
|
||||
});
|
||||
|
||||
it('should handle ago (minutes)', () => {
|
||||
var date = new Date();
|
||||
date.setTime( date.getTime() - 3 * 60000 );
|
||||
|
||||
const dom = shallow(<Dater type="ago" data={date} />);
|
||||
expect(dom.text()).toEqual('3 minutes');
|
||||
});
|
||||
|
||||
it('should handle total time', () => {
|
||||
var data = [
|
||||
{
|
||||
duration: 5 * 60000
|
||||
},
|
||||
{
|
||||
duration: 5 * 60000
|
||||
},
|
||||
{
|
||||
duration: 5 * 60000
|
||||
}
|
||||
];
|
||||
const dom = shallow(<Dater type="total-time" data={data} />);
|
||||
expect(dom.text()).toEqual('15 mins');
|
||||
});
|
||||
});
|
||||
33
tests/components/GridItem.test.js
Executable file
33
tests/components/GridItem.test.js
Executable file
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
// Testing-specific
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
const state = require('../state');
|
||||
|
||||
// Test subjects
|
||||
import GridItem from '../../src/js/components/GridItem';
|
||||
|
||||
describe('<GridItem />', () => {
|
||||
|
||||
it('should handle album', () => {
|
||||
var album = state.core.albums['jest:album:one'];
|
||||
var dom = shallow(<GridItem item={album} />);
|
||||
expect(dom.find('.grid__item__name').text()).toEqual('One');
|
||||
expect(dom.find('.grid__item__secondary__content').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle artist', () => {
|
||||
var artist = state.core.artists['jest:artist:alpha'];
|
||||
var dom = shallow(<GridItem item={artist} />);
|
||||
expect(dom.find('.grid__item__name').text()).toEqual('Alpha');
|
||||
expect(dom.find('.grid__item__secondary__content').text()).toEqual('123 followers 1 albums');
|
||||
});
|
||||
|
||||
it('should handle playlist', () => {
|
||||
var playlist = state.core.playlists['jest:playlist:one'];
|
||||
var dom = shallow(<GridItem item={playlist} />);
|
||||
expect(dom.find('.grid__item__name').text()).toEqual('One');
|
||||
expect(dom.find('.grid__item__secondary__content').text()).toEqual('2 tracks');
|
||||
});
|
||||
});
|
||||
28
tests/components/Link.test.js
Executable file
28
tests/components/Link.test.js
Executable file
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
// Testing-specific
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
|
||||
// Test subjects
|
||||
import Link from '../../src/js/components/Link';
|
||||
|
||||
describe('<Link />', () => {
|
||||
|
||||
const dom = mount(
|
||||
<BrowserRouter>
|
||||
<Link to="test" className="test-classname">Link contents</Link>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
it('should render a valid <a> tag', () => {
|
||||
const a = dom.find('a');
|
||||
expect(a.length).toBe(1);
|
||||
expect(a.find('[href]').length).toBe(1);
|
||||
expect(a.text()).toEqual('Link contents');
|
||||
});
|
||||
|
||||
it('should handle className prop', () => {
|
||||
expect(dom.find('a').hasClass('test-classname')).toBe(true);
|
||||
});
|
||||
});
|
||||
206
tests/helpers.test.js
Executable file
206
tests/helpers.test.js
Executable file
@ -0,0 +1,206 @@
|
||||
|
||||
import * as helpers from '../src/js/helpers';
|
||||
const state = require('./state');
|
||||
|
||||
describe('localStorage', () => {
|
||||
|
||||
it('should handle keys that are not in storage', () => {
|
||||
expect(helpers.getStorage('invalid_key')).toEqual({});
|
||||
expect(helpers.getStorage('invalid_key', 'default_value')).toEqual('default_value');
|
||||
});
|
||||
|
||||
it('should store data', () => {
|
||||
|
||||
// Initially empty
|
||||
expect(helpers.getStorage('test_key')).toEqual({});
|
||||
|
||||
// Set it
|
||||
helpers.setStorage('test_key', 'test_value');
|
||||
|
||||
// Test storage
|
||||
expect(helpers.getStorage('test_key')).toEqual('test_value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isCached', () => {
|
||||
|
||||
it('should return false when not cached', () => {
|
||||
expect(helpers.isCached('https://picsum.photos/200')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when cached', () => {
|
||||
var image = new Image();
|
||||
image.src = 'https://picsum.photos/200';
|
||||
image.onload = function(){
|
||||
expect(helpers.isCached('https://picsum.photos/200')).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatImages', () => {
|
||||
|
||||
it('should ignore already-formatted objects', () => {
|
||||
var images = [
|
||||
{
|
||||
formatted: true,
|
||||
small: 'ignored-image.jpg'
|
||||
}
|
||||
];
|
||||
expect(helpers.formatImages(images).small).toBe('ignored-image.jpg');
|
||||
});
|
||||
|
||||
it('should handle Mopidy object', () => {
|
||||
var images = [
|
||||
{
|
||||
__model__: 'Image',
|
||||
width: 600,
|
||||
url: 'test-image.jpg'
|
||||
}
|
||||
];
|
||||
expect(helpers.formatImages(images).small).toBe('test-image.jpg');
|
||||
});
|
||||
|
||||
it('should handle Mopidy string', () => {
|
||||
var images = ['test-image.jpg'];
|
||||
expect(helpers.formatImages(images).small).toBe('test-image.jpg');
|
||||
});
|
||||
|
||||
it('should handle Spotify image', () => {
|
||||
var images = [
|
||||
{
|
||||
width: 600,
|
||||
url: 'test-image.jpg'
|
||||
}
|
||||
];
|
||||
expect(helpers.formatImages(images).small).toBe('test-image.jpg');
|
||||
});
|
||||
|
||||
it('should handle LastFM image', () => {
|
||||
var images = [
|
||||
{
|
||||
size: 'small',
|
||||
'#text': 'test-image.jpg'
|
||||
}
|
||||
];
|
||||
expect(helpers.formatImages(images).small).toBe('test-image.jpg');
|
||||
});
|
||||
|
||||
it('should handle Genius image', () => {
|
||||
var images = {
|
||||
small: {
|
||||
url: 'test-image.jpg'
|
||||
}
|
||||
};
|
||||
expect(helpers.formatImages(images).small).toBe('test-image.jpg');
|
||||
});
|
||||
|
||||
it('should up-fill sizes', () => {
|
||||
var images = [
|
||||
{
|
||||
width: 50,
|
||||
url: 'small.jpg'
|
||||
}
|
||||
];
|
||||
expect(helpers.formatImages(images).medium).toBe('small.jpg');
|
||||
expect(helpers.formatImages(images).large).toBe('small.jpg');
|
||||
expect(helpers.formatImages(images).huge).toBe('small.jpg');
|
||||
});
|
||||
|
||||
it('should down-fill sizes', () => {
|
||||
var images = [
|
||||
{
|
||||
width: 1900,
|
||||
url: 'huge.jpg'
|
||||
}
|
||||
];
|
||||
expect(helpers.formatImages(images).small).toBe('huge.jpg');
|
||||
expect(helpers.formatImages(images).medium).toBe('huge.jpg');
|
||||
expect(helpers.formatImages(images).large).toBe('huge.jpg');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* TODO: Formatters
|
||||
**/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
describe('uriSource', () => {
|
||||
it('should digest uri into a string', () => {
|
||||
expect(typeof(helpers.uriType('spotify:album:123'))).toBe('string');
|
||||
expect(helpers.uriSource('spotify:album:123')).toBe('spotify');
|
||||
});
|
||||
});
|
||||
|
||||
describe('uriType', () => {
|
||||
it('should digest uri into a string', () => {
|
||||
expect(typeof(helpers.uriType('spotify:album:123'))).toBe('string');
|
||||
expect(helpers.uriType('spotify:album:123')).toBe('album');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sourceIcon', () => {
|
||||
it('should digest uri into a string', () => {
|
||||
expect(typeof(helpers.sourceIcon('spotify:album:123'))).toBe('string');
|
||||
expect(helpers.sourceIcon('spotify:album:123')).toBe('spotify');
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildLink', () => {
|
||||
|
||||
it('should build uri into link as a string', () => {
|
||||
var link = helpers.buildLink('spotify:album:123');
|
||||
expect(typeof(link)).toBe('string');
|
||||
expect(link).toBe('/album/spotify%3Aalbum%3A123');
|
||||
});
|
||||
|
||||
it('should handle special characters', () => {
|
||||
var link = helpers.buildLink('spotify:album:http://test.com/123!@#$%^&[];<>/?" .mp3');
|
||||
expect(typeof(link)).toBe('string');
|
||||
expect(link).toBe('/album/spotify%3Aalbum%3Ahttp%3A%2F%2Ftest.com%2F123!%40%23%24%25%5E%26%5B%5D%3B%3C%3E%2F%3F%22%20.mp3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('arrayOf', () => {
|
||||
|
||||
it('should return a one-dimensional array', () => {
|
||||
var items = [
|
||||
{
|
||||
uri: '123',
|
||||
name: '123'
|
||||
},
|
||||
{
|
||||
uri: '456',
|
||||
name: '456'
|
||||
}
|
||||
];
|
||||
var uris = helpers.arrayOf('uri', items);
|
||||
expect(Array.isArray(uris)).toBe(true);
|
||||
expect(uris.length).toBe(2);
|
||||
|
||||
for (var uri of uris){
|
||||
expect(typeof(uri)).toBe('string');
|
||||
}
|
||||
});
|
||||
|
||||
it('should remove null and undefined items', () => {
|
||||
var items = [
|
||||
{
|
||||
uri: '123',
|
||||
name: '123'
|
||||
},
|
||||
{
|
||||
uri: null,
|
||||
name: '456'
|
||||
},
|
||||
{
|
||||
name: '789'
|
||||
}
|
||||
];
|
||||
var uris = helpers.arrayOf('uri', items);
|
||||
expect(uris.length).toBe(1);
|
||||
});
|
||||
});
|
||||
4
tests/setup.js
Executable file
4
tests/setup.js
Executable file
@ -0,0 +1,4 @@
|
||||
import Enzyme from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
96
tests/state.js
Executable file
96
tests/state.js
Executable file
@ -0,0 +1,96 @@
|
||||
|
||||
import store from '../src/js/store';
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
/**
|
||||
* Artists
|
||||
**/
|
||||
state.core.artists['jest:artist:alpha'] = {
|
||||
uri: 'jest:artist:alpha',
|
||||
name: 'Alpha',
|
||||
followers: 123,
|
||||
albums_uris: [
|
||||
'jest:album:one'
|
||||
],
|
||||
albums_total: 1,
|
||||
biography: "Alpha biography"
|
||||
};
|
||||
|
||||
state.core.artists['jest:artist:beta'] = {
|
||||
uri: 'jest:artist:beta',
|
||||
name: 'Beta',
|
||||
followers: 100
|
||||
};
|
||||
|
||||
state.core.artists['jest:artist:charlie'] = {
|
||||
uri: 'jest:artist:charlie',
|
||||
name: 'Charlie',
|
||||
followers: 999
|
||||
};
|
||||
|
||||
state.core.artists['jest:artist:delta'] = {
|
||||
uri: 'jest:artist:delta',
|
||||
name: 'Delta',
|
||||
followers: 987
|
||||
};
|
||||
|
||||
/**
|
||||
* Albums
|
||||
**/
|
||||
state.core.albums['jest:album:one'] = {
|
||||
uri: 'jest:album:one',
|
||||
name: 'One',
|
||||
artists_uris: [
|
||||
'jest:artist:alpha'
|
||||
],
|
||||
tracks_uris: [
|
||||
'jest:track:one',
|
||||
'jest:track:two'
|
||||
],
|
||||
wiki: "Wiki text"
|
||||
};
|
||||
|
||||
/**
|
||||
* Playlists
|
||||
**/
|
||||
state.core.playlists['jest:playlist:one'] = {
|
||||
uri: 'jest:playlist:one',
|
||||
name: 'One',
|
||||
tracks_uris: [
|
||||
'jest:track:one',
|
||||
'jest:track:three'
|
||||
],
|
||||
tracks_total: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* Tracks
|
||||
**/
|
||||
state.core.tracks['jest:track:one'] = {
|
||||
uri: 'jest:track:one',
|
||||
name: 'One',
|
||||
artists_uris: [
|
||||
'jest:artist:charlie'
|
||||
]
|
||||
};
|
||||
|
||||
state.core.tracks['jest:track:two'] = {
|
||||
uri: 'jest:track:two',
|
||||
name: 'Two',
|
||||
artists_uris: [
|
||||
'jest:artist:alpha',
|
||||
'jest:artist:beta'
|
||||
]
|
||||
};
|
||||
|
||||
state.core.tracks['jest:track:three'] = {
|
||||
uri: 'jest:track:three',
|
||||
name: 'Three',
|
||||
artists_uris: [
|
||||
'jest:artist:charlie',
|
||||
'jest:artist:delta'
|
||||
]
|
||||
};
|
||||
|
||||
module.exports = state;
|
||||
34
tests/views/Album.test.js
Executable file
34
tests/views/Album.test.js
Executable file
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BrowserRouter, Route, IndexRoute } from "react-router-dom";
|
||||
|
||||
// Testing-specific
|
||||
import renderer from 'react-test-renderer';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
const state = require('../state');
|
||||
|
||||
// Test subjects
|
||||
import { Album } from '../../src/js/views/Album';
|
||||
import * as helpers from '../../src/js/helpers';
|
||||
import * as uiActions from '../../src/js/services/ui/actions';
|
||||
import * as coreActions from '../../src/js/services/core/actions';
|
||||
|
||||
describe('<Album />', () => {
|
||||
|
||||
var album = state.core.albums['jest:album:one'];
|
||||
|
||||
it('should render accurately', () => {
|
||||
const dom = shallow(
|
||||
<Album
|
||||
album={album}
|
||||
uiActions={uiActions}
|
||||
coreActions={coreActions}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(dom.find('.album-view').length).toBe(1);
|
||||
|
||||
expect(dom.find('h1').text()).toEqual('One');
|
||||
expect(dom.find('.wiki__text p').text()).toEqual('Wiki text');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user