Karma unit test Vue.js + Foundation

617 views Asked by At

I'm attempting to add Foundation for Sites to my first Vue.js project that is setup with the Vue CLI.

The website runs however the Karma+Phantomjs unit test suite is emitting this error:

PhantomJS 2.1.1 (Mac OS X 0.0.0) ERROR
  SyntaxError: Invalid character: '`'
  at webpack:///~/foundation-sites/js/foundation.util.core.js:24:0 <- index.js:10362

I believe this is related to webpack/babel and the ES module loading of Foundation. I'm not too sure how to further diagnose and resolve the problem.

Here's an overview of the code changes I've made...

Main.js

import jQuery from 'jquery'
window.jQuery = jQuery
window.$ = jQuery

import Foundation from 'foundation-sites'
window.Foundation = Foundation

Hello.vue

<template> ... </template>

 <script>
 export default {
   name: 'hello',
   mounted () {
     this.dropdownMenu = new Foundation.DropdownMenu($('#dropdown-menu'), {
       // These options can be declarative using the data attributes
       hoverDelay: 300
     })
   },
   data () {
     return {
       msg: 'Welcome to Your Vue.js App'
     }
   },
   destroyed () {
     this.dropdownMenu.destroy()
   }
 }
 </script>

test/unit/index.js

import Vue from 'vue'
import Foundation from 'foundation-sites'
window.Foundation = Foundation

// ... auto-generated unit tests

test/unit/karma.conf.js

// 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-chai', 'phantomjs-shim'],
    reporters: ['spec', 'coverage'],
    files: ['./index.js'],
    preprocessors: {
      './index.js': ['webpack', 'sourcemap']
    },
    webpack: webpackConfig,
    webpackMiddleware: {
      noInfo: true
    },
    coverageReporter: {
      dir: './coverage',
      reporters: [
        { type: 'lcov', subdir: '.' },
        { type: 'text-summary' }
      ]
    }
  })
}

webpack.base.conf.js

var path = require('path')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

module.exports = {
  entry: {
    app: './src/main.js'
  },
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
  module: {
    rules: [
      {
        test: /\.(js|vue)$/,
        loader: 'eslint-loader',
        enforce: 'pre',
        include: [resolve('src'), resolve('test')],
        options: {
          formatter: require('eslint-friendly-formatter')
        }
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  }
}
3

There are 3 answers

0
Dwight Gunning On BEST ANSWER

The solution I've found is to load Foundation via Babel.

webpack.base.conf.js

{
  test: /\.js$/,
  loader: 'babel-loader',
  include: [
    resolve('src'),
    resolve('test'),
    resolve('node_modules/foundation-sites') // Added this line.
  ]
},

The main reason for this is actually explained in the Foundation docs:

Our JavaScript uses some features of ECMAScript 2015. If you use Foundation with a build system, you'll need to compile our code to ES5. We use Babel in our templates to do this. Babel has plugins for every build system imaginable, so integrating it into an existing setup is easy.

The Foundation docs go on to recommend including and setting the babel "presets": ["es2015"] however the Vue CLI is already setup slightly differently.

Vue CLI already uses the babel-preset-env and babel-preset-stage2 plugins which I've since learned takes care of the ES2015 > ES5 transpilation. Vue CLI sets up webpack with several loaders so seems to be it's necessary to add Foundation to the babel-loader config.

Currently I'm importing Foundation and adding it to the global window from within Vue's main.js entry point. Next up I'll probably see if I can expose it using the Expose loader.

2
Purple Hexagon On

I'm guessing that you are transpiling with babel and defining this in your webpack config. It would make sense that this transpiling isn't happening as appears to be failing on the backtick for interpolated string which would not be available in es5.

Assume you already have a karma.conf.js defined, you may just need to add your webpack config to it. Maybe good to post your karma.conf.js if you have one.

I use the following:

// karma.conf.js
const webpackConfig = require('./build/webpack.base')

delete webpackConfig.entry

webpackConfig.resolve.alias.vue = 'vue/dist/vue.js'

module.exports = function karmaConfig(config) {
  config.set({
    browsers: ['PhantomJS'],
    frameworks: ['mocha', 'chai'],
    // this is the entry file for all our tests.
    files: ['ui-tests/index.js'],
    // we will pass the entry file to webpack for bundling.
    preprocessors: {
      'ui-tests/index.js': ['webpack'],
    },
    // use the webpack config
    webpack: webpackConfig,
    // avoid walls of useless text
    webpackMiddleware: {
      noInfo: true,
    },
    singleRun: true,
    junitReporter: {
      outputDir: '../../build/logs/',
      outputFile: 'junit-js-ui-test-report.xml',
      suite: 'review',
      useBrowserName: false,
      nameFormatter: undefined,
      classNameFormatter: undefined,
      properties: {},
    },
  })
}

and:

// ui-tests/index.js
const tests = require.context('..', true, /ui-tests\/.*\.spec$/)
tests.keys().forEach(tests)

and have the following in my packages scripts for running:

./node_modules/.bin/karma start karma.conf.js

******* UPDATE *****

Not sure how you are handling your babel presets perhaps you are using the a babelrc and this isn't being found (or some way that isn't in your base webpack config anyway) but you could try adding something like, but matching your requirements:

babel: {
  babelrc: false,
  presets: [
    ['es2015' {modules: false}],
    'stage-1'
 ]
},

to your webpack config in build/webpack.test.conf ... hard to say though as I don't know what you have in your test.conf

1
Tomer On

I had a similar problem with a different package, what I've found is that the coverage code is responsible for it.

Under test/unit open index.js and comment these 2 lines:

const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
srcContext.keys().forEach(srcContext)

This should solve the problem.