Adding test suites, working through helpers; Adding test state

This commit is contained in:
James Barnsley
2019-03-22 09:02:00 +13:00
parent 269bf005ed
commit ccb0d6672a
12 changed files with 522 additions and 153 deletions

View File

@ -1361,6 +1361,48 @@ var uriSource = exports.uriSource = function uriSource(uri) {
var exploded = uri.split(':');
return exploded[0];
};
/**
* Identify what kind of asset a URI is (playlist, album, etc)
*
* @param uri = string
* @return string
**/
var uriType = exports.uriType = function uriType(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;
}
};
var sourceIcon = exports.sourceIcon = function sourceIcon(uri) {
var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
@ -1474,49 +1516,6 @@ var getFromUri = exports.getFromUri = function getFromUri(element) {
return null;
};
/**
* Identify what kind of asset a URI is (playlist, album, etc)
*
* @param uri = string
* @return string
**/
var uriType = exports.uriType = function uriType(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
* to direct the user (eg /track/local:track:1235.mp3)
@ -1556,7 +1555,11 @@ var arrayOf = exports.arrayOf = function arrayOf(property, items) {
for (var _iterator21 = items[Symbol.iterator](), _step21; !(_iteratorNormalCompletion21 = (_step21 = _iterator21.next()).done); _iteratorNormalCompletion21 = true) {
var item = _step21.value;
array.push(item[property]);
// Make sure the property is defined
if (item[property] !== undefined && item[property] != null) {
array.push(item[property]);
}
}
} catch (err) {
_didIteratorError21 = true;
@ -19604,13 +19607,18 @@ var Dater = function (_React$Component) {
value: function render() {
if (!this.props.data) {
return null;
} else {
return _react2.default.createElement(
'span',
{ className: 'dater' },
this.calculate()
);
}
var calculation = this.calculate();
if (!calculation) {
return null;
}
return _react2.default.createElement(
'span',
{ className: 'dater' },
calculation
);
}
}]);
@ -21165,7 +21173,7 @@ var GridItem = function (_React$Component) {
if (item.tracks_total) {
return _react2.default.createElement(
'span',
null,
{ className: 'grid__item__secondary__content' },
item.tracks_total,
' tracks'
);
@ -21175,8 +21183,8 @@ var GridItem = function (_React$Component) {
case 'artist':
return _react2.default.createElement(
'span',
null,
item.followers !== undefined ? item.followers.toLocaleString() + ' followers' : null,
{ className: 'grid__item__secondary__content' },
item.followers !== undefined ? item.followers.toLocaleString() + ' followers ' : null,
item.albums_uris !== undefined ? item.albums_uris.length + ' albums' : null
);
break;
@ -21184,7 +21192,7 @@ var GridItem = function (_React$Component) {
case 'album':
return _react2.default.createElement(
'span',
null,
{ className: 'grid__item__secondary__content' },
item.artists !== undefined ? _react2.default.createElement(_ArtistSentence2.default, { nolinks: true, artists: item.artists }) : null
);
break;
@ -21192,7 +21200,7 @@ var GridItem = function (_React$Component) {
default:
return _react2.default.createElement(
'span',
null,
{ className: 'grid__item__secondary__content' },
item.artists !== undefined ? _react2.default.createElement(_ArtistSentence2.default, { nolinks: true, artists: item.artists }) : null,
item.followers !== undefined ? item.followers.toLocaleString() + ' followers' : null
);
@ -56352,6 +56360,7 @@ module.exports = hoistNonReactStatics;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.state = undefined;
var _redux = __webpack_require__(2);
@ -56443,7 +56452,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
var initialState = {
var state = exports.state = {
core: {
outputs: [],
queue: [],
@ -56517,15 +56526,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'));
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 = (0, _redux.combineReducers)({
core: _reducer2.default,
@ -56539,7 +56548,7 @@ var reducers = (0, _redux.combineReducers)({
snapcast: _reducer14.default
});
exports.default = (0, _redux.createStore)(reducers, initialState, (0, _redux.applyMiddleware)(_reduxThunk2.default, _middleware20.default, _middleware2.default, _middleware4.default, _middleware8.default, _middleware6.default, _middleware14.default, _middleware10.default, _middleware12.default, _middleware16.default, _middleware18.default));
exports.default = (0, _redux.createStore)(reducers, state, (0, _redux.applyMiddleware)(_reduxThunk2.default, _middleware20.default, _middleware2.default, _middleware4.default, _middleware8.default, _middleware6.default, _middleware14.default, _middleware10.default, _middleware12.default, _middleware16.default, _middleware18.default));
/***/ }),
/* 176 */

File diff suppressed because one or more lines are too long

View File

@ -98,7 +98,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1553051875";
var build = "1553131269";
var version = "3.33.0";
// Construct the script tag

View File

@ -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>;
}
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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,15 +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'));
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,
@ -123,7 +123,7 @@ var reducers = combineReducers({
export default createStore(
reducers,
initialState,
state,
applyMiddleware(
thunk,
localstorageMiddleware,

View File

@ -9,6 +9,28 @@ 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');
@ -49,19 +71,17 @@ describe('<Dater />', () => {
});
it('should handle total time', () => {
var data = {
tracks: [
{
duration: 5 * 60000
},
{
duration: 5 * 60000
},
{
duration: 5 * 60000
}
]
}
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');
});

View 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');
});
});

206
tests/helpers.test.js Executable file
View 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);
});
});

96
tests/state.js Executable file
View 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;

View File

@ -5,26 +5,19 @@ 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 store from '../../src/js/store';
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 = {
uri: 'jest:album:test',
name: 'Test album',
tracks: {
}
}
var album = state.core.albums['jest:album:one'];
it('should render', () => {
it('should render accurately', () => {
const dom = shallow(
<Album
album={album}
@ -34,5 +27,8 @@ describe('<Album />', () => {
);
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');
});
});