Jasmine example – selectors and textboxes

2 Comments

Here’s a walk through of a Jasmine example for testing the functionality of the selector arrows show below.

Basically I want to test the jQuery selectors and button functionality. When I click the > button, I want to test that the contents of the ‘Default Trading Group’ box slide over to the ‘Available Trading Groups’ text box.

Step 1: Just get it working.

Before I Jasmine up any of my code, I find it useful just to get the UI working. I am not strong enough in Jasmine or Javascipt yet to TDD stuff like this out. So first pass through, I just hack enough to get the basic controls working, the jQuery selectors setup, and the basic functionality working.

Step 2: Define the model.

With the basic UI going, I was now ready to think about the design.

How did I want this thing to work?

I decided to start with a model (ala MVC). I wanted a model that I could talk to to get the state of any control I wanted on the page. I then wanted this model to be used by a controller (which would actually do this to the page).

Because I already got the page going, my model basically consisted of jQuery selectors to return things off the page. It looked something like this:

function UpdateTradingGroupModel() {
}


UpdateTradingGroupModel.prototype.defaultTradingGroupId = function() {
    return $('#TextDefaultTradingGroup').attr("defaultTradingGroupId");
};

UpdateTradingGroupModel.prototype.defaultTradingGroupName = function() {
    return $('#TextDefaultTradingGroup').val();
};

...

There’s more, but I think you get the gist of it. Anything I want to know the state of, I just talk to my model and it gives it to me.

And then here’s sampling of the controller:

function UpdateTradingGroupController(theModel) {
    this.model = theModel;
}

UpdateTradingGroupController.prototype.configureArrowButtons = function() {

    that = this; // important!

    $('#remove').click(function(){
        $('#SelectedTradingGroups option:selected').appendTo('#AvailableTradingGroups');
    });

    $('#add').click(function(){
        $('#AvailableTradingGroups option:selected').appendTo('#SelectedTradingGroups');
    });

...

    $('#save').click(function(){

        if (that.model.defaultTradingGroupIsEmpty()) {
            that.showWarningDialog();
        } else {
            that.updateTradingGroups(that.model);
        }
        
    });

};

UpdateTradingGroupController.prototype.updateTradingGroups = function(model) {

    params = model.saveParameters();

    $.ajax({
        type: "POST",
        traditional: true,
        url: getControllerAddress('User') + "/@Model.User.Id/UpdateTradingGroups2",
        data:params,
        dataType: 'json',
        success: function(result)
        {
            $('#SuccessMessage').html(result.message);
        }
    });
};

The controller basically sets up the buttons, and actually does the work (like saving any changes done to the page).

And I would add this functionality to the javascript header section of my webpage with something like this:

    var controller = new UpdateTradingGroupController(new UpdateTradingGroupModel());
    controller.configureArrowButtons();

Alright. With that out of the way, we can now look at some Jasmine tests.

Step 3: Create your Jasmine tests.

The first test I wanted to write verified that when a user clicked the > button, the text in the default trading group got removed and added to the available trading group.

That test looked something like this:

describe("UpdateTradingGroupSpec - When clicking the #removeDefault button", function(){

    var model;
    var controller;

    beforeEach(function() {

        setFixtures(
        '<input type="text" id="TextDefaultTradingGroup" defaulttradinggroupid="10" value="ctg1">' +
        '<input id="removeDefault" type="button" value=" &gt; ">' +
        '<select id="AvailableTradingGroups" multiple="multiple" name="AvailableTradingGroups"></select>');

        model = new UpdateTradingGroupModel();
        controller = new UpdateTradingGroupController(model);
        controller.configureArrowButtons();
        
    });

    it("should slide selected field from TextDefaultTradingGroup to available trading groups", function(){
        expect(model.defaultTradingGroupId()).toEqual('10');
        expect(model.availableTradingGroups()).toEqual([]);
        $('#removeDefault').click();
        expect(model.defaultTradingGroupId()).toEqual('');
        expect(model.availableTradingGroups()).toEqual(['10']);
    });

    it("should be able to determine if TextDefaultTradingGroup is empty", function(){
        expect(model.defaultTradingGroupIsEmpty()).toBeFalsy();
    });

});

The first thing to note is this setup fixture line:

        setFixtures(
        '<input type="text" id="TextDefaultTradingGroup" defaulttradinggroupid="10" value="ctg1">' +
        '<input id="removeDefault" type="button" value=" &gt; ">' +
        '<select id="AvailableTradingGroups" multiple="multiple" name="AvailableTradingGroups"></select>');

It’s beautiful because it enables us to take just the html we need from our page, and set it up however we want it. Just view source on your page, grab what you need and put it in here. You can also ‘loadFixture‘ which will load an entire html file and set that up as your test harnass if you like.

In this case I want the TextDefaultTradingGroup textbox to have an id and value of 10 and ctg1 respectfully (note this doesn’t match what’s on the screen), with a remove button, and select box called AvailableTradingGroups which is empty.

I can then configure the buttons on my page:

        model = new UpdateTradingGroupModel();
        controller = new UpdateTradingGroupController(model);
        controller.configureArrowButtons();

And always remember to include whatever javascript you use in your app in your jasmine test runner file (else it won’t find your javascript).

Now I am ready to test what happens when someone clicks the remove button.

By simply asking my model I can verify that things start like this:

        expect(model.defaultTradingGroupId()).toEqual('10');
        expect(model.availableTradingGroups()).toEqual([]);

Then they hit the removeDefault button

        $('#removeDefault').click();

And end up like this:

        expect(model.defaultTradingGroupId()).toEqual('');
        expect(model.availableTradingGroups()).toEqual(['10']);

It was a lot of working setting this up, but now that the tracks are laid, we can setup any configuration of buttons and values we want.

You could test every other button combination and configuration of data (we had bugs in there).

Or if you wanted to display a warning dialog in the event someone tries to save a blank default trading group you could write something like this:

describe("UpdateTradingGroupSpec - When clicking the #save button", function(){

    var model;
    var controller;

    beforeEach(function() {

        setFixtures(
        '<input type="text" id="TextDefaultTradingGroup" defaulttradinggroupid="10" value="ctg1">' +
        '<select id="AvailableTradingGroups" multiple="multiple" name="AvailableTradingGroups"></select>' +
        '<select id="SelectedTradingGroups" multiple="multiple" name="SelectedTradingGroups">' +
            '<option value="11">ctg2</option>' +
            '<option value="12">ctg3</option>' +
        '</select>' +
        '<input type="submit" id="save" value="Save">');

        model = new UpdateTradingGroupModel();
        controller = new UpdateTradingGroupController(model);
        controller.configureArrowButtons();

        spyOn(Application, 'rootDir').andReturn("/");
    });

     it ("should display a warning dialog if defaultTradingGroupIsEmpty", function() {
        spyOn(model, 'defaultTradingGroupIsEmpty').andReturn(true);
        spyOn(controller, 'showWarningDialog');
        $('#save').click();
        expect(model.defaultTradingGroupIsEmpty).toHaveBeenCalled();
        expect(controller.showWarningDialog).toHaveBeenCalled();
    });

    it ("should not display a warning dialog if TextDefaultTradingGroup has data", function() {
        spyOn(model, 'defaultTradingGroupIsEmpty').andReturn(false);
        spyOn(controller, 'showWarningDialog');
        $('#save').click();
        expect(model.defaultTradingGroupIsEmpty).toHaveBeenCalled();
        expect(controller.showWarningDialog).not.toHaveBeenCalled();
    });

    it ("should collect parameters for save", function() {
        expect(model.saveParameters()).toEqual({ defaultTradingGroupId : '10', selectedCompanyTradingGroups : [ '11', '12' ] });
    });

});

Or if I wanted to verify an ajax call was made to the backend I could write something like this:

    it ("should make an ajax call to do the save", function() {
        spyOn($, 'ajax').andReturn('Successful save.');
        controller.updateTradingGroups(model);
        expect($.ajax).toHaveBeenCalled();
    });

I’ve only just scratched the surf of what we can do with Jasmine. But I wanted to get a few examples up to show what it can look like.

One thing I still haven’t got a great grasp out yet is Javascript closures and use of ‘this‘ and ‘that‘.

If you are coming from an enterprise language background like C# or Java, just beware that object state and scope is different in Javascript which is why passing in a model to a controller in Javascript has lines like

UpdateTradingGroupController.prototype.configureArrowButtons = function() {

    that = this; // important!

I am still figuring it out and will write more once I do.

But give Jasmine a chance. It’s helped us reduce the number of bugs in our app and I am looking forward to writing many more.

If you are just starting out checkout Evan Hahn’s Jasmine tutorial. It’s easy peasy.

Git GitHub Heroku RubyOnRails How to

1 Comment

This is a github cheat sheet I created after going through Michael Hartl’s excellent getting start with git tutorial as part of his also excellent online RubyOnRails book.

For a more thorough walk through go here. For a quick fix read on.

Create new git repository

Assuming you’ve installed rails, git, created a github account, and have a rails app you want to put under version control, navigate to the root of your rails app and create a new git repository as follows:

$ git init

Then create an ignore file at the root (in this case using TextMate) and add the following

$ mate .gitignore

log/*.log
tmp/*
tmp/**/*
doc/api
doc/app
db/*.sqlite3
*.swp
*~
.DS_Store

Add your files to the repository recursively

$ git add .

Do your initial commit

$ git commit -m "Initial commit"

Create your new repository in github using the github UI.

Then push (save) your application to github.

$ git remote add origin git@github.com:<username>/first_app.git
$ git push origin master

There. Your repository is setup!

Branch, edit, commit, merge

Create a new branch

$ git checkout -b myChanges

Make some changes (in this case rename a file)

$ git mv README README2

If you’ve made a mistake, roll it back.

$ git checkout -f

Commit the change to your branch

$ git commit -a -m "Moved the README file"

Merge with master branch

$ git checkout master
$ git merge myChanges

Then push to git server

$ git push

Deploy Heroku

For details on Heroku setup go here.

To create an instance of your app on Heroku and deploy go:

$ heroku create
$ git push heroku master

Then push, deploy, and view your app regularly with:

$ git push
$ git push heroku
$ heroku open

Voila! You’re done!

Other handy git commands

status shows you what branch you are on in case you forget

$ git status
# On branch homepage
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)

log shows you a list of your commit messages

$ git log
commit 69197e9cb7c9b3bd8b8f53bf2bfb65419027c201
Author: foo <bar@gmail.com>
Date:   Tue Sep 7 05:41:20 2010 -0600

     Initial commit

branch shows you what branch you are currently on

$ git branch
  master
* tutorial

You can delete old two ways. The first deletes the branch after you’ve committed your changes. The second abandons the branch and ignores any changes:

$ git branch -d topic-branch
$ git branch -D topic-branch

%d bloggers like this: