Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Tools
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker
Robbie Clutton

To build a bookmarklet

Robbie Clutton
Monday, June 17, 2013

Building a bookmarklet provides an interesting challenge. It involves interaction a website your application does not control where that site could be anything with any number of dependencies on CSS or Javascript libraries. The first choice to make is trying to work with that website and probably setting an !important on every CSS selector used and hope that there’s no namespace or versioning clashes with any Javascript included; or use an iframe.

Iframes seem to have fallen out of favour in recent times but the sandboxed nature of the content inside an iframe mean the worries of CSS and Javascript clashes are gone. However this is replaced with a communications overhead of communicating between the host document and the iframe. I wanted to touch on some of the things our team did on a recent project to try and make this fairly seamless.

Getting into the DOM

A bookmarklet is a small piece of Javascript that a user can drag onto the bookmark bar and upon pressing the link the Javascript will run. Because there is no guarentee what site will be loaded and if that will have any number of Javascript libraries included it’s best to use plain old Javascript to create an iframe element and append it to the body of the document. Our bookmarklet also needed to have some javascript if for nothing else but to be able to dismiss and remove the newly created elements. This can be done through the creation of a script element and appending to the body just like the iframe itself.

Once the iframe and script tags are appended they are treated the same as any other element. The content is loaded and the script is executed. The next step is getting the window to talk to the iframe. As a convienence the domain with protocol and port of the iframe is stored in a variable for later use.

element = document.createElement('iframe');
element.id = 'example_iframe';
element.src = 'example.com?referrer=' + window.location;
document.body.appendChild(element);

script = document.createElement('script');
script.src = 'example.com/bookmark.js';
document.body.appendChild(script);

The location is also sent to the remote server to load the iframe so that it can also store that location for passing messages to the host.

At this point this Javascript could also append a script tag to a version of libraries that may be required for it’s own application to run. It could also test for the existance of that library before hand so it doesn’t bring down an incompatible version. For example, bringing in jquery:

if ($ === undefined) {
    var jq = document.createElement('script');
    jq.src = "//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js";
    document.body.appendChild(jq);
}

Sending a message

The postmessage method is available to communicate bewtween the window and the iframe. The host window with a reference to the iframe can call postmessage with a string as a message and as a security measure the target location. We had stored this during the loading of the elements as described above.

iframe.postmessage('hello', 'www.example.com');

That message won’t get anywhere unless the iframe is listening for the message event on the other end.

// native Javascript
window.addEventListener('message', function(event){ … });

// jQuery
$(window).on('message', function(event){ … });

I’ve used the native Javascript above but really, once in the iframe itself the application has full control and could use JQuery or any other library at this point. Our application needs to listen to messages on both sides though so we needed the above to run in the host anyway.

RPC

Sending a message is all well and good but any non-trival application is going to have more than one function to run. We took influence from Remote Procedure Calls (RPC) to call functions within the host and remote sites. The message sent was stringified JSON with a very light ‘schema’ of the function to run and parameters to send to the function.

{   
    f: 'theFunction',
    params: {
        ...
    }
}

The recipient could then parse the string it knew to be JSON, extract the function to call and call it with any optional parameters also sent. This does create a binding between the host and the iframe but as the appliction controlled both sides we deemed it an acceptable risk. The function can be run from the window like so:

window['theFunction']();

// or from the event listener
var fn = JSON.parse(event.data).['f'];
window[fn]();

Dealing with namespaces

It was mentioned earlier that one of the goals of using an iframe was not to clobber any Javascript namespaces but we did end up including Javascript in the host and to avoid this we used an application namespace. However calling that as a property on the window would no longer work.

// doesn't work
window['my.app.function'](); 

// works
window['my']['app']['function']();

We looked to ElementalJS as an example of dealing with namespaced functions to parse the function.

window.addEventListener('message', function(){
    var fn = window; 
    var data = JSON.parse(event.data);
    var namespaced = data['f'].split('.');
    for (var i in namespaced) { 
        fn = fn[namespaced[i]];
    }
    fn(data.params);    
});

This is natually fairly crude, some defensive code could be added but this demonstrates the intent of the processing. Defense like making sure that function existed, or that ‘f’ existed in the data for the window could receive a message from another iframe.

Putting it all together

The bookmarket inserts two elements, an iframe and a script. The iframe has the source example.com?referer=bar.com. bar.com is inserted as a variable in the iframe Javascript code. The iframe inserted has an id of example_iframe

The host Javascript listens to the message event

window.addEventListener('message', function(event){
    var fn = window; 
    var data = JSON.parse(event.data);
    var namespaced = (data['f'] || "").split('.');
    for (var i in namespaced) { 
        fn = fn[namespaced[i]];
    }
    if (typoe(fn) === 'function') {
        fn(data.params);    
    }
});

The host also sets up a namespace and a function to be called.

window.example = window.example || {};

example.hello = function(){ … }   

When the iframe has loaded, it sends a ready message to the host

$(document).ready(function(){
    var data = JSON.strinify({f: 'example.hello'});
    parent.postMessage(data, referer); // referer set by server from the request param for the iframe
});

The host from the script loaded in the bookmarket has the example.hello function and it’s run. This in turn replies to the iframe.

var example.hello = function(){
    var iframe = document.getElementById('example_iframe');
    var data = JSON.stringify({f: 'example.world'})
    iframe.contentWindow.postMessage(data, 'example.com');
};

The iframe has an event listener which is the same code as the host, and runs the function example.world

var example.world = function(){
    // hello, world
};

Wrapping up

This has shown some of the techniques for a ‘hello, world’ bookmark with two way communication between host and iframe that uses Javascript namespaces. This was enough to get our application off the ground as the two way communication acted as a solid base to build upon.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Luan Santos

Ember.js and SoundManager2

Luan Santos
Sunday, June 16, 2013

Continuing the experimentation with cool libraries and ember this time I’ll use SoundManager2, a really useful sound playing library for HTML5 and/or Flash that is compatible with most browsers out there.

It is not very hard to use and it has a pretty decent documentation, what I’ll show is a very simple usage of it that I plan to improve in the future, but this should give an idea on how to start at least.

My app has a Song model that contains a url property, there is a ‘songs’ route that lists the songs and a ‘song’ route that shows the player for a given song.

Let’s start by including SoundManager2 into our app, grab your copy from their website, the downloaded zipfile will have several folders, look for soundmanager2.js in the script folder and soundmanager2_debug.swf in the swf folder. You’ll want to change this to the nodebug version when you’re going production.

Once we did that we need to initialize soundmanager with our app. We do that by adding an initializer:

App.initializer({
  name: "soundmanager",
  initialize: function() {
    soundManager.setup({
      url: '/swf'
    });
  }
});

Note the ‘/swf’ path there, that’s where you put the swf file, update accordingly. This initializer will setup soundmanager with all the default options, you can override them by adding more properties to the setup function call. Please refer to the documentation.

Alright, now that we have everything in place we can use soundmanager to manage our sounds. I said that I have a route for each ‘song’ so let’s start by adding the template for that route.

<script type="text/x-handlebars" id="song">
  <button class="btn" {{action 'playPause'}}>{{playPauseLink}}</button>
  <button class="btn" {{action 'stop'}} {{bindAttr disabled="unstarted"}}>Stop</button>
</script>

This template has some stuff in it, let’s start by looking at the ‘playPause’ button, it’s content is a variable ‘playPauseLink’, we expect that to be either “Play” or “Pause” depending on the current state of the player, and it’s action is ‘playPause’ which will toggle the two.

Let’s take a look at ‘playPauseLink’:

App.SongController = Ember.ObjectController.extend({
  // …
  playPauseLink: function() {
    if (this.get('playing')) {
      return 'Pause';
    } else {
      return 'Play'
    }
  }.property('playing'),
  // …
});

Our controller have a property ‘playing’ which is a boolean for whether the song is playing or not, we use that to compute the play/pause link text with a binding, so it will stay up to date automatically. Now let’s see the playPause action:

App.SongController = Ember.ObjectController.extend({
  // ...
  playPause: function() {
    if (this.get('playing')) {
      this.pause();
    } else {
      this.play();
    }
  },

  play: function() {
    var sound = this.get('sound');

    if (this.get('unstarted')) {
      sound.play();
      this.set('started', true);
    } else {
      sound.resume();
    }

    this.set('playing', true);
  },

  pause: function() {
    this.get('sound').pause();
    this.set('playing', false);
  },
  // …

Now I’m showing you 3 methods, one is the actual action ‘playPause’, that will call either play or pause depending on the current state of the player, play and pause will set the ‘playing’ state accordingly. Play will either ‘play’ or ‘resume’ depending on whether the player has started or not, we use the property ‘started’ and it’s counterpart ‘unstarted’ for that purpose.

That’s all great but it will do nothing if we don’t load the sound first, we have to do that after soundmanager is ready and the template is inserted, let’s do that in our ‘SongView’ on the ‘didInsertElement’ callback.

App.SongView = Ember.View.extend({
  didInsertElement: function() {
    var self  = this;

    soundManager.onready(function() {
      self.get('controller').send('loadSound');
    });
  }
});

Note that we’re calling the ‘loadSound’ method in our controller once soundmanager is ready:

App.SongController = Ember.ObjectController.extend({
  // ...
  loadSound: function() {
    var self = this;
    var sound = soundManager.createSound({
      url: this.get('url'),
      onfinish: function() { self.finish(); }
    });

    this.set('sound', sound);
  },

Loading the sound is as simple as following the soundmanager’s documentation, create a sound passing in the ‘url’ property from our model. We set the ‘onfinish’ callback to reset the player once the sound is over. We’re also missing the ‘stop’ functionality that we have on the template:

App.SongController = Ember.ObjectController.extend({
  // ...
  finish: function() {
    this.set('playing', false);
    this.set('started', false);
  },

  stop: function() {
    this.get('sound').stop();
    this.finish();
  },

‘finish’ is just resetting the states, stop is calling stop on our sound object and then resetting the state by calling finish.

That is about the simplest play/pause/stop player I could think of using soundmanager, I published the full code for this simple implementaiton on this Gist. It has a little bit more to it like position/duration but it’s pretty much what I wrote above.

 

Library versions used in the post:

  • Ember.js: 1.0.0-rc.5
  • Handlebars: 1.0.0-rc.4
  • Ember Data: 0.13
  • jQuery: 1.9.1
  • SoundManager2: 2.97a.20130512
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Trace Wax

Home Sweet Home Away from Home

Trace Wax
Tuesday, June 4, 2013

Today was my first visit to a different Pivotal office.  I’m on personal travel in Los Angeles at the moment, so I spent the day today working out of Pivotal’s Santa Monica location.  I was delighted to find that people so far from New York would have such a direct connection to what I was working on.

I was remoting with my pair in NY and we were fixing a regression on our usage of the data-presents pattern, which has served us quite well on my current project. Turns out we attaching behaviors to DOM elements using their IDs generated by the simple_form gem, and those generated IDs had changed out from after us when we updated to the latest version of simple_form.

It turns out that two feet to my left was Patrick, who had made the commit to simple_form that changed the IDs that are generated. And across the table from me was Jonathan, who had worked quite closely in San Francisco with Rajan, who had created the data-presents pattern on our project. Jonathan told us how it should be used: rather than using classes or IDs from the DOM, it was better to bind it specifically to data attributes to keep the data-presents pattern as loosely coupled as possible.

With such unexpected experts so close at hand, we had the regression tested and fixed in no time and it was cause for a celebratory game of ping-pong, just like home. And that’s when the next important discovery of the day was made:

ping pong scoop

I had no idea that bending over to pick up ping pong balls is a thing of the past! Our Pivotal New York office manager will be receiving a request to purchase one of these devices in the very near future.

I look forward to a continued exchange of ideas as this week continues. Thanks to everyone in Pivotal Los Angeles for making me feel so welcome.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Andrew Bruce

Everything I know about Clojure after one week

Andrew Bruce
Saturday, May 25, 2013

This week I decided to clear out the cobwebs of my Ruby-trained brain and try a completely different language. Ruby and Rails have been my staples for over seven years, and I’m starting to tire of my patterns of thinking, and of the common problems found in large Rails applications. Specifically, I’m pretty bored of running into slow test suites and difficult-to-maintain apps. I suspect the primary cause of these problems is the entanglement of business logic with database and other stateful resource code. I get the feeling that it can’t be just coincidence that so many teams of competent developers fall into the same old traps.

I’d heard a bit about Clojure, so decided to explore the ecosystem and community. I’m attracted to the idea of learning a functional language, avoiding mutable state and re-evaluating the object oriented programming paradigm. Functional programmers are often accused of being “idealistic”, “academic” or a variety of other thinly-veiled primitive insults. If nothing else, I could learn to sympathise with these crackpot underdogs who claim to have the cure for most of the technical pain I suffer in my chosen occupation.

Hopefully this assortment of links and lessons learned will encourage you to try out the language, or inspire you to try something else that’s refreshingly new.

Clojure is easy to get started with

There are various tutorials online. However, they can be a little out of date. The quickest route to Clojure hacking is via leiningen, a sort of rake-meets-bundler utility. Assuming you’re on a Mac, have homebrew installed, a recent JDK and the usual code compilation dependencies:

brew install leiningen
lein repl

That’ll pull in Clojure for you. Once you’ve had a play in the REPL, use leiningen to generate an app:

lein new my-test-project

The project.clj that the above command generates is the equivalent of a gemspec, and declares the dependencies of your app. Leiningen helpfully fetches dependencies lazily when you start a repl, server or run tests. No bundle install step!

Homebrew can also install Clojure for you, but this version will be independent of leiningen apps. When you install it, you’ll be recommended to use a REPL wrapper for rlwrap. I’ve found that the one from the wikibooks site works better (i.e. at all).

Rich Hickey is an entertaining figurehead

Rich Hickey, creator of Clojure and designer of Datomic, the FP-friendly database system everyone’s talking about, has some inspiring talks from the end of last year about Clojure and Datomic, and notably why everybody is doing programming wrong. There are some pretty strong views in the talks, but they’re a refreshing take on the sociological patterns in our industry, and how project pathologies can be treated.

There’s an active community

Many clojure developers are ex- or current-Rubyists. For example, Jay Fields maintains the expectations testing library (more on this below).

Clojure has artsy cred, too: there are several videos on the web showing Clojure being used for live music.

Syntax change requires healthy brain re-wiring and thought simplification

After spending so much time wrapping my head around object-oriented constructs and trying to work out how best to structure OO programs, it’s a refreshing change to work with a functional language. Not having a CS degree, I’d previously tinkered with Scheme following a workbook. I wasn’t put off by parentheses, but did have some fears that I’d be transported back to the bad old days of the top-level PHP namespace. I soon realised, however, that my beef with PHP was mainly the aforementioned comingling of state and logic, as well as the inconsistencies of method signatures and names.

Clojure philosophy is all about consistency and reuse. I’ve trained myself to believe that reuse is all about identifying concepts, naming them and adding an abstraction, often a named value object. I’m pretty attached to my explicitly named classes of values, like SubscriptionEvent or PersonName. This is largely in reaction to the recognisable OO smells like Primitive Obsession. The problems inherent in such smells generally melt away in the FP world, since you’re encouraged to use the built-in types to provide equality and the like, and to use the common functions like map, reduce and so forth to achieve what you would do in OO with a value object’s methods.

Web development is potentially full-stack

This is worth knowing if you don’t already: Clojure already compiles to JavaScript in the browser, as well as to Java on the server. So we potentially could be doing single-language full-stack development like node.js but without the oft-cited pitfalls of JavaScript. An excellent resource for newcomers to Clojure is this translation between JS and Clojure.

I’ve begun a project for playing around with the Compojure web routing library. Compojure forms the basis of many Clojure web apps, and I’m using that repository as a place to stick my learnings. I’m test-driving each new feature, so that it might serve as a reference for other newcomers on how to do TDD for Clojure web apps.

TDD is easy, fast and totally not banned.

Watching the Hickey talks, you might come away with the impression that the Clojure community is anti-TDD. However, there are some excellent developments going on in this area, such as the aforementioned Jay Fields’ expectations library, which provides an achingly simple way to express intent in tests. See my first attempts at testing a web app for an example of this. Structural whitespace optional.

Monads still a mystery

One week just wasn’t enough. See if you can figure it out and send me a postcard with a succinct summary.

Until next time!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Stephan Hagemann

Showing and hiding conditional HTML without Javascript

Stephan Hagemann
Tuesday, May 21, 2013

Have you ever filled out an address form that had a checkbox for “my shipping address differs from my mailing address”? When you click that box a conditional form part gets revealed that allows you to enter another address. We had to build something very similar the other day and stumbled on a neat way to make the conditional part show and hide with CSS only.
Continue reading →

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ken Mayer

Sencha Touch BDD – Part 5 – Controller Testing

Ken Mayer
Saturday, May 18, 2013

Sencha Touch BDD

tl;dr

A multi-part series of articles on how to test Sencha Touch applications. It uses Jasmine for unit testing and Siesta for integration testing.

Part 5 – Controller Testing

Recap

Part 4 Introduced PhantomJS as an easy and faster alternative to headful Jasmine testing. Part 3 added jasmine-ajax so we can verify that stores and models react properly to back-end data. We also learned how to use stores to test views, without depending on a back-end server. In Part 2 I showed you how to unit test Sencha model classes in Jasmine. In Part 1 I showed you how to set up your Sencha Touch development environment to use the Jasmine JavaScript test framework.

It’s a control thing, but I will let you understand

Sencha Touch controllers usually live within the context of a single application object. Normally, this is handled for you when you invoke Ext.Application() in your app.js file. It creates a singleton object for you in the namespace of your application. For example, if you configured your application’s name to be ‘SenchaBdd’, then the application will be available as the .app attribute of the global SenchaBdd object, that is, SenchaBdd.app.

Unit testing should not have a running application, however. The point is that we are testing classes in isolation. There’s nothing isolated about an integrated, running, Javascript application. There is a relatively simple solution, however; You need to create you own “test” application object that you can then pass as a configuration option when you create controllers under test.

$ cat spec/javascripts/controller/MyControllerSpec.js
describe('SenchaBdd.controller.MyController', function() {
    var controller, app;
    beforeEach(function () {
        app = Ext.create('Ext.app.Application', {name: 'SenchaBdd'});
        controller = Ext.create('SenchaBdd.controller.MyController', { application: app });
        controller.launch();
    });

    afterEach(function() { app.destroy(); })

You may want to refactor the application creation and tear-down into a spec helper, to DRY out your tests.

Test behaviors, not events

It’s tempting to write a Jasmine test that tries to trigger an event in the DOM, then follow the event handling through the application. This is the road to hell. If you find yourself trying to simulate an event, please stop. That is what integration tests are better at doing. Controllers are classes like any other, and you should test methods in the same way. For example, let’s drive out a behavior where, when a user taps on the ‘Buy’ button our application sends a request to the back-end.

describe('SenchaBdd.controller.MyController', function () {
  var controller, app;
  beforeEach(function () {
    app = Ext.create('Ext.app.Application', {name: 'SenchaBdd'});
    controller = Ext.create('SenchaBdd.controller.MyController', { application: app });
    controller.launch();
  });

  afterEach(function () {
    app.destroy();
  });

  it('#newOrder', function () {
    var order = controller.newOrder();
    expect(order.$className).toEqual('SenchaBdd.model.MyModel');
    expect(order.phantom).toBeTruthy();
  });

  describe('#onBuy', function() {
    it('calls save on the order', function() {
      var myOrder = Ext.create('SenchaBdd.model.MyModel');
      spyOn(myOrder, 'save');
      spyOn(controller, 'newOrder').andCallFake(function() {
        return myOrder;
      });
      
      controller.onBuy()
      
      expect(myOrder.save).toHaveBeenCalled();
    })
  })
});

You might notice that I neither ‘tap’, nor do I test for a ‘POST’ ajax call. The former is better tested through integration tests. The latter is better tested in the model. All the controller need do assert that the model was saved. We trust external classes to function (because they’re tested, too, right?) Testing the #save method on the model follows the same process as testing stores, as I outlined in Part3. Another thing to note is that, under test, this controller does not have any views associated with it; Ext.ComponentQuery calls will return empty (undefined) results. This is to be expected in an isolated test, but may make for some head scratching when you first encounter it. If you must test something in the DOM, you should be writing an integration test anyway.

Ext.define('SenchaBdd.controller.MyController', {
  extend:   'Ext.app.Controller',
  config:   {
    views:   ['MyView'],
    refs:    {
      buyButton: 'myview #buyButton'
    },
    control: {
      buyButton: { tap: 'onBuy' }
    }
  },
  newOrder: function () {
    return Ext.create('SenchaBdd.model.MyModel');
  },
  onBuy:    function () {
    this.newOrder().save();
  }
});

As a personal preference, and make it easier to test and refactor, I have a #newOrder method that delegates to the model to create a new instance.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ken Mayer

Sencha Touch BDD – Part 4 – PhantomJS

Ken Mayer
Friday, May 10, 2013

Sencha Touch BDD

tl;dr

A multi-part series of articles on how to test Sencha Touch applications. It uses Jasmine for unit testing and Siesta for integration testing.

Part 4 – Headless testing using PhantomJS

Part 3 added jasmine-ajax so we can verify that stores and models react properly to back-end data. We also learned how to use stores to test views, without depending on a back-end server. In Part 2 I showed you how to unit test Sencha model classes in Jasmine. In Part 1 I showed you how to set up your Sencha Touch development environment to use the Jasmine JavaScript test framework.

I hear it’s all about the cloud these days

Not only do we use Test Driven Development, we test all the time (see also TATFT). In fact, continuous testing is a key component in a well-run agile practice. Pivotal developed ciborg (previously known as Lobot), to automatically deploy Jenkins servers in the cloud. Jasmine has a continuous integration target in its rake tasks, rake jasmine:ci. If you’ve never run it before, now is a good time to try it. You’ll notice that Jasmine launches an instance of the Firefox browser, runs the test suite, then reports the results. That’s great if you’ve got (1) Firefox and (2) a machine that has a display! When you run your CI tests in the cloud, the virtual machines may not be configured with a virtual display. Furthermore, the process is terribly slow.

In this installment, we’ll see how to mix in PhantomJS so we can run our Jasmine tests without the need for a visual browser. PhantomJS is orders of magnitude faster, too.

Do the install dance

  1. Add ‘jasmine-phantom’ to your Gemfile
index e6b5daf..612fb25 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,3 +2,4 @@ source "https://rubygems.org"
 
 gem "rake"
 gem "jasmine"
+gem "jasmine-phantom"

and modify your Rakefile to load the PhantomJS tasks:

--- a/Rakefile
+++ b/Rakefile
@@ -2,6 +2,7 @@
 begin
   require 'jasmine'
   load 'jasmine/tasks/jasmine.rake'
+  load 'jasmine-phantom/tasks.rake'
 rescue LoadError
   task :jasmine do

Finally, run bundle install

Now, when you run rake -T from the command line, you’ll see a new target:

$ rake -T jasmine
rake jasmine             # Run specs via server
rake jasmine:ci          # Run continuous integration tests
rake jasmine:phantom:ci  # Run jasmine specs using phantomjs and report the results

Run rake

Running it will the phantom:ci target will start the Jasmine continuous integration tests in PhantomJS instead of Firefox.

$ rake jasmine:phantom:ci 2>1 /dev/null
[2013-05-06 20:28:15] INFO  WEBrick 1.3.1
[2013-05-06 20:28:15] INFO  ruby 1.9.3 (2012-11-10) [x86_64-darwin12.2.0]
[2013-05-06 20:28:15] INFO  WEBrick::HTTPServer#start: pid=17061 port=60080
Waiting for jasmine server on 60080...

8 specs | 0 failing
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ken Mayer

Sencha Touch BDD – Part 3 – Testing Views and Mocking Stores

Ken Mayer
Sunday, May 5, 2013

Sencha Touch BDD

tl;dr

A multi-part series of articles on how to test Sencha Touch applications. It uses Jasmine for unit testing and Siesta for integration testing.

Part 3 – Testing Views and Mocking Stores

In Part 1 I showed you how to set up your Sencha Touch development environment to use the Jasmine JavaScript test framework. In Part 2 I showed you to unit test Sencha model classes in Jasmine.

I don’t normally test views, but when I do

There’s an old MVC mantra: “Fat models, skinny controllers and stupid views.” We don’t want complex logic in our views; that makes them hard to maintain. We don’t want any business logic in our controllers, either. That’s why we have models. Views should be so simple that they don’t require tests. There is a gray area with Sencha, however, where we found testing to be useful in our design process. It has to do with how views interact with stores.

Stores are essentially collections of models. They also encapsulate the persistence layer logic, separate from the business logic of the model. DataViews are a special class of Views in Sencha Touch that will consume a collection to generate a list-type view via a template.

Here are two goals for testing views and stores:

  • Does the view consume the right fields from the store, without hitting the back-end for data?
  • Does the store organize the data from the back-end, i.e. create the interface that is used by the view? Again, without hitting the back-end for data.

Small steps and iterate

Let’s define a very simple view via tests, we’ll make it work using an in-line store, then we’ll refactor the store into its own class. Next, we’ll refactor the storage class to use a remote back-end.

Ext.require('SenchaBdd.view.MyView');

describe('SenchaBdd.view.MyView', function () {
  it("has a list of colors", function () {
    var view = Ext.create('SenchaBdd.view.MyView', {
      renderTo: 'jasmine_content',
      store:    {
        fields: ['color'],
        data:   [
          {color: 'red'},
          {color: 'green'},
          {color: 'blue'}
        ]
      }
    });

    expect(Ext.DomQuery.select('.favorite-color').map(function (el) {
      return el.textContent
    }).join(', ')).toEqual('red, green, blue');

  });
});

Sencha does not come bundled with jQuery, so if you were expecting a DOM query like, $(“.favorite-color”), you might be surprised by the expectation. Ext has its own flavor of querying the DOM, using Ext.DomQuery.select.

Try implementing the view on your own to make the test pass. It should look remarkably similar to this:

Ext.define('SenchaBdd.view.MyView', {
  extend: 'Ext.dataview.DataView',
  xtype:  'myview',
  config: {
    itemTpl: '<div class="favorite-color">{color}</div>'
  }
});

In your application, you probably won’t hardwire a store into the view. In fact, you will probably embed this little view inside a larger container, like so (this adds another tab to the sample app):

--- a/app/view/Main.js
+++ b/app/view/Main.js
@@ -10,6 +10,11 @@ Ext.define('SenchaBdd.view.Main', {
 
         items: [
+            {
+              title: 'Favorites',
+              iconCls: 'star',
+              xtype: 'myview',
+              store: 'mystore',
+              styleHtmlContent: true
+            },
             {
                 title: 'Welcome',
                 iconCls: 'home',

Let’s create a store so our view will show us something:

Ext.define('SenchaBdd.store.MyStore', {
  extend: 'Ext.data.Store',
  config:    {
    storeId: 'mystore',
    fields: ['color'],
    data:   [
      {color: 'red'},
      {color: 'green'},
      {color: 'blue'}
    ]
  }
});

In order to see this view, we’ll need to add it to our app.js file:

--- a/app.js
+++ b/app.js
@@ -31,7 +31,11 @@ Ext.application({
     ],
 
     views: [
-        'Main'
+        'Main', 'MyView'
     ],

+    stores: [
+            'MyStore'
+    ],
 
     icon: {

Now, let’s go back and refactor our test so it uses MyStore instead of one that was hard-wired.

--- a/spec/javascripts/view/MyViewSpec.js
+++ b/spec/javascripts/view/MyViewSpec.js
@@ -2,17 +2,16 @@ Ext.require('SenchaBdd.view.MyView');
 
 describe('SenchaBdd.view.MyView', function () {
   it("has a list of colors", function () {
+    var store = Ext.create('SenchaBdd.store.MyStore', {
+      data:     [
+        {color: 'red'},
+        {color: 'green'},
+        {color: 'blue'}
+      ]
+    });
     var view = Ext.create('SenchaBdd.view.MyView', {
       renderTo: 'jasmine_content',
-      store:    {
-        fields: ['color'],
-        data:   [
-          {color: 'red'},
-          {color: 'green'},
-          {color: 'blue'}
-        ]
-      }
-
+      store:    store
     });
     expect(Ext.DomQuery.select('.favorite-color').map(function (el) {
       return el.textContent

All of our tests should remain green, but we’ve removed the “fake” store from our test.

Let’s get dynamic

Static data stores are boring. The fun starts when you start talking to a back-end API. Let’s say that we have a server that responds to an end point of ‘/colors.json’ with a list of favorite colors. We can even “fake” it by placing a file in the appropriate place. Even so, we don’t want our tests to make network calls to the back-end. That’s not appropriate for unit testing. We’ll use Jasmine’s AJAX mocking helper, jasmine-ajax. At the time of this writing, the 2.0 branch had not been merged into the main line, and we need the 2.0 branch in order to work with Ext.

cd spec/javascripts/helpers
curl -O 'https://raw.github.com/pivotal/jasmine-ajax/2_0/lib/mock-ajax.js'
git add ./mock-ajax.js

And we’ll create our first store spec in spec/javascripts/store/MyStoreSpec.js:

describe('SenchaBdd.store.MyStore', function () {
  var store;
  beforeEach(function () {
    jasmine.Ajax.useMock();
    clearAjaxRequests();
    store = Ext.create('SenchaBdd.store.MyStore')
  });

  it('calls out to the proper url', function () {
    store.load();
    var request = mostRecentAjaxRequest();
    expect(request.url).toEqual('/colors.json');
  });
});

Notice that I call jasmine.Ajax.useMock() and clearAjaxRequests() in the set up block. This is because I want to wait until the very last moment to turn on ajax mocking. The Ext class loader might still be trying to load a class (via xhr), and the mocker will prevent that from happening. I also clear all previous requests (in case there were any left over, to prevent test polution).

When you run Jasmine, you’ll get a test failure, “TypeError: Cannot read property ‘url’ of null” because we haven’t set up the proxy in the store, yet. Let’s do that.

$ cat app/store/MyStore.js
Ext.define('SenchaBdd.store.MyStore', {
  extend: 'Ext.data.Store',
  config: {
    autoLoad: true,
    storeId:  'mystore',
    fields:   ['color'],
    proxy:    {
      type:       'ajax',
      url:        '/colors.json'
    }
  }
});

Now, when you run the test suite, you’ll get a different error! This is because the default settings for the ajax proxy enables caching, paging, etc. Things we don’t need, so we have to turn them off:

    proxy:   {
      type:       'ajax',
      url:        '/colors.json',
      noCache:    false,
      pageParam:  false,
      startParam: false,
      limitParam: false
    }

Now our api test is green! You can even test this in the application by dropping a file, ‘colors.json’ into the public directory.

Let’s add one more test to finish things off.

  it('populates the collection', function () {
    store.load();
    var mockedRequest = mostRecentAjaxRequest();

    mockedRequest.response({
      status:       200,
      responseText: [
        {color: 'red'},
        {color: 'green'},
        {color: 'blue'}
      ]
    });

    expect(store.getCount()).toEqual(3);
    expect(store.getAt(0).get('color')).toEqual('red');
    expect(store.getAt(1).get('color')).toEqual('green');
    expect(store.getAt(2).get('color')).toEqual('blue');
  });

Every mocked ajax request has a response() method that you can use to inject your own response, synchronously, to your tests. This confirms that MyStore properly parses and arranges the received data so that it can be presented by the view.

Validate your mocks

We now have 2 tests that depend on a back-end to respond in a specified way. How do we keep these tests from drifting out of sync? Since we’ve mocked the store in our view test, we might never know if the back-end API changes!

You can wrap Jasmine expectation in functions so that they are reusable. Then we can mix this matcher into our view test to confirm that the store we use there is the same.

Let’s add this function to our SpecHelper.js file:

function myStoreDataIsValid(store) {
  expect(store.getCount()).toEqual(3);
  expect(store.getAt(0).get('color')).toEqual('red');
  expect(store.getAt(1).get('color')).toEqual('green');
  expect(store.getAt(2).get('color')).toEqual('blue');
}

And we’ll replace our existing tests with a single line:

  myStoreDataIsValid(store);

We’ll also modify the MyViewSpec.js:

--- a/spec/javascripts/view/MyViewSpec.js
+++ b/spec/javascripts/view/MyViewSpec.js
@@ -9,6 +9,7 @@ describe('SenchaBdd.view.MyView', function () {
         {color: 'blue'}
       ]
     });
+    myStoreDataIsValid(store);
     var view = Ext.create('SenchaBdd.view.MyView', {
       renderTo: 'jasmine_content',
       store:    store

Similarly, we can refactor our colors array into var:

--- a/spec/javascripts/helpers/SpecHelper.js
+++ b/spec/javascripts/helpers/SpecHelper.js
@@ -16,6 +16,15 @@ afterEach(function () {
     domEl.setAttribute('style', 'display:none;');
 });
 
+var colorsJSON;
+beforeEach(function() {
+  colorsJSON = [
+    {color: 'red'},
+    {color: 'green'},
+    {color: 'blue'}
+  ];
+});

And then use colorsJSON in our tests.

--- a/spec/javascripts/store/MyStoreSpec.js
+++ b/spec/javascripts/store/MyStoreSpec.js
@@ -18,11 +18,7 @@ describe('SenchaBdd.store.MyStore', function () {
 
     request.response({
       status:       200,
-      responseText: [
-        {color: 'red'},
-        {color: 'green'},
-        {color: 'blue'}
-      ]
+      responseText: colorsJSON
     });
     myStoreDataIsValid(store);
   });

and

--- a/spec/javascripts/view/MyViewSpec.js
+++ b/spec/javascripts/view/MyViewSpec.js
@@ -3,12 +3,9 @@ Ext.require('SenchaBdd.view.MyView');
 describe('SenchaBdd.view.MyView', function () {
   it("has a list of colors", function () {
     var store = Ext.create('SenchaBdd.store.MyStore', {
-      data:     [
-        {color: 'red'},
-        {color: 'green'},
-        {color: 'blue'}
-      ]
+      data:     colorsJSON
     });
+    myStoreDataIsValid(store);
     var view = Ext.create('SenchaBdd.view.MyView', {
       renderTo: 'jasmine_content',
       store:    store

What we’ve managed, so far

The toy app is coming along. We’ve test driven a DataView that consumes a back-end API. We’ve added the mock-ajax library so we can unit tests our stores in isolation. We’ve even seen a few techniques for keeping our mocks from getting out of sync (although, if you’ve been paying attention, I’ve still left a gaping hole, that needs to be plugged).

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ken Mayer

Sencha Touch BDD Part 2

Ken Mayer
Friday, April 26, 2013

Sencha Touch BDD

tl;dr

A multi-part series of articles on how to test Sencha Touch applications. It uses Jasmine for unit testing and Siesta for integration testing.

Part 2 – Unit Testing Models

In Part 1 I showed you how to set up your Sencha Touch development environment to use the Jasmine JavaScript test framework. We’re going to take a bit of a breather from all the hard work we did last week. In this blog, I’m going to show you how to test simple models.

Let’s have some fun, shall we?

Test-Driven-Development starts with a test, of course. Let’s write one that just asserts that our model class exists:

$ cat spec/javascripts/model/MyModelSpec.js
describe('SenchaBdd.model.MyModel', function() {
  it('exists', function() {
    var model = Ext.create('SenchaBdd.model.MyModel');
    expect(model.$className).toEqual('SenchaBdd.model.MyModel');
  });
});

When we run our tests in the browser, Jasmine reports this:

  Error: [Ext.Loader] Failed loading synchronously via XHR: 'app/model/MyModel.js'; 
  please verify that the file exists. XHR status code: 404

Which is just Ext’s very formal way of saying, “No such class exists” because we haven’t written it, yet.

Let’s write one that makes the test pass:

$ cat app/model/MyModel.js
Ext.define('SenchaBdd.model.MyModel', {
  extend: 'Ext.data.Model'
});

We have proven that we can create a new class, and that it’s name is what we expect.

Attributes

Let’s assert that our model has some attributes:

$ cat spec/javascripts/model/MyModelSpec.js
it('has data', function () {
  var model = Ext.create('SenchaBdd.model.MyModel', {
    name: 'Test',
    email: 'test@example.com',
    favoriteColor: 'blue'
  });
  expect(model.get('name')).toEqual('Test');
  expect(model.get('email')).toEqual('test@example.com');
  expect(model.get('favoriteColor')).toEqual('blue');
});

Which of course, fails, until we add some fields to our model:

$ cat app/model/MyModel.js
Ext.define('SenchaBdd.model.MyModel', {
  extend: 'Ext.data.Model',
  config: {
    fields: [
      { name: 'name', type: 'string' },
      { name: 'email', type: 'string' },
      { name: 'favoriteColor' }
    ]
  }
});

Default values

Let’s say that our model has some default values

$ cat spec/javascripts/model/MyModelSpec.js
it('has default values', function() {
  var model = Ext.create('SenchaBdd.model.MyModel')
  expect(model.get('favoriteColor')).toEqual('yellow');
})

Reload the Jasmine runner in the browser and …

  SenchaBdd.model.MyModel has default values.
  Expected undefined to equal 'yellow'.

Which is easily resolved by adding it to the class:

$ cat app/model/MyModel.js
{ name: 'favoriteColor', defaultValue: 'yellow' }

Validations

One last simple piece, let’s assert that email is a required field.

$ cat spec/javascripts/model/MyModelSpec.js
it('requires an email address', function() {
  var model = Ext.create('SenchaBdd.model.MyModel');
  var errors = model.validate();
  expect(errors.isValid()).toBeFalsy();

  expect(errors.getByField('email')[0].getMessage()).toEqual('must be present');
})

The first expectation asserts that the model is not valid at all. It’s a gatekeeper test. The second test asserts that there’s validation error on the email field (and not some other field).

$ cat app/model/MyModel.js
  config: {
    ...
    validations: [
      { field: 'email', type: 'presence' }
    ]

Roundup

This is a pretty simple set of tests. If you are at all familiar with unit testing, you won’t find much new here. Testing for validations by inspecting on the Errors collection was a little tricky to suss out. Hopefully I’ve saved you a few frustrating moments digging through the source code. What’s also interesting, I think, is how the test report reads:

  SenchaBdd.model.MyModel
    exists
    has data
    has default values
    requires an email address

The test itself communicates something to the reader about the intention of the model. That’s a key concept to understand about TDD; the most important and expensive reader of the application is not the browser, it’s the person who reads and maintains it. It might be your successor or your team mate, or perhaps, yourself, six months from now, when you’ve forgotten everything about this particular patch of code.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ken Mayer

Sencha Touch BDD Part 1

Ken Mayer
Wednesday, April 17, 2013

Sencha Touch BDD

tl;dr

A multi-part series of articles on how to test Sencha Touch applications. It uses Jasmine for unit testing and Siesta for integration testing.

Part 1 – Getting Started

In this article you will learn how to set up an application to Jasmine tests in your

Opinionated is a good thing

In my not-so-humble professional opinion, every modern web framework should provide a testing infrastructure with each newly generated application. I’m not concerned if it isn’t my preferred testing package. As long as there’s something. Testing is not an option, and the framework authors probably (hopefully?) test, so why not offer a serving suggestion for new projects? The worst that can happen is that you, the developer, disagree with the choice of framework. There’s a little extra bootstrap cost to replace one framework with another. That’s far less expensive than every new developer discovering a way to test.

Sencha Touch 2.1 has a generator built into its sencha command line tool, but it does not create a test structure as part of the template. This article is the first in a series of discoveries about how to test Sencha Touch applications. I am not claiming that this is the one true way to test. This is not necessarily the best way, either. It is, however, something that works. It installs easily on my development laptop. It gets you to your first passing test quickly. It saves you the cost of exploring all of the options and making these discoveries for yourself. You have plenty of other things to worry about.

But first, you need a web server

Once you’ve installed Sencha Touch and Sencha Command (3.1.0.256 when I wrote this), and you’ve generated the template application, you’ll need to serve the pages locally. Most projects will have some sort of app server already running, but it’s not strictly necessary for testing. When I need to serve pages on my own, I prefer pow. It is a zero-configuration server that can host as many applications as you please. I also like the powder ruby gem; it adds a nice command line interface to manage pow. If you are worried about adding a ruby dependency to your project, stop worrying. Sencha Touch uses the compass ruby gem to generate css files; so you already have a ruby dependency.

pow looks for a rack app, but in my sample app, I don’t have one. pow also looks for a directory named public from which it will server static files. The simplest thing that works is to create a symlink named public that points to the root directory of the project.

# Generate a new ST app
$ cd <touch toolkit directory>
$ sencha generate app senchaBdd ~/workspace/sencha-bdd

# Set up pow/powder
$ cd ~/workspace/sencha-bdd
$ ln -s . public
$ powder up
$ powder link

# Test the server
$ open http://sencha-bdd.dev

If all goes well, you should be able to open the application in any web browser at http://sencha-bdd.dev

Running sample app

Running sample app

Install Jasmine

Installing the stand-alone version of Jasmine will work, but it doesn’t scale to hundreds or thousands of specs. That’s why the Jasmine gem was created. I did some more research and found a way to test using the Jasmine gem.

  1. In the root directory of your project add rake and jasmine to your Gemfile
    $ cat Gemfile
    source "https://rubygems.org"
    
    group :development do
    	gem 'rake'
    	gem 'jasmine'
    end
  2. Run bundle install

  3. Run jasmine init

  4. Jasmine will install a basic set up, but there’s some cruft that you won’t need for a Sencha application.
    rm public/javascripts/Player.js
    rm public/javascripts/Song.js
    rm spec/javascripts/PlayerSpec.js
  5. Edit the src_files entry in spec/javascripts/support/jasmine.yml:
    src_files:
        - touch/sencha-touch-all-debug.js   # Load Sencha library
        - spec/app.js                   # Load our spec Ext.Application
        - app/**/*.js                   # Load source files
  6. Create this file in spec/app.js:
    Ext.Loader.setConfig({
        enabled: true,                  // Turn on Ext.Loader
        disableCaching: false           // Turn OFF cache BUSTING
    });
    
    Ext.Loader.setPath({
        'SenchaBdd': 'app'              // Set the path for all SenchaBdd.* classes
    });
    
    Ext.application({
        name: 'SenchaBdd'               // Create (but don't launch) an application
    });
  7. And this one in spec/javascripts/helpers/SpecHelper.js:
    
    Ext.require('Ext.data.Model');
    
    afterEach(function () {
        Ext.data.Model.cache = {};      // Clear any cached models
    });
    
    var domEl;
    beforeEach(function () {            // Reset the div with a new one.
        domEl = document.createElement('div');
        domEl.setAttribute('id', 'jasmine_content');
        var oldEl = document.getElementById('jasmine_content');
        oldEl.parentNode.replaceChild(domEl, oldEl);
    });
    
    afterEach(function () {             // Make the test runner look pretty
        domEl.setAttribute('style', 'display:none;');
    });

So, what’s going on here? Sencha Touch applications need Ext.Loader to manage class loading. You also need an Ext.Application, especially for controller tests. The modifications to jasmine.yml set up the proper load order, and the jasmine gem will find all of the source files underneath the app/ directory. The app.js is a customized version of your normal app.js that sets up the class loader and global namespace configuration. You should replace “SenchaBdd” with the real name of your application. Two things are happening in SpecHelper.js: First, by default Ext.data.Model caches every model created by the application in a global in-memory array. If you don’t clear it between tests, you can be surprised by test pollution. The second part is to set up and clear a space in the test runner for inserting DOM elements, usually for some sort of view testing.

Create a directory structure that matches your application’s

Your application’s directory structure should look something like this:

├── app
│   ├── controller
│   ├── model
│   ├── profile
│   ├── store
│   └── view

Modify the spec directory so that it mirrors the app/ directory:

├── spec
│   ├── app.js
│   └── javascripts
│       ├── model
│       ├── controller
│       ├── view
│       ├── store
│       ├── profile

Install Jasmine

You can get the stand alone version from http://github.com/pivotal/jasmine. Install it at the in the spec directory. I include the version number, so I can experiment with different versions, but that’s a matter of taste.

├── app
│   ├── controller
│   ├── model
│   ├── profile
│   ├── store
│   └── view
├── public -> .
├── resources/
├── spec/
│   ├── controller
│   ├── jasmine-1.3.1
│   ├── model
│   ├── profile
│   ├── store
│   └── view
└── touch

In order to get Jasmine going, you need a special html file named, by convention, SpecRunner. Add it to spec/ as well. It looks like this:

You will need to modify lines 18 and 22 with the name of your ST app (which can be found in app.json and app.js).

Write one passing test

Create a file, spec/javascripts/sanitySpec.js

describe("Sanity", function() {
  it("succeeds", function() {
    expect(true).toEqual(true);
  });
});


Now load the spec runner into a browser. In the case of this sample app, the url is http://sencha-bdd.dev/spec/SpecRunner.html

Now start the Jasmine spec server from the command line:

    bundle exec rake jasmine

And then open a browser window on http://localhost:8888

You should see the test results with one passing spec.

1 Passing Jasmine Spec

1 Passing Jasmine Spec

If you don’t see this, open up the browser’s developer console to look for clues.

Until next time

That’s it! You now have a complete JavaScript testing framework installed in your application. This is a good time to commit your changes. Celebrate in the glory of the green goodness. You’ve earned it.

Next time, I’ll show you how to test a model class.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Topics

  • agile (783)
  • rails (117)
  • testing (90)
  • ruby (86)
  • ruby on rails (71)
  • jobs (62)
  • javascript (59)
  • techtalk (44)
  • ironblogger (42)
  • rspec (39)
  • bloggerdome (34)
  • productivity (34)
  • activerecord (30)
  • rubymine (30)
  • git (29)
  • gogaruco (29)
  • nyc (27)
  • design (24)
  • mobile (23)
  • pivotal tracker (22)
  • process (21)
  • cucumber (21)
  • jasmine (19)
  • ios (18)
  • tracker ecosystem (17)
  • webos (17)
  • objective-c (17)
  • fun (16)
  • android (16)
  • palm (16)
  • ci (16)
  • "soft" ware (16)
  • bdd (15)
  • tdd (15)
  • cedar (15)
  • rails3 (14)
  • performance (14)
  • css (14)
  • gem (13)
  • mouse-free development (12)
  • selenium (12)
  • goruco (12)
  • bundler (12)
  • api (12)
  • keyboard (11)
  • meetup (11)
  • railsconf (11)
  • nyc-standup (11)
  • capybara (10)
  • mac (10)
Subscribe to javascript Feed
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. →
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Tools
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >