Jesse Cravens

Building Modern Web Applications and Teams that Deliver

Just Moved HTML5 Hacks Into Copy/Edit

After a marathon of hackery starting in April, Jeff Burtoft and I pushed out 90 hacks and 435 pages of HTML5 Hacks this week. We also had a few guest hackers that created some really great content as well.

  • Raymond Camden of Adobe
  • Phil Legetter of Pusher.com
  • Alexander Schulze of jWebsocket
  • John Dyer of Dallas Theological Seminary
  • Alex Sirota of FoxyTunes

Overall, it was a great experience. What I enjoyed the most was getting into a workflow groove, much like when you are building an API or contributing to a project. With each hack, it was necessary to ideate, plan, create the code, test it, and then put it into a understandable format. By the end, I was feeling pretty efficient at kicking out interesting hacks pretty quickly.

I felt as though I improved over time, looking back is always interesting. I like to keep fine tuning and revising, but at some point you have to ship the product. I’m really proud of the work, it has been hard, but I learned a ton in the process.

Most of my hacks I tried to make relevant to real world software development challenges, and mix in a number of HTML5 specifications. Hopefully, that turns out to benefit the reader … I’m all ears on the feedback. So don’t hesitate to drop us a line.

Migration From Drupal to Octopress

Why?

For the last 6 years, I have hosted my personal blog using Drupal at bluehost.com, and I finally have made the decision to migrate.

I have been using Ruby on Rails, Sinatra, and Node.js for prototypes, demos, web-based presentations, and production apps, and deploying to Heroku for the past couple of years. The productivity gains and integration with github and social media, have brought me to the point that I’d prefer for my workflow for blogging, updating to my microformats resume and portfolios to be similar to how I create and manage my other content.

So here, it is. Once I have written a proper migration script, and taken care of all the custom urls, I’ll post my solution here.

JavaScript Modules

For a talk last March at USAA’s internal Tech X conference, I put together a demo of JavaScript script loaders, module definitions, dependency management, and polyfill support: JavaScript Modules.

This demonstrates the following Module Definitions:

  • AMD
  • CommonJS
  • YUI3
  • JS Harmony

And a few script loaders:

  • RequireJS
  • yepnope
  • YUI3 - YUI Loader

If you want to know more about the state of modules, dependency loading and management in the JavaScript world, there is always room for more contributors, just fork or clone the project … and you will have a playground to begin playing with some of the most popular implementations.

To learn more, check it out here: jessecravens.github.com/js-modules-demo

Robodeck

Awhile back, I built a websocket powered interactive presentation framework called Robodeck.

The solution uses the following technologies:

  • Node.js
  • Socket.io
  • WebSockets
  • GeoLocation APIs
  • Deck.js

To name a few.

To learn more, check it out here: http://jessecravens.github.com/robodeck

Using Stylus’ Transparent Mixins to Hack Vendor-Specific Properties

One of the most interesting features of Stylus is transparent Mixins. One reason they are interesting is that at the time of this writing, this feature is exclusive to Stylus, other CSS metalanguages like Less and Sass do not offer Transparent Mixin support.

We already defined a mixin earlier, when we explored mixins with Jade. Here they are used similarly.

Before we begin with the transparent aspect of Stylus mixin support, let’s build a simple CSS mixin. Perhaps, the best example, as provided by TJ Holowaychuk in this beginner screencast uses vendor-specific prefixes.

How about we take his example and expand it to a slightly more challenging scenario.

In case you haven’t come across this challenge before, lets set some context. Browser makers, or vendors, implement proprietary extensions to standard CSS specifications to release and test browser features that have been developed pre ‘Candidate Recommendation’ W3C draft status. Although vendor-specific prefixes can be frustrating for web developers, they are a necessary evil, allowing new properties to be widely tested before they become available as standard CSS properties.

Each Vendor maintains a list of their proprietary CSS properties. The following table provides the extension prefixes for all the modern browsers:

Extension Rendering Engine Browser(s) Example
-moz- Camino Mozilla Firefox -moz-border-radius
-ms- Trident Internet Explorer -ms-layout-grid
-o- Presto Opera -o-border-radius
-webkit- Webkit Chrome, Safari -webkit-border-radius

And here is a few valuable resources for finding these properties:

Without a metalanguage that provides logic to CSS, these declarations can become cumbersome and repetitive. Here is an example of a div element that needs to have corners take on different size radiuses:

First, the markup in Jade.

example.html
1
2
3
4
5
6
7
header
  h1= title
  p Welcome to #{title}

div.panel panel test
div.mixin-panel mixin-panel test
div.t-mixin-panel transparent-mixin-panel test

Then, the styles in basic CSS.

example.css
1
2
3
4
5
div.panel {
  -moz-border-radius: 10px 5px;
  -webkit-border-radius: 10px 5px;
  border-radius: 10px 5px;
}

This produces the following effect:

With that context in mind, let’s leverage Stylus to help us manage our code. First, we are able to remove all parenthesis, semi-colons, and colons.

example2.css
1
2
3
4
div.panel
  -moz-border-radius 10px 5px
  -webkit-border-radius 10px 5px
  border-radius 10px 5px

Much like Jade, this simplified syntax goes along way in our ability to quickly write CSS. Now, let’s apply some logic, by creating a border-radius mixin, and pass our values as arguments to our mixin:

example3.css
1
2
3
4
5
6
7
my-border-radius-mixin (...args)
  -moz-border-radius args
  -webkit-border-radius args
  border-radius args

div.panel
  my-border-radius-mixin (10px 5px)

And we can reuse this mixin in other contexts, which continues to help reduce code bloat and keep your code DRY.

example4.css
1
2
3
4
5
6
7
8
9
10
11
12
13
my-border-radius-mixin (...args)
  -moz-border-radius args
  -webkit-border-radius args
  border-radius args

div.panel
  border-radius-mixin (10px 5px)

div.other-panel
  border-radius-mixin (3px 32px 100px 55px)

div.another-panel
  my-border-radius-mixin (5px 10px 33px 43px)

While Stylus allows us to name our mixin as we choose, we are also given the ability to apply mixins ‘transparently.’ In the case of a simple implementation of border-radius, a transparent mixin would make a lot of sense. In effect, this normalizes browser implementations and provides an abstraction layer for CSS developers.

example5.css
1
2
3
4
5
6
7
border-radius (...args)
  -moz-border-radius args
  -webkit-border-radius args
  border-radius args

div.panel
  border-radius 10px 5px

As long as a border-radius mixin is present, Stylus will find it, and apply the vendor-specific properties transparently.

Web Wokers: Basics of the Web Browser’s UI Thread

Single Threadin’

As we set out to build a highly responsive UI for our demo web application, we must fully understand how browsers manage processes. Perhaps the biggest challenge we will face has to do with browsers using a single thread for both JavaScript execution and user interface updates. While the browser’s JavaScript interpreter is executing code, the UI is unable to respond to the user’s input. If a script is taking a long time to complete, after a specified amount of time the browser will provide the user an option to terminate the script. To accommodate for the ‘freeze’ associated with scripts that exceed the browser execution time limit, web developers have traditionally created smaller units of work and used JavaScript timers to return execution to the next event in the queue. As you will see, web workers solve the locking of the UI thread by opening an additional thread, or even multiple threads, for execution of these long running, processor intensive tasks.

When designing your application, especially if you come from more of a ‘server-side’ or Java background, it is important to understand that non-blocking execution is not the same as concurrent threading. While not extremely complex, JavaScript’s event driven style does take some getting used to for developers coming from other languages such as Java and C. Later, we will touch on a few examples where we pass a callback continuation function to take full advantage of JavaScript’s non blocking design.

Thread Safety

Mozilla, in particular, provides a Worker interface which web workers implement. While the Worker interface spawns OS-level threads, web workers use the postMessage mechanism to send messages (with serializable objects) between the two execution contexts. To ensure thread safety the worker is only given access to thread safe components to include the following:

  • timers: setTimeout() and setInterval() methods
  • XMLHttpRequest
  • importScripts() method

The worker script can also make use of:

  • navigator and location objects
  • native JavaScript objects such as Object, String, Date

At the same time, the worker restricts access to DOM APIs, global variables, and the parent page. In Hack #84 Building the DOM with web workers and Handlebars.js, we will explore the restricted access to DOM APIs, and introduce JavaScript templating, importScripts, and the use of timers to poll for postMessage.

HTML5 Web Workers

As mentioned earlier, the Web worker spec defines an API for executing scripts in the background by spawning an independent execution context.

It is important to note that web workers are costly, having an impact on startup and overall memory consumption. So, they are intended to be used lightly and in conjunction with the some of the asynchronous techniques mentioned earlier. A well built client-side application would typically make use of one or two cases where tasks are expensive. Here are a few common uses cases:

  • Single Page Application bootstrapping large amounts of data during initial load
  • Performing long running mathematical calculations in the browser
  • Processing large JSON datasets
  • Text formatting, spell checking, and syntax highlighting
  • Processing multimedia data (Audio/Video)
  • Long polling webservices
  • Filtering images in canvas
  • Calculating points for a 3D image
  • Reading/Writing of local storage database

Long Running Scripts

Our first web worker hack will be a long running script with heavy computation. It will execute 2 loops that output a two-dimensional array. First, we will use this computation to lock up the browser’s UI thread, and later we will move the task to a worker.

init.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

  function init() {
    var r = 1000;
    var c = 1000;
    var a = new Array(r);

    for (var i = 0; i < r; i++) {
        a[i] = new Array(c);

        for (var j = 0; j < c; j++) {
            a[i][j] = "[" + i + "," + j + "]";
        }
    }
  }

  window.onload = init;

Spawning a Worker

Now let’s move our heavy computational task to a dedicated web worker, so that the user doesn’t have to wait for the script to complete execution in order to interact with user interface. First, lets spawn a new worker:

spawn.js
1
2
3
4
5
6

  var worker = new Worker('highComp.js');

  worker.postMessage(JSON.stringify(message));

  worker.addEventListener('message', function(event){}, false);

Here, we define an external file that will contain the logic of our heavy computational task. The file, highComp.js will listen for a message that will receive the serialized JSON payload, and then we will set up an additional listener to receive a message back from highComp.js.

Now, we can move this cpu-intensive task to a separate file: highComp.js

highComp.js
1
2
3
4
5
6
7
8
9
10
11
12
13
var r = 1000;
var c = 1000;

var a = new Array(r);

for (var i = 0; i < r; i++) {
  a[i] = new Array(c);

    for (var j = 0; j < c; j++) {
     a[i][j] = "[" + i + "," + j + "]";
    }
};
postMessage(a);

In highComp.js, our two dimensional array is built and set to variable a. It is then passed back to our main script via the postMessage call.

In the next hack, we will mix our use of timers with the power of a dedicated worker. As we send messages (passing serializable objects as a parameter to postMessage) back and forth to code running in the shared UI thread, our timer will periodically send and check for new messages and use their contents to modify the DOM.

RVM (Ruby Version Manager) and Ruby on Rails

Here is a brief overview of Ruby Version Manager and some explanation as to why you would want to use it. I began to use RVM when I started my first Rails 3 project. It was a bit confusing at first, but now I couldn’t live without it. I have created numerous gemsets for various configurations to include different versions of Ruby (1.87 and 1.91), versions of Rails (2.3, 3.09, 3.1) and different projects that have vastly different gems such as different testing frameworks, different JavaScript libraries, and different ORMs.

This allows me to essentially sandbox each of these applications’ dependencies.

One helpful hint I might offer is to get in the habit of declaring your gemset when you launch a new terminal. I tend to have multiple terminals open at once and it took me awhile to remember that each time I launched a terminal, RVM would fall back to my default gemset. So if I clone a Rails 3.1 project, I need to remember to switch to my rails3.1 gemset prior to runninig bundle install.

Simply put, RVM helps:

  • manage versions of Ruby
  • manage packages of Gemsets

RUBY

1
2
$ rvm list
$ rvm install 1.9.2-head

And, you can set a version as default

1
$ rvm use 1.9.2-head --default

GEMSETS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Start by creating our gemset(s):

$ 
rvm gemset create rails309

# Or create multiple at a time:

$ 
rvm gemset create rails307 rails31

# The result can be verified by listing the available gemsets:

$
 rvm gemset list

# See everything with list_all, this has been very useful:

$ rvm gemset list_all

# If a gem’s name still leaves room for confusion, simply delete it and create a more meaningful one (e.g., rails31rc):

$ 
rvm gemset delete rails31

Now that we have multiple gemsets installed, we must first select the one we want to use, and we can also set it as the default gemset by passing it the —default flag: 


1
$ rvm use 1.9.2-head@rails309 --default

Installing Rails

Installing rails is as easy as installing any other gem: we only need to specify it’s name, but we can always choose a specific version, or to speed up the installation process by skipping the documentation:

1
2
3
4
5
$ gem install rails --no-rdoc --no-ri
# Or
$ gem install rails [-v 3.0.7] [--no-rdoc --no-ri]
# Or
$ gem install rails -v ">=3.1.0rc"

In Summary and Why Am I doing this?

Rails is distributed as a gem, and there are conflicts between Rails 2 and Rails 3, so if you want to run multiple versions of Rails on the same system you need to create a separate gemset for each:

1
2
$ rvm --create 1.8.7-p302@rails2app
$ rvm --create use 1.9.2@rails3app

In other words, for application specific gemsets it is convenient to select the version of Ruby and the collection of gems by doing the following:

1
2
3
4
5
6
$ rvm --create use 1.9.2@mongoid-app

# Also, which gemset am I using?
$ rvm gemset name

$ rvm gemdir

Git Cheatsheet

I made the switch from Subversion to Git awhile back, and early on I created a cheatsheet pulled from various sources on the web. I thought I’d share.

Git First-Time System Setup

After installing Git, you should perform a set of one-time setup steps. These are system setups, meaning you only have to do them once per computer:

1
2
3
4
$ sudo apt-get install git-core
$ git config --global user.email youremail[at symbol]example.com
$ git config --global user.name "Your Name"
$ git config --global alias.co checkout

As a final setup step, you can optionally set the editor Git will use for commit messages.

1
2
3
$ git config --global core.editor "mate -w"

# Replace “mate -w” with “gvim -f” for gVim or “mvim -f” for MacVim.

Quick Reference – Most Often Used Commands

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ cd /path/to/repository
$ git init
$ git add .
$ git add -u
$ git log
$ git status
$ git commit -m "initial commit"

# made a mistake on the git commit
$ git commit -amend -m "initial commit"

# Add the remote repository

# ex 1
$ git remote add unfuddle git@subdomain.unfuddle.com:subdomain/abbreviation.git

# ex 2
$ git remote add origin git@subdomain.unfuddle.com:subdomain/abbreviation.git

# Configure the repository
$ git config remote.unfuddle.push refs/heads/master:refs/heads/master

# Push master branch to remote repository named unfuddle
$ git push unfuddle master

# Other commands:

# Clone an existing remote repo 
$ git clone git@subdomain.unfuddle.com:subdomain/abbreviation.git

# List all branches within your repo
$ git branch -a

#Create and switch to a new branch "whatever"
$ git checkout -b whatever

Those are the basics, should be enough to make you dangerous.

YUI3 Rails Application Template

I decided to put together a Rails template to generate a quick sqlite3 db driven web app to test out YUI3 functionality quickly. Rails 3 makes it super simple to quickly generate real JSON data for testing out various YUI 3 components such as DataSource.

The template takes care of removing the Prototype library, including yui-debug.js and the CSS framework including reset, and the new Grids – currently in beta. I also wanted to deliver my basic markup quickly so I’ve added Haml

Since I have been disciplining myself to follow Test Driven Development on the server, I included all of my Rails testing dependencies in the app template as well (RSpec, Cucumber, WebRat, Factory Girl) and I plan to continue to leverage YUI Test on the client as I run through browser validations of the datasource-polling sub-module. Clone the rails app template here.

yui-rails.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# YUI3 Application Generator Template
# Generates a Rails app; includes YUI3, Haml, RSpec, Cucumber, WebRat, Factory Girl ...

puts "Generating a new YUI3 Rails app"

#----------------------------------------------------------------------------
# Create the database
#----------------------------------------------------------------------------
puts "creating the database..."
run 'rake db:create:all'

#----------------------------------------------------------------------------
# GIT
#----------------------------------------------------------------------------
puts "setting up 'git'"

append_file '.gitignore' do <<-FILE
'.DS_Store'
'.rvmrc'
FILE
end
git :init
git :add => '.'
git :commit => "-m 'Initial Commit of YUI3 Rails App'"

#----------------------------------------------------------------------------
# Remove files
#----------------------------------------------------------------------------
puts "removing files..."
run 'rm public/index.html'
run 'rm public/favicon.ico'
run 'rm public/images/rails.png'
run 'rm README'
run 'touch README'

puts "banning spiders from your site by changing robots.txt..."
gsub_file 'public/robots.txt', /# User-Agent/, 'User-Agent'
gsub_file 'public/robots.txt', /# Disallow/, 'Disallow'

#----------------------------------------------------------------------------
# Haml 
#----------------------------------------------------------------------------
  puts "setting up Gemfile for Haml..."
  append_file 'Gemfile', "\n# Bundle gems needed for Haml\n"
  gem 'haml', '3.0.18'
  gem 'haml-rails', '0.2', :group => :development

#----------------------------------------------------------------------------
# Set up YUI3
#----------------------------------------------------------------------------

puts "replacing Prototype with YUI3"
run 'rm public/javascripts/controls.js'
run 'rm public/javascripts/dragdrop.js'
run 'rm public/javascripts/effects.js'
run 'rm public/javascripts/prototype.js'
run 'rm public/javascripts/rails.js'

get "http://yui.yahooapis.com/combo?3.3.0/build/yui/yui-debug.js",  "public/javascripts/yui-debug.js"
get "http://yui.yahooapis.com/3.3.0/build/cssreset/reset.css",  "public/stylesheets/reset.css"
get "http://yui.yahooapis.com/3.3.0/build/cssbase/base.css",  "public/stylesheets/base.css"
get "http://yui.yahooapis.com/3.3.0/build/cssfonts/fonts.css",  "public/stylesheets/fonts.css"
get "http://yui.yahooapis.com/3.3.0/build/cssgrids/grids.css",  "public/stylesheets/grids.css"

#----------------------------------------------------------------------------
# Create an index page
#----------------------------------------------------------------------------
puts "create a home controller and view"
generate(:controller, "home index")
gsub_file 'config/routes.rb', /get \"home\/index\"/, 'root :to => "home#index"'
append_file 'app/views/home/index.html.haml'do <<-FILE
!!!
%h2{:class => "subtitle"} Get Started
%p{:class => "content"} Update application.js with your logic
%div{:class => "container", :id => "container"}
%div{:id => "testLogger"}
FILE
end

#----------------------------------------------------------------------------
# Generate Application Layout
#----------------------------------------------------------------------------

run 'rm app/views/layouts/application.html.erb'
  create_file 'app/views/layouts/application.html.haml' do <<-FILE
!!!
%html
  %head
    %title YUI3 App
    = stylesheet_link_tag "reset"
    = stylesheet_link_tag "base"
    = stylesheet_link_tag "fonts"
    = stylesheet_link_tag "grids"
    = stylesheet_link_tag "application"
    = javascript_include_tag :all
    = csrf_meta_tag
  %body{:class =>"yui3-skin-sam  yui-skin-sam"}
    = yield
FILE
end

#----------------------------------------------------------------------------
# Add Stylesheets
#----------------------------------------------------------------------------
create_file 'public/stylesheets/application.css' do <<-FILE
div.container {
  width: 100%;
  height: 100px; 
  padding: 10px;
  margin: 10px;
  border: 1px solid red;
}

#testLogger {
    margin-bottom: 1em;
}

#testLogger .yui3-console .yui3-console-title {
    border: 0 none;
    color: #000;
    font-size: 13px;
    font-weight: bold;
    margin: 0;
    text-transform: none;
}
#testLogger .yui3-console .yui3-console-entry-meta {
    margin: 0;
}

.yui3-skin-sam .yui3-console-entry-pass .yui3-console-entry-cat {
    background: #070;
    color: #fff;
}

FILE
end

#----------------------------------------------------------------------------
# Initialize YUI and add YUI Test
#----------------------------------------------------------------------------
append_file 'public/javascripts/application.js' do <<-FILE
  
  YUI({ filter: 'raw' }).use("node", "console", "test",function (Y) {

      Y.namespace("example.test");

      Y.example.test.DataTestCase = new Y.Test.Case({

          //name of the test case - if not provided, one is auto-generated
          name : "Data Tests",

          //---------------------------------------------------------------------
          // setUp and tearDown methods - optional
          //---------------------------------------------------------------------

          /*
           * Sets up data that is needed by each test.
           */
          setUp : function () {
              this.data = {
                  name: "test",
                  year: 2007,
                  beta: true
              };
          },

          /*
           * Cleans up everything that was created by setUp().
           */
          tearDown : function () {
              delete this.data;
          },

          //---------------------------------------------------------------------
          // Test methods - names must begin with "test"
          //---------------------------------------------------------------------

          testName : function () {
              var Assert = Y.Assert;

              Assert.isObject(this.data);
              Assert.isString(this.data.name);
              Assert.areEqual("test", this.data.name);            
          },

          testYear : function () {
              var Assert = Y.Assert;

              Assert.isObject(this.data);
              Assert.isNumber(this.data.year);
              Assert.areEqual(2007, this.data.year);            
          },

          testBeta : function () {
              var Assert = Y.Assert;

              Assert.isObject(this.data);
              Assert.isBoolean(this.data.beta);
              Assert.isTrue(this.data.beta);
          }

      });

      Y.example.test.ArrayTestCase = new Y.Test.Case({

          //name of the test case - if not provided, one is auto-generated
          name : "Array Tests",

          //---------------------------------------------------------------------
          // setUp and tearDown methods - optional
          //---------------------------------------------------------------------

          /*
           * Sets up data that is needed by each test.
           */
          setUp : function () {
              this.data = [0,1,2,3,4]
          },

          /*
           * Cleans up everything that was created by setUp().
           */
          tearDown : function () {
              delete this.data;
          },

          //---------------------------------------------------------------------
          // Test methods - names must begin with "test"
          //---------------------------------------------------------------------

          testPop : function () {
              var Assert = Y.Assert;

              var value = this.data.pop();

              Assert.areEqual(4, this.data.length);
              Assert.areEqual(4, value);            
          },        

          testPush : function () {
              var Assert = Y.Assert;

              this.data.push(5);

              Assert.areEqual(6, this.data.length);
              Assert.areEqual(5, this.data[5]);            
          },

          testSplice : function () {
              var Assert = Y.Assert;

              this.data.splice(2, 1, 6, 7);

              Assert.areEqual(6, this.data.length);
              Assert.areEqual(6, this.data[2]);           
              Assert.areEqual(7, this.data[3]);           
          }

      });    

      Y.example.test.ExampleSuite = new Y.Test.Suite("Example Suite");
      Y.example.test.ExampleSuite.add(Y.example.test.DataTestCase);
      Y.example.test.ExampleSuite.add(Y.example.test.ArrayTestCase);

      //create the console
      var r = new Y.Console({
          newestOnTop : false,
          style: 'block' // to anchor in the example content
      });

      r.render('#testLogger');

      Y.Test.Runner.add(Y.example.test.ExampleSuite);

      //run the tests
      Y.Test.Runner.run();

  });

FILE
end

#----------------------------------------------------------------------------
# Setup RSpec & Cucumber
#----------------------------------------------------------------------------
puts 'Setting up RSpec, Cucumber, webrat, factory_girl, faker'
append_file 'Gemfile' do <<-FILE
group :development, :test do
  gem "rspec-rails", ">= 2.0.1"
  gem "cucumber-rails", ">= 0.3.2"
  gem "webrat", ">= 0.7.2.beta.2"
  gem "factory_girl_rails"
  gem "faker"
end
FILE
end

run 'bundle install'
run 'script/rails generate rspec:install'
run 'script/rails generate cucumber:install'
run 'rake db:migrate'
run 'rake db:test:prepare'

run 'touch spec/factories.rb'
#----------------------------------------------------------------------------
# Finish up
#----------------------------------------------------------------------------
puts "Commiting to Git repository..."
git :add => '.'
git :commit => "-am 'Setup Complete'"

puts "DONE - setting up your YUI3 Rails App."