Skip to content

Instantly share code, notes, and snippets.

@kn
Last active December 15, 2015 04:39
Show Gist options
  • Save kn/5202705 to your computer and use it in GitHub Desktop.
Save kn/5202705 to your computer and use it in GitHub Desktop.
Test Twitter Flight components with Jasmine on Ruby on Rails
// spec/javascripts/ui/button_spec.js
describeComopnent('app/ui/button', function() {
beforeEach(function() {
var html = '<button>click</button>';
prepareComponent(html);
});
describe("when the button is clicked", function() {
it("triggers 'uiButtonClicked' event", function() {
spyOnEvent(document, 'uiButtonClicked');
this.component.click();
expect('uiButtonClicked').toHaveBeenTriggeredOn(document);
});
});
});
# spec/javascripts/support/jasmine.yml
src_file:
- vendor/assets/javascripts/jquery/jquery.js
- vendor/assets/javascripts/es5-shim/es5-shim.js
- vendor/assets/javascripts/es5-sham/es5-sham.js
- vendor/assets/javascripts/requirejs/require.js
helpers:
- "helpers/**/*.js"
spec_files:
- "**/*_spec.js"
// spec/javascripts/helpers/requirejs_config.js
var root = '';
if (typeof window.JHW !== 'undefined') {
root = new RegExp(/(.*)\/[^\/]+$/).exec(window.location.pathname)[1];
}
require.config({
paths: {
app: root + '/app/assets/javascripts'
, vendor: root + '/vendor/assets/javascripts'
}
});

Test Twitter Flight components with Jasmine on Ruby on Rails

Twitter Flight is great framework for organizing frontend codebase. It make it more productive to work with and easier to test. However, it lacks in support for testing its modules. Moreover, there is not many guidance available on the web yet since it is fairly new.

This post explains how I setted up both in-browser and headless jasmine test for Twitter flight components used in Rails app.

Followings are used for the setup:

Some of above dependencies are not strictly required but the rest of this post assumes you have installed all of the above and you have written some flight components and jasmine specs for them.

When you run jasmine test, you will encounter first issue. Jasmine test completes without running any specs. This is because jasmine-gem starts running specs on window load and does not wait for requirejs's asynchronous module loading. To solve this problem, we need to make sure each spec runs after requirejs loaded modules. To do so, we need to make use of run and waitsFor functions provided by jasmine. This is a bit bulky to have in spec files so I wrote jasmine extension for testing flight. This extension provides describeComponent and prepareComponent functions. With the extension, your spec will look like:

describeComopnent('ui/button', function() {
  beforeEach(function() {
    var html = '<button>click</button>';
    prepareComponent(html);
  });
  
  describe("when the button is clicked", function() {
    it("triggers 'uiButtonClicked' event", function() {
      spyOnEvent(document, 'uiButtonClicked');
      this.component.click();
      expect('uiButtonClicked').toHaveBeenTriggeredOn(document);
    });
  });
});

Now jasmine should detect your specs, however, it is likely that requirejs will have trouble finding specified modules. There are two reasons for this. First, jasmine-gem is aware of asset pipeline only when sprocket directives are used. For instance, if a component needs to load app/assets/javascripts/ui/button.js and vendor/assets/javascripts/flight/lib/component.js, you might have defined your component as:

define(['flight/lib/component', 'ui/button'], function(defineComponent, uiButton) {
  ...
});

where jasmine fails to find flight/lib/comopennt and ui/button. To workaround this problem, I configured requirejs paths. For app specific files located in app/assets/javascripts, I prepended app to the module id and for third party files located in vendor/assets/javascripts, I prepended vendor. The requirejs configuration looks like:

require.config({
    paths: {
        app:    '/app/assets/javascripts'
      , vendor: '/vendor/assets/javascripts'
    }
});

and new flight component looks like:

define(['vendor/flight/lib/component', 'app/ui/button'], function(defineComponent, uiButton) {
  ...
});

Here, we used app and vendor for requirejs paths but you can customize the paths in any way you want. Now jasmine test should work properly on browser (You can find jasmine.yml below for a reference).

Unfortunately, you are likely to have one more issue need to be resolved to run jasmine spec in headless mode successfully. The issue is that file paths are slightly different when you run jasmine on-browser mode and headless mode because browser mode serves files via server while headless mode serves files directly from file system (unless you run it with server option). A workaround is to added a script to determine a path to the project folder and prepend it to requirejs paths config if it is run in headless mode:

var root = '';
if (typeof window.JHW !== 'undefined') {
  root = new RegExp(/(.*)\/[^\/]+$/).exec(window.location.pathname)[1];
}

require.config({
    paths: {
        app:    root + '/app/assets/javascripts'
      , vendor: root + '/vendor/assets/javascripts'
    }
});

Jasmine test should work on both browser and headless mode now!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment