Search code examples
jqueryangularjs-directivewebstormwebpackkarma-jasmine

Get WebStorm + Karma + Webpack to load jQuery for AngularJs Directive Unit Tests


NOTE: This is especially going to help someone that runs karma.conf.js through WebStorm.

The Question:

I was trying to run some tests on a directive, but since it kept coming back, "TypeError: 'undefined' is not a function" I started to narrow down where the problem might be. THE RESULT: It seems to be reading the template, but it's like the div's on the template are empty.

My Test

describe("Main Component Directive", function() {
var $scope, $element, directiveTemplate;

beforeEach(angular.mock.module('app'))

beforeEach(inject(function ($rootScope, $compile) {
  $scope = $rootScope.$new();
  directiveTemplate = '<gk-directive></gk-directive>';
  $element = $compile(directiveTemplate)($scope);
  $scope.$digest();
}));

it("should show reset button when current time is <5s under session/breakLength", function() {
  var button = $element.find(".button")
  expect(button).toBeDefined();
  console.log(button.text())
});

The test passes, but it won't console.log the text inside this template: container.html

<div>
    <div class="button">Button Here</div>
</div>

Could there be something wrong with my directive..?

module.exports = angular.module('app')
  .directive('gkTimerDirective', function () {
    return {
      template: require('./container.html') //gets turned into a string by webpack's raw-loader
    }
  });

I've been looking through stackOverflow and there are plenty of examples of testing different kinds of directives and their behaviors, but it seems like I'm missing something basic, here. I do have jQuery added before AngularJs or Angular-Mocks.

I feel like I'm missing something fundamental. If I console.log as I am, shouldn't I get, "Button Here", instead of two single quotes: " '' "?

UPDATE

To clarify, the key problem here is that AngularJs is not traversing the DOM with $element.find() when it looks for a class, despite the fact that jQuery really should be loaded.


Solution

  • Explanation of What Happened

    When I tried running the tests with an npm run command, I noticed that all of the tests were passing, but when I ran the tests through WebStorm's "Run karma.conf.js" it acted like jQuery hadn't been loaded before Angular and, therefore, it couldn't find the div by its class, and the final test, where I used angular.element with jQuery pre-loaded, continued to fail.

    I can only think that this is because jQuery isn't easily assimilated by the build system. In fact, you can see how Webpack had to do some extra work after it got to jQuery.

    Hash: d234b2da503b21a23772
    Version: webpack 1.12.9
    Time: 395ms
                     Asset     Size  Chunks             Chunk Names
                      main  2.77 MB    0, 1  [emitted]  main
    src/app.module.spec.js  2.77 MB    1, 0  [emitted]  src/app.module.spec.js
    chunk    {0} main (main) 2.56 MB [rendered]
        [0] ./src/app.module.spec.js 273 bytes {0} {1}
        [1] ./src/jquery.js 1.38 MB {0} {1}
        [2] (webpack)/buildin/module.js 251 bytes {0} {1}
        [3] (webpack)/buildin/amd-options.js 43 bytes {0} {1}
        [4] ./src/app.module.js 2.42 kB {0} {1}
        [5] ./~/angular/index.js 48 bytes {0} {1}
        [6] ./~/angular/angular.js 1.07 MB {0} {1}
        [7] ./src/factories/index.js 1.03 kB {0} {1}
        [8] ./src/factories/timer.service.js 9.16 kB {0} {1}
        [9] ./src/timer/index.js 1.26 kB {0} {1}
       [10] ./src/timer/gkTimer.directive.js 1.82 kB {0} {1}
       [11] ./src/timer/gk-timer-container.html 144 bytes {0} {1}
       [12] ./src/timer/timerContainerCtrl.controller.js 4.12 kB {0} {1}
       [13] ./~/angular-mocks/angular-mocks.js 82.8 kB {0} {1}
       [14] ./src/tests/timer.service.spec.js 3.27 kB {0} {1}
       [15] ./src/tests/timer.spec.js 1.15 kB {0} {1} [built]
    

    I'm still uncertain why this was a problem, but it did inspire a solution.

    The Solution

    Since I'm only using jQuery for testing directives, I just added it to my files list on Karma:

    var path = require('path');    
    var _ = require('lodash');
    var here = require('path-here');
    
    // SET-UP SETTINGS
    process.env.NODE_ENV = process.env.NODE_ENV || 'test';
    
    // coverage always true when testing (for me)
    var coverage = true // = process.env.COVERAGE === 'true';
    
    // ci determines if autowatch and single run will be true or false
    var ci = process.env.NODE_ENV === 'test:ci';
    if (coverage) {
      console.log('-- recording coverage --');
    }
    
    // all runs after the NODE_ENV has been set to 'test'
    var webpackConfig = require('./webpack.config');
    var entry = path.join(webpackConfig.context, webpackConfig.entry);
    var preprocessors = {};
    preprocessors[entry] = ['webpack'];
    
    // SETTINGS INPUT
    
    module.exports = function(config) {
      config.set({
        basePath: './',
        frameworks: ['jasmine'],
        files: [
          './node_modules/jquery/dist/jquery.js', // <-- Added it right here
          entry
        ],
        exclude: [],
        preprocessors: preprocessors,
        reporters: getReporters(),
        webpack: webpackConfig,
        webpackMiddleware: {noInfo: true},
        coverageReporter: {
          reporters: [
            {type: 'lcov', dir: 'coverage/', subdir: '.'},
            {type: 'json', dir: 'coverage/', subdir: '.'},
            {type: 'text-summary'}
          ]
        },
        port: 9876,
        colors: true,
        logLevel: config.LOG_INFO,
        autoWatch: !ci,
        browsers: ['PhantomJS'],
        singleRun: ci,
        browserNoActivityTimeout: 180000,
        plugins: [
          'karma-webpack',
          'karma-jasmine',
          'karma-coverage',
          'karma-phantomjs-launcher'
        ]
      });
    };
    
    function getReporters() {
      var reps = ['progress'];
      if (coverage) {
        reps.push('coverage');
      }
      return reps;
    }
    

    Now, for testing, jQuery is loaded first and Angular can search for DOM elements by class, id, or whatever selectors jQuery allows.

    But what about for production?

    I have no reason to think it might not be working just fine for a production build in my case, but if that's your problem try this: https://github.com/webpack/webpack/issues/582