Skip to content
This repository has been archived by the owner on Nov 7, 2020. It is now read-only.

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rmurphey committed Nov 11, 2012
0 parents commit 71451d0
Show file tree
Hide file tree
Showing 50 changed files with 25,872 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
node_modules/
test/fixtures/templates.js
chromedriver.log
4 changes: 4 additions & 0 deletions Gemfile
@@ -0,0 +1,4 @@
source "http://rubygems.org"
gem "capybara"
gem "rspec"
gem "test-unit"
48 changes: 48 additions & 0 deletions Gemfile.lock
@@ -0,0 +1,48 @@
GEM
remote: http://rubygems.org/
specs:
addressable (2.3.2)
capybara (1.1.2)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
childprocess (0.3.6)
ffi (~> 1.0, >= 1.0.6)
diff-lcs (1.1.3)
ffi (1.1.5)
libwebsocket (0.1.5)
addressable
mime-types (1.19)
multi_json (1.3.6)
nokogiri (1.5.5)
rack (1.4.1)
rack-test (0.6.2)
rack (>= 1.0)
rspec (2.9.0)
rspec-core (~> 2.9.0)
rspec-expectations (~> 2.9.0)
rspec-mocks (~> 2.9.0)
rspec-core (2.9.0)
rspec-expectations (2.9.1)
diff-lcs (~> 1.1.3)
rspec-mocks (2.9.0)
rubyzip (0.9.9)
selenium-webdriver (2.25.0)
childprocess (>= 0.2.5)
libwebsocket (~> 0.1.3)
multi_json (~> 1.0)
rubyzip
test-unit (2.5.2)
xpath (0.1.4)
nokogiri (~> 1.3)

PLATFORMS
ruby

DEPENDENCIES
capybara
rspec
test-unit
47 changes: 47 additions & 0 deletions README.md
@@ -0,0 +1,47 @@
# Workshop: Writing Testable JavaScript

## Installation

You will need to use the command line to install the dependencies for this project. See the section below on accessing the command line if you need help.

Note that you may need to perform some of these steps as an administrator. On OSX and Linux, you can run commands as an administrator by prefixing them with `sudo` and then typing in your password when prompted. On Windows, you will need to launch the `cmd.exe` program as an administrator.

### Node

You will need to [install Node.js v0.8.8 or newer](http://nodejs.org) to use this repo (you can also use a [package manager](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager)). You can verify that Node is installed correctly by running `node --version` from the command line.

### Packages

Once you have Node installed, run `npm install` from the root directory of this project to install the dependencies.

### Grunt

Install [grunt](http://gruntjs.com) by running `npm install -g grunt`.

### PhantomJS

See the PhantomJS section [here](https://github.com/gruntjs/grunt/blob/master/docs/faq.md) for details on installing PhantomJS.


## Running the Code and Tests

- To run the tests: `grunt test`
- To run the server: `node server`

## Running Specs

The specs in this repo are for demonstrating how you can use Selenium to write integration tests. You don't need to run them, but you can if you want to.

The specs make use of Ruby, so if you want to run them, you will need to have Ruby ~1.9.3 installed. You will also need to `gem install bundler` and then `bundle install` from the root directory of this project.

Once you have Ruby, bundler, and the required gems, you can run `grunt spec` to run the specs.

## Accessing the command line

**Windows**

Open **cmd.exe** (From the Start Menu, search for "cmd"). When installing npm modules globally, you may need to "Run as administrator," which you can do by right-clicking the `cmd` name in the programs list.

**OS X**

Open **Terminal.app** in `/Applications/Utilities`.
76 changes: 76 additions & 0 deletions grunt.js
@@ -0,0 +1,76 @@
/*global module:false*/
var child_process = require('child_process');

module.exports = function(grunt) {

// Project configuration.
grunt.initConfig({
lint: {
files: ['lib/**/*.js', 'test/**/*.js', '! test/lib/**/*.js', 'www/js/**/*.js']
},
qunit: {
files: ['test/**/test-*.html']
},
watch: {
files: [ '<config:lint.files>', 'www/templates/*.tmpl' ],
tasks: 'test'
},
jshint: {
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
browser: true
},
globals: {
$ : true,
_ : true,
RSVP : true,
app : true
}
},
uglify: {}
});

grunt.registerTask('build-template-mocks', 'Build template mocks', function() {
var obj = {};

var addFile = function(filepath, contents) {
obj[ filepath.replace('templates/', '') ] = contents;
};

var options = {cwd: 'www'};

grunt.file.expand(options, 'templates/*.tmpl').forEach(function(filepath) {
addFile(filepath, grunt.file.read('www/' + filepath));
});

var src = 'window._templateMocks = ' + JSON.stringify(obj, null, 2) + ';';

grunt.file.write('test/fixtures/templates.js', src);
});

grunt.registerTask('spec', 'Run integration tests', function() {
var done = this.async();
var server = child_process.exec('node server');
var spec = child_process.exec('ruby spec/*.rb', function(err) {
server.kill();
done( err ? false : true );
});

spec.stdout.on('data', function(data) {
console.log(data);
});
});

grunt.registerTask('default', 'lint qunit');
grunt.registerTask('test', 'build-template-mocks qunit lint');
};

10 changes: 10 additions & 0 deletions package.json
@@ -0,0 +1,10 @@
{
"name": "testable-javascript",
"version": "0.0.0",
"main": "server/index.js",
"dependencies" : {
"Faker": "~0.1",
"express": "~3",
"underscore": "~1"
}
}
40 changes: 40 additions & 0 deletions server/index.js
@@ -0,0 +1,40 @@
var express = require( 'express' );
var Faker = require( 'Faker' );
var _ = require( 'underscore' )
var app = express( express.bodyParser() );
var baseDir = __dirname + '/../';

var fakeData = [];

for (var i = 0; i < 1000; i++) {
fakeData.push(Faker.Helpers.userCard());
}

app.use( '/', express.static( baseDir + 'www' ) );

app.get( '/data/search.json', function(req, res) {
var query = req.query.q;
var results = [];

if (!query || !query.trim()) {
console.log('Query was empty.');
} else {
results = _.filter(fakeData, function(item) {
var possibles = [ item.name, item.email, item.company.name ];
return _.any(possibles, function(p) {
return p.toLowerCase().match(query.toLowerCase());
});
});
}

// simulate latency
setTimeout(function() {
res.end(JSON.stringify({ results : results }));
}, 500);
});

app.use( '/test', express.static( baseDir + 'test') );

app.listen( 4000 );

console.log( 'Serving on http://localhost:4000' );
53 changes: 53 additions & 0 deletions spec/app.rb
@@ -0,0 +1,53 @@
require 'rubygems'
require 'capybara/rspec'
require 'minitest/autorun'

Capybara.default_driver = :selenium
Capybara.default_selector = :css

Capybara.register_driver :selenium do |app|
Capybara::Selenium::Driver.new(app, :browser => :chrome)
end

Capybara.app_host = 'http://localhost:4000'

class Srchr < MiniTest::Unit::TestCase
include Capybara::DSL

def setup
visit('/')
end

def test_page
assert page.has_content?('Srchr')
end

def test_search
fill_in('q', :with => 'cat')
find('.btn').click
assert(
find('#results li').has_content?('cat') || find('#results li').has_content?('Cat'), 'Search results are shown' )
assert( page.has_no_selector?('#results li.no-results'), 'No results is not shown' )
end

def test_no_results
fill_in('q', :with => 'foobarbazbimbop')
find('.btn').click
assert( page.has_selector?('#results li.no-results'), 'No results is shown' )
assert( find('#results li.no-results').has_content?('No results found'), 'No results message is shown' )
end

def test_invalid_search
fill_in('q', :with => ' ')
find('.btn').click
assert( page.has_selector?('#results li.no-results') )
end

def test_like
fill_in('q', :with => 'cat')
find('.btn').click
find('.like').click
assert_equal find('#results li h2').text, find('#liked li').text
end

end
40 changes: 40 additions & 0 deletions test/index.html
@@ -0,0 +1,40 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>

<style>
body { font-family: Helvetica, Arial, sans-serif; }
.pass { color: green; }
.fail { color: red; }
.assertion { padding-left: 10px; font-size: 90%; }
</style>

<script src="../www/lib/myUnit.js"></script>
</head>
<body>
<script>
function fizzbuzz(num) {
if (!(num % 3) && !(num % 5)) {
return 'fizzbuzz';
}
}
</script>

<script>
test('fizzbuzz returns fizzbuzz for multiples of 3 and 5', function() {
assert( fizzbuzz(15) === 'fizzbuzz');
});
test('fizzbuzz returns fizz for multiples of 3', function() {
assert( fizzbuzz(3) === 'fizz' );
});
test('fizzbuzz returns buzz for multiples of 5', function() {
assert( fizzbuzz(5) === 'buzz' );
});
test('fizzbuzz returns number otherwise', function() {
assert( fizzbuzz(2) === 2 );
});
</script>
</body>
</html>
50 changes: 50 additions & 0 deletions test/js/app.js
@@ -0,0 +1,50 @@
/*global
test:true,
module:true,
ok:true,
equal:true,
sinon:true,
stop:true,
start:true
*/

module( 'model' );

test('attribute setting/getting', function() {
var m = new app.Model();

equal( m.get('key'), undefined );
m.set( 'key', 'value' );
equal( m.get('key'), 'value' );
});

test('adding', function() {
var m = new app.Model();
m.set( 'key', [ 'value' ]);
m.add( 'key', 'newValue' );

equal( m.get('key').length, 2, 'value is added');
equal( m.get('key')[1], 'newValue', 'value is pushed' );

m.add( 'key2', 'value2' );
equal( m.get('key2')[0], 'value2', 'array is created if empty' );
});

test('change events', function() {
var m = new app.Model();

var changeSpy = sinon.spy();
var keyChangeSpy = sinon.spy();

m.on( 'change', function( data ) {
equal( data.key, 'key' );
equal( data.value, 'value' );
});

m.on( 'change:key', function( data ) {
equal( data.key, 'key' );
equal( data.value, 'value' );
});

m.set( 'key', 'value' );
});

0 comments on commit 71451d0

Please sign in to comment.