2014-02-16

travis-lint, how to check your .travis.yml file


If you are a developer probably you already know the travis-ci.org continuous integration service and this kind of image status:
Adding CI support to your software hosted on github.com is very easy:
  1. add a file named .travis.yml to your repository, it's a YAML configuration file
  2. got to travis-ci and enable CI for your repo
You you need more details check the official documentation: this blog post is not focused on how to enable travis or what is CI.

The question is: how you can check the .travis.yml syntax? And deprecations or possible issues without having to lose time with failed builds?
The answer is travis-lint.

travis-lint

travis-lint is a tool that check your .travis.yml for possible issues, deprecations and so on. It's recommended for all travis-ci.org users.

How to install travis-lint

If you have already installed the gem utility it requires you just one command:
gem install travis-lint

travis-lint online

Too lazy for locally installing travis-lint? Go to the Travis WebLint service:

More links

2014-02-13

Testing AngularJS directives with isolate scope (.isolateScope() is your friend)

In the previous post we have seen how to test directives with templateUrl
Now we are talking about how to test AngularJS directives with an isolated scope.

Let's say that you have a directive that should help you to select items from a given long list with filtering, ordering and selecting facilities:
<my-list model="exampleList" />
where exampleList is the following:
$scope.exampleList = [
    {id: '001', label:'First item', selected:false},
    {id: '002', label:'Second item', selected: false},
     ...
  ];

It sounds like a complex thing but you can implement it with Angular with few lines of Javascript code (app/scripts/directives/mylist.js): 
        'use strict';
angular.module('myListApp')
  .directive('myList', [function () {
    return {
      templateUrl: 'views/mylist.html',
      restrict: 'E',
      replace: true,
      scope: {model: '='},
      controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
        ... other logics       }]
    };
  }]);
with this directive's template (app/views/mylist.html):
<div>
  <div class="input-group filtercontrols">
    <span class="input-group-addon">Filter</span>
    <input type="text" class="form-control" placeholder="filter by" ng-model="filter">
    <span class="input-group-addon" ng-click="filter = ''">reset</span>
  </div>

  <div class="input-group ordercontrols">
    <label class="radio-inline">
      <input type="radio" value="label" ng-model="orderby"> Order by label
    </label>

    <label class="radio-inline">
      <input type="radio" value="id" ng-model="orderby"> Order by id
    </label>

    <label>
      <input type="checkbox" ng-model="reverse" />
      Reverse
    </label>
  </div>

  <div class="checkboxes">
    <div class="checkbox widgetcontrols" ng-repeat="elem in model | filter:{$:filter} | orderBy: orderby:reverse">
      <label>
        <input class="smart" type="checkbox" ng-model="elem.value" value="{{elem.value || false}}" />
        <span class="itemid">[{{elem.id}}]</span> {{elem.label}}
      </label>
    </div>
  </div>
</div>
Done!


How it works? This reusable component let you filter items and order them by label, id or reversed depending on the user input. If the user clicks on reversed, the list is reversed, if the user digits "Fir" as filter the list will be reduced and so on. Selecting one or more items, the binded $scope.exampleList will reflect the user choice.

How to test this directive

The directive has an isolate scope where model is two-way binded to $scope.exampleList (see model: '=' on the directive definition). The other ng-models you see into the template directives are just internal models that lives into the isolated scope of the myList directive (orderby, reverse, filter) and they don't affect the outer scope.

So when you are trying to change the reverse, orderby or filter properties, you'll have to do it on the right isolated scope!
'use strict';

describe('Directive: myList', function () {
  // load the directive's module
  beforeEach(module('myListApp', 'views/mylist.html'));

  var element,
    $rootScope,
    $compile,
    $element,
    scope;

  beforeEach(inject(function (_$rootScope_, _$compile_) {
    $rootScope = _$rootScope_;
    scope = $rootScope.$new();
    $compile = _$compile_;
  
  $rootScope.model = [
        {id: '001', label:'First item'},
        {id: '002', label:'Second item'}
      ];

    $element = angular.element('<my-list model="model"></my-list>');
    element = $compile($element)(scope);
    $rootScope.$apply();
  }));

  it('Labels order (reverse)', function () {
    var isolateScope = $element.isolateScope();

    isolateScope.reverse = true;
    isolateScope.orderby = 'label';
    isolateScope.$apply();

    expect($element.find('.itemid').eq(0).text()).toBe('[002]');
  });
});


So remember: .isolateScope is your friend!

Links:

2014-02-09

Testing AngularJS directives with templateUrl generated with Yeoman's generator-angular

Are you in trouble while testing your reusable directive you are developing with AngularJS? Because are you testing your application, right?!

I had to solve some problems testing my AngularJS directive and if you are in trouble I hope you will save time reading this post. One of them is:
  • testing directives that use the templateUrl statement

directives that use the templateUrl statement (and Yeoman)

The templateUrl option let you put the directive template into external html files instead of inlining html into your Javascript code.

It's a very useful option but if you try to test your directive with the templateUrl option you will get a nasty error:
Error: Unexpected request: GET views/yourdirectivetemplate.html
If you want to solve you can use use the ng-html2js Karma preprocessor. This way Karma immediately generates the js file, that puts the html into $templateCache. Alternatively you can alter the $templateCache by hand, not covered in this post.

All you need to know about testing AngularJS directives with templateUrl is described here:
Once followed all vojtajina's hints you will get this error if you have used the Yeoman's generator-angular because you have a different code structure (remember the app folder?):
Error: [$injector:modulerr] Failed to instantiate module views/yourdirectivetemplate.html due to: Error: [$injector:nomod] Module 'views/yourdirectivetemplate.html' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
Don't worry, you only need to to adjust the karma.conf.js as suggested here (see the stripPrefix usage of ngHtml2JsPreprocessor):

Summary

Hope it might help you. Let me summarize what you need to do in order to fix your tests.

Happy testing!

[update 20140210] Install karma-ng-html2js-preprocessor

Remember to install the html2js Karma preprocessor:
$ npm install karma-ng-html2js-preprocessor --save-dev
Your package.json should contains the following line into devDependencies:
+    "karma-ng-html2js-preprocessor": "~0.1.0"

karma.conf.js

module.exports = function(config) {
  config.set({
    // base path, that will be used to resolve files and exclude
    basePath: '',

    // testing framework to use (jasmine/mocha/qunit/...)
    frameworks: ['jasmine'],

+    // generate js files from html templates
+    preprocessors: {+      'app/views/*.html': 'ng-html2js'+    },
+
+    // needed if you have used the Yeoman's generator-angular or the app dir 

+    ngHtml2JsPreprocessor: {
+      stripPrefix: 'app/',
+    },
+

    // list of files / patterns to load in the browser
    files: [
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
+    'app/views/*.html',
      'app/scripts/*.js',
      'app/scripts/**/*.js',
      'test/mock/**/*.js',
      'test/spec/**/*.js'
    ],

test/spec/directives/yourdirective.js

describe('Directive: yourdirective', function () {

  // load the directive's module
  ...
+  beforeEach(module('views/yourdirectivetemplate.html'));