Testing External Templates in Angular

One of Angular’s best offers is its testability. Modules (controllers, directives, filters, etc.) were designed to be testable from the ground up.

Documentation on testing Angular directives usually show how directives are tested with an internal template (template: '<div class="progress">...</div>') but rarely show how external templates (templateUrl: 'tpl/progress-bar.html') are tested. Here’s how it’s done.

I won’t go into the details of setting up an Angular app but I’ll show what’s needed to test external templates. The three files below show a standard index.html, directives.js, and progress-bar.html. There isn’t really anything special going on here. The 'progressBar' directive is just a shortcut for rendering a Bootstrap progress bar.

1 <!-- index.html -->
2 
3 ...
4 
5 <progress-bar percentage="50" />
6 
7 ...
 1 // js/directives.js
 2 
 3 angular.module('app.directives', []).
 4   directive('progressBar', [function() {
 5     return {
 6       replace: true,
 7       restrict: 'E',
 8       scope: { percentage: '@' },
 9       templateUrl: 'tpl/progress-bar.html'
10     };
11   }]);
1 <!-- tpl/progress-bar.html -->
2 
3 <div class="progress">
4   <div class="progress-bar" role="progressbar" aria-valuenow="{{percentage}}" aria-valuemin="0" aria-valuemax="100" style="width: {{percentage}}%;">
5     <span class="sr-only">{{percentage}}% Complete</span>
6   </div>
7 </div>

To make external templates work in your tests you’ll need to use Karma as your test runner. Not using Karma? You should use Karma :) Once you have Karma setup, you’ll need to install the ng-html2js preprocessor.

$ npm install karma-ng-html2js-preprocessor --save-dev

Here's what the karma.conf.js file looks like with ng-html2js configured:

 1 // karma.conf.js
 2 
 3 module.exports = function(config) {
 4   config.set({
 5     basePath: '',
 6     frameworks: ['jasmine'],
 7     files: [
 8       'lib/jquery-1.10.2.js',
 9       'lib/angular.js',
10       'js/**/*.js',
11       'specs/lib/**/*.js',
12       'specs/**/*Spec.js',
13       'tpl/**/*.html'
14     ],
15     reporters: ['progress'],
16     preprocessors: {
17       'tpl/**/*.html': ['ng-html2js']
18     },
19     browsers: ['PhantomJS'],
20   });
21 };

Notice that the ng-html2js preprocessor is enabled on all template files (line 17). ng-html2js will automatically convert the templates into modules available for your tests.

Now in directivesSpec.js we just have to load the module created by ng-html2js (line 10). The module will automatically cache the template and put it in the $templateCache service. When the compiler attempts to compile the directive, it will get the cached template from $templateCache.

 1 // specs/directivesSpec.js
 2 
 3 'use strict';
 4 
 5 describe('app.directives', function() {
 6   describe('progessBar', function() {
 7     var element, scope;
 8 
 9     beforeEach(module('app.directives'));
10     beforeEach(module('tpl/progress-bar.html'));
11     beforeEach(inject(function($rootScope, $compile) {
12       element = angular.element('<div><progress-bar percentage="50" /></div>');
13       scope = $rootScope;
14       $compile(element)(scope);
15       scope.$digest();
16     }));
17 
18     it('renders the progress bar', function() {
19       var bar = element.find('.progress-bar');
20       expect(bar.length).toBe(1);
21       expect(bar.attr('aria-valuenow')).toBe('50');
22       expect(bar.css('width')).toBe('50%');
23     });
24 
25     it('renders caption text', function() {
26       var caption = element.find('span');
27       expect(caption.length).toBe(1);
28       expect(caption.text()).toBe('50% Complete');
29     });
30   });
31 });

And now you have working external templates in your directive tests.

HTTP vs HTTPS performance with/without keep-alive

For those deciding whether or not to have an entire web app on SSL or only areas where sensitive information is handled and are concerned about network performance, having a persistent HTTP connection (keep-alive) may help speed up response times. Let's see how HTTP response times compare against HTTPS when keep-alive is off and then when keep-alive is on.

Testing Environment

A little background on the tests: To run the tests, I'm using PhantomJS to send 200 requests to a minimal Ruby on Rails app for a 100KB document. There's a 20 second interval between each request.

The Rails app is deployed on Heroku's Celadon Cedar Stack running on a single Dyno. I'm using Rails 3.2.12 running on MRI 1.9.3-p327.

I am testing from Chicago and my ISP is Comcast (DL: 3Mbps, UL: 1Mbps), which means these numbers may appear horribly slow to you. :(

The histograms below show the distribution of response times. All response times are in milliseconds.

Scenario 1: Keep-alive off

In this scenario, I'm forcing PhantomJS to send requests with HTTP persistent connections closed:

page.customHeaders = { 'Connection': 'close' };

HTTP

# NumSamples = 200; Min = 282.00; Max = 1589.00
# Mean = 416.920000; SD = 157.608006
# each * represents a count of 2

  0 - 49  [  0] 
 50 - 99  [  0] 
100 - 149 [  0] 
150 - 199 [  0] 
200 - 249 [  0] 
250 - 299 [  2] *
300 - 349 [ 26] *************
350 - 399 [110] *******************************************************
400 - 449 [ 39] *******************
450 - 499 [  8] ****
500 - 549 [  4] **
550 - 599 [  1] 
600 - 649 [  2] *
650 - 699 [  0] 
700 - 749 [  1] 
750 - 799 [  1] 
800 - 849 [  0] 
850 - 899 [  0] 
900 - 949 [  2] *
950 - 999 [  0] 
    1000+ [  4] **

Quite normal here, mean response time is 417ms. Four requests were timed beyond one second (I'll blame my 3Mbps connection on these!), causing the mean to appear a bit higher than the 350-399ms bucket, where most responses occur.

HTTPS

# NumSamples = 200; Min = 434.00; Max = 2545.00
# Mean = 653.180000; SD = 294.454712
# each * represents a count of 2

  0 - 49  [  0] 
 50 - 99  [  0] 
100 - 149 [  0] 
150 - 199 [  0] 
200 - 249 [  0] 
250 - 299 [  0] 
300 - 349 [  0] 
350 - 399 [  0] 
400 - 449 [  3] *
450 - 499 [ 23] ***********
500 - 549 [ 43] *********************
550 - 599 [ 70] ***********************************
600 - 649 [ 21] **********
650 - 699 [ 12] ******
700 - 749 [  6] ***
750 - 799 [  0] 
800 - 849 [  4] **
850 - 899 [  1] 
900 - 949 [  0] 
950 - 999 [  1] 
    1000+ [ 16] ********

With keep-alive off, HTTPS handshaking overhead adds 236ms to the mean response time. That isn't terribly large, but the distribution of response times appear wider than HTTP. With HTTP, 110 responses landed in the 350-399ms bucket, more than half the sample. Here, the largest bucket only contains 70 responses, and 16 responses exceeded 1 second. The slowest response was 2.5 seconds.

Scenario 2: Keep-alive on

HTTP

# NumSamples = 200; Min = 276.00; Max = 1411.00
# Mean = 403.265000; SD = 167.676936
# each * represents a count of 2

  0 - 49  [  0] 
 50 - 99  [  0] 
100 - 149 [  0] 
150 - 199 [  0] 
200 - 249 [  0] 
250 - 299 [  5] **
300 - 349 [ 43] *********************
350 - 399 [116] **********************************************************
400 - 449 [ 20] **********
450 - 499 [  7] ***
500 - 549 [  0] 
550 - 599 [  1] 
600 - 649 [  0] 
650 - 699 [  0] 
700 - 749 [  1] 
750 - 799 [  0] 
800 - 849 [  0] 
850 - 899 [  0] 
900 - 949 [  0] 
950 - 999 [  1] 
    1000+ [  6] ***

As expected, very similar results to HTTP with keep-alive off.

HTTPS

# NumSamples = 200; Min = 284.00; Max = 1240.00
# Mean = 384.825000; SD = 83.344252
# each * represents a count of 2

  0 - 49  [  0] 
 50 - 99  [  0] 
100 - 149 [  0] 
150 - 199 [  0] 
200 - 249 [  0] 
250 - 299 [  5] **
300 - 349 [ 44] **********************
350 - 399 [103] ***************************************************
400 - 449 [ 32] ****************
450 - 499 [  7] ***
500 - 549 [  3] *
550 - 599 [  2] *
600 - 649 [  0] 
650 - 699 [  3] *
700 - 749 [  0] 
750 - 799 [  0] 
800 - 849 [  0] 
850 - 899 [  0] 
900 - 949 [  0] 
950 - 999 [  0] 
    1000+ [  1] 

Here the distribution appears similar to both HTTP tests. This test actually performed better than the other three tests. Mean response time is lower, and standard deviation is much tighter. Only a single response exceeded 1 second.

When keep-alive is off, SSL handshaking overhead is required for every request. In this case, that adds about 236ms for most requests. When keep-alive is on, a persistent TCP connection is made between the server and client after the intial SSL handshaking. Only the first request gets that 236ms addition, and every request thereafter recieves HTTP performance. That's great, but the down side is that the server is now unable to serve as many requests as it did without keep-alive, due to the persistent TCP connection.

A 236ms difference in mean response time may not appear troublesome, but this is only for a 100KB document that contains no CSS, JS, or image resources. And on production apps, it's good practice to minimize response times wherever possible. If you're not expecting huge amounts of traffic all the time, leave keep-alive on. But if you're striving for maximum efficiency, it may be best to only have SSL on for sensitive areas.

ruby-opencnam, caller ID service

I’ve just open sourced a Ruby wrapper for OpenCNAM’s API service called ruby-opencnam. It will look up phone numbers and give you a name, much like a caller ID! Check it out on GitHub!

Here’s a sample usage:

1 require 'opencnam'
2 
3 caller = OpenCNAM.lookup('7731234567')
4 
5 puts caller[:name]   # => 'VANN,NYSA'
6 puts caller[:number] # => '7731234567'

Rendering undefined, false, and empty in mustache.js

Since mustache.js 0.5.0-dev, some behavior has changed when rendering undefined, false, or empty. For example, before 0.5.0-dev, you could have an object literal containing:

var view = { languages: ['JavaScript', 'Ruby', 'Python', ''] };

When rendering view using a template that looks like:

1 var template = '<ul>{{#languages}}<li>{{.}}</li>{{/languages}}</ul>';
2 var output   = Mustache.render(template, view);
3 
4 document.getElementById('container').innerHTML = output;

We end up with this list:

  • JavaScript
  • Ruby
  • Python

But after 0.5.0-dev, this now renders:

  • JavaScript
  • Ruby
  • Python
  •  

Notice the extra bullet for the empty string. The ‘fix’ (this is actually a feature not a bug!) for this is to render each line using a section. The template should really be:

var template = '<ul>{{#languages}}{{#.}}<li>{{.}}</li>{{/.}}{{/languages}}</ul>';

Now, by wrapping {{.}} between {{#.}} and {{/.}}, mustache.js will only render the line if it isn’t undefined, false, or empty (though it seems that it will still render null on a new line). You can even add {{^.}}This is an empty string!{{/.}} between {{#languages}}{{/languages}} to handle undefined, false, and empty if you need to.

Geolocation and Firefox

I was working on a Rails app that required the location of users to dynamically generate personalized content for the user. This can be done by using libraries like Ruby Geocoder, or one of your preferred language’s geocoding libraries, to reverse lookup the IP address of the user (which isn’t very accurate (sometimes the lookup would think I was located in Aurora, IL when I live in Chicago, IL)), or you can use the new Geolocation API supported by modern browsers! Which is simple to use and a bit more accurate.

1 var success = function() { alert('Success!'); };
2 var error   = function() { alert('Error!'); };
3  
4 if (navigator.geolocation) {
5   navigator.geolocation.getCurrentPosition(success, error);
6 } else {
7   error();
8 }

If the browser supports the Geolocation API, it will run navigator.geolocation.getCurrentPosition. If the browser doesn’t, then it will call error(). If navigator.geolocation.getCurrentPosition does get called, then the browser will prompt the user to allow or deny the site to determine the location of the user. If the user allows, then success is called, otherwise error is called.

This seems to work perfectly fine in Chrome, Internet Explorer 9, and earlier versions of Firefox, but since Firefox 4, the UI prompt for Geolocation has changed. Users are given choices to Share Location, Always Share, Never Share, or Not Now. Selecting Share Location, Always Share, or Never Share works, no problem. But if users select Not Now, the error callback for navigator.geolocation.getCurrentPosition never gets called and Firefox just hangs. I’ve done some research and others have had the same issue. Apparently this isn’t a bug, and is actually the way Firefox was intended to behave. See here.

I haven’t had much time to research further into the issue, but for now, I’ve just decided to disable geolocation altogether if the user is on Firefox and determine the user’s location by reverse IP address lookup. So, it looks something like this:

1 var success = function() { alert('Success!'); };
2 var error   = function() { alert('Error!'); };
3  
4 if (!navigator.geolocation || /Firefox/i.test(navigator.userAgent)) {
5   error();
6 } else {
7   navigator.geolocation.getCurrentPosition(success, error);
8 }