diff --git a/.gitignore b/.gitignore
index a9ae24354836b828a4695fd6a93b33848ec48d3a..1a88c891c167adc56125dd376d01e3cd8d207ec7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -81,8 +81,8 @@ front/dist/
 front/npm-debug.log*
 front/yarn-debug.log*
 front/yarn-error.log*
-front/test/unit/coverage
-front/test/e2e/reports
+front/tests/unit/coverage
+front/tests/e2e/reports
 front/selenium-debug.log
 docs/_build
 
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 57e258e9387f3225ffbfd576e828d84fb048db71..42bca2d3466e96a4c0b93da66b885f9dc15601d9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -156,7 +156,6 @@ test_api:
   tags:
     - docker
 
-
 test_front:
   stage: test
   image: node:9
@@ -166,7 +165,7 @@ test_front:
     - branches
   script:
     - yarn install
-    - yarn run unit
+    - yarn test:unit
   cache:
     key: "funkwhale__front_dependencies"
     paths:
@@ -179,7 +178,6 @@ test_front:
   tags:
     - docker
 
-
 build_front:
   stage: build
   image: node:9
@@ -192,7 +190,7 @@ build_front:
     - yarn run i18n-compile
     # this is to ensure we don't have any errors in the output,
     # cf https://code.eliotberriot.com/funkwhale/funkwhale/issues/169
-    - yarn run build | tee /dev/stderr | (! grep -i 'ERROR in')
+    - yarn build | tee /dev/stderr | (! grep -i 'ERROR in')
     - chmod -R 750 dist
   cache:
     key: "funkwhale__front_dependencies"
@@ -210,7 +208,6 @@ build_front:
   tags:
     - docker
 
-
 pages:
   stage: test
   image: python:3.6
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index f8b18ebe3e82c232b91e37f3de53ffb745bf8816..86832841d46c9faec137f5999f23160b9d5861d5 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -466,12 +466,12 @@ Running tests
 
 To run the front-end test suite, use the following command::
 
-    docker-compose -f dev.yml run --rm front yarn run unit
+    docker-compose -f dev.yml run --rm front yarn test:unit
 
 We also support a "watch and test" mode were we continually relaunch
 tests when changes are recorded on the file system::
 
-    docker-compose -f dev.yml run --rm front yarn run unit-watch
+    docker-compose -f dev.yml run --rm front yarn test:unit -w
 
 The latter is especially useful when you are debugging failing tests.
 
diff --git a/front/package.json b/front/package.json
index c8d982065bc0ce50031833b28c42e83729438b41..9c8cba9fee9d8382b4efd46ad54d44f8566946fd 100644
--- a/front/package.json
+++ b/front/package.json
@@ -1,18 +1,14 @@
 {
   "name": "front",
-  "private": true,
   "version": "0.1.0",
-  "description": "Funkwhale front-end",
-  "author": "Eliot Berriot <contact@eliotberriot.com>",
+  "private": true,
   "scripts": {
     "serve": "scripts/i18n-compile.sh && vue-cli-service serve --port ${VUE_PORT:-8000} --host ${VUE_HOST:-0.0.0.0}",
     "build": "scripts/i18n-compile.sh && vue-cli-service build",
+    "lint": "vue-cli-service lint",
     "i18n-extract": "scripts/i18n-extract.sh",
     "i18n-compile": "scripts/i18n-compile.sh",
-    "test": "npm run unit && npm run e2e",
-    "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
-    "unit-watch": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js",
-    "lint": "vue-cli-service lint"
+    "test:unit": "vue-cli-service test:unit"
   },
   "dependencies": {
     "axios": "^0.18.0",
@@ -41,11 +37,17 @@
   "devDependencies": {
     "@vue/cli-plugin-babel": "^3.0.0",
     "@vue/cli-plugin-eslint": "^3.0.0",
+    "@vue/cli-plugin-unit-mocha": "^3.0.0",
     "@vue/cli-service": "^3.0.0",
+    "@vue/test-utils": "^1.0.0-beta.20",
+    "chai": "^4.1.2",
     "easygettext": "^2.6.3",
     "eslint-plugin-html": "^4.0.5",
+    "mocha": "^5.2.0",
+    "moxios": "^0.4.0",
     "node-sass": "^4.9.3",
     "sass-loader": "^7.1.0",
+    "sinon": "^6.1.5",
     "vue-template-compiler": "^2.5.17"
   },
   "eslintConfig": {
@@ -84,5 +86,7 @@
     "> 1%",
     "last 2 versions",
     "not ie <= 8"
-  ]
+  ],
+  "author": "Eliot Berriot <contact@eliotberriot.com>",
+  "description": "Funkwhale front-end"
 }
diff --git a/front/test/e2e/custom-assertions/elementCount.js b/front/test/e2e/custom-assertions/elementCount.js
deleted file mode 100644
index c0d5fe00af8ecd2541f8d5347be15ed17932f69c..0000000000000000000000000000000000000000
--- a/front/test/e2e/custom-assertions/elementCount.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// A custom Nightwatch assertion.
-// the name of the method is the filename.
-// can be used in tests like this:
-//
-//   browser.assert.elementCount(selector, count)
-//
-// for how to write custom assertions see
-// http://nightwatchjs.org/guide#writing-custom-assertions
-exports.assertion = function (selector, count) {
-  this.message = 'Testing if element <' + selector + '> has count: ' + count
-  this.expected = count
-  this.pass = function (val) {
-    return val === this.expected
-  }
-  this.value = function (res) {
-    return res.value
-  }
-  this.command = function (cb) {
-    var self = this
-    return this.api.execute(function (selector) {
-      return document.querySelectorAll(selector).length
-    }, [selector], function (res) {
-      cb.call(self, res)
-    })
-  }
-}
diff --git a/front/test/e2e/nightwatch.conf.js b/front/test/e2e/nightwatch.conf.js
deleted file mode 100644
index f019c0ac440c7dc531d9b61640b26e28a0ebcee3..0000000000000000000000000000000000000000
--- a/front/test/e2e/nightwatch.conf.js
+++ /dev/null
@@ -1,46 +0,0 @@
-require('babel-register')
-var config = require('../../config')
-
-// http://nightwatchjs.org/gettingstarted#settings-file
-module.exports = {
-  src_folders: ['test/e2e/specs'],
-  output_folder: 'test/e2e/reports',
-  custom_assertions_path: ['test/e2e/custom-assertions'],
-
-  selenium: {
-    start_process: true,
-    server_path: require('selenium-server').path,
-    host: '127.0.0.1',
-    port: 4444,
-    cli_args: {
-      'webdriver.chrome.driver': require('chromedriver').path
-    }
-  },
-
-  test_settings: {
-    default: {
-      selenium_port: 4444,
-      selenium_host: 'localhost',
-      silent: true,
-      globals: {
-        devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
-      }
-    },
-
-    chrome: {
-      desiredCapabilities: {
-        browserName: 'chrome',
-        javascriptEnabled: true,
-        acceptSslCerts: true
-      }
-    },
-
-    firefox: {
-      desiredCapabilities: {
-        browserName: 'firefox',
-        javascriptEnabled: true,
-        acceptSslCerts: true
-      }
-    }
-  }
-}
diff --git a/front/test/e2e/runner.js b/front/test/e2e/runner.js
deleted file mode 100644
index 85d67d6bae0a2aa3b86e29a34e3e4f5703c99bbb..0000000000000000000000000000000000000000
--- a/front/test/e2e/runner.js
+++ /dev/null
@@ -1,33 +0,0 @@
-// 1. start the dev server using production config
-process.env.NODE_ENV = 'testing'
-var server = require('../../build/dev-server.js')
-
-server.ready.then(() => {
-  // 2. run the nightwatch test suite against it
-  // to run in additional browsers:
-  //    1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
-  //    2. add it to the --env flag below
-  // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
-  // For more information on Nightwatch's config file, see
-  // http://nightwatchjs.org/guide#settings-file
-  var opts = process.argv.slice(2)
-  if (opts.indexOf('--config') === -1) {
-    opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
-  }
-  if (opts.indexOf('--env') === -1) {
-    opts = opts.concat(['--env', 'chrome'])
-  }
-
-  var spawn = require('cross-spawn')
-  var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
-
-  runner.on('exit', function (code) {
-    server.close()
-    process.exit(code)
-  })
-
-  runner.on('error', function (err) {
-    server.close()
-    throw err
-  })
-})
diff --git a/front/test/e2e/specs/test.js b/front/test/e2e/specs/test.js
deleted file mode 100644
index a7b1bd920fd7869da00aabfb2c1ed80382f4c40c..0000000000000000000000000000000000000000
--- a/front/test/e2e/specs/test.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// For authoring Nightwatch tests, see
-// http://nightwatchjs.org/guide#usage
-
-module.exports = {
-  'default e2e tests': function (browser) {
-    // automatically uses dev Server port from /config.index.js
-    // default: http://localhost:8080
-    // see nightwatch.conf.js
-    const devServer = browser.globals.devServerURL
-
-    browser
-      .url(devServer)
-      .waitForElementVisible('#app', 5000)
-      .assert.elementPresent('.hello')
-      .assert.containsText('h1', 'Welcome to Your Vue.js App')
-      .assert.elementCount('img', 1)
-      .end()
-  }
-}
diff --git a/front/test/unit/index.js b/front/test/unit/index.js
deleted file mode 100644
index c69f33fd8a0fb2796073222645c42c7c15188a6e..0000000000000000000000000000000000000000
--- a/front/test/unit/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import Vue from 'vue'
-
-Vue.config.productionTip = false
-
-// require all test files (files that ends with .spec.js)
-const testsContext = require.context('./specs', true, /\.spec$/)
-testsContext.keys().forEach(testsContext)
-
-// require all src files except main.js for coverage.
-// you can also change this to match only the subset of files that
-// you want coverage for.
-const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
-srcContext.keys().forEach(srcContext)
diff --git a/front/test/unit/karma.conf.js b/front/test/unit/karma.conf.js
deleted file mode 100644
index 193aaff7662983b2b428af919c5b18b6e9179303..0000000000000000000000000000000000000000
--- a/front/test/unit/karma.conf.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// This is a karma config file. For more details see
-//   http://karma-runner.github.io/0.13/config/configuration-file.html
-// we are also using it with karma-webpack
-//   https://github.com/webpack/karma-webpack
-
-var webpackConfig = require('../../build/webpack.test.conf')
-
-module.exports = function (config) {
-  config.set({
-    // to run in additional browsers:
-    // 1. install corresponding karma launcher
-    //    http://karma-runner.github.io/0.13/config/browsers.html
-    // 2. add it to the `browsers` array below.
-    browsers: ['PhantomJS'],
-    frameworks: ['mocha', 'sinon-stub-promise', 'sinon-chai', 'phantomjs-shim'],
-    reporters: ['spec', 'coverage'],
-    files: [
-      '../../node_modules/es6-promise/dist/es6-promise.auto.js',
-      './index.js'
-    ],
-    preprocessors: {
-      './index.js': ['webpack', 'sourcemap']
-    },
-    captureTimeout: 15000,
-    retryLimit: 1,
-    webpack: webpackConfig,
-    webpackMiddleware: {
-      noInfo: true
-    },
-    coverageReporter: {
-      dir: './coverage',
-      reporters: [
-        { type: 'lcov', subdir: '.' },
-        { type: 'text-summary' }
-      ]
-    }
-  })
-}
diff --git a/front/test/unit/.eslintrc b/front/tests/unit/.eslintrc
similarity index 100%
rename from front/test/unit/.eslintrc
rename to front/tests/unit/.eslintrc
diff --git a/front/test/unit/specs/components/common.spec.js b/front/tests/unit/specs/components/common.spec.js
similarity index 90%
rename from front/test/unit/specs/components/common.spec.js
rename to front/tests/unit/specs/components/common.spec.js
index 1af4144ca858bb1c264d43cc602994569ce8fc8b..c0ae3a65e666b14170c3be83966e80922b42370e 100644
--- a/front/test/unit/specs/components/common.spec.js
+++ b/front/tests/unit/specs/components/common.spec.js
@@ -1,3 +1,5 @@
+import {expect} from 'chai'
+
 import Username from '@/components/common/Username.vue'
 
 import { render } from '../../utils'
diff --git a/front/test/unit/specs/filters/filters.spec.js b/front/tests/unit/specs/filters/filters.spec.js
similarity index 97%
rename from front/test/unit/specs/filters/filters.spec.js
rename to front/tests/unit/specs/filters/filters.spec.js
index f4789ca48c640c0da83ab7d89cf148f60270854c..8ee6b4b710bfe6ce5727a3f8797588551516a227 100644
--- a/front/test/unit/specs/filters/filters.spec.js
+++ b/front/tests/unit/specs/filters/filters.spec.js
@@ -1,3 +1,5 @@
+import {expect} from 'chai'
+
 import {truncate, markdown, ago, capitalize, year} from '@/filters'
 
 describe('filters', () => {
diff --git a/front/test/unit/specs/store/auth.spec.js b/front/tests/unit/specs/store/auth.spec.js
similarity index 94%
rename from front/test/unit/specs/store/auth.spec.js
rename to front/tests/unit/specs/store/auth.spec.js
index 46901cd16edd25ee9be4f7137a8835f1336174fb..05581979fb122e9f4d789d653ef1baa56d827226 100644
--- a/front/test/unit/specs/store/auth.spec.js
+++ b/front/tests/unit/specs/store/auth.spec.js
@@ -1,4 +1,6 @@
 var sinon = require('sinon')
+import {expect} from 'chai'
+
 import moxios from 'moxios'
 import store from '@/store/auth'
 
@@ -8,7 +10,7 @@ describe('store/auth', () => {
   var sandbox
 
   beforeEach(function () {
-    sandbox = sinon.sandbox.create()
+    sandbox = sinon.createSandbox()
     moxios.install()
   })
   afterEach(function () {
@@ -84,7 +86,7 @@ describe('store/auth', () => {
     })
   })
   describe('actions', () => {
-    it('logout', (done) => {
+    it('logout', () => {
       testAction({
         action: store.actions.logout,
         params: {state: {}},
@@ -96,18 +98,18 @@ describe('store/auth', () => {
           { type: 'queue/reset', payload: null, options: {root: true} },
           { type: 'radios/reset', payload: null, options: {root: true} }
         ]
-      }, done)
+      })
     })
-    it('check jwt null', (done) => {
+    it('check jwt null', () => {
       testAction({
         action: store.actions.check,
         params: {state: {}},
         expectedMutations: [
           { type: 'authenticated', payload: false }
         ]
-      }, done)
+      })
     })
-    it('check jwt set', (done) => {
+    it('check jwt set', () => {
       testAction({
         action: store.actions.check,
         params: {state: {token: 'test', username: 'user'}},
@@ -118,9 +120,9 @@ describe('store/auth', () => {
           { type: 'fetchProfile' },
           { type: 'refreshToken' }
         ]
-      }, done)
+      })
     })
-    it('login success', (done) => {
+    it('login success', () => {
       moxios.stubRequest('token/', {
         status: 200,
         response: {
@@ -139,9 +141,9 @@ describe('store/auth', () => {
         expectedActions: [
           { type: 'fetchProfile' }
         ]
-      }, done)
+      })
     })
-    it('login error', (done) => {
+    it('login error', () => {
       moxios.stubRequest('token/', {
         status: 500,
         response: {
@@ -160,7 +162,7 @@ describe('store/auth', () => {
         done()
       })
     })
-    it('fetchProfile', (done) => {
+    it('fetchProfile', () => {
       const profile = {
         username: 'bob',
         permissions: {
@@ -183,9 +185,9 @@ describe('store/auth', () => {
           { type: 'favorites/fetch', payload: null, options: {root: true} },
           { type: 'playlists/fetchOwn', payload: null, options: {root: true} }
         ]
-      }, done)
+      })
     })
-    it('refreshToken', (done) => {
+    it('refreshToken', () => {
       moxios.stubRequest('token/refresh/', {
         status: 200,
         response: {token: 'newtoken'}
@@ -196,7 +198,7 @@ describe('store/auth', () => {
         expectedMutations: [
           { type: 'token', payload: 'newtoken' }
         ]
-      }, done)
+      })
     })
   })
 })
diff --git a/front/test/unit/specs/store/favorites.spec.js b/front/tests/unit/specs/store/favorites.spec.js
similarity index 92%
rename from front/test/unit/specs/store/favorites.spec.js
rename to front/tests/unit/specs/store/favorites.spec.js
index 6d4314ca661a2ffbb3865d3a4edc53a2b40b1cc8..1c0f29a9de5ae70b7563a8add3dc4a81e79f025c 100644
--- a/front/test/unit/specs/store/favorites.spec.js
+++ b/front/tests/unit/specs/store/favorites.spec.js
@@ -1,3 +1,5 @@
+import {expect} from 'chai'
+
 import store from '@/store/favorites'
 
 import { testAction } from '../../utils'
@@ -28,7 +30,7 @@ describe('store/favorites', () => {
     })
   })
   describe('actions', () => {
-    it('toggle true', (done) => {
+    it('toggle true', () => {
       testAction({
         action: store.actions.toggle,
         payload: 1,
@@ -36,9 +38,9 @@ describe('store/favorites', () => {
         expectedActions: [
           { type: 'set', payload: {id: 1, value: true} }
         ]
-      }, done)
+      })
     })
-    it('toggle true', (done) => {
+    it('toggle true', () => {
       testAction({
         action: store.actions.toggle,
         payload: 1,
@@ -46,7 +48,7 @@ describe('store/favorites', () => {
         expectedActions: [
           { type: 'set', payload: {id: 1, value: false} }
         ]
-      }, done)
+      })
     })
   })
 })
diff --git a/front/test/unit/specs/store/instance.spec.js b/front/tests/unit/specs/store/instance.spec.js
similarity index 93%
rename from front/test/unit/specs/store/instance.spec.js
rename to front/tests/unit/specs/store/instance.spec.js
index 4b06cb5f029bc0b2180cb4e3aa90854eda4b73b2..b60b1fd477e888f3a4a70b98d940fb05dccebdcb 100644
--- a/front/test/unit/specs/store/instance.spec.js
+++ b/front/tests/unit/specs/store/instance.spec.js
@@ -1,3 +1,4 @@
+import {expect} from 'chai'
 var sinon = require('sinon')
 import moxios from 'moxios'
 import store from '@/store/instance'
@@ -7,7 +8,7 @@ describe('store/instance', () => {
   var sandbox
 
   beforeEach(function () {
-    sandbox = sinon.sandbox.create()
+    sandbox = sinon.createSandbox()
     moxios.install()
   })
   afterEach(function () {
@@ -26,7 +27,7 @@ describe('store/instance', () => {
     })
   })
   describe('actions', () => {
-    it('fetchSettings', (done) => {
+    it('fetchSettings', () => {
       moxios.stubRequest('instance/settings/', {
         status: 200,
         response: [
@@ -64,7 +65,7 @@ describe('store/instance', () => {
             }
           }
         ]
-      }, done)
+      })
     })
   })
 })
diff --git a/front/test/unit/specs/store/player.spec.js b/front/tests/unit/specs/store/player.spec.js
similarity index 93%
rename from front/test/unit/specs/store/player.spec.js
rename to front/tests/unit/specs/store/player.spec.js
index 1e6c9b33a5fd3c2c43911e4c8ae26e78bd239558..a110bf4c2472ce9dea25128fab1165c7da5118cc 100644
--- a/front/test/unit/specs/store/player.spec.js
+++ b/front/tests/unit/specs/store/player.spec.js
@@ -1,3 +1,5 @@
+import {expect} from 'chai'
+
 import store from '@/store/player'
 
 import { testAction } from '../../utils'
@@ -100,7 +102,7 @@ describe('store/player', () => {
     })
   })
   describe('actions', () => {
-    it('incrementVolume', (done) => {
+    it('incrementVolume', () => {
       testAction({
         action: store.actions.incrementVolume,
         payload: 0.2,
@@ -108,27 +110,27 @@ describe('store/player', () => {
         expectedMutations: [
           { type: 'volume', payload: 0.7 + 0.2 }
         ]
-      }, done)
+      })
     })
-    it('toggle play false', (done) => {
+    it('toggle play false', () => {
       testAction({
         action: store.actions.togglePlay,
         params: {state: {playing: false}},
         expectedMutations: [
           { type: 'playing', payload: true }
         ]
-      }, done)
+      })
     })
-    it('toggle play true', (done) => {
+    it('toggle play true', () => {
       testAction({
         action: store.actions.togglePlay,
         params: {state: {playing: true}},
         expectedMutations: [
           { type: 'playing', payload: false }
         ]
-      }, done)
+      })
     })
-    it('trackEnded', (done) => {
+    it('trackEnded', () => {
       testAction({
         action: store.actions.trackEnded,
         payload: {test: 'track'},
@@ -137,9 +139,9 @@ describe('store/player', () => {
           { type: 'trackListened', payload: {test: 'track'} },
           { type: 'queue/next', payload: null, options: {root: true} }
         ]
-      }, done)
+      })
     })
-    it('trackEnded calls populateQueue if last', (done) => {
+    it('trackEnded calls populateQueue if last', () => {
       testAction({
         action: store.actions.trackEnded,
         payload: {test: 'track'},
@@ -149,9 +151,9 @@ describe('store/player', () => {
           { type: 'radios/populateQueue', payload: null, options: {root: true} },
           { type: 'queue/next', payload: null, options: {root: true} }
         ]
-      }, done)
+      })
     })
-    it('trackErrored', (done) => {
+    it('trackErrored', () => {
       testAction({
         action: store.actions.trackErrored,
         payload: {test: 'track'},
@@ -163,16 +165,16 @@ describe('store/player', () => {
         expectedActions: [
           { type: 'queue/next', payload: null, options: {root: true} }
         ]
-      }, done)
+      })
     })
-    it('updateProgress', (done) => {
+    it('updateProgress', () => {
       testAction({
         action: store.actions.updateProgress,
         payload: 1,
         expectedMutations: [
           { type: 'currentTime', payload: 1 }
         ]
-      }, done)
+      })
     })
   })
 })
diff --git a/front/test/unit/specs/store/playlists.spec.js b/front/tests/unit/specs/store/playlists.spec.js
similarity index 86%
rename from front/test/unit/specs/store/playlists.spec.js
rename to front/tests/unit/specs/store/playlists.spec.js
index e82af60bbb470f59bae63d1b2fe89ee30ca6bbb5..0fe0c0ae2e1332492580bf56893a14914cb8149a 100644
--- a/front/test/unit/specs/store/playlists.spec.js
+++ b/front/tests/unit/specs/store/playlists.spec.js
@@ -1,3 +1,4 @@
+import {expect} from 'chai'
 var sinon = require('sinon')
 import moxios from 'moxios'
 import store from '@/store/playlists'
@@ -8,7 +9,7 @@ describe('store/playlists', () => {
   var sandbox
 
   beforeEach(function () {
-    sandbox = sinon.sandbox.create()
+    sandbox = sinon.createSandbox()
     moxios.install()
   })
   afterEach(function () {
@@ -24,13 +25,13 @@ describe('store/playlists', () => {
     })
   })
   describe('actions', () => {
-    it('fetchOwn does nothing with no user', (done) => {
+    it('fetchOwn does nothing with no user', () => {
       testAction({
         action: store.actions.fetchOwn,
         payload: null,
         params: {state: { playlists: [] }, rootState: {auth: {profile: {}}}},
         expectedMutations: []
-      }, done)
+      })
     })
   })
 })
diff --git a/front/test/unit/specs/store/queue.spec.js b/front/tests/unit/specs/store/queue.spec.js
similarity index 86%
rename from front/test/unit/specs/store/queue.spec.js
rename to front/tests/unit/specs/store/queue.spec.js
index cc2f04fa087714cd8a0fcb8afc03be87ab9688f9..373f4938e034864d65db318ba82a550fdcc012c4 100644
--- a/front/test/unit/specs/store/queue.spec.js
+++ b/front/tests/unit/specs/store/queue.spec.js
@@ -1,4 +1,6 @@
 var sinon = require('sinon')
+import {expect} from 'chai'
+
 import _ from 'lodash'
 
 import store from '@/store/queue'
@@ -9,7 +11,7 @@ describe('store/queue', () => {
 
   beforeEach(function () {
     // Create a sandbox for the test
-    sandbox = sinon.sandbox.create()
+    sandbox = sinon.createSandbox()
   })
 
   afterEach(function () {
@@ -83,7 +85,7 @@ describe('store/queue', () => {
     })
   })
   describe('actions', () => {
-    it('append at end', (done) => {
+    it('append at end', () => {
       testAction({
         action: store.actions.append,
         payload: {track: 4, skipPlay: true},
@@ -91,9 +93,9 @@ describe('store/queue', () => {
         expectedMutations: [
           { type: 'insert', payload: {track: 4, index: 3} }
         ]
-      }, done)
+      })
     })
-    it('append at index', (done) => {
+    it('append at index', () => {
       testAction({
         action: store.actions.append,
         payload: {track: 2, index: 1, skipPlay: true},
@@ -101,9 +103,9 @@ describe('store/queue', () => {
         expectedMutations: [
           { type: 'insert', payload: {track: 2, index: 1} }
         ]
-      }, done)
+      })
     })
-    it('append and play', (done) => {
+    it('append and play', () => {
       testAction({
         action: store.actions.append,
         payload: {track: 3},
@@ -114,9 +116,9 @@ describe('store/queue', () => {
         expectedActions: [
           { type: 'resume' }
         ]
-      }, done)
+      })
     })
-    it('appendMany', (done) => {
+    it('appendMany', () => {
       const tracks = [{title: 1}, {title: 2}]
       testAction({
         action: store.actions.appendMany,
@@ -127,9 +129,9 @@ describe('store/queue', () => {
           { type: 'append', payload: {track: tracks[1], index: 1, skipPlay: true} },
           { type: 'resume' }
         ]
-      }, done)
+      })
     })
-    it('appendMany at index', (done) => {
+    it('appendMany at index', () => {
       const tracks = [{title: 1}, {title: 2}]
       testAction({
         action: store.actions.appendMany,
@@ -140,34 +142,34 @@ describe('store/queue', () => {
           { type: 'append', payload: {track: tracks[1], index: 2, skipPlay: true} },
           { type: 'resume' }
         ]
-      }, done)
+      })
     })
-    it('cleanTrack after current', (done) => {
+    it('cleanTrack after current', () => {
       testAction({
         action: store.actions.cleanTrack,
         payload: 3,
-        params: {state: {currentIndex: 2}},
+        params: {state: {currentIndex: 2, tracks: []}},
         expectedMutations: [
           { type: 'splice', payload: {start: 3, size: 1} }
         ]
-      }, done)
+      })
     })
-    it('cleanTrack before current', (done) => {
+    it('cleanTrack before current', () => {
       testAction({
         action: store.actions.cleanTrack,
         payload: 1,
-        params: {state: {currentIndex: 2}},
+        params: {state: {currentIndex: 2, tracks: []}},
         expectedMutations: [
           { type: 'splice', payload: {start: 1, size: 1} },
           { type: 'currentIndex', payload: 1 }
         ]
-      }, done)
+      })
     })
-    it('cleanTrack current', (done) => {
+    it('cleanTrack current', () => {
       testAction({
         action: store.actions.cleanTrack,
         payload: 2,
-        params: {state: {currentIndex: 2}},
+        params: {state: {currentIndex: 2, tracks: []}},
         expectedMutations: [
           { type: 'splice', payload: {start: 2, size: 1} },
           { type: 'currentIndex', payload: 2 }
@@ -175,88 +177,88 @@ describe('store/queue', () => {
         expectedActions: [
           { type: 'player/stop', payload: null, options: {root: true} }
         ]
-      }, done)
+      })
     })
-    it('resume when ended', (done) => {
+    it('resume when ended', () => {
       testAction({
         action: store.actions.resume,
         params: {state: {ended: true}, rootState: {player: {errored: false}}},
         expectedActions: [
           { type: 'next' }
         ]
-      }, done)
+      })
     })
-    it('resume when errored', (done) => {
+    it('resume when errored', () => {
       testAction({
         action: store.actions.resume,
         params: {state: {ended: false}, rootState: {player: {errored: true}}},
         expectedActions: [
           { type: 'next' }
         ]
-      }, done)
+      })
     })
-    it('skip resume when not ended or not error', (done) => {
+    it('skip resume when not ended or not error', () => {
       testAction({
         action: store.actions.resume,
         params: {state: {ended: false}, rootState: {player: {errored: false}}},
         expectedActions: []
-      }, done)
+      })
     })
-    it('previous when at beginning', (done) => {
+    it('previous when at beginning', () => {
       testAction({
         action: store.actions.previous,
         params: {state: {currentIndex: 0}},
         expectedActions: [
           { type: 'currentIndex', payload: 0 }
         ]
-      }, done)
+      })
     })
-    it('previous after less than 3 seconds of playback', (done) => {
+    it('previous after less than 3 seconds of playback', () => {
       testAction({
         action: store.actions.previous,
         params: {state: {currentIndex: 1}, rootState: {player: {currentTime: 1}}},
         expectedActions: [
           { type: 'currentIndex', payload: 0 }
         ]
-      }, done)
+      })
     })
-    it('previous after more than 3 seconds of playback', (done) => {
+    it('previous after more than 3 seconds of playback', () => {
       testAction({
         action: store.actions.previous,
         params: {state: {currentIndex: 1}, rootState: {player: {currentTime: 3}}},
         expectedActions: [
           { type: 'currentIndex', payload: 1 }
         ]
-      }, done)
+      })
     })
-    it('next on last track when looping on queue', (done) => {
+    it('next on last track when looping on queue', () => {
       testAction({
         action: store.actions.next,
         params: {state: {tracks: [1, 2], currentIndex: 1}, rootState: {player: {looping: 2}}},
         expectedActions: [
           { type: 'currentIndex', payload: 0 }
         ]
-      }, done)
+      })
     })
-    it('next track when last track', (done) => {
+    it('next track when last track', () => {
       testAction({
         action: store.actions.next,
         params: {state: {tracks: [1, 2], currentIndex: 1}, rootState: {player: {looping: 0}}},
         expectedMutations: [
           { type: 'ended', payload: true }
         ]
-      }, done)
+      })
     })
-    it('next track when not last track', (done) => {
+    it('next track when not last track', () => {
       testAction({
         action: store.actions.next,
         params: {state: {tracks: [1, 2], currentIndex: 0}, rootState: {player: {looping: 0}}},
         expectedActions: [
           { type: 'currentIndex', payload: 1 }
         ]
-      }, done)
+      })
     })
-    it('currentIndex', (done) => {
+    it('currentIndex', () => {
       testAction({
         action: store.actions.currentIndex,
         payload: 1,
@@ -268,9 +270,9 @@ describe('store/queue', () => {
           { type: 'player/errored', payload: false, options: {root: true} },
           { type: 'currentIndex', payload: 1 }
         ]
-      }, done)
+      })
     })
-    it('currentIndex with radio and many tracks remaining', (done) => {
+    it('currentIndex with radio and many tracks remaining', () => {
       testAction({
         action: store.actions.currentIndex,
         payload: 1,
@@ -282,9 +284,9 @@ describe('store/queue', () => {
           { type: 'player/errored', payload: false, options: {root: true} },
           { type: 'currentIndex', payload: 1 }
         ]
-      }, done)
+      })
     })
-    it('currentIndex with radio and less than two tracks remaining', (done) => {
+    it('currentIndex with radio and less than two tracks remaining', () => {
       testAction({
         action: store.actions.currentIndex,
         payload: 1,
@@ -299,9 +301,9 @@ describe('store/queue', () => {
         expectedActions: [
           { type: 'radios/populateQueue', payload: null, options: {root: true} }
         ]
-      }, done)
+      })
     })
-    it('clean', (done) => {
+    it('clean', () => {
       testAction({
         action: store.actions.clean,
         expectedMutations: [
@@ -313,9 +315,9 @@ describe('store/queue', () => {
           { type: 'player/stop', payload: null, options: {root: true} },
           { type: 'currentIndex', payload: -1 }
         ]
-      }, done)
+      })
     })
-    it('shuffle', (done) => {
+    it('shuffle', () => {
       let _shuffle = sandbox.stub(_, 'shuffle')
       let tracks = ['a', 'b', 'c', 'd', 'e']
       let shuffledTracks = ['e', 'd', 'c']
@@ -329,7 +331,7 @@ describe('store/queue', () => {
         expectedActions: [
           { type: 'appendMany', payload: {tracks: ['a', 'b'].concat(shuffledTracks)} }
         ]
-      }, done)
+      })
     })
   })
 })
diff --git a/front/test/unit/specs/store/radios.spec.js b/front/tests/unit/specs/store/radios.spec.js
similarity index 85%
rename from front/test/unit/specs/store/radios.spec.js
rename to front/tests/unit/specs/store/radios.spec.js
index 3a8c48f04ec942ee8ecf8d71c8a64a4e009987ab..ff14f6145cd91a2326f01b3967bac82cb1f66797 100644
--- a/front/test/unit/specs/store/radios.spec.js
+++ b/front/tests/unit/specs/store/radios.spec.js
@@ -1,4 +1,6 @@
 var sinon = require('sinon')
+import {expect} from 'chai'
+
 import moxios from 'moxios'
 import store from '@/store/radios'
 import { testAction } from '../../utils'
@@ -7,7 +9,7 @@ describe('store/radios', () => {
   var sandbox
 
   beforeEach(function () {
-    sandbox = sinon.sandbox.create()
+    sandbox = sinon.createSandbox()
     moxios.install()
   })
   afterEach(function () {
@@ -28,7 +30,7 @@ describe('store/radios', () => {
     })
   })
   describe('actions', () => {
-    it('start', (done) => {
+    it('start', () => {
       moxios.stubRequest('radios/sessions/', {
         status: 200,
         response: {id: 2}
@@ -51,23 +53,23 @@ describe('store/radios', () => {
         expectedActions: [
           { type: 'populateQueue' }
         ]
-      }, done)
+      })
     })
-    it('stop', (done) => {
-      testAction({
+    it('stop', () => {
+      return testAction({
         action: store.actions.stop,
         expectedMutations: [
           { type: 'current', payload: null },
           { type: 'running', payload: false }
         ]
-      }, done)
+      })
     })
-    it('populateQueue', (done) => {
+    it('populateQueue', () => {
       moxios.stubRequest('radios/tracks/', {
         status: 201,
         response: {track: {id: 1}}
       })
-      testAction({
+      return testAction({
         action: store.actions.populateQueue,
         params: {
           state: {running: true, current: {session: 1}},
@@ -77,17 +79,17 @@ describe('store/radios', () => {
         expectedActions: [
           { type: 'queue/append', payload: {track: {id: 1}}, options: {root: true} }
         ]
-      }, done)
+      })
     })
-    it('populateQueue does nothing when not running', (done) => {
+    it('populateQueue does nothing when not running', () => {
       testAction({
         action: store.actions.populateQueue,
         params: {state: {running: false}},
         expectedActions: []
-      }, done)
+      })
     })
-    it('populateQueue does nothing when too much errors', (done) => {
-      testAction({
+    it('populateQueue does nothing when too much errors', () => {
+      return testAction({
         action: store.actions.populateQueue,
         payload: {test: 'track'},
         params: {
@@ -95,7 +97,7 @@ describe('store/radios', () => {
           state: {running: true}
         },
         expectedActions: []
-      }, done)
+      })
     })
   })
 })
diff --git a/front/test/unit/specs/store/ui.spec.js b/front/tests/unit/specs/store/ui.spec.js
similarity index 94%
rename from front/test/unit/specs/store/ui.spec.js
rename to front/tests/unit/specs/store/ui.spec.js
index ddce055a571e887a3b7bf3bcefdff901e033ea40..2f810e064346355f7df8b2fbaec8ac9d1df1ff0e 100644
--- a/front/test/unit/specs/store/ui.spec.js
+++ b/front/tests/unit/specs/store/ui.spec.js
@@ -1,3 +1,4 @@
+import {expect} from 'chai'
 import store from '@/store/ui'
 
 describe('store/ui', () => {
diff --git a/front/test/unit/utils.js b/front/tests/unit/utils.js
similarity index 74%
rename from front/test/unit/utils.js
rename to front/tests/unit/utils.js
index 6471fc97f710385ff20dcfe2fdd4ee9ac7b9ec36..642b3b5098b7f846f9a2ead9b7ab7e14f56f8127 100644
--- a/front/test/unit/utils.js
+++ b/front/tests/unit/utils.js
@@ -1,5 +1,7 @@
 // helper for testing action with expected mutations
 import Vue from 'vue'
+import {expect} from 'chai'
+
 
 export const render = (Component, propsData) => {
   const Constructor = Vue.extend(Component)
@@ -23,38 +25,32 @@ export const testAction = ({action, payload, params, expectedMutations, expected
   const commit = (type, payload) => {
     const mutation = expectedMutations[mutationsCount]
 
-    try {
-      expect(mutation.type).to.equal(type)
-      if (payload) {
-        expect(mutation.payload).to.deep.equal(payload)
-      }
-    } catch (error) {
-      done(error)
+    expect(mutation.type).to.equal(type)
+    if (payload) {
+      expect(mutation.payload).to.deep.equal(payload)
     }
 
     mutationsCount++
     if (isOver()) {
-      done()
+      return
     }
   }
   // mock dispatch
   const dispatch = (type, payload, options) => {
     const a = expectedActions[actionsCount]
-    try {
-      expect(a.type).to.equal(type)
-      if (payload) {
-        expect(a.payload).to.deep.equal(payload)
-      }
-      if (a.options) {
-        expect(options).to.deep.equal(a.options)
-      }
-    } catch (error) {
-      done(error)
+    if (!a) {
+      throw Error(`Unexecpted action ${type}`)
+    }
+    expect(a.type).to.equal(type)
+    if (payload) {
+      expect(a.payload).to.deep.equal(payload)
+    }
+    if (a.options) {
+      expect(options).to.deep.equal(a.options)
     }
-
     actionsCount++
     if (isOver()) {
-      done()
+      return
     }
   }
 
@@ -67,13 +63,14 @@ export const testAction = ({action, payload, params, expectedMutations, expected
       expect(actionsCount).to.equal(0)
     }
     if (isOver()) {
-      done()
+      return
     }
   }
   // call the action with mocked store and arguments
   let promise = action({ commit, dispatch, ...params }, payload)
   if (promise) {
-    return promise.then(end)
+    promise.then(end)
+    return promise
   } else {
     return end()
   }
diff --git a/front/yarn.lock b/front/yarn.lock
index 0c3fe33cee888821464483b582eb967f83879bcc..b3d6a460b9a089424ed76e390f02445b5fcd6cc6 100644
--- a/front/yarn.lock
+++ b/front/yarn.lock
@@ -653,6 +653,22 @@
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz#50c1e2260ac0ed9439a181de3725a0168d59c48a"
 
+"@sinonjs/commons@^1.0.1":
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.0.2.tgz#3e0ac737781627b8844257fadc3d803997d0526e"
+  dependencies:
+    type-detect "4.0.8"
+
+"@sinonjs/formatio@^2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2"
+  dependencies:
+    samsam "1.3.0"
+
+"@sinonjs/samsam@^2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.0.0.tgz#9163742ac35c12d3602dece74317643b35db6a80"
+
 "@types/babel-types@*", "@types/babel-types@^7.0.0":
   version "7.0.4"
   resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.4.tgz#bfd5b0d0d1ba13e351dff65b6e52783b816826c8"
@@ -704,6 +720,16 @@
     eslint-loader "^2.0.0"
     eslint-plugin-vue "^4.5.0"
 
+"@vue/cli-plugin-unit-mocha@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-unit-mocha/-/cli-plugin-unit-mocha-3.0.0.tgz#615202a9d4aa09adbd360ad4b94ec797d19805d2"
+  dependencies:
+    "@vue/cli-shared-utils" "^3.0.0"
+    jsdom "^11.11.0"
+    jsdom-global "^3.0.2"
+    mocha "^5.2.0"
+    mocha-webpack "^2.0.0-beta.0"
+
 "@vue/cli-service@^3.0.0":
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-3.0.0.tgz#c808a846072dcf5751aad786439f53fc0a812bf4"
@@ -805,6 +831,12 @@
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.0.tgz#d768dba004261c029b53a77c5ea2d5f9ee4f3cce"
 
+"@vue/test-utils@^1.0.0-beta.20":
+  version "1.0.0-beta.24"
+  resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.24.tgz#da7c3165f49f57f23fdb98caccba0f511effb76f"
+  dependencies:
+    lodash "^4.17.4"
+
 "@vue/web-component-wrapper@^1.2.0":
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/@vue/web-component-wrapper/-/web-component-wrapper-1.2.0.tgz#bb0e46f1585a7e289b4ee6067dcc5a6ae62f1dd1"
@@ -956,6 +988,14 @@
     text-table "^0.2.0"
     webpack-log "^1.1.2"
 
+abab@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
+
+abab@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
+
 abbrev@1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@@ -991,6 +1031,12 @@ acorn-globals@^3.0.0:
   dependencies:
     acorn "^4.0.4"
 
+acorn-globals@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.1.0.tgz#ab716025dbe17c54d3ef81d32ece2b2d99fe2538"
+  dependencies:
+    acorn "^5.0.0"
+
 acorn-import-meta@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/acorn-import-meta/-/acorn-import-meta-0.2.1.tgz#ac91e06e00facece7e96ff76a0fe9ec7b1cb5b5c"
@@ -1171,6 +1217,10 @@ arr-union@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
 
+array-equal@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
+
 array-filter@~0.0.0:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec"
@@ -1252,6 +1302,10 @@ assert@^1.1.1:
   dependencies:
     util "0.10.3"
 
+assertion-error@^1.0.1:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
+
 assign-symbols@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
@@ -1361,7 +1415,7 @@ babel-plugin-transform-vue-jsx@^4.0.1:
   dependencies:
     esutils "^2.0.2"
 
-babel-runtime@^6.26.0:
+babel-runtime@^6.18.0, babel-runtime@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
   dependencies:
@@ -1513,6 +1567,14 @@ brorand@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
 
+browser-process-hrtime@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz#425d68a58d3447f02a04aa894187fce8af8b7b8e"
+
+browser-stdout@1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
+
 browserify-aes@^1.0.0, browserify-aes@^1.0.4:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
@@ -1726,6 +1788,17 @@ center-align@^0.1.1:
     align-text "^0.1.3"
     lazy-cache "^1.0.3"
 
+chai@^4.1.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c"
+  dependencies:
+    assertion-error "^1.0.1"
+    check-error "^1.0.1"
+    deep-eql "^3.0.0"
+    get-func-name "^2.0.0"
+    pathval "^1.0.0"
+    type-detect "^4.0.0"
+
 chalk@^1.1.1, chalk@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -1754,6 +1827,10 @@ chardet@^0.4.0:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
 
+check-error@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
+
 check-types@^7.3.0:
   version "7.4.0"
   resolved "https://registry.yarnpkg.com/check-types/-/check-types-7.4.0.tgz#0378ec1b9616ec71f774931a3c6516fad8c152f4"
@@ -1951,6 +2028,10 @@ combined-stream@1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6:
   dependencies:
     delayed-stream "~1.0.0"
 
+commander@2.15.1:
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
+
 commander@2.16.x, commander@~2.16.0:
   version "2.16.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50"
@@ -2333,6 +2414,16 @@ csso@^3.5.0:
   dependencies:
     css-tree "1.0.0-alpha.29"
 
+cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
+  version "0.3.4"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797"
+
+cssstyle@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.0.0.tgz#79b16d51ec5591faec60e688891f15d2a5705129"
+  dependencies:
+    cssom "0.3.x"
+
 currently-unhandled@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
@@ -2355,6 +2446,14 @@ dashdash@^1.12.0:
   dependencies:
     assert-plus "^1.0.0"
 
+data-urls@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.0.0.tgz#24802de4e81c298ea8a9388bb0d8e461c774684f"
+  dependencies:
+    abab "^1.0.4"
+    whatwg-mimetype "^2.0.0"
+    whatwg-url "^6.4.0"
+
 date-now@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -2373,7 +2472,7 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.
   dependencies:
     ms "2.0.0"
 
-debug@^3.1.0:
+debug@3.1.0, debug@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
   dependencies:
@@ -2387,6 +2486,12 @@ decode-uri-component@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
 
+deep-eql@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
+  dependencies:
+    type-detect "^4.0.0"
+
 deep-equal@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
@@ -2501,6 +2606,10 @@ detect-node@^2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127"
 
+diff@3.5.0, diff@^3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
+
 diffie-hellman@^5.0.0:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -2574,6 +2683,12 @@ domelementtype@~1.1.1:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
 
+domexception@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
+  dependencies:
+    webidl-conversions "^4.0.2"
+
 domhandler@2.1:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594"
@@ -2762,10 +2877,21 @@ escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
 
-escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
 
+escodegen@^1.9.1:
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589"
+  dependencies:
+    esprima "^3.1.3"
+    estraverse "^4.2.0"
+    esutils "^2.0.2"
+    optionator "^0.8.1"
+  optionalDependencies:
+    source-map "~0.6.1"
+
 eslint-loader@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.1.0.tgz#61334c548aeb0b8e20ec3a552fb7a88c47261c6a"
@@ -2863,6 +2989,10 @@ espree@^3.5.2, espree@^3.5.4:
     acorn "^5.5.0"
     acorn-jsx "^3.0.0"
 
+esprima@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
+
 esprima@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
@@ -2879,7 +3009,7 @@ esrecurse@^4.1.0:
   dependencies:
     estraverse "^4.1.0"
 
-estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1:
+estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
 
@@ -3369,6 +3499,10 @@ get-caller-file@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
 
+get-func-name@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
+
 get-size@^2.0.2:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/get-size/-/get-size-2.0.3.tgz#54a1d0256b20ea7ac646516756202769941ad2ef"
@@ -3415,24 +3549,24 @@ glob-to-regexp@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
 
-glob@^6.0.4:
-  version "6.0.4"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
+glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2, glob@~7.1.1:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
   dependencies:
+    fs.realpath "^1.0.0"
     inflight "^1.0.4"
     inherits "2"
-    minimatch "2 || 3"
+    minimatch "^3.0.4"
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2, glob@~7.1.1:
-  version "7.1.2"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
+glob@^6.0.4:
+  version "6.0.4"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
   dependencies:
-    fs.realpath "^1.0.0"
     inflight "^1.0.4"
     inherits "2"
-    minimatch "^3.0.4"
+    minimatch "2 || 3"
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
@@ -3496,6 +3630,10 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
 
+growl@1.10.5:
+  version "1.10.5"
+  resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
+
 gzip-size@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-4.1.0.tgz#8ae096257eabe7d69c45be2b67c448124ffb517c"
@@ -3594,7 +3732,7 @@ hash.js@^1.0.0, hash.js@^1.0.3:
     inherits "^2.0.3"
     minimalistic-assert "^1.0.1"
 
-he@1.1.x, he@^1.1.0:
+he@1.1.1, he@1.1.x, he@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
 
@@ -3643,6 +3781,12 @@ html-comment-regex@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
 
+html-encoding-sniffer@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
+  dependencies:
+    whatwg-encoding "^1.0.1"
+
 html-entities@^1.2.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
@@ -3875,6 +4019,10 @@ internal-ip@1.2.0:
   dependencies:
     meow "^3.3.0"
 
+interpret@^1.0.1:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
+
 invariant@^2.2.0, invariant@^2.2.2:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@@ -4249,6 +4397,41 @@ jsbn@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
 
+jsdom-global@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/jsdom-global/-/jsdom-global-3.0.2.tgz#6bd299c13b0c4626b2da2c0393cd4385d606acb9"
+
+jsdom@^11.11.0:
+  version "11.12.0"
+  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8"
+  dependencies:
+    abab "^2.0.0"
+    acorn "^5.5.3"
+    acorn-globals "^4.1.0"
+    array-equal "^1.0.0"
+    cssom ">= 0.3.2 < 0.4.0"
+    cssstyle "^1.0.0"
+    data-urls "^1.0.0"
+    domexception "^1.0.1"
+    escodegen "^1.9.1"
+    html-encoding-sniffer "^1.0.2"
+    left-pad "^1.3.0"
+    nwsapi "^2.0.7"
+    parse5 "4.0.0"
+    pn "^1.1.0"
+    request "^2.87.0"
+    request-promise-native "^1.0.5"
+    sax "^1.2.4"
+    symbol-tree "^3.2.2"
+    tough-cookie "^2.3.4"
+    w3c-hr-time "^1.0.1"
+    webidl-conversions "^4.0.2"
+    whatwg-encoding "^1.0.3"
+    whatwg-mimetype "^2.1.0"
+    whatwg-url "^6.4.1"
+    ws "^5.2.0"
+    xml-name-validator "^3.0.0"
+
 jsesc@^2.5.1:
   version "2.5.1"
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe"
@@ -4315,6 +4498,10 @@ jstransformer@1.0.0:
     is-promise "^2.0.0"
     promise "^7.0.1"
 
+just-extend@^1.1.27:
+  version "1.1.27"
+  resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905"
+
 jwt-decode@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79"
@@ -4366,6 +4553,10 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
+left-pad@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
+
 levn@^0.3.0, levn@~0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@@ -4438,6 +4629,10 @@ lodash.defaultsdeep@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz#bec1024f85b1bd96cbea405b23c14ad6443a6f81"
 
+lodash.get@^4.4.2:
+  version "4.4.2"
+  resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
+
 lodash.mapvalues@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
@@ -4450,6 +4645,10 @@ lodash.mergewith@^4.6.0:
   version "4.6.1"
   resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
 
+lodash.sortby@^4.7.0:
+  version "4.7.0"
+  resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
+
 lodash.tail@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
@@ -4483,6 +4682,10 @@ loglevelnext@^1.0.1:
     es6-symbol "^3.1.1"
     object.assign "^4.1.0"
 
+lolex@^2.3.2, lolex@^2.7.1:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.1.tgz#e40a8c4d1f14b536aa03e42a537c7adbaf0c20be"
+
 long@4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
@@ -4582,7 +4785,7 @@ mem@^1.1.0:
   dependencies:
     mimic-fn "^1.0.0"
 
-memory-fs@^0.4.0, memory-fs@~0.4.1:
+memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
   dependencies:
@@ -4703,7 +4906,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
 
-"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
+"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
   dependencies:
@@ -4765,6 +4968,44 @@ mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi
   dependencies:
     minimist "0.0.8"
 
+mocha-webpack@^2.0.0-beta.0:
+  version "2.0.0-beta.0"
+  resolved "https://registry.yarnpkg.com/mocha-webpack/-/mocha-webpack-2.0.0-beta.0.tgz#d85fc9a70f82a4ad595b7702a1181605dfa59549"
+  dependencies:
+    babel-runtime "^6.18.0"
+    chalk "^2.3.0"
+    chokidar "^2.0.2"
+    glob-parent "^3.1.0"
+    globby "^7.1.1"
+    interpret "^1.0.1"
+    is-glob "^4.0.0"
+    loader-utils "^1.1.0"
+    lodash "^4.3.0"
+    memory-fs "^0.4.1"
+    nodent-runtime "^3.0.3"
+    normalize-path "^2.0.1"
+    progress "^2.0.0"
+    source-map-support "^0.5.0"
+    strip-ansi "^4.0.0"
+    toposort "^1.0.0"
+    yargs "^11.0.0"
+
+mocha@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6"
+  dependencies:
+    browser-stdout "1.3.1"
+    commander "2.15.1"
+    debug "3.1.0"
+    diff "3.5.0"
+    escape-string-regexp "1.0.5"
+    glob "7.1.2"
+    growl "1.10.5"
+    he "1.1.1"
+    minimatch "3.0.4"
+    mkdirp "0.5.1"
+    supports-color "5.4.0"
+
 moment@^2.22.2:
   version "2.22.2"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
@@ -4780,6 +5021,10 @@ move-concurrently@^1.0.1:
     rimraf "^2.5.4"
     run-queue "^1.0.3"
 
+moxios@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/moxios/-/moxios-0.4.0.tgz#fc0da2c65477d725ca6b9679d58370ed0c52f53b"
+
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -4847,6 +5092,16 @@ nice-try@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
 
+nise@^1.4.2:
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.2.tgz#a9a3800e3994994af9e452333d549d60f72b8e8c"
+  dependencies:
+    "@sinonjs/formatio" "^2.0.0"
+    just-extend "^1.1.27"
+    lolex "^2.3.2"
+    path-to-regexp "^1.7.0"
+    text-encoding "^0.6.4"
+
 no-case@^2.2.0:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
@@ -4955,6 +5210,10 @@ node-sass@^4.9.3:
     stdout-stream "^1.4.0"
     "true-case-path" "^1.0.2"
 
+nodent-runtime@^3.0.3:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/nodent-runtime/-/nodent-runtime-3.2.1.tgz#9e2755d85e39f764288f0d4752ebcfe3e541e00e"
+
 "nopt@2 || 3":
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
@@ -5035,6 +5294,10 @@ number-is-nan@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
 
+nwsapi@^2.0.7:
+  version "2.0.8"
+  resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.8.tgz#e3603579b7e162b3dbedae4fb24e46f771d8fa24"
+
 oauth-sign@~0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
@@ -5143,7 +5406,7 @@ opn@^5.1.0, opn@^5.3.0:
   dependencies:
     is-wsl "^1.1.0"
 
-optionator@^0.8.2:
+optionator@^0.8.1, optionator@^0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
   dependencies:
@@ -5286,6 +5549,10 @@ parse-json@^4.0.0:
     error-ex "^1.3.1"
     json-parse-better-errors "^1.0.1"
 
+parse5@4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
+
 parse5@^3.0.1:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
@@ -5338,6 +5605,12 @@ path-to-regexp@0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
 
+path-to-regexp@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
+  dependencies:
+    isarray "0.0.1"
+
 path-type@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@@ -5352,6 +5625,10 @@ path-type@^3.0.0:
   dependencies:
     pify "^3.0.0"
 
+pathval@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
+
 pbkdf2@^3.0.3:
   version "3.0.16"
   resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c"
@@ -5400,6 +5677,10 @@ pluralize@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
 
+pn@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
+
 pofile@^1.0.10:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.0.11.tgz#35aff58c17491d127a07336d5522ebc9df57c954"
@@ -6351,6 +6632,10 @@ safe-regex@^1.1.0:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
 
+samsam@1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
+
 sass-graph@^2.2.4:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
@@ -6548,6 +6833,20 @@ simple-swizzle@^0.2.2:
   dependencies:
     is-arrayish "^0.3.1"
 
+sinon@^6.1.5:
+  version "6.1.5"
+  resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.1.5.tgz#41451502d43cd5ffb9d051fbf507952400e81d09"
+  dependencies:
+    "@sinonjs/commons" "^1.0.1"
+    "@sinonjs/formatio" "^2.0.0"
+    "@sinonjs/samsam" "^2.0.0"
+    diff "^3.5.0"
+    lodash.get "^4.4.2"
+    lolex "^2.7.1"
+    nise "^1.4.2"
+    supports-color "^5.4.0"
+    type-detect "^4.0.8"
+
 slash@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -6625,6 +6924,13 @@ source-map-resolve@^0.5.0:
     source-map-url "^0.4.0"
     urix "^0.1.0"
 
+source-map-support@^0.5.0:
+  version "0.5.8"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.8.tgz#04f5581713a8a65612d0175fbf3a01f80a162613"
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
 source-map-url@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
@@ -6639,7 +6945,7 @@ source-map@^0.4.2:
   dependencies:
     amdefine ">=0.0.4"
 
-source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
+source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
 
@@ -6869,16 +7175,16 @@ stylehacks@^4.0.0:
     postcss "^6.0.0"
     postcss-selector-parser "^3.0.0"
 
-supports-color@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
-
-supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0:
+supports-color@5.4.0, supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0:
   version "5.4.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
   dependencies:
     has-flag "^3.0.0"
 
+supports-color@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
+
 svgo@^1.0.0:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.0.5.tgz#7040364c062a0538abacff4401cea6a26a7a389a"
@@ -6898,6 +7204,10 @@ svgo@^1.0.0:
     unquote "~1.1.1"
     util.promisify "~1.0.0"
 
+symbol-tree@^3.2.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
+
 table@4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"
@@ -6933,6 +7243,10 @@ tar@^4:
     safe-buffer "^5.1.2"
     yallist "^3.0.2"
 
+text-encoding@^0.6.4:
+  version "0.6.4"
+  resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
+
 text-table@^0.2.0, text-table@~0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@@ -7024,7 +7338,7 @@ toposort@^1.0.0:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
 
-tough-cookie@>=2.3.3, tough-cookie@~2.4.3:
+tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3:
   version "2.4.3"
   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
   dependencies:
@@ -7037,6 +7351,12 @@ tough-cookie@~2.3.3:
   dependencies:
     punycode "^1.4.1"
 
+tr46@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
+  dependencies:
+    punycode "^2.1.0"
+
 trim-newlines@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
@@ -7079,6 +7399,10 @@ type-check@~0.3.2:
   dependencies:
     prelude-ls "~1.1.2"
 
+type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.8:
+  version "4.0.8"
+  resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
+
 type-is@~1.6.15, type-is@~1.6.16:
   version "1.6.16"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
@@ -7409,6 +7733,12 @@ vuex@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2"
 
+w3c-hr-time@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045"
+  dependencies:
+    browser-process-hrtime "^0.1.2"
+
 watchpack@^1.5.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
@@ -7429,6 +7759,10 @@ wcwidth@^1.0.1:
   dependencies:
     defaults "^1.0.3"
 
+webidl-conversions@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
+
 webpack-bundle-analyzer@^2.13.1:
   version "2.13.1"
   resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.13.1.tgz#07d2176c6e86c3cdce4c23e56fae2a7b6b4ad526"
@@ -7561,6 +7895,24 @@ websocket-extensions@>=0.1.1:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
 
+whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3"
+  dependencies:
+    iconv-lite "0.4.19"
+
+whatwg-mimetype@^2.0.0, whatwg-mimetype@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f0f21d76cbba72362eb609dbed2a30cd17fcc7d4"
+
+whatwg-url@^6.4.0, whatwg-url@^6.4.1:
+  version "6.5.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
+  dependencies:
+    lodash.sortby "^4.7.0"
+    tr46 "^1.0.1"
+    webidl-conversions "^4.0.2"
+
 which-module@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
@@ -7630,6 +7982,16 @@ ws@^4.0.0:
     async-limiter "~1.0.0"
     safe-buffer "~5.1.0"
 
+ws@^5.2.0:
+  version "5.2.2"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f"
+  dependencies:
+    async-limiter "~1.0.0"
+
+xml-name-validator@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
+
 xtend@^4.0.0, xtend@~4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
@@ -7702,6 +8064,23 @@ yargs@^10.0.3:
     y18n "^3.2.1"
     yargs-parser "^8.1.0"
 
+yargs@^11.0.0:
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77"
+  dependencies:
+    cliui "^4.0.0"
+    decamelize "^1.1.1"
+    find-up "^2.1.0"
+    get-caller-file "^1.0.1"
+    os-locale "^2.0.0"
+    require-directory "^2.1.1"
+    require-main-filename "^1.0.1"
+    set-blocking "^2.0.0"
+    string-width "^2.0.0"
+    which-module "^2.0.0"
+    y18n "^3.2.1"
+    yargs-parser "^9.0.2"
+
 yargs@^7.0.0:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"