MINOR Updated jquery-concrete to 0.9 release

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@92560 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2009-11-21 02:33:26 +00:00
parent 0e93bcec53
commit 83267cdd45
43 changed files with 1562 additions and 605 deletions

View File

@ -1,7 +1,7 @@
---
format: 1
handler:
commit: 767bc6d5ae87459cc6534ddcf91054d28fdd252c
commit: bae4684bd5ab659bd78ebd990cc68ecba8a36669
branch: master
lock: false
repository_class: Piston::Git::Repository

View File

@ -163,11 +163,13 @@ h2. Namespaces
To avoid name clashes, to allow multiple bindings to the same event, and to generally seperate a set of functions from other code you can use namespaces
<pre><code>
$('div').concrete('foo.bar', function($){ return {
$.concrete('foo.bar', function($){
$('div').concrete({
baz: function(){}
}});
});
});
</code></pre>
You can then call these functions like this:
<pre><code>
@ -182,9 +184,11 @@ This is particularly useful for events, because given this:
onclick: function(){ this.css({backgroundColor: 'blue'}); }
});
$('div').concrete('foo', function($){ return {
$.concrete('foo', function($){
$('div').concrete({
onclick: function(){ this.css({color: 'green'}); }
}});
});
});
</code></pre>
Clicking on a div will change the background **and** foreground color.
@ -199,9 +203,11 @@ Inside a namespace definition, functions remember the namespace they are in, and
Where they don't exist, they will be looked up in the base namespace
<pre><code>
$('div').concrete('foo', {
bar: function() { this.baz(); this.qux(); }
baz: function() { console.log('baz'); }
$.concrete('foo', function($){
$('div').concrete({
bar: function() { this.baz(); this.qux(); }
baz: function() { console.log('baz'); }
})
})
$('div').concrete({
@ -220,12 +226,15 @@ Note that 'exists' means that a function is declared in this namespace for _any_
And the concrete definitions
<pre><code>
$('div').concrete('foo', {
bar: function() { this.baz(); }
$.concrete('foo', function($){
$('div').concrete({
bar: function() { this.baz(); }
});
$('span').concrete({
baz: function() { console.log('a'); }
});
})
$('span').concrete('foo' {
baz: function() { console.log('a'); }
$('div').concrete({
baz: function() { console.log('b'); }
@ -234,17 +243,7 @@ And the concrete definitions
Then doing $('div').bar(); will _not_ display b. Even though the span rule could never match a div, because baz is defined for some rule in the foo namespace, the base namespace will never be checked
h4. Cleaner namespace blocks
When declaring a lot of namespaces, you can declare the namespace seperately from the concrete definitions, like so
<pre><code>
$.concrete('foo', function($){
$('div').concrete({
bar: function() { .. }
})
})
</code></pre>
h4. Nesting namespace blocks
You can also nest declarations. In this next example, we're defining the functions $().concrete('zap').bar() and $().concrete('zap.pow').baz()
@ -268,8 +267,10 @@ Inside a namespace, namespace lookups are by default relative to the current nam
In some situations (such as the last example) you may want to force using the base namespace. In this case you can call concrete with the first argument being the base namespace code '.'. For example, if the first definition in the previous example was
<pre><code>
$('div').concrete('foo', {
bar: function() { this.concrete('.').baz(); }
$.concrete('foo', function($){
$('div').concrete({
bar: function() { this.concrete('.').baz(); }
})
})
</code></pre>
@ -280,12 +281,16 @@ h4. Using
Sometimes a block outside of a namespace will need to refer to that namespace repeatedly. By passing a function to the concrete function, you can change the looked-up namespace
<pre><code>
$('div').concrete('foo', {
bar: function() { console.log('a'); }
$.concrete('foo', function($){
$('div').concrete({
bar: function() { console.log('a'); }
})
})
$('div').concrete('foo', function(){
this.bar();
this.bar();
this.bar();
});
</code></pre>

View File

@ -1284,6 +1284,7 @@ var console;
order: 40,
bind: function(selector, k, v){
var match, event;
if ($.isFunction(v) && (match = k.match(/^on(.*)/))) {
event = match[1];
this.bind_event(selector, k, v, event);

View File

@ -2,71 +2,77 @@
describe 'Concrete'
describe 'Basics'
before
$.concrete.warningLevel = $.concrete.WARN_LEVEL_BESTPRACTISE;
$('body').append('<div id="dom_test"></div>')
$.concrete.warningLevel = $.concrete.WARN_LEVEL_BESTPRACTISE;
$('body').append('<div id="dom_test"></div>');
end
after
$('#dom_test').remove()
$('#dom_test').remove();
end
before_each
$.concrete.clear_all_rules()
$('#dom_test').html('<div id="a" class="a b c"></div><div id="b" class="c d e"></div>')
$.concrete.clear_all_rules();
$('#dom_test').html('<div id="a" class="a b c"></div><div id="b" class="c d e"></div>');
end
it 'can attach and call a base function'
$('#a').concrete({
foo: function(){return this.attr('id');}
});
$('.a').foo().should.equal 'a'
$('.a').foo().should.equal 'a'
end
it 'can attach and call several base functions'
$('#a').concrete({
foo: function(){return 'foo_' + this.attr('id');},
bar: function(){return 'bar_' + this.attr('id');}
});
$('.a').foo().should.equal 'foo_a'
$('.a').bar().should.equal 'bar_a'
});
$('.a').foo().should.equal 'foo_a'
$('.a').bar().should.equal 'bar_a'
end
it 'can attach and call a namespaced function'
$('#a').concrete('bar', function($){return{
foo: function(){return this.attr('id');}
}});
$('.a').concrete('bar').foo().should.equal 'a'
$.concrete('bar', function($){
$('#a').concrete({
foo: function(){return this.attr('id');}
});
});
$('.a').concrete('bar').foo().should.equal 'a'
end
it 'can attach and call a nested namespaced function'
$('#a').concrete('qux.baz.bar', function($){return{
foo: function(){return this.attr('id');}
}});
$('.a').concrete('qux.baz.bar').foo().should.equal 'a'
$.concrete('qux.baz.bar', function($){
$('#a').concrete({
foo: function(){return this.attr('id');}
});
});
$('.a').concrete('qux.baz.bar').foo().should.equal 'a'
end
it 'can call two functions on two elements'
it 'can call two functions on two elements'
var res = []
$('#a').concrete({
foo: function(){res.push(this.attr('id'));},
})
foo: function(){res.push(this.attr('id'));}
});
$('#b.c').concrete({
foo: function(){res.push(this.attr('id'));},
})
foo: function(){res.push(this.attr('id'));}
});
$('#dom_test div').foo();
res.should.eql ['b', 'a']
end
it 'can call two namespaced functions on two elements'
it 'can call two namespaced functions on two elements'
var res = []
$('#a').concrete('bar', function($){return{
foo: function(){res.push(this.attr('id'));},
}})
$('#b.c').concrete('bar', function($){return{
foo: function(){res.push(this.attr('id'));},
}})
$.concrete('bar', function($){
$('#a').concrete({
foo: function(){res.push(this.attr('id'));}
});
$('#b.c').concrete({
foo: function(){res.push(this.attr('id'));}
});
});
$('#dom_test div').concrete('bar').foo();
res.should.eql ['b', 'a']
end
end
end

View File

@ -109,7 +109,7 @@ describe 'Concrete'
it 'internal to namespace, will look up functions in base when not present in namespace'
var res = []
$('#a').concrete({
foo: function(){res.push(1);},
foo: function(){res.push(1);}
})
$('#a').concrete('bar', function($){return{
bar: function(){res.push(2); this.foo();}
@ -121,10 +121,10 @@ describe 'Concrete'
it 'internal to namespace, will not look up functions in base if present in namespace, even when not applicable to selector'
var res = []
$('#a').concrete('bar', function($){return{
foo: function(){this.bar();},
foo: function(){this.bar();}
}})
$('#a').concrete({
bar: function(){res.push(1);},
bar: function(){res.push(1);}
})
$('span').concrete('bar', function($){return{
bar: function(){res.push(2);}
@ -227,10 +227,10 @@ describe 'Concrete'
it 'using block functions'
var res = []
$('#a').concrete({
foo: function(){res.push(1);},
foo: function(){res.push(1);}
})
$('#a').concrete('bar', function($){return{
foo: function(){res.push(3);},
foo: function(){res.push(3);}
}})
$('#dom_test div').foo();

View File

@ -144,6 +144,7 @@
order: 40,
bind: function(selector, k, v){
var match, event;
if ($.isFunction(v) && (match = k.match(/^on(.*)/))) {
event = match[1];
this.bind_event(selector, k, v, event);

View File

@ -1,8 +1,8 @@
---
format: 1
handler:
commit: 8fe63b186e349433b6da1cd6ec4c9a751fefb13e
branch: master
lock: false
repository_class: Piston::Git::Repository
handler:
commit: 75336bc94061b818f90361ff163436c8eb61d4c3
branch: tags/2.8.4
repository_url: git://github.com/visionmedia/jspec.git

View File

@ -1,4 +1,83 @@
=== 2.11.7 / 2009-10-15
* Fixed minor grammar issues for windows users [thanks Tony]
* Fixes installation issue when XCode is not present; changed dependency json -> json_pure [thanks esbie]
* Fixed Chrome#visit; latest builds of Chrome for the mac are now "Google Chrome"
=== 2.11.6 / 2009-10-12
* Added Tony to contributor list
* Removed JSpec.paramsFor()
* Removed module contexts [#72]
* Fixed some css styling issues in IE8 [#71]
* Fixed; DOM formatter supporting \r\n \r \n for EOL in the body source [Thanks Tony]
* Fixed "Access is denied" error in IE
* Fixed some css support for older browsers [Thanks Tony]
=== 2.11.5 / 2009-10-10
* Fixed dependencies (created by github's gem builder removal)
=== 2.11.4 / 2009-10-10
* Updated installation docs
* Removed namespaced dependencies (thanks alot github...)
=== 2.11.3 / 2009-09-30
* Updated to mock timers 1.0.1
fixes an issue where setTimeout(function(){}, 0); tick(100) is never called
=== 2.11.2 / 2009-09-21
* Fixed example path in rails template
=== 2.11.1 / 2009-09-10
* Fixed JSpec root when using --symlink, --freeze [#36]
* Added __END__ to the grammar (works like Ruby's __END__)
=== 2.11.0 / 2009-09-04
* Added --symlink switch (links the current version of JSpec to ./spec/lib) [#4]
* Added --freeze switch (copies the current version of JSpec to ./spec/lib) [#4]
=== 2.10.1 / 2009-09-02
* Added `jspec shell` sub command (interactive Rhino shell through JSpec)
* Added jspec.shell.js
=== 2.10.0 / 2009-08-27
* Added Async support via mock timers (lib/jspec.timers.js) [#19]
* IRC channel up and running! irc://irc.freenode.net#jspec
=== 2.9.1 / 2009-08-21
* Added module support for formatters
=== 2.9.0 / 2009-08-21
* Server output matching Rhino when using verbose or failuresOnly options
* Added mock_request() and unmock_request() as aliases for mockRequest() and unmockRequest()
* Added JSpec.JSON.encode()
* Added default Sinatra routes /slow/NUMBER and /status/NUMBER for simulating
slow reponses and HTTP status codes.
* Added server support for loading spec/jspec.rb (or jspec/jspec.rb for rails)
Allowing additional browser support to be plugged in, as well as Sinatra routes.
* Added dependency for Sinatra (new server)
* Added a new Ruby server
* Added support for --bind and --server on various platforms
* Added Google Chrome support
* Added Internet Explorer support
* Change; --server without --browsers defaults to all supported browsers
* Removed JSpec.reportToServer()
Now utilizes JSpec.formatters.Server to handle this
functionality.
* Fixed Server output escaping (removed html escaping from puts()) [#13]
* Fixed JSpec.load(); returns responseText when 2xx or 0 (for file://)
=== 2.8.4 / 2009-08-02
* Fixed error thrown when a module has no utilities

View File

@ -1,5 +1,8 @@
bin/jspec
History.rdoc
Manifest
README.rdoc
Rakefile
bin/jspec
jspec.gemspec
lib/images/bg.png
lib/images/hr.png
@ -10,18 +13,20 @@ lib/images/vr.png
lib/jspec.css
lib/jspec.jquery.js
lib/jspec.js
lib/jspec.shell.js
lib/jspec.timers.js
lib/jspec.xhr.js
Manifest
Rakefile
README.rdoc
server/browsers.rb
server/helpers.rb
server/routes.rb
server/server.rb
spec/async
spec/env.js
spec/fixtures/test.html
spec/fixtures/test.json
spec/fixtures/test.xml
spec/modules.js
spec/helpers.js
spec/server.rb
spec/spec.dom.html
spec/spec.fixtures.js
spec/spec.grammar-less.js
@ -38,12 +43,14 @@ spec/spec.shared-behaviors.js
spec/spec.utils.js
spec/spec.xhr.js
templates/default/History.rdoc
templates/default/lib/yourlib.core.js
templates/default/README.rdoc
templates/default/lib/yourlib.core.js
templates/default/spec/server.rb
templates/default/spec/spec.core.js
templates/default/spec/spec.dom.html
templates/default/spec/spec.rhino.js
templates/default/spec/spec.server.html
templates/rails/server.rb
templates/rails/spec.application.js
templates/rails/spec.dom.html
templates/rails/spec.rhino.js

View File

@ -33,6 +33,7 @@ and much more.
* Method Stubbing
* Shared behaviors
* Profiling
* Interactive Shell
* Ruby on Rails Integration
* Tiny (15 kb compressed, 1300-ish LOC)
@ -42,13 +43,19 @@ Simply download JSpec and include JSpec.css and JSpec.js in your markup.
Head over to the downloads section on Github, clone this public repo, or
add JSpec as a git submodule with in your project. Alternatively JSpec is
also available as a Ruby Gem (though this is not required), which also
provides the `jspec` executable. To install execute:
$ gem sources -a http://gems.github.com (if you have not previously done so)
$ sudo gem install visionmedia-jspec
provides the `jspec` executable. First install [Gemcutter](http://gemcutter.org/) then execute:
$ sudo gem install jspec
At which point you may:
$ jspec init myproject
By default, the command above will use absolute path for all JSpec library files.
This behavior can be a problem when you're working across different computers or
operating systems. You can freeze the library or symlink it.
$ jspec init myproject --freeze
$ jspec init myproject --symlink
JSpec scripts should NOT be referenced via the <script> tag, they should be
loaded using the exec method (unless you are using the grammar-less alternative).
Below is an example:
@ -125,9 +132,10 @@ JSpec.options hash, by passing string-based option values via the
query string, or passing a hash to run(). For example
JSpec.options.failuresOnly = true, and ?failuresOnly=1 will both work.
* profile {bool} when enabled, uses console.time() in order to display performance information in your console log as specs are completed. (DOM, Console)
* failuresOnly {bool} displays only failing specs, making them quick to discover and fix (DOM, Terminal)
* reportToId {string} an element id to report to when using the DOM formatter (DOM)
* profile {bool} when enabled, uses console.time() in order to display performance information in your console log as specs are completed. (DOM, Console)
* failuresOnly {bool} displays only failing specs, making them quick to discover and fix (DOM, Terminal, Server)
* reportToId {string} an element id to report to when using the DOM formatter (DOM)
* verbose {bool} verbose server output, defaults to false (Server)
== Matchers
@ -188,6 +196,44 @@ JSpec.options.failuresOnly = true, and ?failuresOnly=1 will both work.
- be_disabled
- be_selected
- be_checked
== Async Support With Mock Timers
The javascript mock timers library is available at http://github.com/visionmedia/js-mock-timers
although it is already bundled with JSpec at lib/jspec.timers.js
Timers return ids and may be passed to clearInterval(), however
they do not execute in threads, they must be manually scheduled and
controlled via the tick() function.
setTimeout(function(){
alert('Wahoo!')
}, 400)
tick(200) // Nothing happens
tick(400) // Wahoo!
setInterval() works as expected, although it persists, where as setTimeout()
is destroyed after a single call. As conveyed by the last tick() call below,
a large increment in milliseconds may cause the callbacks to be called several times
to 'catch up'.
progress = ''
var id = setInterval(function(){
progress += '.'
}, 100)
tick(50), print(progress) // ''
tick(50), print(progress) // '.'
tick(100), print(progress) // '..'
tick(100), print(progress) // '...'
tick(300), print(progress) // '......'
clearInterval(id)
tick(800) // Nothing happens
You may also reset at any time using resetTimers()
== Proxy Assertions
@ -242,9 +288,9 @@ on any object when using the JSpec grammar:
* Core
- an_instance_of used in conjunction with the 'receive' matcher
- mockRequest mock a request (requires jspec.xhr.js)
- unmockRequest unmock requests (requests jspec.xhr.js)
- an_instance_of used in conjunction with the 'receive' matcher
- mockRequest, mock_request mock a request (requires jspec.xhr.js)
- unmockRequest, unmock_request unmock requests (requests jspec.xhr.js)
* jQuery
@ -530,11 +576,13 @@ example view lib/jspec.jquery.js.
The following methods or properties are utilized by JSpec:
- init : called to initialize a module
- utilities : hash of utility functions merged with JSpec.defaultContext
- matchers : hash of matchers merged with JSpec's core matchers via JSpec.addMatchers()
- DSLs : hash of DSL methods; for example DSLs.snake contains before_each, after_each, etc.
Where as DSLs.camel may contain beforeEach, afterEach, etc.
- name : module name string
- init : called to initialize a module
- formatters : hash of formatters merged with JSpec.formatters
- utilities : hash of utility functions merged with JSpec.defaultContext
- matchers : hash of matchers merged with JSpec's core matchers via JSpec.addMatchers()
- DSLs : hash of DSL methods; for example DSLs.snake contains before_each, after_each, etc.
Where as DSLs.camel may contain beforeEach, afterEach, etc.
Below is a list of hooks, descriptions, and valid return values which
may simply be implemented as module methods. beforeSuite, afterSuite, beforeSpec, and afterSpec have lower
@ -545,7 +593,6 @@ anything that has been done by a Module.
- loading(file) : loading a file : returning 'stop' will prevent loading
- executing(file) : executing a file : returning 'stop' will prevent execution
- posting(data, url) : posting data to a url : returning 'stop' will prevent request
- reportingToServer(url) : reporting to server url : returning 'stop' will prevent reporting to server
- preprocessing(input) : before input string is preprocessed : return input string for next hook to preprocess
- stubbing(object, method, result) : called when stubbing an object's method, and return value (result). : (no return value)
- requiring(dependency, message) : requiring a dependency : (no return value)
@ -631,13 +678,49 @@ back to the terminal. It is essentially the same as using the DOM formatter
and auto-testing each browser, however results are centralized to the terminal,
removing the need to manually view each browser's output.
Initialize project with:
$ jspec init myproject
When utilizing the server if a file named spec/jspec.rb (or jspec/jspec.rb for rails)
is present, then it will be loaded before the server is started. This allows you to
add Sinatra routes, support additional Browsers, etc.
Run with:
Run with all supported browsers:
$ jspec run --server
Run with specific browsers:
$ jspec run --browsers Safari,Firefox,Chrome,Explorer
Run with alternative browser names:
$ jspec run --browsers safari,ff,chrome,ie
Browsers supported in core:
Browser::Safari
Browser::Chrome
Browser::Opera
Browser::Firefox
Browser::IE
Supplied routes:
/slow/NUMBER
/status/NUMBER
For example $.get('/slow/4', function(){}) will take 4 seconds
to reply, where as $.get('/status/404', function(){}) will respond
with an 404 status code. Add additional Sinatra routes to the jspec.rb
file to add your own functionality.
== Interactive Shell
JSpec provides an interactive shell through Rhino, utilize with:
$ jspec shell
Or to specify additional files to load:
$ jspec shell lib/*.js
Or view additional shell help
$ jspec help shell
== Ruby on Rails
No additional gems are required for JSpec to work with rails, although
@ -660,6 +743,17 @@ Or run via the terminal using Rhino:
$ jspec run --rhino
== Support Browsers
Browsers below are supported and can be found in server/browsers.rb, however
your spec/server.rb file may support additional browsers.
* Safari
* Chrome
* Firefox
* Opera
* Internet Explorer
== Known Issues
* Tabs may cause a parse error. To prevent this use 'soft tabs' (setting in your IDE/Editor)
@ -677,8 +771,13 @@ Or run via the terminal using Rhino:
return 'html'
})
== Additional JSpec Modules
* JSocka stubbing http://github.com/gisikw/jsocka/tree/master
== More Information
* IRC Channel irc://irc.freenode.net#jspec
* Featured article in JSMag: http://www.jsmag.com/main.issues.description/id=21/
* Syntax comparison with other frameworks http://gist.github.com/92283
* Get the TextMate bundle at https://github.com/visionmedia/jspec.tmbundle/tree
@ -694,7 +793,9 @@ JSpec more enjoyable, and bug free ;)
* Lawrence Pit
* mpd@jesters-court.ne
* kevin.gisi@gmail.com
* tony_t_tubbs@yahoo.com
* enno84@gmx.net
* fnando
== License

View File

@ -12,8 +12,10 @@ Echoe.new "jspec", version do |p|
p.email = "tj@vision-media.ca"
p.summary = "JavaScript BDD Testing Framework"
p.url = "http://visionmedia.github.com/jspec"
p.runtime_dependencies << "visionmedia-commander >=3.2.9"
p.runtime_dependencies << "visionmedia-bind >=0.2.6"
p.runtime_dependencies << "sinatra"
p.runtime_dependencies << "json_pure"
p.runtime_dependencies << "commander >=4.0.0"
p.runtime_dependencies << "bind >=0.2.8"
end
namespace :pkg do

View File

@ -2,16 +2,17 @@
JSPEC_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
$:.unshift JSPEC_ROOT
require 'rubygems'
require 'commander'
require 'commander/import'
require 'bind'
require 'fileutils'
require 'server/server'
RHINO = 'java org.mozilla.javascript.tools.shell.Main'
program :name, 'JSpec'
program :version, '2.8.4'
program :version, '2.11.7'
program :description, 'JavaScript BDD Testing Framework'
default_command :bind
@ -20,17 +21,47 @@ command :init do |c|
c.summary = 'Initialize a JSpec project template'
c.description = 'Initialize a JSpec project template. Defaults to the current directory
when [dest] is not specified. The template includes several files for
running via Rhino, DOM, and the JSpec Rack server.'
running via Rhino, DOM, and the JSpec Rack server.
Additional switches --freeze, and --symlink are available in order
to preserve the version of JSpec at the time of initialization. Otherwise
incompatibilities from later versions may prevent your suite from
running properly.'
c.option '-R', '--rails', 'Initialize rails template from rails root directory'
c.option '-f', '--freeze', 'Copy the JSpec library'
c.option '-s', '--symlink', 'Symlink the JSpec library instead of copying it'
c.example 'Create a directory foo, initialized with a jspec template', 'jspec init foo'
c.option '-R', '--rails', 'Initialize rails template'
c.when_called do |args, options|
dest = args.shift || '.'
if options.rails
initialize_rails_to dest
initialize_rails_at dest, options
else
initialize_to dest
initialize_at dest, options
end
say "Template initialized at '#{dest}'"
say "Template initialized at `#{dest}'"
end
end
command :shell do |c|
c.syntax = 'jspec shell [path ...]'
c.summary = 'JSpec interactive shell'
c.description = 'Launch interactive shell with jspec.js, jspec.shell.js,
and any [path]s given. Simply type "quit" or "exit" to
terminate the shell.'
c.example 'Run shell', 'jspec shell'
c.example 'Run shell with glob of files', 'jspec shell lib/*.js'
c.example 'Run shell with list of files', 'jspec shell lib/foo.js lib/bar.js'
c.when_called do |args, options|
paths = ['jspec.js', 'jspec.shell.js'] | args
paths.map! do |path|
if path.include? 'jspec'
"-f #{JSPEC_ROOT}/lib/#{path}"
else
"-f #{path}"
end
end
say "JSpec #{program(:version)}"
`#{RHINO} #{paths.join(' ')} -f -`
end
end
@ -71,38 +102,44 @@ command :run do |c|
the [path] to spec/server.html'
c.example 'Run once in Safari', 'jspec run'
c.example 'Run once in Safari and Firefox', 'jspec run --browsers Safari,Firefox'
c.example 'Run once in Opera, Firefox, and Chrome', 'jspec run --browsers opera,ff,chrome'
c.example 'Run custom spec file', 'jspec run foo.html'
c.example 'Auto-run browsers when a file is altered', 'jspec run --bind --browsers Safari,Firefox'
c.example 'Shortcut for the previous example', 'jspec --browsers Safari,Firefox'
c.example 'Auto-run rhino when a file is altered', 'jspec --rhino'
c.example 'Run Rhino specs at spec/rhino.js', 'jspec run --rhino'
c.example 'Run Rhino specs once', 'jspec run specs/something.js --rhino'
c.option '-b', '--browsers BROWSERS', Array, 'Specify browsers to test, defaults to Safari'
c.option '-b', '--browsers BROWSERS', Array, 'Specify browsers to test'
c.option '-p', '--paths PATHS', Array, 'Specify paths when binding, defaults to javascript within ./lib and ./spec'
c.option '-B', '--bind', 'Auto-run specs when source files or specs are altered'
c.option '-R', '--rhino', 'Run specs using Rhino'
c.option '-S', '--server', 'Run specs using the JSpec server'
c.option '-s', '--server-only', 'Start JSpec server without running browsers'
c.option '-P', '--port NUMBER', Integer, 'Start JSpec server using the given port number'
c.when_called do |args, options|
# Rails
if rails?
options.default :browsers => %w( Safari ), :paths => ['public/javascripts/**/*.js', 'jspec/**/*.js']
else
options.default :browsers => %w( Safari ), :paths => ['lib/**/*.js', 'spec/**/*.js']
options.default :paths => ['public/javascripts/**/*.js', 'jspec/**/*.js'], :port => 4444
else
options.default :paths => ['lib/**/*.js', 'spec/**/*.js'], :port => 4444
end
# Actions
if options.rhino
spec = args.shift || path_to('spec.rhino.js')
action = lambda { rhino spec }
elsif options.server_only
start_server options, nil
suite = args.shift || path_to('spec.rhino.js')
action = lambda { rhino suite }
elsif options.server
spec = args.shift || path_to('spec.server.html')
action = lambda { start_server options, spec }
raise 'Cannot use --server with --bind' if options.bind
suite = args.shift || path_to('spec.server.html')
action = lambda { start_server suite, options }
else
spec = args.shift || path_to('spec.dom.html')
action = Bind::Actions::RefreshBrowsers.new spec, *options.browsers
suite = args.shift || path_to('spec.dom.html')
browsers = browsers_for options.browsers || ['safari']
action = lambda do
browsers.each do |browser|
browser.visit File.expand_path(suite)
end
end
end
# Binding
@ -110,73 +147,159 @@ command :run do |c|
listener = Bind::Listener.new :paths => options.paths, :interval => 1, :actions => [action], :debug => $stdout
listener.run!
else
action.call File.new(spec)
action.call File.new(suite)
end
end
end
alias_command :bind, :run, '--bind'
def initialize_to dest
##
# Initialize template at _dest_.
def initialize_at dest, options
unless Dir[dest + '/*'].empty?
abort unless agree "'#{dest}' is not empty; continue? "
end
copy_template_to 'default', dest
setup_lib_dir dest, options
replace_root_in dest, 'spec/spec.dom.html', 'spec/spec.rhino.js'
end
def initialize_rails_to dest
##
# Initialize rails template at _dest_.
def initialize_rails_at dest, options
unless looks_like_rails_root?(dest)
abort unless agree "'#{dest}' does not look like root of a rails project; continue? "
end
copy_template_to 'rails', "#{dest}/jspec"
setup_lib_dir "#{dest}/jspec", options
replace_root_in "#{dest}/jspec", 'spec.dom.html', 'spec.rhino.js'
end
##
# Copy template _name_ to _dest_.
def copy_template_to name, dest
FileUtils.mkdir_p dest
FileUtils.cp_r path_to_template(name), dest
end
##
# Return path to template _name_.
def path_to_template name
File.join JSPEC_ROOT, 'templates', name, '.'
end
##
# Resolve path to _file_. Supports rails and unbound projects.
def path_to file
rails? ? "jspec/#{file}" : "spec/#{file}"
end
##
# Execute _file_ with Rhino.
def rhino file
abort "#{file} not found" unless File.exists? file
raise "#{file} not found" unless File.exists? file
system "#{RHINO} #{file}"
end
def start_server options, spec
require 'server/server'
JSpec::Server.start options, spec
##
# Start server with _suite_ html and _options_.
def start_server suite, options
set :port, options.port
set :server, 'Mongrel'
enable :sessions
disable :logging
hook = File.expand_path path_to('server.rb')
load hook if File.exists? hook
JSpec::Server.new(suite, options.port).start(options.browsers ? browsers_for(options.browsers) : nil)
end
##
# Return array of browser instances for the given _names_.
def browsers_for names
names.map do |name|
begin
Browser.subclasses.find do |browser|
browser.matches_name? name
end.new
rescue
raise "Unsupported browser `#{name}'"
end
end
end
##
# Check if the current directory looks like a rails app.
def rails?
File.directory? 'jspec'
end
##
# Replace JSPEC_ROOT placeholder in _paths_ relative to _dest_.
def replace_root_in dest, *paths
if rails? && File.exist?("#{dest}/jspec/lib")
root = './jspec'
elsif File.exist?("#{dest}/spec/lib")
root = "./spec"
else
root = JSPEC_ROOT
end
paths.each do |path|
path = File.join dest, path
contents = File.read(path).gsub 'JSPEC_ROOT', JSPEC_ROOT
contents = File.read(path).gsub 'JSPEC_ROOT', root
File.open(path, 'w') { |file| file.write contents }
end
end
##
# Update JSpec version in _paths_. Matches jspec-TRIPLE
def update_version_in *paths
paths.each do |path|
next unless File.exists? path
contents = File.read(path).gsub /visionmedia-jspec-(\d+\.\d+\.\d+)/, "visionmedia-jspec-#{program(:version)}"
contents = File.read(path).gsub /jspec-(\d+\.\d+\.\d+)/, "jspec-#{program(:version)}"
File.open(path, 'r+'){ |file| file.write contents }
say "Updated #{path}; #{$1} -> #{program(:version)}"
end
say "Finished updating JSpec"
end
##
# Check if _path_ looks like a rails root directory.
def looks_like_rails_root? path = '.'
File.directory? "#{path}/vendor"
end
##
# Copy or symlink library to the specified path.
def setup_lib_dir dest, options
return unless options.symlink || options.freeze
if rails?
dest = File.join dest, "lib"
else
dest = File.join dest, "spec", "lib"
end
from = File.join JSPEC_ROOT, "lib"
if options.symlink
FileUtils.symlink from, dest, :force => true
else
FileUtils.cp_r from, dest
end
end

View File

@ -2,17 +2,17 @@
Gem::Specification.new do |s|
s.name = %q{jspec}
s.version = "2.8.4"
s.version = "2.11.7"
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
s.authors = ["TJ Holowaychuk"]
s.date = %q{2009-08-02}
s.date = %q{2009-10-15}
s.default_executable = %q{jspec}
s.description = %q{JavaScript BDD Testing Framework}
s.email = %q{tj@vision-media.ca}
s.executables = ["jspec"]
s.extra_rdoc_files = ["bin/jspec", "lib/images/bg.png", "lib/images/hr.png", "lib/images/loading.gif", "lib/images/sprites.bg.png", "lib/images/sprites.png", "lib/images/vr.png", "lib/jspec.css", "lib/jspec.jquery.js", "lib/jspec.js", "lib/jspec.xhr.js", "README.rdoc"]
s.files = ["bin/jspec", "History.rdoc", "jspec.gemspec", "lib/images/bg.png", "lib/images/hr.png", "lib/images/loading.gif", "lib/images/sprites.bg.png", "lib/images/sprites.png", "lib/images/vr.png", "lib/jspec.css", "lib/jspec.jquery.js", "lib/jspec.js", "lib/jspec.xhr.js", "Manifest", "Rakefile", "README.rdoc", "server/browsers.rb", "server/server.rb", "spec/async", "spec/env.js", "spec/fixtures/test.html", "spec/fixtures/test.json", "spec/fixtures/test.xml", "spec/modules.js", "spec/spec.dom.html", "spec/spec.fixtures.js", "spec/spec.grammar-less.js", "spec/spec.grammar.js", "spec/spec.jquery.js", "spec/spec.jquery.xhr.js", "spec/spec.js", "spec/spec.matchers.js", "spec/spec.modules.js", "spec/spec.node.js", "spec/spec.rhino.js", "spec/spec.server.html", "spec/spec.shared-behaviors.js", "spec/spec.utils.js", "spec/spec.xhr.js", "templates/default/History.rdoc", "templates/default/lib/yourlib.core.js", "templates/default/README.rdoc", "templates/default/spec/spec.core.js", "templates/default/spec/spec.dom.html", "templates/default/spec/spec.rhino.js", "templates/default/spec/spec.server.html", "templates/rails/spec.application.js", "templates/rails/spec.dom.html", "templates/rails/spec.rhino.js", "templates/rails/spec.server.html"]
s.extra_rdoc_files = ["README.rdoc", "bin/jspec", "lib/images/bg.png", "lib/images/hr.png", "lib/images/loading.gif", "lib/images/sprites.bg.png", "lib/images/sprites.png", "lib/images/vr.png", "lib/jspec.css", "lib/jspec.jquery.js", "lib/jspec.js", "lib/jspec.shell.js", "lib/jspec.timers.js", "lib/jspec.xhr.js"]
s.files = ["History.rdoc", "Manifest", "README.rdoc", "Rakefile", "bin/jspec", "jspec.gemspec", "lib/images/bg.png", "lib/images/hr.png", "lib/images/loading.gif", "lib/images/sprites.bg.png", "lib/images/sprites.png", "lib/images/vr.png", "lib/jspec.css", "lib/jspec.jquery.js", "lib/jspec.js", "lib/jspec.shell.js", "lib/jspec.timers.js", "lib/jspec.xhr.js", "server/browsers.rb", "server/helpers.rb", "server/routes.rb", "server/server.rb", "spec/async", "spec/env.js", "spec/fixtures/test.html", "spec/fixtures/test.json", "spec/fixtures/test.xml", "spec/helpers.js", "spec/server.rb", "spec/spec.dom.html", "spec/spec.fixtures.js", "spec/spec.grammar-less.js", "spec/spec.grammar.js", "spec/spec.jquery.js", "spec/spec.jquery.xhr.js", "spec/spec.js", "spec/spec.matchers.js", "spec/spec.modules.js", "spec/spec.node.js", "spec/spec.rhino.js", "spec/spec.server.html", "spec/spec.shared-behaviors.js", "spec/spec.utils.js", "spec/spec.xhr.js", "templates/default/History.rdoc", "templates/default/README.rdoc", "templates/default/lib/yourlib.core.js", "templates/default/spec/server.rb", "templates/default/spec/spec.core.js", "templates/default/spec/spec.dom.html", "templates/default/spec/spec.rhino.js", "templates/default/spec/spec.server.html", "templates/rails/server.rb", "templates/rails/spec.application.js", "templates/rails/spec.dom.html", "templates/rails/spec.rhino.js", "templates/rails/spec.server.html"]
s.homepage = %q{http://visionmedia.github.com/jspec}
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Jspec", "--main", "README.rdoc"]
s.require_paths = ["lib"]
@ -25,14 +25,20 @@ Gem::Specification.new do |s|
s.specification_version = 3
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<visionmedia-commander>, [">= 3.2.9"])
s.add_runtime_dependency(%q<visionmedia-bind>, [">= 0.2.6"])
s.add_runtime_dependency(%q<sinatra>, [">= 0"])
s.add_runtime_dependency(%q<json_pure>, [">= 0"])
s.add_runtime_dependency(%q<commander>, [">= 4.0.0"])
s.add_runtime_dependency(%q<bind>, [">= 0.2.8"])
else
s.add_dependency(%q<visionmedia-commander>, [">= 3.2.9"])
s.add_dependency(%q<visionmedia-bind>, [">= 0.2.6"])
s.add_dependency(%q<sinatra>, [">= 0"])
s.add_dependency(%q<json_pure>, [">= 0"])
s.add_dependency(%q<commander>, [">= 4.0.0"])
s.add_dependency(%q<bind>, [">= 0.2.8"])
end
else
s.add_dependency(%q<visionmedia-commander>, [">= 3.2.9"])
s.add_dependency(%q<visionmedia-bind>, [">= 0.2.6"])
s.add_dependency(%q<sinatra>, [">= 0"])
s.add_dependency(%q<json_pure>, [">= 0"])
s.add_dependency(%q<commander>, [">= 4.0.0"])
s.add_dependency(%q<bind>, [">= 0.2.8"])
end
end

View File

@ -2,10 +2,11 @@ body.jspec {
margin: 45px 0;
font: 12px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
background: #efefef url(images/bg.png) top left repeat-x;
text-align: center;
}
#jspec {
margin: 0 auto;
padding-top: 25px;
padding-top: 30px;
width: 1008px;
background: url(images/vr.png) top left repeat-y;
text-align: left;
@ -30,8 +31,8 @@ body.jspec {
background: url(images/loading.gif) 50% 50% no-repeat;
}
#jspec-title {
position: relative;
top: 35px;
position: absolute;
top: 15px;
left: 20px;
width: 160px;
font-size: 22px;
@ -74,7 +75,6 @@ body.jspec {
color: #FA1616;
}
#jspec-report table {
width: 100%;
font-size: 11px;
border-collapse: collapse;
}
@ -90,7 +90,8 @@ body.jspec {
margin: 0;
padding: 0 0 5px 25px;
}
#jspec-report tr:not(.body):hover + tr.body {
#jspec-report tr.even:hover + tr.body,
#jspec-report tr.odd:hover + tr.body {
display: block;
}
#jspec-report tr td:first-child em {
@ -98,7 +99,8 @@ body.jspec {
font-weight: normal;
color: #7B8D9B;
}
#jspec-report tr:not(.description):hover {
#jspec-report tr.even:hover,
#jspec-report tr.odd:hover {
text-shadow: 1px 1px 1px #fff;
background: #F2F5F7;
}

View File

@ -4,18 +4,19 @@
JSpec
.requires('jQuery', 'when using jspec.jquery.js')
.include({
name: 'jQuery',
// --- Initialize
init : function() {
jQuery.ajaxSetup({ async : false })
jQuery.ajaxSetup({ async: false })
},
// --- Utilities
utilities : {
element : jQuery,
elements : jQuery,
element: jQuery,
elements: jQuery,
sandbox : function() {
return jQuery('<div class="sandbox"></div>')
}

View File

@ -5,7 +5,7 @@
JSpec = {
version : '2.8.4',
version : '2.11.7',
cache : {},
suites : [],
modules : [],
@ -13,8 +13,8 @@
matchers : {},
stubbed : [],
request : 'XMLHttpRequest' in this ? XMLHttpRequest : null,
stats : { specs : 0, assertions : 0, failures : 0, passes : 0, specsFinished : 0, suitesFinished : 0 },
options : { profile : false },
stats : { specs: 0, assertions: 0, failures: 0, passes: 0, specsFinished: 0, suitesFinished: 0 },
options : { profile: false },
/**
* Default context in which bodies are evaluated.
@ -81,6 +81,46 @@
// --- Objects
formatters : {
/**
* Report to server.
*
* Options:
* - uri specific uri to report to.
* - verbose weither or not to output messages
* - failuresOnly output failure messages only
*
* @api public
*/
Server : function(results, options) {
var uri = options.uri || 'http://' + window.location.host + '/results'
JSpec.post(uri, {
stats: JSpec.stats,
options: options,
results: map(results.allSuites, function(suite) {
if (suite.hasSpecs())
return {
description: suite.description,
specs: map(suite.specs, function(spec) {
return {
description: spec.description,
message: !spec.passed() ? spec.failure().message : null,
status: spec.requiresImplementation() ? 'pending' :
spec.passed() ? 'pass' :
'fail',
assertions: map(spec.assertions, function(assertion){
return {
passed: assertion.passed
}
})
}
})
}
})
})
if ('close' in main) main.close()
},
/**
* Default formatter, outputting to the DOM.
@ -98,48 +138,33 @@
var failuresOnly = option('failuresOnly')
var classes = results.stats.failures ? 'has-failures' : ''
if (!report) throw 'JSpec requires the element #' + id + ' to output its reports'
var markup =
'<div id="jspec-report" class="' + classes + '"><div class="heading"> \
<span class="passes">Passes: <em>' + results.stats.passes + '</em></span> \
<span class="failures">Failures: <em>' + results.stats.failures + '</em></span> \
</div><table class="suites">'
bodyContents = function(body) {
function bodyContents(body) {
return JSpec.
escape(JSpec.contentsOf(body)).
replace(/^ */gm, function(a){ return (new Array(Math.round(a.length / 3))).join(' ') }).
replace("\n", '<br/>')
replace(/\r\n|\r|\n/gm, '<br/>')
}
renderSuite = function(suite) {
report.innerHTML = '<div id="jspec-report" class="' + classes + '"><div class="heading"> \
<span class="passes">Passes: <em>' + results.stats.passes + '</em></span> \
<span class="failures">Failures: <em>' + results.stats.failures + '</em></span> \
</div><table class="suites">' + map(results.allSuites, function(suite) {
var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
if (displaySuite && suite.hasSpecs()) {
markup += '<tr class="description"><td colspan="2">' + escape(suite.description) + '</td></tr>'
each(suite.specs, function(i, spec){
markup += '<tr class="' + (i % 2 ? 'odd' : 'even') + '">'
if (spec.requiresImplementation())
markup += '<td class="requires-implementation" colspan="2">' + escape(spec.description) + '</td>'
else if (spec.passed() && !failuresOnly)
markup += '<td class="pass">' + escape(spec.description)+ '</td><td>' + spec.assertionsGraph() + '</td>'
else if(!spec.passed())
markup += '<td class="fail">' + escape(spec.description) + ' <em>' + spec.failure().message + '</em>' + '</td><td>' + spec.assertionsGraph() + '</td>'
markup += '<tr class="body"><td colspan="2"><pre>' + bodyContents(spec.body) + '</pre></td></tr>'
})
markup += '</tr>'
}
}
renderSuites = function(suites) {
each(suites, function(suite){
renderSuite(suite)
if (suite.hasSuites()) renderSuites(suite.suites)
})
}
renderSuites(results.suites)
markup += '</table></div>'
report.innerHTML = markup
if (displaySuite && suite.hasSpecs())
return '<tr class="description"><td colspan="2">' + escape(suite.description) + '</td></tr>' +
map(suite.specs, function(i, spec) {
return '<tr class="' + (i % 2 ? 'odd' : 'even') + '">' +
(spec.requiresImplementation() ?
'<td class="requires-implementation" colspan="2">' + escape(spec.description) + '</td>' :
(spec.passed() && !failuresOnly) ?
'<td class="pass">' + escape(spec.description)+ '</td><td>' + spec.assertionsGraph() + '</td>' :
!spec.passed() ?
'<td class="fail">' + escape(spec.description) + ' <em>' + escape(spec.failure().message) + '</em>' + '</td><td>' + spec.assertionsGraph() + '</td>' :
'') +
'<tr class="body"><td colspan="2"><pre>' + bodyContents(spec.body) + '</pre></td></tr>'
}).join('') + '</tr>'
}).join('') + '</table></div>'
},
/**
@ -153,42 +178,33 @@
print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') +
color(" Failures: ", 'bold') + color(results.stats.failures, 'red') + "\n")
indent = function(string) {
function indent(string) {
return string.replace(/^(.)/gm, ' $1')
}
renderSuite = function(suite) {
displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
if (displaySuite && suite.hasSpecs()) {
print(color(' ' + suite.description, 'bold'))
each(suite.specs, function(spec){
var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){
return graph + color('.', assertion.passed ? 'green' : 'red')
})
if (spec.requiresImplementation())
print(color(' ' + spec.description, 'blue') + assertionsGraph)
else if (spec.passed() && !failuresOnly)
print(color(' ' + spec.description, 'green') + assertionsGraph)
else if (!spec.passed())
print(color(' ' + spec.description, 'red') + assertionsGraph +
"\n" + indent(spec.failure().message) + "\n")
})
print("")
}
}
renderSuites = function(suites) {
each(suites, function(suite){
renderSuite(suite)
if (suite.hasSuites()) renderSuites(suite.suites)
})
}
renderSuites(results.suites)
each(results.allSuites, function(suite) {
var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
if (displaySuite && suite.hasSpecs()) {
print(color(' ' + suite.description, 'bold'))
each(suite.specs, function(spec){
var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){
return graph + color('.', assertion.passed ? 'green' : 'red')
})
if (spec.requiresImplementation())
print(color(' ' + spec.description, 'blue') + assertionsGraph)
else if (spec.passed() && !failuresOnly)
print(color(' ' + spec.description, 'green') + assertionsGraph)
else if (!spec.passed())
print(color(' ' + spec.description, 'red') + assertionsGraph +
"\n" + indent(spec.failure().message) + "\n")
})
print("")
}
})
},
/**
* Console formatter, tested with Firebug and Safari 4.
* Console formatter.
*
* @api public
*/
@ -196,8 +212,7 @@
Console : function(results, options) {
console.log('')
console.log('Passes: ' + results.stats.passes + ' Failures: ' + results.stats.failures)
renderSuite = function(suite) {
each(results.allSuites, function(suite) {
if (suite.ran) {
console.group(suite.description)
each(suite.specs, function(spec){
@ -210,28 +225,19 @@
console.error(assertionCount + ' ' + spec.description + ', ' + spec.failure().message)
})
console.groupEnd()
}
}
renderSuites = function(suites) {
each(suites, function(suite){
renderSuite(suite)
if (suite.hasSuites()) renderSuites(suite.suites)
})
}
renderSuites(results.suites)
}
})
}
},
Assertion : function(matcher, actual, expected, negate) {
extend(this, {
message : '',
passed : false,
actual : actual,
negate : negate,
matcher : matcher,
expected : expected,
message: '',
passed: false,
actual: actual,
negate: negate,
matcher: matcher,
expected: expected,
// Report assertion results
@ -269,18 +275,18 @@
// Times
this.times = {
'once' : 1,
'twice' : 2
once : 1,
twice : 2
}[times] || times || 1
extend(this, {
calls : [],
message : '',
defer : true,
passed : false,
negate : negate,
object : object,
method : method,
calls: [],
message: '',
defer: true,
passed: false,
negate: negate,
object: object,
method: method,
// Proxy return value
@ -370,7 +376,7 @@
// Report assertion results
report : function() {
this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++
this.passed ? ++JSpec.stats.passes : ++JSpec.stats.failures
return this
},
@ -380,7 +386,7 @@
var methodString = 'expected ' + object.toString() + '.' + method + '()' + (negate ? ' not' : '' )
function times(n) {
return n > 2 ? n + ' times' : { 1 : 'once', 2 : 'twice' }[n]
return n > 2 ? n + ' times' : { 1: 'once', 2: 'twice' }[n]
}
if (this.expectedResult != null && (negate ? this.anyResultsPass() : this.anyResultsFail()))
@ -488,21 +494,21 @@
Spec : function(description, body) {
extend(this, {
body : body,
description : description,
assertions : [],
body: body,
description: description,
assertions: [],
// Add passing assertion
pass : function(message) {
this.assertions.push({ passed : true, message : message })
this.assertions.push({ passed: true, message: message })
++JSpec.stats.passes
},
// Add failing assertion
fail : function(message) {
this.assertions.push({ passed : false, message : message })
this.assertions.push({ passed: false, message: message })
++JSpec.stats.failures
},
@ -556,6 +562,63 @@
extend(this, methods)
},
JSON : {
/**
* Generic sequences.
*/
meta : {
'\b' : '\\b',
'\t' : '\\t',
'\n' : '\\n',
'\f' : '\\f',
'\r' : '\\r',
'"' : '\\"',
'\\' : '\\\\'
},
/**
* Escapable sequences.
*/
escapable : /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
/**
* JSON encode _object_.
*
* @param {mixed} object
* @return {string}
* @api private
*/
encode : function(object) {
var self = this
if (object == undefined || object == null) return 'null'
if (object === true) return 'true'
if (object === false) return 'false'
switch (typeof object) {
case 'number': return object
case 'string': return this.escapable.test(object) ?
'"' + object.replace(this.escapable, function (a) {
return typeof self.meta[a] === 'string' ? self.meta[a] :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4)
}) + '"' :
'"' + object + '"'
case 'object':
if (object.constructor == Array)
return '[' + map(object, function(val){
return self.encode(val)
}).join(', ') + ']'
else if (object)
return '{' + map(object, function(key, val){
return self.encode(key) + ':' + self.encode(val)
}).join(', ') + '}'
}
return 'null'
}
},
// --- DSLs
DSLs : {
@ -623,6 +686,7 @@
if ('init' in module) module.init()
if ('utilities' in module) extend(this.defaultContext, module.utilities)
if ('matchers' in module) this.addMatchers(module.matchers)
if ('formatters' in module) extend(this.formatters, module.formatters)
if ('DSLs' in module)
each(module.DSLs, function(name, methods){
JSpec.DSLs[name] = JSpec.DSLs[name] || {}
@ -662,14 +726,9 @@
*/
evalHook : function(module, name, args) {
var context = this.context || this.defaultContext
var contents = this.contentsOf(module[name])
var params = this.paramsFor(module[name])
module.utilities = module.utilities || {}
params.unshift('context'); args.unshift(context)
hook('evaluatingHookBody', module, name, context)
try { return new Function(params.join(), 'with (this.utilities) { with (context) { with (JSpec) { ' + contents + ' }}}').apply(module, args) }
catch(e) { error('Error in hook ' + module.name + "." + name + ': ', e) }
hook('evaluatingHookBody', module, name)
try { return module[name].apply(module, args) }
catch(e) { error('Error in hook ' + module.name + '.' + name + ': ', e) }
},
/**
@ -820,10 +879,10 @@
case String:
if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)]
if (body.length < 4) body = 'actual ' + body + ' expected'
return { match : function(actual, expected) { return eval(body) }}
return { match: function(actual, expected) { return eval(body) }}
case Function:
return { match : body }
return { match: body }
default:
return body
@ -856,7 +915,7 @@
hash : function(object) {
if (object == null) return 'null'
if (object == undefined) return 'undefined'
serialize = function(prefix) {
function serialize(prefix) {
return inject(object, prefix + ':', function(buffer, key, value){
return buffer += hash(value)
})
@ -905,13 +964,13 @@
if (object === false) return 'false'
if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name
if (object.jquery && object.selector.length > 0) return 'selector ' + puts(object.selector) + ''
if (object.jquery) return escape(object.html())
if (object.nodeName) return escape(object.outerHTML)
if (object.jquery) return object.html()
if (object.nodeName) return object.outerHTML
switch (object.constructor) {
case String: return "'" + escape(object) + "'"
case String: return "'" + object + "'"
case Number: return object
case Function: return object.name || object
case Array :
case Array:
return inject(object, '[', function(b, v){
return b + ', ' + puts(v)
}).replace('[,', '[') + ' ]'
@ -920,7 +979,7 @@
return b + ', ' + puts(k) + ' : ' + puts(v)
}).replace('{,', '{') + ' }'
default:
return escape(object.toString())
return object.toString()
}
},
@ -933,11 +992,11 @@
*/
escape : function(html) {
return html.toString().
replace(/&/gmi, '&amp;').
replace(/"/gmi, '&quot;').
replace(/>/gmi, '&gt;').
replace(/</gmi, '&lt;')
return html.toString()
.replace(/&/gmi, '&amp;')
.replace(/"/gmi, '&quot;')
.replace(/>/gmi, '&gt;')
.replace(/</gmi, '&lt;')
},
/**
@ -1247,18 +1306,6 @@
contentsOf : function(body) {
return body.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]
},
/**
* Return param names for a function body.
*
* @param {function} body
* @return {array}
* @api public
*/
paramsFor : function(body) {
return body.toString().match(/\((.*?)\)/)[1].match(/[\w]+/g) || []
},
/**
* Evaluate a JSpec capture body.
@ -1288,19 +1335,22 @@
*/
preprocess : function(input) {
if (typeof input != 'string') return
input = hookImmutable('preprocessing', input)
return input.
replace(/\r\n/gm, '\n').
replace(/([\w\.]+)\.(stub|destub)\((.*?)\)$/gm, '$2($1, $3)').
replace(/describe\s+(.*?)$/gm, 'describe($1, function(){').
replace(/^\s+it\s+(.*?)$/gm, ' it($1, function(){').
replace(/^(?: *)(before_each|after_each|before|after)(?= |\n|$)/gm, 'JSpec.currentSuite.addHook("$1", function(){').
replace(/^\s*(before_each|after_each|before|after)(?=\s|$)/gm, 'JSpec.currentSuite.addHook("$1", function(){').
replace(/^\s*end(?=\s|$)/gm, '});').
replace(/-\{/g, 'function(){').
replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }).
replace(/\.should([_\.]not)?[_\.](\w+)(?: |;|$)(.*)$/gm, '.should$1_$2($3)').
replace(/([\/\s]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)\s*;?$/gm, '$1 expect($2).$3($4, $5)').
replace(/, \)/gm, ')').
replace(/should\.not/gm, 'should_not')
replace(/should\.not/gm, 'should_not').
replace(/__END__.*/m, '')
},
/**
@ -1467,41 +1517,49 @@
/**
* Ad-hoc POST request for JSpec server usage.
*
* @param {string} url
* @param {string} uri
* @param {string} data
* @api private
*/
post : function(url, data) {
if (any(hook('posting', url, data), haveStopped)) return
post : function(uri, data) {
if (any(hook('posting', uri, data), haveStopped)) return
var request = this.xhr()
request.open('POST', url, false)
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
request.send(data)
request.open('POST', uri, false)
request.setRequestHeader('Content-Type', 'application/json')
request.send(JSpec.JSON.encode(data))
},
/**
* Report to server with statistics.
*
* @param {string} url
* @api private
*/
reportToServer : function(url) {
if (any(hook('reportingToServer', url), haveStopped)) return
JSpec.post(url || 'http://localhost:4444', 'passes=' + JSpec.stats.passes + '&failures=' + JSpec.stats.failures)
if ('close' in main) main.close()
},
/**
* Instantiate an XMLHttpRequest.
*
* Here we utilize IE's lame ActiveXObjects first which
* allow IE access serve files via the file: protocol, otherwise
* we then default to XMLHttpRequest.
*
* @return {XMLHttpRequest, ActiveXObject}
* @api private
*/
xhr : function() {
return new (JSpec.request || ActiveXObject("Microsoft.XMLHTTP"))
return this.ieXhr() || new JSpec.request
},
/**
* Return Microsoft piece of crap ActiveXObject.
*
* @return {ActiveXObject}
* @api public
*/
ieXhr : function() {
function object(str) {
try { return new ActiveXObject(str) } catch(e) {}
}
return object('Msxml2.XMLHTTP.6.0') ||
object('Msxml2.XMLHTTP.3.0') ||
object('Msxml2.XMLHTTP') ||
object('Microsoft.XMLHTTP')
},
/**
@ -1546,7 +1604,10 @@
var request = this.xhr()
request.open('GET', file, false)
request.send(null)
if (request.readyState == 4) return request.responseText
if (request.readyState == 4 &&
(request.status == 0 ||
request.status.toString().charAt(0) == 2))
return request.responseText
}
else
error("failed to load `" + file + "'")

View File

@ -0,0 +1,36 @@
// JSpec - Shell - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
;(function(){
var _quit = quit
Shell = {
// --- Global
main: this,
// --- Commands
commands: {
quit: ['Terminate the shell', function(){ _quit() }],
exit: ['Terminate the shell', function(){ _quit() }]
},
/**
* Start the interactive shell.
*
* @api public
*/
start : function() {
for (var name in this.commands)
if (this.commands.hasOwnProperty(name))
this.main.__defineGetter__(name, this.commands[name][1])
}
}
Shell.start()
})()

View File

@ -0,0 +1,90 @@
// JSpec - Mock Timers - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
;(function(){
/**
* Version.
*/
mockTimersVersion = '1.0.1'
/**
* Localized timer stack.
*/
var timers = []
/**
* Set mock timeout with _callback_ and timeout of _ms_.
*
* @param {function} callback
* @param {int} ms
* @return {int}
* @api public
*/
setTimeout = function(callback, ms) {
var id
return id = setInterval(function(){
callback()
clearInterval(id)
}, ms)
}
/**
* Set mock interval with _callback_ and interval of _ms_.
*
* @param {function} callback
* @param {int} ms
* @return {int}
* @api public
*/
setInterval = function(callback, ms) {
callback.step = ms, callback.current = callback.last = 0
return timers[timers.length] = callback, timers.length
}
/**
* Destroy timer with _id_.
*
* @param {int} id
* @return {bool}
* @api public
*/
clearInterval = function(id) {
return delete timers[--id]
}
/**
* Reset timers.
*
* @return {array}
* @api public
*/
resetTimers = function() {
return timers = []
}
/**
* Increment each timers internal clock by _ms_.
*
* @param {int} ms
* @api public
*/
tick = function(ms) {
for (var i = 0, len = timers.length; i < len; ++i)
if (timers[i] && (timers[i].current += ms))
if (timers[i].current - timers[i].last >= timers[i].step) {
var times = Math.floor((timers[i].current - timers[i].last) / timers[i].step)
var remainder = (timers[i].current - timers[i].last) % timers[i].step
timers[i].last = timers[i].current - remainder
while (times-- && timers[i]) timers[i]()
}
}
})()

View File

@ -16,12 +16,12 @@
}
MockXMLHttpRequest.prototype = {
status : 0,
async : true,
readyState : 0,
responseText : '',
abort : function(){},
onreadystatechange : function(){},
status: 0,
async: true,
readyState: 0,
responseText: '',
abort: function(){},
onreadystatechange: function(){},
/**
* Return response headers hash.
@ -77,47 +77,47 @@
// --- Response status codes
JSpec.statusCodes = {
100 : 'Continue',
101 : 'Switching Protocols',
200 : 'OK',
201 : 'Created',
202 : 'Accepted',
203 : 'Non-Authoritative Information',
204 : 'No Content',
205 : 'Reset Content',
206 : 'Partial Content',
300 : 'Multiple Choice',
301 : 'Moved Permanently',
302 : 'Found',
303 : 'See Other',
304 : 'Not Modified',
305 : 'Use Proxy',
307 : 'Temporary Redirect',
400 : 'Bad Request',
401 : 'Unauthorized',
402 : 'Payment Required',
403 : 'Forbidden',
404 : 'Not Found',
405 : 'Method Not Allowed',
406 : 'Not Acceptable',
407 : 'Proxy Authentication Required',
408 : 'Request Timeout',
409 : 'Conflict',
410 : 'Gone',
411 : 'Length Required',
412 : 'Precondition Failed',
413 : 'Request Entity Too Large',
414 : 'Request-URI Too Long',
415 : 'Unsupported Media Type',
416 : 'Requested Range Not Satisfiable',
417 : 'Expectation Failed',
422 : 'Unprocessable Entity',
500 : 'Internal Server Error',
501 : 'Not Implemented',
502 : 'Bad Gateway',
503 : 'Service Unavailable',
504 : 'Gateway Timeout',
505 : 'HTTP Version Not Supported'
100: 'Continue',
101: 'Switching Protocols',
200: 'OK',
201: 'Created',
202: 'Accepted',
203: 'Non-Authoritative Information',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
300: 'Multiple Choice',
301: 'Moved Permanently',
302: 'Found',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
307: 'Temporary Redirect',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Timeout',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Request Entity Too Large',
414: 'Request-URI Too Long',
415: 'Unsupported Media Type',
416: 'Requested Range Not Satisfiable',
417: 'Expectation Failed',
422: 'Unprocessable Entity',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
505: 'HTTP Version Not Supported'
}
/**
@ -136,10 +136,10 @@
headers = headers || {}
headers['content-type'] = type
JSpec.extend(XMLHttpRequest.prototype, {
responseText : body,
responseHeaders : headers,
status : status,
statusText : JSpec.statusCodes[status]
responseText: body,
responseHeaders: headers,
status: status,
statusText: JSpec.statusCodes[status]
})
}}
}
@ -155,18 +155,28 @@
}
JSpec.include({
name: 'Mock XHR',
// --- Utilities
utilities : {
mockRequest : mockRequest,
unmockRequest : unmockRequest
mockRequest: mockRequest,
unmockRequest: unmockRequest
},
// --- Hooks
afterSpec : function() {
this.utilities.unmockRequest()
unmockRequest()
},
// --- DSLs
DSLs : {
snake : {
mock_request: mockRequest,
unmock_request: unmockRequest
}
}
})

View File

@ -1,16 +1,228 @@
module JSpec
class Browser
def open url
`open -g -a #{name} #{url}`
end
require 'rbconfig'
#--
# Browser
#++
class Browser
##
# Check if the user agent _string_ matches this browser.
def self.matches_agent? string; end
##
# Check if the browser matches the name _string_.
def self.matches_name? string; end
def name
self.class.to_s.split('::').last
end
class Firefox < self; end
class Safari < self; end
class Opera < self; end
##
# Subclasses.
def self.subclasses
@subclasses ||= []
end
end
##
# Stack subclasses.
def self.inherited subclass
subclasses << subclass
end
##
# Weither or not the browser is supported.
def supported?; true end
##
# Server setup.
def setup; end
##
# Server teardown.
def teardown; end
##
# Host environment.
def host
Config::CONFIG['host']
end
##
# Check if we are using macos.
def macos?
host.include? 'darwin'
end
##
# Check if we are using windows.
def windows?
host.include? 'mswin'
end
##
# Check if we are using linux.
def linux?
host.include? 'linux'
end
##
# Run applescript _code_.
def applescript code
raise "Can't run AppleScript on #{host}" unless macos?
system "osascript -e '#{code}' 2>&1 >/dev/null"
end
#--
# Firefox
#++
class Firefox < self
def self.matches_agent? string
string =~ /firefox/i
end
def self.matches_name? string
string =~ /ff|firefox|mozilla/i
end
def visit uri
system "open -g -a Firefox '#{uri}'" if macos?
system "firefox #{uri}" if linux?
system "#{File.join(ENV['ProgramFiles'] || 'c:\Program Files', '\Mozilla Firefox\firefox.exe')} #{uri}" if windows?
end
def to_s
'Firefox'
end
end
#--
# Safari
#++
class Safari < self
def self.matches_agent? string
string =~ /safari/i && string !~ /chrome/i
end
def self.matches_name? string
string =~ /safari/i
end
def supported?
macos?
end
def setup
applescript 'tell application "Safari" to make new document'
end
def visit uri
applescript 'tell application "Safari" to set URL of front document to "' + uri + '"'
end
def matches_agent? string
string =~ /safari/i
end
def to_s
'Safari'
end
end
#--
# Chrome
#++
class Chrome < self
def self.matches_agent? string
string =~ /chrome/i
end
def self.matches_name? string
string =~ /google|chrome/i
end
def supported?
macos?
end
def visit uri
system "open -g -a 'Google Chrome' #{uri}" if macos?
end
def to_s
'Chrome'
end
end
#--
# Internet Explorer
#++
class IE < self
def self.matches_agent? string
string =~ /microsoft/i
end
def self.matches_name? string
string =~ /ie|explorer/i
end
def supported?
windows?
end
def setup
require 'win32ole'
end
def visit uri
ie = WIN32OLE.new 'InternetExplorer.Application'
ie.visible = true
ie.Navigate uri
while ie.ReadyState != 4 do
sleep 1
end
end
def to_s
'Internet Explorer'
end
end
#--
# Opera
#++
class Opera < self
def self.matches_agent? string
string =~ /opera/i
end
def self.matches_name? string
string =~ /opera/i
end
def visit uri
system "open -g -a Opera #{uri}" if macos?
system "c:\Program Files\Opera\Opera.exe #{uri}" if windows?
system "opera #{uri}" if linux?
end
def to_s
'Opera'
end
end
end

View File

@ -0,0 +1,82 @@
helpers do
##
# Return dotted assertion graph for _assertions_.
def assertion_graph_for assertions
return if assertions.empty?
assertions.map do |assertion|
assertion['passed'] ? green('.') : red('.')
end.join
end
##
# Override Sinatra's #send_file to prevent caching.
def send_file path, opts = {}
stat = File.stat(path)
response['Cache-Control'] = 'no-cache'
content_type media_type(opts[:type]) ||
media_type(File.extname(path)) ||
response['Content-Type'] ||
'application/octet-stream'
response['Content-Length'] ||= (opts[:length] || stat.size).to_s
if opts[:disposition] == 'attachment' || opts[:filename]
attachment opts[:filename] || path
elsif opts[:disposition] == 'inline'
response['Content-Disposition'] = 'inline'
end
halt ::Sinatra::Application::StaticFile.open(path, 'rb')
rescue Errno::ENOENT
not_found
end
##
# Find the browser name for the current user agent.
def browser_name
Browser.subclasses.find do |browser|
browser.matches_agent? env['HTTP_USER_AGENT']
end.new
rescue
'Unknown'
end
##
# Wrap _string_ with ansi escape sequence using _code_.
def color string, code
"\e[#{code}m#{string}\e[0m"
end
##
# Bold _string_.
def bold string
color string, 1
end
##
# Color _string_ red.
def red string
color string, 31
end
##
# Color _string_ green.
def green string
color string, 32
end
##
# Color _string_ blue.
def blue string
color string, 34
end
end

View File

@ -0,0 +1,57 @@
get '/jspec/*' do |path|
send_file JSPEC_ROOT + '/lib/' + path
end
post '/results' do
require 'json/pure'
data = JSON.parse request.body.read
if data['options'].include?('verbose') && data['options']['verbose'] ||
data['options'].include?('failuresOnly') && data['options']['failuresOnly']
puts "\n\n %s Passes: %s Failures: %s\n\n" % [
bold(browser_name),
green(data['stats']['passes']),
red(data['stats']['failures'])]
data['results'].compact.each do |suite|
specs = suite['specs'].compact.map do |spec|
case spec['status'].to_sym
when :pass
next if data['options'].include?('failuresOnly') && data['options']['failuresOnly']
' ' + green(spec['description']) + assertion_graph_for(spec['assertions']).to_s + "\n"
when :fail
" #{red(spec['description'])}\n #{spec['message']}\n\n"
else
" #{blue(spec['description'])}\n"
end
end.join
unless specs.strip.empty?
puts "\n " + bold(suite['description'])
puts specs
end
end
else
puts "%20s Passes: %s Failures: %s" % [
bold(browser_name),
green(data['stats']['passes']),
red(data['stats']['failures'])]
end
halt 200
end
get '/*' do |path|
pass unless File.exists?(path)
send_file path
end
#--
# Simulation Routes
#++
get '/slow/*' do |seconds|
sleep seconds.to_i
halt 200
end
get '/status/*' do |code|
halt code.to_i
end

View File

@ -1,100 +1,88 @@
require 'rubygems'
require 'rack'
require 'server/browsers'
$:.unshift File.dirname(__FILE__)
require 'sinatra'
require 'thread'
require 'browsers'
require 'helpers'
require 'routes'
module JSpec
class Server
attr_reader :responses, :browsers, :root
def initialize options = {}
@responses = []
@browsers = options.delete :browsers
@root = options.delete :root
##
# Suite HTML.
attr_accessor :suite
##
# Host string.
attr_reader :host
##
# Port number.
attr_reader :port
##
# Server instance.
attr_reader :server
##
# Initialize.
def initialize suite, port
@suite, @port, @host = suite, port, :localhost
end
def call env
request = Rack::Request.new env
path = request.path_info
body = case path
when '/'
agent = env['HTTP_USER_AGENT']
responses << browser(agent)
display_results browser(agent), request['failures'], request['passes']
type = 'text/plain'
'close'
when /jspec/
type = 'application/javascript'
File.read File.join(JSPEC_ROOT, 'lib', File.basename(path))
else
type = Rack::Mime.mime_type File.extname(path)
File.read File.join(root, path) rescue ''
end
[200, { 'Content-Type' => type, 'Content-Length' => body.length.to_s }, body]
end
def display_results browser, failures, passes
puts '%-14s - passes: %s failures: %s' % [bold(browser), green(passes), red(failures)]
end
def browser string
case string
when /Safari/ ; :Safari
when /Firefox/ ; :Firefox
when /MSIE/ ; :MSIE
when /Opera/ ; :Opera
end
end
def bold string
color string, 1
end
def red string
color string, 31
end
def green string
color string, 32
end
def color string, code
"\e[#{code}m#{string}\e[m"
##
# URI formed by the given host and port.
def uri
'http://%s:%d' % [host, port]
end
def when_finished &block
Thread.new {
sleep 0.1 while responses.length < browsers.length
yield
}
end
##
# Start the server with _browsers_ which defaults to all supported browsers.
def self.start options, spec
app = Rack::Builder.new do
server = JSpec::Server.new :browsers => options.browsers, :root => '.'
server.when_finished { exit }
run server
end
unless options.server_only
Thread.new {
sleep 2
puts "Running browsers: #{options.browsers.join(', ')}\n\n"
run_browsers options.browsers, spec
def start browsers = nil
browsers ||= Browser.subclasses.map { |browser| browser.new }
browsers.map do |browser|
Thread.new {
sleep 1
if browser.supported?
browser.setup
browser.visit uri + '/' + suite
browser.teardown
end
}
end
puts "JSpec server started\n"
Rack::Handler::Mongrel.run app, :Port => 4444
self
end.push(Thread.new {
start!
}).reverse.each { |thread| thread.join }
end
def self.run_browsers browsers, spec
browsers.each do |name|
browser(name).open "http://localhost:4444/#{spec}"
private
#:nodoc:
def start!
Sinatra::Application.class_eval do
begin
$stderr.puts 'Started JSpec server at http://%s:%d' % [host, port.to_i]
detect_rack_handler.run self, :Host => host, :Port => port do |server|
trap 'INT' do
server.respond_to?(:stop!) ? server.stop! : server.stop
end
end
rescue Errno::EADDRINUSE
raise "Port #{port} already in use"
rescue Errno::EACCES
raise "Permission Denied on port #{port}"
end
end
end
def self.browser name
eval("JSpec::Browser::#{name}").new
end
end
end

View File

@ -0,0 +1,62 @@
JSpec.include({
name: 'Helpers',
utilities : {
mock_it : function(body) {
var spec = new JSpec.Spec('mock', body)
var prev = JSpec.currentSpec
JSpec.runSpec(spec)
JSpec.currentSpec = prev
return spec
}
},
matchers : {
have_failure_message : function(spec, expected) {
return JSpec.any(spec.assertions, function(assertion){
if (assertion.passed) return
switch (expected.constructor) {
case String: return assertion.message == expected
case RegExp: return expected.test(assertion.message)
default : return false
}
})
}
}
})
JSpec.include({
name: 'ExampleModule',
utilities : {
doFoo : function(){ return 'foo' },
doBar : function(){ return 'bar' }
},
randomHook : function(a, b) {
return [a, b]
},
beforeSpec : function() { addedBeforeSpec = true; this.utilities.doFoo() },
afterSpec : function() { addedAfterSpec = true },
beforeSuite : function() { addedBeforeSuite = true },
afterSuite : function() { addedAfterSuite = true },
matchers : {
be_foo_bar : function() {
return true
}
},
DSLs : {
snake : {
some_snake_case_stuff : function(){
return true
}
},
camel : {
someCamelCaseStuff : function() {
return true
}
}
}
})
JSpec.include({
name : 'EmptyModule'
})

View File

@ -1,42 +0,0 @@
ExampleModule = {
name : 'ExampleModule',
utilities : {
doFoo : function(){ return 'foo' },
doBar : function(){ return 'bar' }
},
randomHook : function(a, b) {
return [a, b]
},
beforeSpec : function() { addedBeforeSpec = true; doFoo() },
afterSpec : function() { addedAfterSpec = true },
beforeSuite : function() { addedBeforeSuite = true },
afterSuite : function() { addedAfterSuite = true },
checkJSpecContext : function(){ return each },
checkContext : function() { return fixture('test') },
checkModuleContext : function() { return this.name },
checkUtilityContext : function() { return doFoo() },
matchers : {
be_foo_bar : function() {
return true
}
},
DSLs : {
snake : {
some_snake_case_stuff : function(){
return true
}
},
camel : {
someCamelCaseStuff : function() {
return true
}
}
}
}
JSpec.include(ExampleModule)
JSpec.include({
name : 'EmptyModule'
})

View File

@ -0,0 +1,2 @@
puts 'Use spec/jspec.rb to alter anything you like, provide routes, browsers, etc'

View File

@ -5,7 +5,8 @@
<script src="../lib/jspec.js"></script>
<script src="../lib/jspec.jquery.js"></script>
<script src="../lib/jspec.xhr.js"></script>
<script src="modules.js"></script>
<script src="../lib/jspec.timers.js"></script>
<script src="helpers.js"></script>
<script src="spec.grammar-less.js"></script>
<script>
function runSuites() {
@ -20,7 +21,7 @@
.exec('spec.modules.js')
.exec('spec.xhr.js')
.exec('spec.jquery.xhr.js')
.run()
.run({ failuresOnly: true })
.report()
}
</script>

View File

@ -215,4 +215,12 @@ describe 'Grammar'
end
end
end
__END__
describe 'Grammar'
it 'should consider everything below __END__ a comment'
end
end

View File

@ -20,16 +20,16 @@ describe 'jQuery'
describe 'async'
it 'should load mah cookies (textfile)'
$.post('async', function(text){
$.get('async', function(text){
text.should_eql 'cookies!'
})
end
it 'should load mah cookies twice (ensure multiple async requests work)'
$.post('async', function(text){
$.get('async', function(text){
text.should.eql 'cookies!'
})
$.post('async', function(text){
$.get('async', function(text){
text.should.not.eql 'rawr'
})
end
@ -49,7 +49,10 @@ describe 'jQuery'
end
it 'should fail with pretty print of element'
elem.should.not.have_tag 'label'
spec = mock_it(function() {
elem.should.not.have_tag 'label'
})
spec.should.have_failure_message(/<label>\s*<em>Save?/i)
end
describe 'have_tag / have_one'

View File

@ -1,5 +1,35 @@
describe 'jQuery'
describe '.ajax()'
it "should call the success function when 200"
mock_request().and_return('{ foo: "bar" }', 'application/json')
var successCalled = false
$.ajax({
type: "POST",
url: 'foo',
dataType: 'json',
success: function() {
successCalled = true
}
})
successCalled.should.be_true
end
it "should call the error function when 404"
mock_request().and_return('{ foo: "bar" }', 'application/json', 404)
var errorCalled = false
$.ajax({
type: "POST",
url: 'foo',
dataType: 'json',
error: function() {
errorCalled = true
}
})
errorCalled.should.be_true
end
end
describe '.getJSON()'
it 'should work with mockRequest'
mockRequest().and_return('{ foo : "bar" }')

View File

@ -1,107 +1,146 @@
describe 'Negative specs'
describe 'Failing specs'
it 'should fail'
'test'.should.not_eql 'test'
spec = mock_it(function(){
'test'.should.not.eql 'test'
})
spec.should.have_failure_message("expected 'test' to not eql 'test'")
end
it 'should fail with one faulty assertion'
'test'.should.equal 'test'
'test'.should.equal 'foo'
spec = mock_it(function() {
'test'.should.equal 'test'
'test'.should.equal 'foo'
})
spec.should.have_failure_message("expected 'test' to be 'foo'")
end
it 'should fail and print array with square braces'
[1,2].should.equal [1,3]
spec = mock_it(function() {
[1,2].should.equal [1,3]
})
spec.should.have_failure_message("expected [ 1, 2 ] to be [ 1, 3 ]")
end
it 'should fail and print nested array'
[1, ['foo']].should.equal [1, ['bar', ['whatever', 1.0, { foo : 'bar', bar : { 1 : 2 } }]]]
end
it 'should fail and print html elements'
elem = document.createElement('a')
elem.setAttribute('href', 'http://vision-media.ca')
elem.should.not.eql elem
spec = mock_it(function() {
[1, ['foo']].should.equal [1, ['bar', ['whatever', 1.0, { foo : 'bar', bar : { 1 : 2 } }]]]
})
spec.should.have_failure_message(/^expected \[\s*1,\s*\[\s*'foo'/)
end
it 'should fail with selector for jQuery objects'
elem = { jquery : '1.3.1', selector : '.foobar' }
elem.should.eql 'foo'
spec = mock_it(function() {
elem = { jquery : '1.3.1', selector : '.foobar' }
elem.should.eql 'foo'
})
spec.should.have_failure_message("expected selector '.foobar' to eql 'foo'")
end
it 'should fail with negative message'
'1'.should.not.be_true
it 'should fail with negated message'
spec = mock_it(function(){
'1'.should.not.be_true
})
spec.should.have_failure_message(/expected '1' to not be true/)
end
it 'should fail with positive message'
false.should.be_true
end
it 'should fail saying an error was throw'
-{ throw 'foo' }.should.not.throw_error
spec = mock_it(function() {
false.should.be_true
})
spec.should.have_failure_message(/expected false to be true/)
end
it 'should fail saying which error has been thrown'
-{ throw 'foo' }.should.throw_error 'bar'
spec = mock_it(function() {
-{ throw 'foo' }.should.throw_error 'bar'
})
spec.should.have_failure_message("expected exception of 'bar' to be thrown, but got 'foo'")
end
it 'should fail saying no error was thrown'
-{ }.should.throw_error 'foo'
spec = mock_it(function() {
-{ }.should.throw_error 'foo'
})
spec.should.have_failure_message("expected exception of 'foo' to be thrown, but nothing was")
end
it 'should fail saying no error matching was thrown'
-{ throw 'bar' }.should.throw_error(/foo/)
end
it 'should fail saying no error matching foo should be thrown'
-{ throw 'foo' }.should.not.throw_error(/foo/)
spec = mock_it(function() {
-{ throw 'bar' }.should.throw_error(/foo/)
})
spec.should.have_failure_message("expected exception matching /foo/ to be thrown, but got 'bar'")
end
it 'should fail saying constructors'
-{ throw new TypeError('oh no') }.should.throw_error(Error)
spec = mock_it(function() {
-{ throw new TypeError('oh no') }.should.throw_error(Error)
})
spec.should.have_failure_message("expected Error to be thrown, but got TypeError: oh no")
end
it 'should fail saying multiple arg messages'
-{ throw new TypeError('oh no') }.should.throw_error(TypeError, /foo/)
spec = mock_it(function() {
-{ throw new TypeError('oh no') }.should.throw_error(TypeError, /foo/)
})
spec.should.have_failure_message("expected TypeError and exception matching /foo/ to be thrown, but got TypeError: oh no")
end
it 'should fail with constructor name'
function Foo(){}
function Bar(){}
Bar.prototype.toString = function(){ return 'Bar error: oh no' }
-{ throw new Bar }.should.throw_error Foo
end
it 'should fail with function body string'
-{ 'foo' }.should.not.include 'foo'
spec = mock_it(function() {
function Foo(){}
function Bar(){}
Bar.prototype.toString = function(){ return 'Bar: oh no' }
-{ throw new Bar }.should.throw_error Foo
})
spec.should.have_failure_message("expected Foo to be thrown, but got Bar: oh no")
end
it 'should fail with constructor name'
function Foo(){ this.toString = function(){ return '<Foo>' }}
foo = new Foo
foo.should.not.be_an_instance_of Foo
spec = mock_it(function() {
function Foo(){ this.toString = function(){ return '<Foo>' }}
foo = new Foo
foo.should.not.be_an_instance_of Foo
})
spec.should.have_failure_message("expected <Foo> to not be an instance of Foo")
end
it 'should fail with message of first failure'
true.should.be_true
'bar'.should.match(/foo/gm)
'bar'.should.include 'foo'
spec = mock_it(function() {
true.should.be_true
'bar'.should.match(/foo/gm)
'bar'.should.include 'foo'
})
spec.should.have_failure_message("expected 'bar' to match /foo/gm")
end
it 'should fail with list'
['foo', 'bar'].should.include 'foo', 'car'
spec = mock_it(function() {
['foo', 'bar'].should.include 'foo', 'car'
})
spec.should.have_failure_message("expected [ 'foo', 'bar' ] to include 'foo', 'car'")
end
it 'should catch exceptions throw within specs'
throw new Error('Oh noes!')
spec = mock_it(function() {
throw new Error('Oh noes!')
})
spec.should.have_failure_message(/Error: Oh noes!/)
end
it 'should catch improper exceptions'
throw 'oh noes'
it 'should catch exceptions without constructors'
spec = mock_it(function() {
throw 'oh noes'
})
spec.should.have_failure_message(/oh noes/)
end
it 'should catch proper exceptions'
iDoNotExist.neitherDoI()
it 'should catch indirect exceptions'
spec = mock_it(function() {
iDoNotExist.neitherDoI()
})
spec.should.have_failure_message(/iDoNotExist/)
end
end

View File

@ -299,6 +299,7 @@ describe 'Matchers'
addPets : function(a, b) { return ['izzy', a, b] }
}
end
it 'should pass when the method is invoked'
personWithPets.should.receive('getPets')
personWithPets.getPets()

View File

@ -19,22 +19,6 @@ describe 'JSpec'
it 'should run afterSuite'
addedAfterSuite.should.be_true
end
it 'should run in context with JSpec'
hook('checkJSpecContext')[0].should.equal JSpec.each
end
it 'should run in context to JSpecs default context'
hook('checkContext')[0].should.eql fixture('test')
end
it 'should run in context to the module itself'
hook('checkModuleContext')[0].should.eql 'ExampleModule'
end
it 'should run in context to the modules utilities'
hook('checkUtilityContext')[0].should.eql 'foo'
end
end
describe '.hook()'

View File

@ -1,7 +1,7 @@
load('lib/jspec.js')
load('lib/jspec.xhr.js')
load('spec/modules.js')
load('spec/helpers.js')
load('spec/spec.grammar-less.js')
JSpec

View File

@ -1,10 +1,10 @@
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js"></script>
<script src="jspec.js"></script>
<script src="jspec.jquery.js"></script>
<script src="jspec.xhr.js"></script>
<script src="modules.js"></script>
<script src="/jspec/jspec.js"></script>
<script src="/jspec/jspec.jquery.js"></script>
<script src="/jspec/jspec.xhr.js"></script>
<script src="helpers.js"></script>
<script src="spec.grammar-less.js"></script>
<script>
function runSuites() {
@ -19,8 +19,8 @@
.exec('spec.modules.js')
.exec('spec.xhr.js')
.exec('spec.jquery.xhr.js')
.run()
.reportToServer()
.run({ formatter : JSpec.formatters.Server })
.report()
}
</script>
</head>

View File

@ -2,7 +2,10 @@
describe 'Utility'
describe 'fail()'
it 'should fail the current spec'
fail('I failed!')
spec = mock_it(function() {
fail('I failed!')
})
spec.should.have_failure_message('I failed!')
end
end
@ -256,21 +259,4 @@ describe 'Utility'
end
end
describe 'paramsFor()'
it 'should return an array of function parameter names'
JSpec.paramsFor(function(foo, bar){}).should.eql ['foo', 'bar']
end
it 'should return only the params for the root function'
foo = function(bar){
function baz(test) {}
var something = function(foo, bar){}
}
JSpec.paramsFor(foo).should.eql ['bar']
end
it 'should return empty array when no params are present'
JSpec.paramsFor(function(){}).should.eql []
end
end
end

View File

@ -1,6 +1,6 @@
describe 'JSpec'
describe '.mockRequest'
describe 'Mock XHR'
before
responseFrom = function(path) {
request = new XMLHttpRequest
@ -10,6 +10,11 @@ describe 'JSpec'
}
end
it 'should provide snake DSL methods'
mock_request.should.equal mockRequest
unmock_request.should.equal unmockRequest
end
it 'should mock XMLHttpRequests if unmockRequest() is called or the spec block has finished'
original = XMLHttpRequest
mockRequest().and_return('test')

View File

@ -0,0 +1,4 @@
get '/lib/*' do |path|
send_file File.dirname(__FILE__) + '/../lib/' + path
end

View File

@ -4,5 +4,5 @@ load('lib/yourlib.core.js')
JSpec
.exec('spec/spec.core.js')
.run({ formatter : JSpec.formatters.Terminal })
.run({ formatter: JSpec.formatters.Terminal })
.report()

View File

@ -1,13 +1,13 @@
<html>
<head>
<script src="jspec.js"></script>
<script src="../lib/yourlib.core.js"></script>
<script src="/jspec/jspec.js"></script>
<script src="/lib/yourlib.core.js"></script>
<script>
function runSuites() {
JSpec
.exec('spec.core.js')
.run()
.reportToServer()
.run({ formatter: JSpec.formatters.Server, verbose: true, failuresOnly: true })
.report()
}
</script>
</head>

View File

@ -0,0 +1,4 @@
get '/public/*' do |path|
send_file File.dirname(__FILE__) + '/../public/' + path
end

View File

@ -3,6 +3,6 @@ load('JSPEC_ROOT/lib/jspec.js')
load('public/javascripts/application.js')
JSpec
.exec('spec/spec.application.js')
.run({ formatter : JSpec.formatters.Terminal })
.exec('jspec/spec.application.js')
.run({ formatter: JSpec.formatters.Terminal })
.report()

View File

@ -1,13 +1,13 @@
<html>
<head>
<script src="jspec.js"></script>
<script src="../public/javascripts/application.js"></script>
<script src="/jspec/jspec.js"></script>
<script src="/public/javascripts/application.js"></script>
<script>
function runSuites() {
JSpec
.exec('spec.application.js')
.run()
.reportToServer()
.run({ formatter: JSpec.formatters.Server, verbose: true, failuresOnly: true })
.report()
}
</script>
</head>