Dr Nic

TDD for Greasemonkey scripts; and introducing Ninja Search JS

“this article shows how I used test-driven development tools and processes on a Greasemonkey script.” Though it also includes free ninjas.

1. Long drop downs hate humans

When I do online banking I need to select from a large list of other people’s bank accounts to which I might like to transfer money too. It is the massive drop down list that I must scroll through that I wish to raise issue with today. The problem of having to give other people money is probably a different discussion.

And take those time-zone selector drop down lists, for example, the massively long list rendered by Rails’ time_zone_select helper. Granted, I am thankful for you letting me choose my timezone in your web app. Though for those of us not living in the USA we must hunt for our closest city in the list. Dozens of locations, ordered by time zone and not the name of the city (see adjacent image). Unfortunately you can’t easily type a few letters of your current city to find it. Rather, you have to scroll. And if you live in the GMT+1000 time zone group (Eastern Australia), you have to scroll all the way to the bottom.

5. Choose from a small list

So I got to thinking I’d like a Greasemonkey (for Firefox) or GreaseKit (for Safari) script that automatically converted all ridiculously long HTML drop down lists into a sexy, autocompletion text field. You could then type in “bris” and be presented with “(GMT+1000) Brisbane”, or given the less amusing banking scenario then I could type “ATO” and get the bank account details for the Australian Tax Office.

I mean, how hard could it be?

This article is two things: an introduction to Ninja Search JS which gives a friendly ninja for every drop down field to solve the above problem. Mostly, the rest of this article shows how I used test-driven development tools and processes on a Greasemonkey script.

Introducing Ninja Search JS

Ninja Search JS banner

Click the banner to learn about and install the awesome Ninja Search JS. It includes free ninjas.

Currently it is a script for Greasemonkey (FireFox) or GreaseKit (Safari). It could be dynamically installed as necessary via a bookmarklet. I just haven’t done that yet. It could also be a FireFox extension so it didn’t have to fetch remote CSS and JS assets each time.

Ninja Search JS uses liquidmetal and jquery-flexselect projects created by Ryan McGeary.

Most importantly of all, I think, is that I wrote it all using TDD. That is, tests first. I don’t think this is an erroneous statement given the relatively ridiculous, and unimportant nature of Ninja Search JS itself.

TDD for Greasemonkey scripts

I love the simple idea of Greasemonkey scripts: run a script on a subset of all websites you visit. You can’t easily do this on desktop apps, which is why web apps are so awesome – its just HTML inside your browser, and with Greasemoney or browser extensions you can hook into that HTML, add your own DOM, remove DOM, add events etc.

But what stops me writing more of them is that once you cobble together a script, you push it out into the wild and then bug reports start coming back. Or feature requests, preferably. I’d now have a code base without any test coverage, so each new change is likely to break something else. Its also difficult to isolate bugs across different browsers, or in different environments (running Ninja Search JS in a page that used prototypejs originally failed), without a test suite.

And the best way to get yourself a test suite is to write it before you write the code itself. I believe this to be true because I know it sucks writing tests after I’ve writing the code.

I mostly focused on unit testing this script rather than integration testing. With integration testing I’d need to install the script into Greasemonkey, then display some HTML, then run the tests. I’ve no idea how’d I’d do that.

testing running

But I do know how to unit test JavaScript, and if I can get good coverage of the core libraries, then I should be able to slap the Greasemonkey specific code on top and do manual QA testing after that. The Greasemonkey specific code shouldn’t ever change much (it just loads up CSS and more JS code dynamically) so I feel ok about this approach.

For this project I used Screw.Unit for the first time (via a modified version of the blue-ridge rails plugin) and it was pretty sweet. Especially being able to run single tests or groups of tests in isolation.

Project structure

summary of project structure

All the JavaScript source – including dependent libraries such as jquery and jquery-flexselect – was put into the public folder. This is because I needed to be able to load the files into the browser without using file:// protocol (which was failing for me). So, I moved the entire project into my Sites folder, and added the project as a Passenger web app. I’m ahead of myself, but there is a reason I went with public for the JavaScript + assets folder.

In vendor/plugins, The blue-ridge rails plugin is a composite of several JavaScript libraries, including the test framework Screw.Unit, and a headless rake task to run all the tests without browser windows popping up everywhere. In my code base blue-ridge is slightly modified since my project doesn’t look like a rails app.

Our tests go in spec. In a Rails app using blue-ridge, they’d go in spec/javascripts, but since JavaScript is all we have in this project I’ve flattened the spec folder structure.

The website folder houses the github pages website (a git submodule to the gh-pages branch) and also the greasemonkey script and its runtime JavaScript, CSS, and ninja image assets.

A simple first test

For the Ninja Search JS I wanted to add the little ninja icon next to every <select> element on every page I ever visited. When the icon is clicked, it would convert the corresponding <select> element into a text field with fantastical autocompletion support.

For Screw.Unit, the first thing we need is a spec/ninja_search_spec.js file for the tests, and an HTML fixture file that will be loaded into the browser. The HTML file’s name must match to the corresponding test name, so it must be spec/fixtures/ninja_search.html.

For our first test we want the cute ninja icon to appear next to <select> drop downs.

require("spec_helper.js");
require("../public/ninja_search.js"); // relative to spec folder

Screw.Unit(function(){
  describe("inline activation button", function(){
    it("should display NinjaSearch image button", function(){
      var button = $('a.ninja_search_activation');
      expect(button.size()).to(be_gte, 1);
    });
  });
});

The Blue Ridge textmate bundle makes it really easy to create the describe (des) and it (it) blocks, and ex expands into a useful expects(...).to(matcher, ...) snippet.

The two ellipses are values that are compared by a matcher. Matchers are available via global names such as equals, be_gte (greater than or equal) etc. See the matchers.js file for the default available matchers.

The HTML fixture file is important in that it includes the sample HTML upon which the tests are executed.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
  <title>Ninja Search | JavaScript Testing Results</title>
  <link rel="stylesheet" href="screw.css" type="text/css" charset="utf-8" />
  <script src="../../vendor/plugins/blue-ridge/lib/blue-ridge.js"></script>
</head>

<body>
  <div>
    <label for="person_user_time_zone_id">Main drop down for tests</label>
    <select name="person[user][time_zone_id]" id="person_user_time_zone_id" style="display: inline;">
      <option value="Hawaii">(GMT-10:00) Hawaii</option>
      <option value="Alaska">(GMT-09:00) Alaska</option>
      <option value="Pacific Time (US & Canada)">(GMT-08:00) Pacific Time (US & Canada)</option>
      <option value="Arizona">(GMT-07:00) Arizona</option>
      <option value="Mountain Time (US & Canada)">(GMT-07:00) Mountain Time (US & Canada)</option>
      <option value="Central Time (US & Canada)">(GMT-06:00) Central Time (US & Canada)</option>
      <option value="Eastern Time (US & Canada)">(GMT-05:00) Eastern Time (US & Canada)</option>
    </select>
  </div>
</body>
</html>

In its header it loads the blue-ridge JavaScript library, which in turn loads Screw.Unit and ultimately our spec.js test file (based on corresponding file name), so ninja_search.html will cause a file spec/ninja_search_spec.js to be loaded.

To run our first test just load up the spec/fixtures/ninja_search.html file into your browser.

Your first test will fail. But that’s ok, that’s the point of TDD. Red, green, refactor.

Simple passing code

So now we need some code to make the test pass.

Create a file public/ninja_search.js and something like the following should work:

(function($){
  $(function() {
    $('select').each(function(index) {
      var id = $(this).attr('id');

      // create the Ninja Search button, with rel attribute referencing corresponding >select id="...">
      $('> class="ninja_search_activation" rel="' + id + '">ninja search>/a>')
      .insertAfter($(this));
    });
  });
})(jQuery);

Reload your test fixtures HTML file and the test should pass.

Now rinse and repeat. The final suite of tests and fixture files for Ninja Search JS are on github.

Building a Greasemonkey script

Typically Greasemonkey scripts are all-inclusive affairs. One JavaScript file, named my_script.user.js, typically does the trick.

I decided I wanted a thin Greasemonkey script that would dynamically load my ninja-search.js, and any stylesheets and dependent libraries. This would allow people to install the thin Greasemonkey script once, and I can deploy new versions of the actual code base over time without them having to re-install anything.

Ultimately in production, the stylesheets, images, and JavaScript code would be hosted on the intertubes somewhere. Though during development that would be long-winded and painful to push the code to a remote host just to run tests.

So I have three Greasemonkey scripts:

  • public/ninja_search.dev.user.js – loads each dependent library and asset from the local file system
  • public/ninja_search.local.user.js – loads compressed library and asset from the local file system
  • public/ninja_search.user.js – loads compressed library and assets from remote server

Let’s ignore the optimisation of compressing dependent JavaScript libraries for the moment and just look at the dev.user.js and user.js files.

The two scripts differ in the target host from which they load assets and libraries. ninja_search.dev.user.js loads them from the local machine and ninja_search.user.js loads them from a remote server.

For example ninja_search.dev.user.js loads local dependencies like this:

require("http://ninja-search-js.local/jquery.js");
require("http://ninja-search-js.local/ninja_search.js");

And ninja_search.user.js loads remote dependencies like this:

require("http://drnic.github.com/ninja-search-js/dist/jquery.js");
require("http://drnic.github.com/ninja-search-js/dist/ninja_search.js");

In the final version of ninja_search.user.js we load a simple, conpressed library containing jquery, our code, and other dependencies, called ninja_search_complete.js.

Using Passenger to server local libraries

The problem with loading local JavaScript libraries using the file:// protocol, inferred earlier, is that it doesn’t work. So if I can’t load libraries using file:// then I must use the http:// protocol. That means I must route the requests through Apache/Ningx.

Fortunately there is a very simple solution: use Phusion Passenger which serves a “web app’s” public folder automatically. That’s why all the javascript, CSS and image assets have been placed in a folder public instead of src or lib or javascript.

On my OS X machine, I moved the repository folder into my Sites folder and wired up the folder as a Passenger web app using PassengerPane. It took 2 minutes and now I had http://ninja-search.local as a valid base URL to serve my JavaScript libraries to my Greasemonkey script.

Testing the Greasemonkey scripts

I can only have one of the three Greasemonkey scripts installed at a time, so I install the ninja-search.dev.user.js file to check that everything is basically working inside a browser on interesting, foreign sites (outside of the unit test HTML pages).

Once I’ve deployed the JavaScript files and assets to the remote server I can then install the ninja-search.user.js file (so can you) and double check that I haven’t screwed anything up.

Deploying via GitHub Pages

The normal, community place to upload and share Greasemonkey scripts is userscripts.org. This is great for one file scripts, though if your script includes CSS and image assets, let alone additional JavaScript libraries, then I don’t think its as helpful, which is a pity.

So I decided to deploy the ninja-search-js files into the project’s own GitHub Pages site.

After creating the GitHub Pages site using Pages Generator, I then pulled down the gh-pages branch, and then linked (via submodules) that branch into my master branch as website folder.

Something like:


git checkout origin/gh-pages -b gh-pages
git checkout master
git submodule add -b gh-pages git@github.com:drnic/ninja-search-js.git website

Now I can access the gh-pages branch from my master branch (where the code is).

Then to deploy our Greasemonkey script we just copy over all the public files into website/dist, and then commit and push the changes to the gh-pages branch.


mkdir -p website/dist
cp -R public/* website/dist/
cd website
git commit -a "latest script release"
git push origin gh-pages
cd ..

Then you wait very patiently for GitHub to deploy your latest website, which now contains your Greasemonkey script (dist/ninja-search.user.js) and all the libraries (our actual code), stylesheets and images.

Summary

Greasemonkey scripts might seem like small little chunks of code. But all code starts small and grows. At some stage you’ll wish you had some test coverage. And later you’ll hate yourself for ever having release the bloody thing in the first place.

I wrote all this up to summarise how I’d done TDD for the Ninja Search JS project, which is slightly different from how I added test cases to _why’s the octocat’s pajamas greasemonkey script when I first started hacking with unit testing Greasemonkey scripts. The next one will probably be slightly different again.

I feel good about the current project structure, I liked Screw.Unit and blue-ridge, and I’m amused by my use of GitHub Pages to deploy the application itself.

If anyone has any ideas on how this could be improved, or done radically differently, I’d love to hear them!

Polish your Rails project with Mocra

I want to help you, your business, your boss and your project reach delightful levels of wickedly awesomeness. I’m so proud of the small team of ace Rails developers here at Mocra and what I know we can do for you.

Send an email to rails@mocra.com about your current/future projects. Dare us to be more awesome!

While you wait for a reply perhaps you’d like to learn more about How we do it at Mocra?

Closing in on The Dream: “one-click-to-deploy Rails apps”

Got a simple app you want to build? Allocate 5 minutes for initial code generation, slice setup, and initial deployment. All from the command line. In one command. Booya!

How long does it take to start a new Rails project? Surely just a moment? rails new_project -m path/to/some/template.rb

But your application isn’t deployed yet. The DNS isn’t ready, the remote slice doesn’t exist or the config for this new application isn’t setup. Heck, the code hasn’t even been pushed to a non-existent remote repository yet.

And what if you’re going to use something like twitter_auth for authentication? You’ll need to register your application with Twitter at http://twitter.com/oauth_clients.

All these things could be automated, surely. Surely?

If they were then you’d have a “one click” command. A new Rails app, pushed into production, and ready to rock and roll. Complete with either restful_authentication or twitter-based oauth integration.

What does a “one click” rails and deploy command look like?

cd Sites
rails -m rails-templates/mocra.rb default-twitter-auth-app

1. restful_authentication
2. twitter_auth
Which user authentication system? 2

1. mocra-primary
2. mocra-secondary
3. crazy-pron-sites
Install http://default-twitter-auth-app.mocra.com application on which slice? 3^H1

1. drnic
2. mocra
Which twitter user?  1

Then you wait 3 minutes and 53 seconds.

Then you visit http://default-twitter-auth-app.mocra.com and it is working. You click the “Protected” link and you are redirected to Twitter to click the “Allow” link. You return to the app. You are registered with an account and logged in. You see your own face. You rock.

The rest of the article shows you how to test run it yourself, explain the dependencies and how to install them, and how to unit test your own templates to do similarly fancy things. Hopefully its helpful.

WAITING A MINUTE – is this tutorial destructive to my precious slices?

It is safe. IF you have an existing slicehost slice that was created with the latest deprec gem.

Perhaps backup your slice anyway. But it should be safe.

Deprec installs each application in its own folder and the apache settings are in their own file etc. But when deprec installs apache, passenger, etc it may put them in places you aren’t expecting. It might not. I just can’t promise anything.

Testimonial that the tutorial works

Ryan Bigg offers the following testimonial to encourage you to actually try out the tutorial:

“I have, dear readers, with as little effort as a few keystrokes, an an application base that allows my users to sign up using their twitter credentials. I have my code base here on my machine, and the running application in production over there at http://ryan.mocra.com” Ryan Bigg (radar)

Required gems for the template

There are a couple of one-time-only steps to run to install some gems and setup github and slicehost API keys locally.

gem install highline
gem install deprec
gem install defunkt-github --source http://gems.github.com
gem install booster-slicehost-tools --source=http://gems.github.com

To setup the github gem with your API key:

* login to [http://github.com/](http://github.com/)
* click [account](https://github.com/account)
* click "Global Git Config"
* copy and paste the two lines of config into the terminal to install the config

The github gem will now use this configuration automatically.

To setup the slicehost gem with your API key:

slicehost-slice list
Please enter your API key since you did not provide one:

To get your slicehost API:

Required gems and steps for using twitter_auth

My fork of the twitter gem includes a twitter register_oauth ... command. If John integrates the code, or writes his own, I’ll drop my fork and rewrite my template to use his gem. Til then use this one.

gem install drnic-twitter --source=http://gems.github.com
twitter install
twitter add

And enter your twitter account details. You can run the last command any number of times to add personal and corporate/product twitter accounts. I am really impressed with the internals of this gem – it stores your data in a sqlite3 gem and uses ActiveRecord models to retrieve it. Might create a generator for this in newgem. I also liked the use of the main gem for its command definition. Anyway, we’re off the topic.

Cloning and running the rails-templates

Imagine the above steps were “Buy an Xbox 360. Buy Guitar Hero.” Now its the final step. It’s time to rock.

cd ~/Sites
git clone git://github.com/drnic/rails-templates.git
DOMAIN=yourdomain.com ORGANIZATION="Your Company of Legends" rails -m rails-templates/mocra.rb my_app

A few minutes later you can open http://my-app.yourdomain.com in a browser. It will have restful_authentication or twitter oauth integration all setup and working (except email settings for restful_authentication).

It makes me very happy watching it work.

If the above command has stalled after printing ‘executing slicehost-slice list’ then you haven’t set up your slicehost API key. See the instructions above.

Dirty, nasty assumptions?

How can you deploy an entire app and have it up and running without some more configuration? Surely… surely I’ve made some nasty assumptions and come up with some dirty defaults?

Yes.

Your application url will be my-app-name.mocra.com. The subdomain is a dasherized version of your application’s folder name. Use DOMAIN=mycompany.com to change the domain.

Your local and remote database is mysql, accessible with user root and no password. I… look… you see there was this dog… and he ate my homework… it wasn’t me… there was an earthquate, a volcano, a flood… it wasn’t me!

You are deploying to slicehost. I do. It has command-line applications to manage slices and DNS. Since I’m deploying to a subdomain of mocra.com I use slicehost to create a CNAME in the DNS.

Your target slice already exists and has been built using the latest deprec. The template let’s you select an existing slicehost slice to use. If you don’t have one that was built with deprec, perhaps create a new one.

Gems? Plugins? Yes this template installs the ones that I want. That’s the point of rails templates – you create your own set of defaults.

What do I do to get my own uber-template?

Copy + paste the mocra.rb template and hack in your configuration. Fork the github project and push up your file so others can see your awesomeness.

See the section on unit testing templates too.

What’s missing?

The primary thing that I want that I haven’t gotten around to writing/fixing/finding a solution is the creation of private, company github projects. That is, instead of public/open-source, personal projects using github create-from-local.

I guess I would want a github create-from-local --private flag to create a private repo instead of a public repo.

Then I’d want the github gem to know that I live in a world of multiple github accounts: my personal account (drnic) and my company’s account (mocra). That is, I’ll want new private company projects to go on the company account.

And then I’ll want it to add me (drnic) as a collaborator. Or a whole group of people.

Since the github gem currently derives its user + API token information from your global git config (git config --get github.user), instead of a nice external sqlite3 database like the twitter gem, I’m not sure what the best/correct data structure would be to add multiple user support to the github gem.

Or perhaps the github create-from-local mechanism should be extracted out of the github gem all together into a github-admin gem which would have multiple users, create public/private repos, add collaborators etc. Yeah, that might be better.

Want a default application theme?

The template will attempt to invoke a generator app_layout if it can find it. Watch the railscast on generators for an example of how to create a local generator and he gives an example of creating a default application theme generator. That’s why all the railscasts applications look the same!

Bonus section: unit testing a Rails template

If you look in the rails-templates project you cloned you’ll see a spec folder. There is a mock template_runner.rb which is used by the spec/mocra/template_spec.rb. This way I can stub out calls to command-line tools like slicehost-slice list and twitter register_oauth and check that the template installs the correct plugins, creates the correct files, etc without actually installing or creating anything.

More importantly its a lot bloody faster to run than the full template.

If you’re creating Rails templates with interesting logic in them then writing some unit tests for your template might be a helpful idea to save time.

RailsCamp.au #5 – book now

Imagine yourself and a posse of like-minded Ruby hackers on a country retreat with zero internet for a weekend of fun. You’ll laugh, hack, learn, cry (well, you probably won’t cry… but you know… it felt poetic) and most likely play a crap-load of guitar hero. [manifesto]

In chronological order, the first RailsCamps in Australia were in the states: New South Wales, Victoria, New South Wales, and South Australia.

This time, between May 15th and 18th, it’s in Queensland. God’s Country. The Sunshine State. Home of Steve Irwin (deceased), Sir Joh Bjelke-Petersen (deceased), Greg Norman (expat), Keith Urban (expat), and Kristy Hinze (marrying ex-Silicon Graphics/ex-Netscape billionaire Jim Clark; expat). So its a pretty famous and popular place to come from.

More Mexican (by Lachlan Hardy)

Book your tickets now for RailsCamp #5.

(more…)

Migrating project websites to github pages with sake tasks, new websites with jekyll_generator

Its almost Christmas time and that means presents.

It also means that sometime between today and December 25th you need to go out and buy other people some presents because you haven’t done it yet.

But there’s someone else special in your life that deserves an Xmas gift this year. That special someone is your open source projects.

You might think, “But, Dr Nic, what do I get for the open source project that already has everything? What gift would my open source projects possible appreciate?”

Its true – this year has been a boon for open source. You probably migrated your projects from SVN to Git this year. Specifically, you probably clicked the “create a new one” link on your GitHub home page a lot this year: either to migrate your old projects to github or start new ones. Lots of new ones.

And now that your code is on github, your README file is rendered beautifully on the home page, so you were more inclined to rename it to README.markdown or README.textile and thusforthly fill it full of education information about your projects.

More people would then instantaneously figure out what the f@#$ your project actually does, thus more people used it, thus more people wrote patches or forked your code and sent Pull Requests.

All round, in 2008, I bet your open source projects had a sweet year.

Nonetheless, it is Christmas time and you should now get them a present. Something they wouldn’t get for themselves. Something you wouldn’t have done for them except for the free loving spirit of Christmas.

The Gift for all Open Source projects

A website.

Oh sure, github renders your README.markdown file. Yeah, yeah, github gives you a wiki for your project. Sure, sure, Google Groups let you communicate with your co-developers and users. Certainly, you don’t need a website for your project.

That’s why its a Christmas gift. You’re going to do it because you care.

Websites sell your project. READMEs and Wikis educate. The project website will sell it and make people want to use your stuff.

A website could have a blog with an RSS/atom feed. Then you could post updates about your project. People could subscribe and also leave comments. Oh the novelty.

And if it only took 5 minutes to get all this setup – the website code, the blog engine, the RSS feed, the comments, and published to its own hosted server – then that would just be oh so sweet.

So we’re going to do some craft for our Xmas present. A little DIY project, if you will. You’ll need a few things that you’ll find around your house, a command-line interface, and a beer or perhaps some port or sherry. Christmas cake is good too.

(more…)

To WebKit or not to WebKit within your iPhone app?

Oakley's Surf Report

I know HTML. Its on my CV. Expert level. I also know CSS and a whole bunch of JavaScript. I can even do TDD with JavaScript.

And on the iPhone there is this nifty object called UIWebView. Otherwise known as WebKit. Otherwise known as an embedded browser in your iPhone app.

And if you want a sexily awesome looking UI view, like the Today view of the Surf Report app (see right or free on AppStore) that was released on the AppStore recently, then the WebKit is just the best thing since the electric bread slicer for speed of development.

Holy grail of iPhone development?

Well, that’s what we thought. When I chatted with Dan Grigsby last week I mentioned there were good and bad things about the WebKit within an iPhone app.

This article is about good and bad things. The pros and cons. How we managed the integration of the two code-bases. And the answer to the big question: Would we do it again?

Its probably wonderfully useful stuff to know.

(more…)